From 9a0876f729da22f988434395609089ce88cc887f Mon Sep 17 00:00:00 2001 From: MobiusDevelopment <8391001+MobiusDevelopment@users.noreply.github.com> Date: Thu, 11 Mar 2021 22:03:28 +0000 Subject: [PATCH] Addition of L2D GeoEngine patch. --- .../game/data/geodata/L2D_GeoEngine.patch | 6052 ++++++++++++++++ .../game/data/geodata/L2D_GeoEngine.patch | 6052 ++++++++++++++++ .../game/data/geodata/L2D_GeoEngine.patch | 6052 ++++++++++++++++ .../game/data/geodata/L2D_GeoEngine.patch | 6052 ++++++++++++++++ .../game/data/geodata/L2D_GeoEngine.patch | 6052 ++++++++++++++++ .../game/data/geodata/L2D_GeoEngine.patch | 6052 ++++++++++++++++ .../game/data/geodata/L2D_GeoEngine.patch | 6052 ++++++++++++++++ .../game/data/geodata/L2D_GeoEngine.patch | 6052 ++++++++++++++++ .../game/data/geodata/L2D_GeoEngine.patch | 6052 ++++++++++++++++ .../game/data/geodata/L2D_GeoEngine.patch | 6052 ++++++++++++++++ .../game/data/geodata/L2D_GeoEngine.patch | 5951 ++++++++++++++++ .../game/data/geodata/L2D_GeoEngine.patch | 5951 ++++++++++++++++ .../game/data/geodata/L2D_GeoEngine.patch | 6075 +++++++++++++++++ .../game/data/geodata/L2D_GeoEngine.patch | 6075 +++++++++++++++++ .../game/data/geodata/L2D_GeoEngine.patch | 6052 ++++++++++++++++ .../game/data/geodata/L2D_GeoEngine.patch | 6052 ++++++++++++++++ .../game/data/geodata/L2D_GeoEngine.patch | 6052 ++++++++++++++++ .../game/data/geodata/L2D_GeoEngine.patch | 6052 ++++++++++++++++ .../game/data/geodata/L2D_GeoEngine.patch | 6052 ++++++++++++++++ .../game/data/geodata/L2D_GeoEngine.patch | 6052 ++++++++++++++++ .../game/data/geodata/L2D_GeoEngine.patch | 6052 ++++++++++++++++ .../game/data/geodata/L2D_GeoEngine.patch | 6052 ++++++++++++++++ .../game/data/geodata/L2D_GeoEngine.patch | 6052 ++++++++++++++++ 23 files changed, 139040 insertions(+) create mode 100644 L2J_Mobius_1.0_Ertheia/dist/game/data/geodata/L2D_GeoEngine.patch create mode 100644 L2J_Mobius_2.5_Underground/dist/game/data/geodata/L2D_GeoEngine.patch create mode 100644 L2J_Mobius_3.0_Helios/dist/game/data/geodata/L2D_GeoEngine.patch create mode 100644 L2J_Mobius_4.0_GrandCrusade/dist/game/data/geodata/L2D_GeoEngine.patch create mode 100644 L2J_Mobius_5.0_Salvation/dist/game/data/geodata/L2D_GeoEngine.patch create mode 100644 L2J_Mobius_5.5_EtinasFate/dist/game/data/geodata/L2D_GeoEngine.patch create mode 100644 L2J_Mobius_6.0_Fafurion/dist/game/data/geodata/L2D_GeoEngine.patch create mode 100644 L2J_Mobius_7.0_PreludeOfWar/dist/game/data/geodata/L2D_GeoEngine.patch create mode 100644 L2J_Mobius_8.0_Homunculus/dist/game/data/geodata/L2D_GeoEngine.patch create mode 100644 L2J_Mobius_9.0_ReturnOfTheQueenAnt/dist/game/data/geodata/L2D_GeoEngine.patch create mode 100644 L2J_Mobius_C4_ScionsOfDestiny/dist/game/data/geodata/L2D_GeoEngine.patch create mode 100644 L2J_Mobius_C6_Interlude/dist/game/data/geodata/L2D_GeoEngine.patch create mode 100644 L2J_Mobius_CT_2.4_Epilogue/dist/game/data/geodata/L2D_GeoEngine.patch create mode 100644 L2J_Mobius_CT_2.6_HighFive/dist/game/data/geodata/L2D_GeoEngine.patch create mode 100644 L2J_Mobius_Classic_2.0_Saviors/dist/game/data/geodata/L2D_GeoEngine.patch create mode 100644 L2J_Mobius_Classic_2.1_Zaken/dist/game/data/geodata/L2D_GeoEngine.patch create mode 100644 L2J_Mobius_Classic_2.2_Antharas/dist/game/data/geodata/L2D_GeoEngine.patch create mode 100644 L2J_Mobius_Classic_2.3_SevenSigns/dist/game/data/geodata/L2D_GeoEngine.patch create mode 100644 L2J_Mobius_Classic_2.4_SecretOfEmpire/dist/game/data/geodata/L2D_GeoEngine.patch create mode 100644 L2J_Mobius_Classic_3.0_TheKamael/dist/game/data/geodata/L2D_GeoEngine.patch create mode 100644 L2J_Mobius_Classic_Interlude/dist/game/data/geodata/L2D_GeoEngine.patch create mode 100644 L2J_Mobius_Essence_4.0_DwellingOfSpirits/dist/game/data/geodata/L2D_GeoEngine.patch create mode 100644 L2J_Mobius_Essence_5.0_Sylph/dist/game/data/geodata/L2D_GeoEngine.patch diff --git a/L2J_Mobius_1.0_Ertheia/dist/game/data/geodata/L2D_GeoEngine.patch b/L2J_Mobius_1.0_Ertheia/dist/game/data/geodata/L2D_GeoEngine.patch new file mode 100644 index 0000000000..63e2faf0c9 --- /dev/null +++ b/L2J_Mobius_1.0_Ertheia/dist/game/data/geodata/L2D_GeoEngine.patch @@ -0,0 +1,6052 @@ +Index: dist/game/GeoDataConverter.bat +=================================================================== +--- dist/game/GeoDataConverter.bat (nonexistent) ++++ dist/game/GeoDataConverter.bat (working copy) +@@ -0,0 +1,6 @@ ++@echo off ++title L2D geodata converter ++ ++java -Xmx512m -cp ./../libs/* org.l2jmobius.tools.geodataconverter.GeoDataConverter ++ ++pause +Index: dist/game/GeoDataConverter.sh +=================================================================== +--- dist/game/GeoDataConverter.sh (nonexistent) ++++ dist/game/GeoDataConverter.sh (working copy) +@@ -0,0 +1,4 @@ ++#! /bin/sh ++ ++java -Xmx512m -cp ../libs/*: org.l2jmobius.tools.geodataconverter.GeoDataConverter > log/stdout.log 2>&1 ++ +Index: dist/game/config/GeoEngine.ini +=================================================================== +--- dist/game/config/GeoEngine.ini (revision 8397) ++++ dist/game/config/GeoEngine.ini (working copy) +@@ -1,6 +1,10 @@ + # ================================================================= + # Geodata + # ================================================================= ++# Because of real-time performance we are using geodata files only in ++# diagonal L2D format now (using filename e.g. 22_16.l2d). ++# L2D geodata can be obtained by conversion of existing L2J or L2OFF geodata. ++# Launch "GeoDataConverter.bat/sh" and follow instructions to start the conversion. + + # 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/ +@@ -13,6 +17,16 @@ + CoordSynchronize = 2 + + # ================================================================= ++# Path checking ++# ================================================================= ++ ++# 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 finding + # ================================================================= + +@@ -23,19 +37,23 @@ + # Pathfinding array buffers configuration, default: 100x6;128x6;192x6;256x4;320x4;384x4;500x2 + PathFindBuffers = 100x6;128x6;192x6;256x4;320x4;384x4;500x2 + +-# Weight for nodes without obstacles far from walls +-LowWeight = 0.5 ++# Base path weight, when moving from one node to another on axis direction, default: 10 ++BaseWeight = 10 + +-# Weight for nodes near walls +-MediumWeight = 2 ++# Path weight, when moving from one node to another on diagonal direction, default: BaseWeight * sqrt(2) = 14 ++DiagonalWeight = 14 + +-# Weight for nodes with obstacles +-HighWeight = 3 ++# When movement flags of target node is blocked to any direction, multiply movement weight by this multiplier. ++# This causes pathfinding algorithm to avoid path construction exactly near an obstacle, default: 10 ++ObstacleMultiplier = 10 + +-# Weight for diagonal movement. +-# Default: LowWeight * sqrt(2) +-DiagonalWeight = 0.707 ++# Weight of the heuristic algorithm, which is giving estimated cost from node to target, default: 20 ++# For proper function must be higher than BaseWeight and/or DiagonalWeight. ++HeuristicWeight = 20 + ++# Maximum number of generated nodes per one path-finding process, default 3500 ++MaxIterations = 3500 ++ + # ================================================================= + # Other + # ================================================================= +Index: dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java +=================================================================== +--- dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java (revision 8397) ++++ dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java (working copy) +@@ -55,9 +55,8 @@ + final int worldX = activeChar.getX(); + final int worldY = activeChar.getY(); + final int worldZ = activeChar.getZ(); +- final int geoX = GeoEngine.getInstance().getGeoX(worldX); +- final int geoY = GeoEngine.getInstance().getGeoY(worldY); +- ++ final int geoX = GeoEngine.getGeoX(worldX); ++ final int geoY = GeoEngine.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)); +@@ -73,9 +72,8 @@ + final int worldX = activeChar.getX(); + final int worldY = activeChar.getY(); + final int worldZ = activeChar.getZ(); +- final int geoX = GeoEngine.getInstance().getGeoX(worldX); +- final int geoY = GeoEngine.getInstance().getGeoY(worldY); +- ++ final int geoX = GeoEngine.getGeoX(worldX); ++ final int geoY = GeoEngine.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)); +Index: dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java +=================================================================== +--- dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java (revision 8397) ++++ dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java (working copy) +@@ -18,11 +18,11 @@ + + import java.util.List; + +-import org.l2jmobius.Config; +-import org.l2jmobius.gameserver.geoengine.GeoEnginePathfinding; +-import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; ++import org.l2jmobius.gameserver.geoengine.GeoEngine; + import org.l2jmobius.gameserver.handler.IAdminCommandHandler; ++import org.l2jmobius.gameserver.model.Location; + import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance; ++import org.l2jmobius.gameserver.network.SystemMessageId; + import org.l2jmobius.gameserver.util.BuilderUtil; + + public class AdminPathNode implements IAdminCommandHandler +@@ -37,29 +37,30 @@ + { + if (command.equals("admin_path_find")) + { +- if (!Config.PATHFINDING) +- { +- BuilderUtil.sendSysMessage(activeChar, "PathFinding is disabled."); +- return true; +- } + if (activeChar.getTarget() != null) + { +- final List path = GeoEnginePathfinding.getInstance().findPath(activeChar.getX(), activeChar.getY(), (short) activeChar.getZ(), activeChar.getTarget().getX(), activeChar.getTarget().getY(), (short) activeChar.getTarget().getZ(), activeChar.getInstanceWorld()); ++ 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()); + if (path == null) + { +- BuilderUtil.sendSysMessage(activeChar, "No Route!"); +- return true; ++ BuilderUtil.sendSysMessage(activeChar, "No route found or pathfinding disabled."); + } +- for (AbstractNodeLoc a : path) ++ else + { +- BuilderUtil.sendSysMessage(activeChar, "x:" + a.getX() + " y:" + a.getY() + " z:" + a.getZ()); ++ for (Location point : path) ++ { ++ BuilderUtil.sendSysMessage(activeChar, "x:" + point.getX() + " y:" + point.getY() + " z:" + point.getZ()); ++ } + } + } + else + { +- BuilderUtil.sendSysMessage(activeChar, "No Target!"); ++ activeChar.sendPacket(SystemMessageId.INVALID_TARGET); + } + } ++ else ++ { ++ return false; ++ } + return true; + } + +Index: java/org/l2jmobius/Config.java +=================================================================== +--- java/org/l2jmobius/Config.java (revision 8397) ++++ java/org/l2jmobius/Config.java (working copy) +@@ -32,7 +32,6 @@ + import java.net.UnknownHostException; + import java.nio.charset.StandardCharsets; + import java.nio.file.Files; +-import java.nio.file.Path; + import java.nio.file.Paths; + import java.time.Duration; + import java.util.ArrayList; +@@ -927,14 +926,17 @@ + // -------------------------------------------------- + // GeoEngine + // -------------------------------------------------- +- public static Path GEODATA_PATH; ++ public static String GEODATA_PATH; ++ public static int COORD_SYNCHRONIZE; ++ public static int PART_OF_CHARACTER_HEIGHT; ++ public static int MAX_OBSTACLE_HEIGHT; + public static boolean PATHFINDING; + public static String PATHFIND_BUFFERS; +- public static float LOW_WEIGHT; +- public static float MEDIUM_WEIGHT; +- public static float HIGH_WEIGHT; +- public static float DIAGONAL_WEIGHT; +- public static int COORD_SYNCHRONIZE; ++ public static int BASE_WEIGHT; ++ public static int DIAGONAL_WEIGHT; ++ public static int HEURISTIC_WEIGHT; ++ public static int OBSTACLE_MULTIPLIER; ++ public static int MAX_ITERATIONS; + public static boolean CORRECT_PLAYER_Z; + + /** Attribute System */ +@@ -2468,14 +2470,17 @@ + + // Load GeoEngine config file (if exists) + final PropertiesParser GeoEngine = new PropertiesParser(GEOENGINE_CONFIG_FILE); +- GEODATA_PATH = Paths.get(GeoEngine.getString("GeoDataPath", "./data/geodata")); ++ GEODATA_PATH = GeoEngine.getString("GeoDataPath", "./data/geodata/"); ++ COORD_SYNCHRONIZE = GeoEngine.getInt("CoordSynchronize", -1); ++ PART_OF_CHARACTER_HEIGHT = GeoEngine.getInt("PartOfCharacterHeight", 75); ++ MAX_OBSTACLE_HEIGHT = GeoEngine.getInt("MaxObstacleHeight", 32); + PATHFINDING = GeoEngine.getBoolean("PathFinding", true); + PATHFIND_BUFFERS = GeoEngine.getString("PathFindBuffers", "100x6;128x6;192x6;256x4;320x4;384x4;500x2"); +- LOW_WEIGHT = GeoEngine.getFloat("LowWeight", 0.5f); +- MEDIUM_WEIGHT = GeoEngine.getFloat("MediumWeight", 2); +- HIGH_WEIGHT = GeoEngine.getFloat("HighWeight", 3); +- DIAGONAL_WEIGHT = GeoEngine.getFloat("DiagonalWeight", 0.707f); +- COORD_SYNCHRONIZE = GeoEngine.getInt("CoordSynchronize", -1); ++ BASE_WEIGHT = GeoEngine.getInt("BaseWeight", 10); ++ DIAGONAL_WEIGHT = GeoEngine.getInt("DiagonalWeight", 14); ++ OBSTACLE_MULTIPLIER = GeoEngine.getInt("ObstacleMultiplier", 10); ++ HEURISTIC_WEIGHT = GeoEngine.getInt("HeuristicWeight", 20); ++ MAX_ITERATIONS = GeoEngine.getInt("MaxIterations", 3500); + CORRECT_PLAYER_Z = GeoEngine.getBoolean("CorrectPlayerZ", false); + + // Load AllowedPlayerRaces config file (if exists) +Index: java/org/l2jmobius/gameserver/geoengine/GeoEngine.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/GeoEngine.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/GeoEngine.java (working copy) +@@ -16,101 +16,91 @@ + */ + package org.l2jmobius.gameserver.geoengine; + +-import java.io.IOException; ++import java.io.File; + import java.io.RandomAccessFile; + import java.nio.ByteOrder; +-import java.nio.channels.FileChannel.MapMode; +-import java.nio.file.Files; +-import java.nio.file.Path; +-import java.util.concurrent.atomic.AtomicReferenceArray; +-import java.util.logging.Level; ++import java.nio.MappedByteBuffer; ++import java.nio.channels.FileChannel; ++import java.util.List; + 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.geoengine.geodata.Cell; +-import org.l2jmobius.gameserver.geoengine.geodata.IRegion; +-import org.l2jmobius.gameserver.geoengine.geodata.NullRegion; +-import org.l2jmobius.gameserver.geoengine.geodata.Region; ++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.GeoFormat; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoLocation; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; + import org.l2jmobius.gameserver.instancemanager.WarpedSpaceManager; + 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; ++import org.l2jmobius.gameserver.util.MathUtil; + + /** +- * @author -Nemesiss-, HorridoJoho ++ * @author Hasha + */ + public class GeoEngine + { +- private static final Logger LOGGER = Logger.getLogger(GeoEngine.class.getName()); ++ protected static final Logger LOGGER = Logger.getLogger(GeoEngine.class.getName()); + +- private static final int WORLD_MIN_X = -655360; +- private static final int WORLD_MIN_Y = -589824; +- private static final int WORLD_MIN_Z = -16384; ++ private final ABlock[][] _blocks; ++ private final BlockNull _nullBlock; + +- /** 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; +- +- /** The regions array */ +- private final AtomicReferenceArray _regions = new AtomicReferenceArray<>(GEO_REGIONS); +- +- 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; +- +- protected GeoEngine() ++ /** ++ * GeoEngine constructor. Loads all geodata files of chosen geodata format. ++ */ ++ public GeoEngine() + { + LOGGER.info("GeoEngine: Initializing..."); +- for (int i = 0; i < _regions.length(); i++) +- { +- _regions.set(i, NullRegion.INSTANCE); +- } + ++ // 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; +- try ++ long fileSize = 0; ++ for (int rx = World.TILE_X_MIN; rx <= World.TILE_X_MAX; rx++) + { +- for (int regionX = World.TILE_X_MIN; regionX <= World.TILE_X_MAX; regionX++) ++ for (int ry = World.TILE_Y_MIN; ry <= World.TILE_Y_MAX; ry++) + { +- for (int regionY = World.TILE_Y_MIN; regionY <= World.TILE_Y_MAX; regionY++) ++ final File f = new File(Config.GEODATA_PATH + String.format(GeoFormat.L2D.getFilename(), rx, ry)); ++ if (f.exists() && !f.isDirectory()) + { +- 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(rx, ry)) + { +- try (RandomAccessFile raf = new RandomAccessFile(geoFilePath.toFile(), "r")) +- { +- _regions.set((regionX * GEO_REGIONS_Y) + regionY, new Region(raf.getChannel().map(MapMode.READ_ONLY, 0, raf.length()).load().order(ByteOrder.LITTLE_ENDIAN))); +- loaded++; +- } ++ loaded++; ++ fileSize += f.length(); + } + } ++ else ++ { ++ // region file is not load-able, load null blocks ++ loadNullBlocks(rx, ry); ++ } + } + } +- catch (Exception e) ++ ++ LOGGER.info("GeoEngine: Loaded " + loaded + " geodata files."); ++ if (loaded > 0) + { +- LOGGER.log(Level.SEVERE, "GeoEngine: Failed to load geodata!", e); +- System.exit(1); ++ LOGGER.info("GeoEngine: Total geodata file size " + (fileSize / 1024 / 1024) + " MB."); + } + +- LOGGER.info("GeoEngine: Loaded " + loaded + " geodata files."); +- +- // Avoid wrong configs when no files are loaded. ++ // avoid wrong configs when no files are loaded + if (loaded == 0) + { + if (Config.PATHFINDING) +@@ -124,627 +114,832 @@ + LOGGER.info("GeoEngine: Forcing CoordSynchronize setting to -1."); + } + } ++ ++ // release multilayer block temporarily buffer ++ BlockMultilayer.release(); + } + + /** +- * @param geoX +- * @param geoY +- * @return the region ++ * 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 IRegion getRegion(int geoX, int geoY) ++ private final boolean loadGeoBlocks(int regionX, int regionY) + { +- final int region = ((geoX / IRegion.REGION_CELLS_X) * GEO_REGIONS_Y) + (geoY / IRegion.REGION_CELLS_Y); +- if ((region < 0) || (region >= _regions.length())) ++ final String filename = String.format(GeoFormat.L2D.getFilename(), regionX, regionY); ++ ++ // standard load ++ try (RandomAccessFile raf = new RandomAccessFile(Config.GEODATA_PATH + filename, "r"); ++ FileChannel fc = raf.getChannel()) + { +- return null; ++ // initialize file buffer ++ final MappedByteBuffer buffer = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()).load(); ++ buffer.order(ByteOrder.LITTLE_ENDIAN); ++ ++ // 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++) ++ { ++ // get block type ++ final byte type = buffer.get(); ++ ++ // load block according to block type ++ switch (type) ++ { ++ case GeoStructure.TYPE_FLAT_L2D: ++ { ++ _blocks[blockX + ix][blockY + iy] = new BlockFlat(buffer, GeoFormat.L2D); ++ break; ++ } ++ case GeoStructure.TYPE_COMPLEX_L2D: ++ { ++ _blocks[blockX + ix][blockY + iy] = new BlockComplex(buffer, GeoFormat.L2D); ++ break; ++ } ++ case GeoStructure.TYPE_MULTILAYER_L2D: ++ { ++ _blocks[blockX + ix][blockY + iy] = new BlockMultilayer(buffer, GeoFormat.L2D); ++ break; ++ } ++ default: ++ { ++ throw new IllegalArgumentException("Unknown block type: " + type); ++ } ++ } ++ } ++ } ++ ++ // check data consistency ++ if (buffer.remaining() > 0) ++ { ++ LOGGER.warning("GeoEngine: Region file " + filename + " can be corrupted, remaining " + buffer.remaining() + " bytes to read."); ++ } ++ ++ // loading was successful ++ return true; + } +- return _regions.get(region); +- } +- +- /** +- * @param filePath +- * @param regionX +- * @param regionY +- * @throws IOException +- */ +- 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")) ++ catch (Exception e) + { +- _regions.set(regionOffset, new Region(raf.getChannel().map(MapMode.READ_ONLY, 0, raf.length()).load().order(ByteOrder.LITTLE_ENDIAN))); ++ // an error occured while loading, load null blocks ++ LOGGER.warning("GeoEngine: Error while loading " + filename + " region file."); ++ LOGGER.warning(e.getMessage()); ++ ++ // replace whole region file with null blocks ++ loadNullBlocks(regionX, regionY); ++ ++ // loading was not successful ++ return false; + } + } + + /** +- * @param regionX +- * @param regionY ++ * 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. + */ +- public void unloadRegion(int regionX, int regionY) ++ private final void loadNullBlocks(int regionX, int regionY) + { +- _regions.set((regionX * GEO_REGIONS_Y) + regionY, NullRegion.INSTANCE); +- } +- +- /** +- * @param geoX +- * @param geoY +- * @return if geodata exist +- */ +- public boolean hasGeoPos(int geoX, int geoY) +- { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) ++ // 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++) + { +- return false; ++ for (int iy = 0; iy < GeoStructure.REGION_BLOCKS_Y; iy++) ++ { ++ _blocks[blockX + ix][blockY + iy] = _nullBlock; ++ } + } +- return region.hasGeo(); + } + ++ // GEODATA - GENERAL ++ + /** +- * Checks the specified position for available geodata. +- * @param x the world x +- * @param y the world y +- * @return {@code true} if there is geodata for the given coordinates, {@code false} otherwise ++ * Converts world X to geodata X. ++ * @param worldX ++ * @return int : Geo X + */ +- public boolean hasGeo(int x, int y) ++ public static int getGeoX(int worldX) + { +- return hasGeoPos(getGeoX(x), getGeoY(y)); ++ return (MathUtil.limit(worldX, World.MAP_MIN_X, World.MAP_MAX_X) - World.MAP_MIN_X) >> 4; + } + + /** +- * @param geoX +- * @param geoY +- * @param worldZ +- * @param nswe +- * @return the nearest nswe check ++ * Converts world Y to geodata Y. ++ * @param worldY ++ * @return int : Geo Y + */ +- public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) ++ public static int getGeoY(int worldY) + { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) +- { +- return true; +- } +- return region.checkNearestNswe(geoX, geoY, worldZ, nswe); ++ return (MathUtil.limit(worldY, World.MAP_MIN_Y, World.MAP_MAX_Y) - World.MAP_MIN_Y) >> 4; + } + + /** ++ * Converts geodata X to world X. + * @param geoX +- * @param geoY +- * @param worldZ +- * @param nswe +- * @return the nearest nswe anti-corner cut ++ * @return int : World X + */ +- public boolean checkNearestNsweAntiCornerCut(int geoX, int geoY, int worldZ, int nswe) ++ public static int getWorldX(int geoX) + { +- boolean can = true; +- if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) +- { +- can = checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_EAST) && checkNearestNswe(geoX + 1, geoY, worldZ, Cell.NSWE_NORTH); +- } +- if (can && ((nswe & Cell.NSWE_NORTH_WEST) == Cell.NSWE_NORTH_WEST)) +- { +- 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 = 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 = checkNearestNswe(geoX, geoY + 1, worldZ, Cell.NSWE_WEST) && checkNearestNswe(geoX - 1, geoY, worldZ, Cell.NSWE_SOUTH); +- } +- return can && checkNearestNswe(geoX, geoY, worldZ, nswe); ++ return (MathUtil.limit(geoX, 0, GeoStructure.GEO_CELLS_X) << 4) + World.MAP_MIN_X + 8; + } + + /** +- * @param geoX ++ * Converts geodata Y to world Y. + * @param geoY +- * @param worldZ +- * @return the nearest Z value ++ * @return int : World Y + */ +- public int getNearestZ(int geoX, int geoY, int worldZ) ++ public static int getWorldY(int geoY) + { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) +- { +- return worldZ; +- } +- return region.getNearestZ(geoX, geoY, worldZ); ++ return (MathUtil.limit(geoY, 0, GeoStructure.GEO_CELLS_Y) << 4) + World.MAP_MIN_Y + 8; + } + + /** +- * @param geoX +- * @param geoY +- * @param worldZ +- * @return the next lower Z value ++ * Returns block of geodata on given coordinates. ++ * @param geoX : Geodata X ++ * @param geoY : Geodata Y ++ * @return {@link ABlock} : Block of geodata. + */ +- public int getNextLowerZ(int geoX, int geoY, int worldZ) ++ private final ABlock getBlock(int geoX, int geoY) + { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) ++ final int x = geoX / GeoStructure.BLOCK_CELLS_X; ++ final int y = geoY / GeoStructure.BLOCK_CELLS_Y; ++ ++ // if x or y is out of array return null ++ if ((x > -1) && (y > -1) && (x < GeoStructure.GEO_BLOCKS_X) && (y < GeoStructure.GEO_BLOCKS_Y)) + { +- return worldZ; ++ return _blocks[x][y]; + } +- return region.getNextLowerZ(geoX, geoY, worldZ); ++ return null; + } + + /** +- * @param geoX +- * @param geoY +- * @param worldZ +- * @return the next higher Z value ++ * Check if geo coordinates has geo. ++ * @param geoX : Geodata X ++ * @param geoY : Geodata Y ++ * @return boolean : True, if given geo coordinates have geodata + */ +- public int getNextHigherZ(int geoX, int geoY, int worldZ) ++ public boolean hasGeoPos(int geoX, int geoY) + { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) ++ final ABlock block = getBlock(geoX, geoY); ++ if (block == null) // null block check + { +- return worldZ; ++ // TODO: Find when this can be null. (Bad geodata? Check World getRegion method.) ++ // LOGGER.warning("Could not find geodata block at " + getWorldX(geoX) + ", " + getWorldY(geoY) + "."); ++ return false; + } +- return region.getNextHigherZ(geoX, geoY, worldZ); ++ return block.hasGeoPos(); + } + + /** +- * Gets the Z height. +- * @param x the world x +- * @param y the world y +- * @param z the world z +- * @return the nearest Z height ++ * 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 int getHeight(int x, int y, int z) ++ public short getHeightNearest(int geoX, int geoY, int worldZ) + { +- return getNearestZ(getGeoX(x), getGeoY(y), z); ++ final ABlock block = getBlock(geoX, geoY); ++ return block != null ? block.getHeightNearest(geoX, geoY, worldZ) : (short) worldZ; + } + + /** +- * Gets the next lower Z height. +- * @param x the world x +- * @param y the world y +- * @param z the world z +- * @return the nearest Z height ++ * 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 int getLowerHeight(int x, int y, int z) ++ public byte getNsweNearest(int geoX, int geoY, int worldZ) + { +- return getNextLowerZ(getGeoX(x), getGeoY(y), z); ++ final ABlock block = getBlock(geoX, geoY); ++ return block != null ? block.getNsweNearest(geoX, geoY, worldZ) : (byte) 0xFF; + } + + /** +- * Gets the next higher Z height. +- * @param x the world x +- * @param y the world y +- * @param z the world z +- * @return the nearest Z height ++ * Check if world coordinates has geo. ++ * @param worldX : World X ++ * @param worldY : World Y ++ * @return boolean : True, if given world coordinates have geodata + */ +- public int getHigherHeight(int x, int y, int z) ++ public boolean hasGeo(int worldX, int worldY) + { +- return getNextHigherZ(getGeoX(x), getGeoY(y), z); ++ return hasGeoPos(getGeoX(worldX), getGeoY(worldY)); + } + + /** +- * @param worldX +- * @return the geo X ++ * 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 int getGeoX(int worldX) ++ public short getHeight(int worldX, int worldY, int worldZ) + { +- return (worldX - WORLD_MIN_X) / 16; ++ return getHeightNearest(getGeoX(worldX), getGeoY(worldY), worldZ); + } + +- /** +- * @param worldY +- * @return the geo Y +- */ +- public int getGeoY(int worldY) +- { +- return (worldY - WORLD_MIN_Y) / 16; +- } ++ // PATHFINDING + + /** +- * @param worldZ +- * @return the geo Z ++ * Check line of sight from {@link WorldObject} to {@link WorldObject}. ++ * @param origin : The origin object. ++ * @param target : The target object. ++ * @return {@code boolean} : True if origin can see target + */ +- public int getGeoZ(int worldZ) ++ public boolean canSeeTarget(WorldObject origin, WorldObject target) + { +- return (worldZ - WORLD_MIN_Z) / 16; +- } +- +- /** +- * @param geoX +- * @return the world X +- */ +- public int getWorldX(int geoX) +- { +- return (geoX * 16) + WORLD_MIN_X + 8; +- } +- +- /** +- * @param geoY +- * @return the world Y +- */ +- public int getWorldY(int geoY) +- { +- return (geoY * 16) + WORLD_MIN_Y + 8; +- } +- +- /** +- * @param geoZ +- * @return the world Z +- */ +- public int getWorldZ(int geoZ) +- { +- return (geoZ * 16) + WORLD_MIN_Z + 8; +- } +- +- /** +- * 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) +- { +- if (target.isDoor()) ++ if (target.isDoor() || target.isArtefact() || (target.isCreature() && ((Creature) target).isFlying())) + { +- // Can always see doors. + return true; + } +- return 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(), cha.getInstanceWorld(), worldPosition.getX(), worldPosition.getY(), worldPosition.getZ()); +- } +- +- /** +- * Can see target. Checks doors between. +- * @param x the x coordinate +- * @param y the y coordinate +- * @param z the z coordinate +- * @param world +- * @param tx the target's x coordinate +- * @param ty the target's y coordinate +- * @param tz the target's z coordinate +- * @param tworld the target's instanceId +- * @return +- */ +- public boolean canSeeTarget(int x, int y, int z, Instance world, int tx, int ty, int tz, Instance tworld) +- { +- return (world != tworld) ? false : canSeeTarget(x, y, z, world, tx, ty, tz); +- } +- +- /** +- * 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 +- * @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, Instance instance, int tx, int ty, int tz) +- { +- if (DoorData.getInstance().checkIfDoorsBetween(x, y, z, tx, ty, tz, instance, true)) ++ ++ // get origin and target world coordinates ++ final int ox = origin.getX(); ++ final int oy = origin.getY(); ++ final int oz = origin.getZ(); ++ final int tx = target.getX(); ++ final int ty = target.getY(); ++ final int tz = target.getZ(); ++ ++ if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld(), true)) + { + return false; + } +- return canSeeTarget(x, y, z, tx, ty, tz); +- } +- +- /** +- * @param prevX +- * @param prevY +- * @param prevGeoZ +- * @param curX +- * @param curY +- * @param nswe +- * @return the LOS Z value +- */ +- 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))) ++ if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld())) + { +- throw new RuntimeException("Multiple directions!"); ++ return false; + } ++ if (WarpedSpaceManager.getInstance().checkForWarpedSpace(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld())) ++ { ++ return false; ++ } + +- if (checkNearestNsweAntiCornerCut(prevX, prevY, prevGeoZ, nswe)) ++ // get origin and check existing geo coordinates ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) + { +- return getNearestZ(curX, curY, prevGeoZ); ++ return true; + } + +- return getNextHigherZ(curX, curY, prevGeoZ); ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coordinates ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) ++ { ++ return true; ++ } ++ ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // origin and target coordinates are same ++ if ((gox == gtx) && (goy == gty)) ++ { ++ return goz == gtz; ++ } ++ ++ // get origin and target height, real height = collision height * 2 ++ double oheight = 0; ++ if (origin.isCreature()) ++ { ++ oheight = ((Creature) origin).getCollisionHeight() * 2; ++ } ++ ++ double theight = 0; ++ if (target.isCreature()) ++ { ++ theight = ((Creature) target).getCollisionHeight() * 2; ++ } ++ ++ // perform geodata check ++ return checkSee(gox, goy, goz, oheight, gtx, gty, gtz, theight, origin.getInstanceWorld()); + } + + /** +- * Can see target. Does not check doors between. +- * @param xValue the x coordinate +- * @param yValue the y coordinate +- * @param zValue the z coordinate +- * @param txValue the target's x coordinate +- * @param tyValue the target's y coordinate +- * @param tzValue the target's z coordinate +- * @return {@code true} if there is line of sight between the given coordinate sets, {@code false} otherwise ++ * Check line of sight from {@link WorldObject} to {@link Location}. ++ * @param origin : The origin object. ++ * @param position : The target position. ++ * @return {@code boolean} : True if object can see position + */ +- public boolean canSeeTarget(int xValue, int yValue, int zValue, int txValue, int tyValue, int tzValue) ++ public boolean canSeeTarget(WorldObject origin, Location position) + { +- int x = xValue; +- int y = yValue; +- int tx = txValue; +- int ty = tyValue; ++ // get origin and target world coordinates ++ final int ox = origin.getX(); ++ final int oy = origin.getY(); ++ final int oz = origin.getZ(); ++ final int tx = position.getX(); ++ final int ty = position.getY(); ++ final int tz = position.getZ(); + +- int geoX = getGeoX(x); +- int geoY = getGeoY(y); +- int tGeoX = getGeoX(tx); +- int tGeoY = getGeoY(ty); ++ if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld(), true)) ++ { ++ return false; ++ } ++ if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld())) ++ { ++ return false; ++ } ++ if (WarpedSpaceManager.getInstance().checkForWarpedSpace(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld())) ++ { ++ return false; ++ } + +- int z = getNearestZ(geoX, geoY, zValue); +- int tz = getNearestZ(tGeoX, tGeoY, tzValue); ++ // get origin and check existing geo coordinates ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) ++ { ++ return true; ++ } + +- // Fastpath. +- if ((geoX == tGeoX) && (geoY == tGeoY)) ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coordinates ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) + { +- if (hasGeoPos(tGeoX, tGeoY)) +- { +- return z == tz; +- } + return true; + } + +- if (tz > z) ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // origin and target coordinates are same ++ if ((gox == gtx) && (goy == gty)) + { +- int tmp = tx; +- tx = x; +- x = tmp; +- +- tmp = ty; +- ty = y; +- y = tmp; +- +- tmp = tz; +- tz = z; +- z = tmp; +- +- tmp = tGeoX; +- tGeoX = geoX; +- geoX = tmp; +- +- tmp = tGeoY; +- tGeoY = geoY; +- geoY = tmp; ++ return goz == gtz; + } + +- final LinePointIterator3D pointIter = new LinePointIterator3D(geoX, geoY, z, tGeoX, tGeoY, tz); +- // 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()) ++ // get origin and target height, real height = collision height * 2 ++ double oheight = 0; ++ if (origin.isCreature()) + { +- final int curX = pointIter.x(); +- final int curY = pointIter.y(); ++ oheight = ((Creature) origin).getTemplate().getCollisionHeight(); ++ } ++ ++ // perform geodata check ++ return checkSee(gox, goy, goz, oheight, gtx, gty, gtz, 0, origin.getInstanceWorld()); ++ } ++ ++ /** ++ * Simple check for origin to target visibility. ++ * @param goxValue : origin X geodata coordinate ++ * @param goyValue : origin Y geodata coordinate ++ * @param gozValue : origin Z geodata coordinate ++ * @param oheight : origin height (if instance of {@link Character}) ++ * @param gtxValue : target X geodata coordinate ++ * @param gtyValue : target Y geodata coordinate ++ * @param gtzValue : target Z geodata coordinate ++ * @param theight : target height (if instance of {@link Character}) ++ * @param instance ++ * @return {@code boolean} : True, when target can be seen. ++ */ ++ private final boolean checkSee(int goxValue, int goyValue, int gozValue, double oheight, int gtxValue, int gtyValue, int gtzValue, double theight, Instance instance) ++ { ++ int goz = gozValue; ++ int gtz = gtzValue; ++ int gox = goxValue; ++ int goy = goyValue; ++ int gtx = gtxValue; ++ int gty = gtyValue; ++ ++ // get line of sight Z coordinates ++ double losoz = goz + ((oheight * Config.PART_OF_CHARACTER_HEIGHT) / 100); ++ double lostz = gtz + ((theight * Config.PART_OF_CHARACTER_HEIGHT) / 100); ++ ++ // get X delta and signum ++ final int dx = Math.abs(gtx - gox); ++ final int sx = gox < gtx ? 1 : -1; ++ final byte dirox = sx > 0 ? GeoStructure.CELL_FLAG_E : GeoStructure.CELL_FLAG_W; ++ final byte dirtx = sx > 0 ? GeoStructure.CELL_FLAG_W : GeoStructure.CELL_FLAG_E; ++ ++ // get Y delta and signum ++ final int dy = Math.abs(gty - goy); ++ final int sy = goy < gty ? 1 : -1; ++ final byte diroy = sy > 0 ? GeoStructure.CELL_FLAG_S : GeoStructure.CELL_FLAG_N; ++ final byte dirty = sy > 0 ? GeoStructure.CELL_FLAG_N : GeoStructure.CELL_FLAG_S; ++ ++ // get Z delta ++ final int dm = Math.max(dx, dy); ++ final double dz = (lostz - losoz) / dm; ++ ++ // get direction flag for diagonal movement ++ final byte diroxy = getDirXY(dirox, diroy); ++ final byte dirtxy = getDirXY(dirtx, dirty); ++ ++ // delta, determines axis to move on (+..X axis, -..Y axis) ++ int d = dx - dy; ++ ++ // NSWE direction of movement ++ byte diro; ++ byte dirt; ++ ++ // initialize node values ++ int nox = gox; ++ int noy = goy; ++ int ntx = gtx; ++ int nty = gty; ++ byte nsweo = getNsweNearest(gox, goy, goz); ++ byte nswet = getNsweNearest(gtx, gty, gtz); ++ ++ // loop ++ ABlock block; ++ int index; ++ for (int i = 0; i < ((dm + 1) / 2); i++) ++ { ++ // reset direction flag ++ diro = 0; ++ dirt = 0; + +- if ((curX == prevX) && (curY == prevY)) ++ // calculate next point coordinates ++ final int e2 = 2 * d; ++ if ((e2 > -dy) && (e2 < dx)) + { +- continue; ++ // calculate next point XY coordinates ++ d -= dy; ++ d += dx; ++ nox += sx; ++ ntx -= sx; ++ noy += sy; ++ nty -= sy; ++ diro |= diroxy; ++ dirt |= dirtxy; + } ++ else if (e2 > -dy) ++ { ++ // calculate next point X coordinate ++ d -= dy; ++ nox += sx; ++ ntx -= sx; ++ diro |= dirox; ++ dirt |= dirtx; ++ } ++ else if (e2 < dx) ++ { ++ // calculate next point Y coordinate ++ d += dx; ++ noy += sy; ++ nty -= sy; ++ diro |= diroy; ++ dirt |= dirty; ++ } + +- final int beeCurZ = pointIter.z(); +- int curGeoZ = prevGeoZ; +- +- // Check if the position has geodata. +- if (hasGeoPos(curX, curY)) + { +- final int beeCurGeoZ = getNearestZ(curX, curY, beeCurZ); +- final int nswe = GeoUtils.computeNswe(prevX, prevY, curX, curY); // .computeDirection(prevX, prevY, curX, curY); +- curGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, curX, curY, nswe); +- int maxHeight; +- if (ptIndex < ELEVATED_SEE_OVER_DISTANCE) ++ // get block of the next cell ++ block = getBlock(nox, noy); ++ ++ // get index of particular layer, based on movement conditions ++ if ((nsweo & diro) == 0) + { +- maxHeight = z + MAX_SEE_OVER_HEIGHT; ++ index = block.getIndexAbove(nox, noy, goz - GeoStructure.CELL_IGNORE_HEIGHT); + } + else + { +- maxHeight = beeCurZ + MAX_SEE_OVER_HEIGHT; ++ index = block.getIndexBelow(nox, noy, goz + GeoStructure.CELL_IGNORE_HEIGHT); + } + +- boolean canSeeThrough = false; +- if ((curGeoZ <= maxHeight) && (curGeoZ <= beeCurGeoZ)) ++ // layer does not exist, return ++ if (index == -1) + { +- if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) +- { +- 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 +- { +- canSeeThrough = true; +- } ++ return false; + } + +- if (!canSeeThrough) ++ // get layer and next line of sight Z coordinate ++ goz = block.getHeight(index); ++ losoz += dz; ++ ++ // perform line of sight check, return when fails ++ if ((goz - losoz) > Config.MAX_OBSTACLE_HEIGHT) + { + return false; + } ++ ++ // get layer nswe ++ nsweo = block.getNswe(index); + } ++ { ++ // get block of the next cell ++ block = getBlock(ntx, nty); ++ ++ // get index of particular layer, based on movement conditions ++ if ((nswet & dirt) == 0) ++ { ++ index = block.getIndexAbove(ntx, nty, gtz - GeoStructure.CELL_IGNORE_HEIGHT); ++ } ++ else ++ { ++ index = block.getIndexBelow(ntx, nty, gtz + GeoStructure.CELL_IGNORE_HEIGHT); ++ } ++ ++ // layer does not exist, return ++ if (index == -1) ++ { ++ return false; ++ } ++ ++ // get layer and next line of sight Z coordinate ++ gtz = block.getHeight(index); ++ lostz -= dz; ++ ++ // perform line of sight check, return when fails ++ if ((gtz - lostz) > Config.MAX_OBSTACLE_HEIGHT) ++ { ++ return false; ++ } ++ ++ // get layer nswe ++ nswet = block.getNswe(index); ++ } + +- prevX = curX; +- prevY = curY; +- prevGeoZ = curGeoZ; +- ++ptIndex; ++ // update coords ++ gox = nox; ++ goy = noy; ++ gtx = ntx; ++ gty = nty; + } + +- return true; ++ // when iteration is completed, compare final Z coordinates ++ return Math.abs(goz - gtz) < (GeoStructure.CELL_HEIGHT * 4); + } + + /** +- * Move check. +- * @param x the x coordinate +- * @param y the y coordinate +- * @param zValue the z coordinate +- * @param tx the target's x coordinate +- * @param ty the target's y coordinate +- * @param tzValue the target's z coordinate +- * @param instance the instance +- * @return the last Location (x,y,z) where player can walk - just before wall ++ * Check movement from coordinates to 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 {code boolean} : True if target coordinates are reachable from origin coordinates + */ +- public Location canMoveToTargetLoc(int x, int y, int zValue, int tx, int ty, int tzValue, Instance instance) ++ public boolean canMoveToTarget(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + { +- final int geoX = getGeoX(x); +- final int geoY = getGeoY(y); +- final int z = getNearestZ(geoX, geoY, zValue); +- final int tGeoX = getGeoX(tx); +- final int tGeoY = getGeoY(ty); +- final int tz = getNearestZ(tGeoX, tGeoY, tzValue); ++ // get origin and check existing geo coordinates ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) ++ { ++ return true; ++ } + +- if (DoorData.getInstance().checkIfDoorsBetween(x, y, z, tx, ty, tz, instance, false)) ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coordinates ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) + { +- return new Location(x, y, getHeight(x, y, z)); ++ return true; + } +- if (FenceData.getInstance().checkIfFenceBetween(x, y, z, tx, ty, tz, instance)) ++ ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // target coordinates reached ++ if ((gox == gtx) && (goy == gty) && (goz == gtz)) + { +- return new Location(x, y, getHeight(x, y, z)); ++ return true; + } +- if (WarpedSpaceManager.getInstance().checkForWarpedSpace(x, y, z, tx, ty, tz, instance)) ++ ++ // perform geodata check ++ final GeoLocation loc = checkMove(gox, goy, goz, gtx, gty, gtz, instance); ++ return (loc.getGeoX() == gtx) && (loc.getGeoY() == gty); ++ } ++ ++ /** ++ * Check movement from origin to target. Returns last available point in the checked path. ++ * @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 {@link Location} : Last point where object can walk (just before wall) ++ */ ++ public Location canMoveToTargetLoc(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) ++ { ++ // Mobius: Double check for doors before normal checkMove to avoid exploiting key movement. ++ if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, instance, false)) + { +- return new Location(x, y, getHeight(x, y, z)); ++ return new Location(ox, oy, oz); + } ++ if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, instance)) ++ { ++ return new Location(ox, oy, oz); ++ } + +- 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 = z; ++ // get origin and check existing geo coordinates ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) ++ { ++ return new Location(tx, ty, tz); ++ } + +- while (pointIter.next()) ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coordinates ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) + { +- final int curX = pointIter.x(); +- final int curY = pointIter.y(); +- final int curZ = getNearestZ(curX, curY, prevZ); +- +- if (hasGeoPos(prevX, prevY)) +- { +- final int nswe = GeoUtils.computeNswe(prevX, prevY, curX, curY); +- if (!checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, nswe)) +- { +- // Can't move, return previous location. +- return new Location(getWorldX(prevX), getWorldY(prevY), prevZ); +- } +- } +- +- prevX = curX; +- prevY = curY; +- prevZ = curZ; ++ return new Location(tx, ty, tz); + } + +- if (hasGeoPos(prevX, prevY) && (prevZ != tz)) ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // target coordinates reached ++ if ((gox == gtx) && (goy == gty) && (goz == gtz)) + { +- // Different floors, return start location. +- return new Location(x, y, z); ++ return new Location(tx, ty, tz); + } + +- return new Location(tx, ty, tz); ++ // perform geodata check ++ return checkMove(gox, goy, goz, gtx, gty, gtz, instance); + } + + /** +- * 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 fromZvalue 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 toZvalue 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 ++ * With this method you can check if a position is visible or can be reached by beeline movement.
++ * 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 gox : origin X geodata coordinate ++ * @param goy : origin Y geodata coordinate ++ * @param goz : origin Z geodata coordinate ++ * @param gtx : target X geodata coordinate ++ * @param gty : target Y geodata coordinate ++ * @param gtz : target Z geodata coordinate ++ * @param instance ++ * @return {@link GeoLocation} : The last allowed point of movement. + */ +- public boolean canMoveToTarget(int fromX, int fromY, int fromZvalue, int toX, int toY, int toZvalue, Instance instance) ++ protected final GeoLocation checkMove(int gox, int goy, int goz, int gtx, int gty, int gtz, Instance instance) + { +- final int geoX = getGeoX(fromX); +- final int geoY = getGeoY(fromY); +- final int fromZ = getNearestZ(geoX, geoY, fromZvalue); +- final int tGeoX = getGeoX(toX); +- final int tGeoY = getGeoY(toY); +- final int toZ = getNearestZ(tGeoX, tGeoY, toZvalue); +- +- if (DoorData.getInstance().checkIfDoorsBetween(fromX, fromY, fromZ, toX, toY, toZ, instance, false)) ++ if (DoorData.getInstance().checkIfDoorsBetween(gox, goy, goz, gtx, gty, gtz, instance, false)) + { +- return false; ++ return new GeoLocation(gox, goy, goz); + } +- if (FenceData.getInstance().checkIfFenceBetween(fromX, fromY, fromZ, toX, toY, toZ, instance)) ++ if (FenceData.getInstance().checkIfFenceBetween(gox, goy, goz, gtx, gty, gtz, instance)) + { +- return false; ++ return new GeoLocation(gox, goy, goz); + } +- if (WarpedSpaceManager.getInstance().checkForWarpedSpace(fromX, fromY, fromZ, toX, toY, toZ, instance)) ++ if (WarpedSpaceManager.getInstance().checkForWarpedSpace(gox, goy, goz, gtx, gty, gtz, instance)) + { +- return false; ++ return new GeoLocation(gox, goy, goz); + } + +- 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 = fromZ; ++ // get X delta, signum and direction flag ++ final int dx = Math.abs(gtx - gox); ++ final int sx = gox < gtx ? 1 : -1; ++ final byte dirX = sx > 0 ? GeoStructure.CELL_FLAG_E : GeoStructure.CELL_FLAG_W; + +- while (pointIter.next()) ++ // get Y delta, signum and direction flag ++ final int dy = Math.abs(gty - goy); ++ final int sy = goy < gty ? 1 : -1; ++ final byte dirY = sy > 0 ? GeoStructure.CELL_FLAG_S : GeoStructure.CELL_FLAG_N; ++ ++ // get direction flag for diagonal movement ++ final byte dirXY = getDirXY(dirX, dirY); ++ ++ // delta, determines axis to move on (+..X axis, -..Y axis) ++ int d = dx - dy; ++ ++ // NSWE direction of movement ++ byte direction; ++ ++ // load pointer coordinates ++ int gpx = gox; ++ int gpy = goy; ++ int gpz = goz; ++ ++ // load next pointer ++ int nx = gpx; ++ int ny = gpy; ++ ++ // loop ++ int count = 0; ++ while (count++ < Config.MAX_ITERATIONS) + { +- final int curX = pointIter.x(); +- final int curY = pointIter.y(); +- final int curZ = getNearestZ(curX, curY, prevZ); ++ direction = 0; + +- if (hasGeoPos(prevX, prevY)) ++ // calculate next point coordinates ++ final int e2 = 2 * d; ++ if ((e2 > -dy) && (e2 < dx)) + { +- final int nswe = GeoUtils.computeNswe(prevX, prevY, curX, curY); +- if (!checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, nswe)) ++ d -= dy; ++ d += dx; ++ nx += sx; ++ ny += sy; ++ direction |= dirXY; ++ } ++ else if (e2 > -dy) ++ { ++ d -= dy; ++ nx += sx; ++ direction |= dirX; ++ } ++ else if (e2 < dx) ++ { ++ d += dx; ++ ny += sy; ++ direction |= dirY; ++ } ++ ++ // obstacle found, return ++ if ((getNsweNearest(gpx, gpy, gpz) & direction) == 0) ++ { ++ return new GeoLocation(gpx, gpy, gpz); ++ } ++ ++ // update pointer coordinates ++ gpx = nx; ++ gpy = ny; ++ gpz = getHeightNearest(nx, ny, gpz); ++ ++ // target coordinates reached ++ if ((gpx == gtx) && (gpy == gty)) ++ { ++ if (gpz == gtz) + { +- return false; ++ // path found, Z coordinates are okay, return target point ++ return new GeoLocation(gtx, gty, gtz); + } ++ ++ // path found, Z coordinates are not okay, return last good point ++ return new GeoLocation(gpx, gpy, gpz); + } ++ } ++ ++ return new GeoLocation(gox, goy, goz); ++ } ++ ++ /** ++ * Returns diagonal NSWE flag format of combined two NSWE flags. ++ * @param dirX : X direction NSWE flag ++ * @param dirY : Y direction NSWE flag ++ * @return byte : NSWE flag of combined direction ++ */ ++ private static byte getDirXY(byte dirX, byte dirY) ++ { ++ // check axis directions ++ if (dirY == GeoStructure.CELL_FLAG_N) ++ { ++ if (dirX == GeoStructure.CELL_FLAG_W) ++ { ++ return GeoStructure.CELL_FLAG_NW; ++ } + +- prevX = curX; +- prevY = curY; +- prevZ = curZ; ++ return GeoStructure.CELL_FLAG_NE; + } + +- if (hasGeoPos(prevX, prevY) && (prevZ != toZ)) ++ if (dirX == GeoStructure.CELL_FLAG_W) + { +- // Different floors. +- return false; ++ return GeoStructure.CELL_FLAG_SW; + } + +- return true; ++ return GeoStructure.CELL_FLAG_SE; + } + ++ /** ++ * 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 ++ */ ++ public List findPath(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) ++ { ++ return null; ++ } ++ ++ /** ++ * Returns the instance of the {@link GeoEngine}. ++ * @return {@link GeoEngine} : The instance. ++ */ + public static GeoEngine getInstance() + { + return SingletonHolder.INSTANCE; +@@ -752,6 +947,6 @@ + + private static class SingletonHolder + { +- protected static final GeoEngine INSTANCE = new GeoEngine(); ++ protected static final GeoEngine INSTANCE = Config.PATHFINDING ? new GeoEnginePathfinding() : new GeoEngine(); + } +-} ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/GeoEnginePathfinding.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/GeoEnginePathfinding.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/GeoEnginePathfinding.java (working copy) +@@ -20,88 +20,84 @@ + import java.util.LinkedList; + import java.util.List; + import java.util.ListIterator; +-import java.util.logging.Level; +-import java.util.logging.Logger; + + import org.l2jmobius.Config; +-import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; +-import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +-import org.l2jmobius.gameserver.geoengine.pathfinding.CellNode; +-import org.l2jmobius.gameserver.geoengine.pathfinding.CellNodeBuffer; +-import org.l2jmobius.gameserver.geoengine.pathfinding.NodeLoc; +-import org.l2jmobius.gameserver.model.World; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoLocation; ++import org.l2jmobius.gameserver.geoengine.pathfinding.Node; ++import org.l2jmobius.gameserver.geoengine.pathfinding.NodeBuffer; ++import org.l2jmobius.gameserver.model.Location; + import org.l2jmobius.gameserver.model.instancezone.Instance; + + /** +- * @author -Nemesiss- ++ * @author Hasha + */ +-public class GeoEnginePathfinding ++final class GeoEnginePathfinding extends GeoEngine + { +- private static final Logger LOGGER = Logger.getLogger(GeoEnginePathfinding.class.getName()); ++ // pre-allocated buffers ++ private final BufferHolder[] _buffers; + +- private BufferInfo[] _buffers; +- + protected GeoEnginePathfinding() + { +- try ++ super(); ++ ++ final String[] array = Config.PATHFIND_BUFFERS.split(";"); ++ _buffers = new BufferHolder[array.length]; ++ int count = 0; ++ for (int i = 0; i < array.length; i++) + { +- final String[] array = Config.PATHFIND_BUFFERS.split(";"); ++ final String buf = array[i]; ++ final String[] args = buf.split("x"); + +- _buffers = new BufferInfo[array.length]; +- +- String buf; +- String[] args; +- for (int i = 0; i < array.length; i++) ++ try + { +- buf = array[i]; +- args = buf.split("x"); +- if (args.length != 2) +- { +- throw new Exception("Invalid buffer definition: " + buf); +- } +- +- _buffers[i] = new BufferInfo(Integer.parseInt(args[0]), Integer.parseInt(args[1])); ++ final int size = Integer.parseInt(args[1]); ++ count += size; ++ _buffers[i] = new BufferHolder(Integer.parseInt(args[0]), size); + } ++ catch (Exception e) ++ { ++ LOGGER.warning("GeoEnginePathfinding: Can not load buffer setting: " + buf); ++ } + } +- catch (Exception e) +- { +- LOGGER.log(Level.WARNING, "CellPathFinding: Problem during buffer init: " + e.getMessage(), e); +- throw new Error("CellPathFinding: load aborted"); +- } ++ ++ LOGGER.info("GeoEnginePathfinding: Loaded " + count + " node buffers."); + } + +- public boolean pathNodesExist(short regionoffset) ++ @Override ++ public List findPath(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + { +- return false; +- } +- +- public List findPath(int x, int y, int z, int tx, int ty, int tz, Instance instance) +- { +- final int gx = GeoEngine.getInstance().getGeoX(x); +- final int gy = GeoEngine.getInstance().getGeoY(y); +- if (!GeoEngine.getInstance().hasGeo(x, y)) ++ // get origin and check existing geo coords ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) + { + 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)) ++ ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coords ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) + { + 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)))); ++ ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // Prepare buffer for pathfinding calculations ++ final NodeBuffer buffer = getBuffer(64 + (2 * Math.max(Math.abs(gox - gtx), Math.abs(goy - gty)))); + if (buffer == null) + { + return null; + } + +- List path = null; ++ // find path ++ List path = null; + try + { +- final CellNode result = buffer.findPath(gx, gy, gz, gtx, gty, gtz); +- ++ final Node result = buffer.findPath(gox, goy, goz, gtx, gty, gtz); + if (result == null) + { + return null; +@@ -125,33 +121,45 @@ + return path; + } + +- int currentX, currentY, currentZ; +- ListIterator middlePoint; ++ // get path list iterator ++ final ListIterator point = path.listIterator(); + +- middlePoint = path.listIterator(); +- currentX = x; +- currentY = y; +- currentZ = z; ++ // get node A (origin) ++ int nodeAx = gox; ++ int nodeAy = goy; ++ short nodeAz = goz; + +- while (middlePoint.hasNext()) ++ // get node B ++ GeoLocation nodeB = (GeoLocation) point.next(); ++ ++ // iterate thought the path to optimize it ++ int count = 0; ++ while (point.hasNext() && (count++ < Config.MAX_ITERATIONS)) + { +- final AbstractNodeLoc locMiddle = middlePoint.next(); +- if (!middlePoint.hasNext()) +- { +- break; +- } ++ // get node C ++ final GeoLocation nodeC = (GeoLocation) path.get(point.nextIndex()); + +- final AbstractNodeLoc locEnd = path.get(middlePoint.nextIndex()); +- if (GeoEngine.getInstance().canMoveToTarget(currentX, currentY, currentZ, locEnd.getX(), locEnd.getY(), locEnd.getZ(), instance)) ++ // check movement from node A to node C ++ final GeoLocation loc = checkMove(nodeAx, nodeAy, nodeAz, nodeC.getGeoX(), nodeC.getGeoY(), nodeC.getZ(), instance); ++ if ((loc.getGeoX() == nodeC.getGeoX()) && (loc.getGeoY() == nodeC.getGeoY())) + { +- middlePoint.remove(); ++ // can move from node A to node C ++ ++ // remove node B ++ point.remove(); + } + else + { +- currentX = locMiddle.getX(); +- currentY = locMiddle.getY(); +- currentZ = locMiddle.getZ(); ++ // can not move from node A to node C ++ ++ // set node A (node B is part of path, update A coordinates) ++ nodeAx = nodeB.getGeoX(); ++ nodeAy = nodeB.getGeoY(); ++ nodeAz = (short) nodeB.getZ(); + } ++ ++ // set node B ++ nodeB = (GeoLocation) point.next(); + } + + return path; +@@ -158,144 +166,102 @@ + } + + /** +- * Convert geodata position to pathnode position +- * @param geo_pos +- * @return pathnode position ++ * Create list of node locations as result of calculated buffer node tree. ++ * @param node : the entry point ++ * @return List : list of node location + */ +- public short getNodePos(int geo_pos) ++ private static List constructPath(Node node) + { +- return (short) (geo_pos >> 3); // OK? +- } +- +- /** +- * Convert node position to pathnode block position +- * @param node_pos +- * @return pathnode block position (0...255) +- */ +- public short getNodeBlock(int node_pos) +- { +- return (short) (node_pos % 256); +- } +- +- public byte getRegionX(int node_pos) +- { +- return (byte) ((node_pos >> 8) + World.TILE_X_MIN); +- } +- +- public byte getRegionY(int node_pos) +- { +- return (byte) ((node_pos >> 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 node_x rx +- * @return +- */ +- public int calculateWorldX(short node_x) +- { +- return World.MAP_MIN_X + (node_x * 128) + 48; +- } +- +- /** +- * Convert pathnode y to World y position +- * @param node_y +- * @return +- */ +- public int calculateWorldY(short node_y) +- { +- return World.MAP_MIN_Y + (node_y * 128) + 48; +- } +- +- private List constructPath(AbstractNode nodeValue) +- { +- final LinkedList path = new LinkedList<>(); +- int previousDirectionX = Integer.MIN_VALUE; +- int previousDirectionY = Integer.MIN_VALUE; +- int directionX, directionY; ++ // create empty list ++ final LinkedList list = new LinkedList<>(); + +- AbstractNode node = nodeValue; +- while (node.getParent() != null) ++ // set direction X/Y ++ int dx = 0; ++ int dy = 0; ++ ++ // get target parent ++ Node target = node; ++ Node parent = target.getParent(); ++ ++ // while parent exists ++ int count = 0; ++ while ((parent != null) && (count++ < Config.MAX_ITERATIONS)) + { +- directionX = node.getLoc().getNodeX() - node.getParent().getLoc().getNodeX(); +- directionY = node.getLoc().getNodeY() - node.getParent().getLoc().getNodeY(); ++ // get parent <> target direction X/Y ++ final int nx = parent.getLoc().getGeoX() - target.getLoc().getGeoX(); ++ final int ny = parent.getLoc().getGeoY() - target.getLoc().getGeoY(); + +- // only add a new route point if moving direction changes +- if ((directionX != previousDirectionX) || (directionY != previousDirectionY)) ++ // direction has changed? ++ if ((dx != nx) || (dy != ny)) + { +- previousDirectionX = directionX; +- previousDirectionY = directionY; ++ // add node to the beginning of the list ++ list.addFirst(target.getLoc()); + +- path.addFirst(node.getLoc()); +- node.setLoc(null); ++ // update direction X/Y ++ dx = nx; ++ dy = ny; + } + +- node = node.getParent(); ++ // move to next node, set target and get its parent ++ target = parent; ++ parent = target.getParent(); + } + +- return path; ++ // return list ++ return list; + } + +- private CellNodeBuffer alloc(int size) ++ /** ++ * Provides optimize selection of the buffer. When all pre-initialized buffer are locked, creates new buffer. ++ * @param size : pre-calculated minimal required size ++ * @return NodeBuffer : buffer ++ */ ++ private final NodeBuffer getBuffer(int size) + { +- CellNodeBuffer current = null; +- for (BufferInfo i : _buffers) ++ NodeBuffer current = null; ++ for (BufferHolder holder : _buffers) + { +- if (i.mapSize >= size) ++ // Find proper size of buffer ++ if (holder._size < size) + { +- for (CellNodeBuffer buf : i.buffer) ++ continue; ++ } ++ ++ // Find unlocked NodeBuffer ++ for (NodeBuffer buffer : holder._buffer) ++ { ++ if (!buffer.isLocked()) + { +- if (buf.lock()) +- { +- current = buf; +- break; +- } ++ continue; + } +- if (current != null) +- { +- break; +- } + +- // not found, allocate temporary buffer +- current = new CellNodeBuffer(i.mapSize); +- current.lock(); +- if (i.buffer.size() < i.count) +- { +- i.buffer.add(current); +- break; +- } ++ return buffer; + } ++ ++ // NodeBuffer not found, allocate temporary buffer ++ current = new NodeBuffer(holder._size); ++ current.isLocked(); + } + + return current; + } + +- private static final class BufferInfo ++ /** ++ * NodeBuffer container with specified size and count of separate buffers. ++ */ ++ private static final class BufferHolder + { +- final int mapSize; +- final int count; +- ArrayList buffer; ++ final int _size; ++ List _buffer; + +- public BufferInfo(int size, int cnt) ++ public BufferHolder(int size, int count) + { +- mapSize = size; +- count = cnt; +- buffer = new ArrayList<>(count); ++ _size = size; ++ _buffer = new ArrayList<>(count); ++ for (int i = 0; i < count; i++) ++ { ++ _buffer.add(new NodeBuffer(size)); ++ } + } + } +- +- public static GeoEnginePathfinding getInstance() +- { +- return SingletonHolder.INSTANCE; +- } +- +- private static class SingletonHolder +- { +- protected static final GeoEnginePathfinding INSTANCE = new GeoEnginePathfinding(); +- } +-} ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java (working copy) +@@ -0,0 +1,197 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++import java.io.IOException; ++ ++/** ++ * @author Hasha ++ */ ++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 height of cell, which is closest to given coordinates.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @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 getHeightNearestOriginal(int geoX, int geoY, int worldZ); ++ ++ /** ++ * Returns the height of cell, which is first above 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, above given coordinates. ++ */ ++ public abstract short getHeightAbove(int geoX, int geoY, int worldZ); ++ ++ /** ++ * Returns the height of cell, which is first below 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, below given coordinates. ++ */ ++ public abstract short getHeightBelow(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 the NSWE flag byte of cell, which is closest to given coordinates.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @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 getNsweNearestOriginal(int geoX, int geoY, int worldZ); ++ ++ /** ++ * Returns the NSWE flag byte of cell, which is first above 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 getNsweAbove(int geoX, int geoY, int worldZ); ++ ++ /** ++ * Returns the NSWE flag byte of cell, which is first below 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 getNsweBelow(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 above given coordinates.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @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 getIndexAboveOriginal(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 index to data of the cell, which is first layer below given coordinates.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @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 getIndexBelowOriginal(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 height of cell given by cell index.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @param index : Index of the cell. ++ * @return short : Cell geodata Z coordinate, below given coordinates. ++ */ ++ public abstract short getHeightOriginal(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); ++ ++ /** ++ * Returns the NSWE flag byte of cell given by cell index.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @param index : Index of the cell. ++ * @return short : Cell geodata Z coordinate, below given coordinates. ++ */ ++ public abstract byte getNsweOriginal(int index); ++ ++ /** ++ * Sets the NSWE flag byte of cell given by cell index. ++ * @param index : Index of the cell. ++ * @param nswe : New NSWE flag byte. ++ */ ++ public abstract void setNswe(int index, byte nswe); ++ ++ /** ++ * Saves the block in L2D format to {@link BufferedOutputStream}. Used only for L2D geodata conversion. ++ * @param stream : The stream. ++ * @throws IOException : Can't save the block to steam. ++ */ ++ public abstract void saveBlock(BufferedOutputStream stream) throws IOException; ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java (working copy) +@@ -0,0 +1,252 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++import java.io.IOException; ++import java.nio.ByteBuffer; ++ ++/** ++ * @author Hasha ++ */ ++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. ++ * @param format : GeoFormat specifying format of loaded data. ++ */ ++ public BlockComplex(ByteBuffer bb, GeoFormat format) ++ { ++ // initialize buffer ++ _buffer = new byte[GeoStructure.BLOCK_CELLS * 3]; ++ ++ // load data ++ for (int i = 0; i < GeoStructure.BLOCK_CELLS; i++) ++ { ++ if (format != GeoFormat.L2D) ++ { ++ // 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); ++ } ++ else ++ { ++ // get nswe ++ final byte nswe = bb.get(); ++ _buffer[i * 3] = nswe; ++ ++ // get height ++ final short height = bb.getShort(); ++ _buffer[(i * 3) + 1] = (byte) (height & 0x00FF); ++ _buffer[(i * 3) + 2] = (byte) (height >> 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 short getHeightNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getHeightNearest(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeightAbove(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 short height = (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ ++ // check and return height ++ return height > worldZ ? height : Short.MIN_VALUE; ++ } ++ ++ @Override ++ public short getHeightBelow(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 short height = (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ ++ // check and return height ++ return height < worldZ ? height : Short.MAX_VALUE; ++ } ++ ++ @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 byte getNsweNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getNsweNearest(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public byte getNsweAbove(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 ? _buffer[index] : 0; ++ } ++ ++ @Override ++ public byte getNsweBelow(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 ? _buffer[index] : 0; ++ } ++ ++ @Override ++ public 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 getIndexAboveOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexAbove(geoX, geoY, worldZ); ++ } ++ ++ @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 int getIndexBelowOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexBelow(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeight(int index) ++ { ++ return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ } ++ ++ @Override ++ public short getHeightOriginal(int index) ++ { ++ return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ } ++ ++ @Override ++ public byte getNswe(int index) ++ { ++ return _buffer[index]; ++ } ++ ++ @Override ++ public byte getNsweOriginal(int index) ++ { ++ return _buffer[index]; ++ } ++ ++ @Override ++ public void setNswe(int index, byte nswe) ++ { ++ _buffer[index] = nswe; ++ } ++ ++ @Override ++ public void saveBlock(BufferedOutputStream stream) throws IOException ++ { ++ // write block type ++ stream.write(GeoStructure.TYPE_COMPLEX_L2D); ++ ++ // write block data ++ stream.write(_buffer, 0, GeoStructure.BLOCK_CELLS * 3); ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java (working copy) +@@ -0,0 +1,176 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++import java.io.IOException; ++import java.nio.ByteBuffer; ++ ++/** ++ * @author Hasha ++ */ ++public class BlockFlat extends ABlock ++{ ++ protected final short _height; ++ protected byte _nswe; ++ ++ /** ++ * Creates FlatBlock. ++ * @param bb : Input byte buffer. ++ * @param format : GeoFormat specifying format of loaded data. ++ */ ++ public BlockFlat(ByteBuffer bb, GeoFormat format) ++ { ++ _height = bb.getShort(); ++ _nswe = format != GeoFormat.L2D ? 0x0F : (byte) (0xFF); ++ if (format == GeoFormat.L2OFF) ++ { ++ bb.getShort(); ++ } ++ } ++ ++ @Override ++ public boolean hasGeoPos() ++ { ++ return true; ++ } ++ ++ @Override ++ public short getHeightNearest(int geoX, int geoY, int worldZ) ++ { ++ return _height; ++ } ++ ++ @Override ++ public short getHeightNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return _height; ++ } ++ ++ @Override ++ public short getHeightAbove(int geoX, int geoY, int worldZ) ++ { ++ // check and return height ++ return _height > worldZ ? _height : Short.MIN_VALUE; ++ } ++ ++ @Override ++ public short getHeightBelow(int geoX, int geoY, int worldZ) ++ { ++ // check and return height ++ return _height < worldZ ? _height : Short.MAX_VALUE; ++ } ++ ++ @Override ++ public byte getNsweNearest(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweAbove(int geoX, int geoY, int worldZ) ++ { ++ // check height and return nswe ++ return _height > worldZ ? _nswe : 0; ++ } ++ ++ @Override ++ public byte getNsweBelow(int geoX, int geoY, int worldZ) ++ { ++ // check height and return nswe ++ return _height < worldZ ? _nswe : 0; ++ } ++ ++ @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 getIndexAboveOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexAbove(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public int getIndexBelow(int geoX, int geoY, int worldZ) ++ { ++ // check height and return index ++ return _height < worldZ ? 0 : -1; ++ } ++ ++ @Override ++ public int getIndexBelowOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexBelow(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeight(int index) ++ { ++ return _height; ++ } ++ ++ @Override ++ public short getHeightOriginal(int index) ++ { ++ return _height; ++ } ++ ++ @Override ++ public byte getNswe(int index) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweOriginal(int index) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public void setNswe(int index, byte nswe) ++ { ++ _nswe = nswe; ++ } ++ ++ @Override ++ public void saveBlock(BufferedOutputStream stream) throws IOException ++ { ++ // write block type ++ stream.write(GeoStructure.TYPE_FLAT_L2D); ++ ++ // write height ++ stream.write((byte) (_height & 0x00FF)); ++ stream.write((byte) (_height >> 8)); ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java (working copy) +@@ -0,0 +1,465 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++import java.io.IOException; ++import java.nio.ByteBuffer; ++import java.nio.ByteOrder; ++import java.util.Arrays; ++ ++/** ++ * @author Hasha ++ */ ++public class BlockMultilayer extends ABlock ++{ ++ private static final int MAX_LAYERS = Byte.MAX_VALUE; ++ ++ private static ByteBuffer _temp; ++ ++ /** ++ * Initializes the temporarily buffer. ++ */ ++ public static void initialize() ++ { ++ // initialize temporarily buffer and sorting mechanism ++ _temp = ByteBuffer.allocate(GeoStructure.BLOCK_CELLS * MAX_LAYERS * 3); ++ _temp.order(ByteOrder.LITTLE_ENDIAN); ++ } ++ ++ /** ++ * Releases temporarily buffer. ++ */ ++ public static void release() ++ { ++ _temp = null; ++ } ++ ++ protected byte[] _buffer; ++ ++ /** ++ * Implicit constructor for children class. ++ */ ++ protected BlockMultilayer() ++ { ++ _buffer = null; ++ } ++ ++ /** ++ * Creates MultilayerBlock. ++ * @param bb : Input byte buffer. ++ * @param format : GeoFormat specifying format of loaded data. ++ */ ++ public BlockMultilayer(ByteBuffer bb, GeoFormat format) ++ { ++ // 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 = format != GeoFormat.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++) ++ { ++ if (format != GeoFormat.L2D) ++ { ++ // get data ++ final short data = bb.getShort(); ++ ++ // add nswe and height ++ _temp.put((byte) (data & 0x000F)); ++ _temp.putShort((short) ((short) (data & 0xFFF0) >> 1)); ++ } ++ else ++ { ++ // add nswe ++ _temp.put(bb.get()); ++ ++ // add height ++ _temp.putShort(bb.getShort()); ++ } ++ } ++ } ++ ++ // 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 short getHeightNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getHeightNearest(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeightAbove(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 height ++ if (height > worldZ) ++ { ++ return (short) height; ++ } ++ ++ // move index to next layer ++ index -= 3; ++ } ++ ++ // none layer found, return minimum value ++ return Short.MIN_VALUE; ++ } ++ ++ @Override ++ public short getHeightBelow(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 height ++ if (height < worldZ) ++ { ++ return (short) height; ++ } ++ ++ // move index to next layer ++ index += 3; ++ } ++ ++ // none layer found, return maximum value ++ return Short.MAX_VALUE; ++ } ++ ++ @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 byte getNsweNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getNsweNearest(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public byte getNsweAbove(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 nswe ++ if (height > worldZ) ++ { ++ return _buffer[index]; ++ } ++ ++ // move index to next layer ++ index -= 3; ++ } ++ ++ // none layer found, block movement ++ return 0; ++ } ++ ++ @Override ++ public byte getNsweBelow(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 nswe ++ if (height < worldZ) ++ { ++ return _buffer[index]; ++ } ++ ++ // move index to next layer ++ index += 3; ++ } ++ ++ // none layer found, block movement ++ return 0; ++ } ++ ++ @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 first layer data (first from bottom) ++ byte layers = _buffer[index++]; ++ ++ // loop though all cell layers, find closest layer ++ 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; ++ } ++ ++ // none layer found ++ return -1; ++ } ++ ++ @Override ++ public int getIndexAboveOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexAbove(geoX, geoY, worldZ); ++ } ++ ++ @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; ++ } ++ ++ // none layer found ++ return -1; ++ } ++ ++ @Override ++ public int getIndexBelowOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexBelow(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeight(int index) ++ { ++ // get height ++ return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ } ++ ++ @Override ++ public short getHeightOriginal(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]; ++ } ++ ++ @Override ++ public byte getNsweOriginal(int index) ++ { ++ // get nswe ++ return _buffer[index]; ++ } ++ ++ @Override ++ public void setNswe(int index, byte nswe) ++ { ++ // set nswe ++ _buffer[index] = nswe; ++ } ++ ++ @Override ++ public void saveBlock(BufferedOutputStream stream) throws IOException ++ { ++ // write block type ++ stream.write(GeoStructure.TYPE_MULTILAYER_L2D); ++ ++ // for each cell ++ int index = 0; ++ for (int i = 0; i < GeoStructure.BLOCK_CELLS; i++) ++ { ++ // write layers count ++ final byte layers = _buffer[index++]; ++ stream.write(layers); ++ ++ // write cell data ++ stream.write(_buffer, index, layers * 3); ++ ++ // move index to next cell ++ index += layers * 3; ++ } ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java (working copy) +@@ -0,0 +1,150 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++ ++/** ++ * @author Hasha ++ */ ++public class BlockNull extends ABlock ++{ ++ private final byte _nswe; ++ ++ public BlockNull() ++ { ++ _nswe = (byte) 0xFF; ++ } ++ ++ @Override ++ public boolean hasGeoPos() ++ { ++ return false; ++ } ++ ++ @Override ++ public short getHeightNearest(int geoX, int geoY, int worldZ) ++ { ++ return (short) worldZ; ++ } ++ ++ @Override ++ public short getHeightNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return (short) worldZ; ++ } ++ ++ @Override ++ public short getHeightAbove(int geoX, int geoY, int worldZ) ++ { ++ return (short) worldZ; ++ } ++ ++ @Override ++ public short getHeightBelow(int geoX, int geoY, int worldZ) ++ { ++ return (short) worldZ; ++ } ++ ++ @Override ++ public byte getNsweNearest(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweAbove(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweBelow(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) ++ { ++ return 0; ++ } ++ ++ @Override ++ public int getIndexAboveOriginal(int geoX, int geoY, int worldZ) ++ { ++ return 0; ++ } ++ ++ @Override ++ public int getIndexBelow(int geoX, int geoY, int worldZ) ++ { ++ return 0; ++ } ++ ++ @Override ++ public int getIndexBelowOriginal(int geoX, int geoY, int worldZ) ++ { ++ return 0; ++ } ++ ++ @Override ++ public short getHeight(int index) ++ { ++ return 0; ++ } ++ ++ @Override ++ public short getHeightOriginal(int index) ++ { ++ return 0; ++ } ++ ++ @Override ++ public byte getNswe(int index) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweOriginal(int index) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public void setNswe(int index, byte nswe) ++ { ++ } ++ ++ @Override ++ public void saveBlock(BufferedOutputStream stream) ++ { ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java (nonexistent) +@@ -1,48 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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() +- { +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/ComplexBlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/ComplexBlock.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/ComplexBlock.java (nonexistent) +@@ -1,77 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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; +- +-/** +- * @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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/FlatBlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/FlatBlock.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/FlatBlock.java (nonexistent) +@@ -1,56 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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; +- +-/** +- * @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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/GeoFormat.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/GeoFormat.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/GeoFormat.java (working copy) +@@ -0,0 +1,39 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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 Hasha ++ */ ++public enum GeoFormat ++{ ++ L2J("%d_%d.l2j"), ++ L2OFF("%d_%d_conv.dat"), ++ L2D("%d_%d.l2d"); ++ ++ private final String _filename; ++ ++ private GeoFormat(String filename) ++ { ++ _filename = filename; ++ } ++ ++ public String getFilename() ++ { ++ return _filename; ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/GeoLocation.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/GeoLocation.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/GeoLocation.java (working copy) +@@ -0,0 +1,67 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.geoengine.GeoEngine; ++import org.l2jmobius.gameserver.model.Location; ++ ++/** ++ * @author Hasha ++ */ ++public class GeoLocation extends Location ++{ ++ private byte _nswe; ++ ++ public GeoLocation(int x, int y, int z) ++ { ++ super(x, y, GeoEngine.getInstance().getHeightNearest(x, y, z)); ++ _nswe = GeoEngine.getInstance().getNsweNearest(x, y, z); ++ } ++ ++ public void set(int x, int y, short z) ++ { ++ super.setXYZ(x, y, GeoEngine.getInstance().getHeightNearest(x, y, z)); ++ _nswe = GeoEngine.getInstance().getNsweNearest(x, y, z); ++ } ++ ++ public int getGeoX() ++ { ++ return _x; ++ } ++ ++ public int getGeoY() ++ { ++ return _y; ++ } ++ ++ @Override ++ public int getX() ++ { ++ return GeoEngine.getWorldX(_x); ++ } ++ ++ @Override ++ public int getY() ++ { ++ return GeoEngine.getWorldY(_y); ++ } ++ ++ public byte getNSWE() ++ { ++ return _nswe; ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java (working copy) +@@ -0,0 +1,70 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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; ++ ++/** ++ * @author Hasha ++ */ ++public class GeoStructure ++{ ++ // cells ++ public static final byte CELL_FLAG_E = 1 << 0; ++ public static final byte CELL_FLAG_W = 1 << 1; ++ public static final byte CELL_FLAG_S = 1 << 2; ++ public static final byte CELL_FLAG_N = 1 << 3; ++ public static final byte CELL_FLAG_SE = 1 << 4; ++ public static final byte CELL_FLAG_SW = 1 << 5; ++ public static final byte CELL_FLAG_NE = 1 << 6; ++ public static final byte CELL_FLAG_NW = (byte) (1 << 7); ++ ++ public static final int CELL_HEIGHT = 8; ++ public static final int CELL_IGNORE_HEIGHT = CELL_HEIGHT * 6; ++ ++ // blocks ++ public static final byte TYPE_FLAT_L2J_L2OFF = 0; ++ public static final byte TYPE_FLAT_L2D = (byte) 0xD0; ++ public static final byte TYPE_COMPLEX_L2J = 1; ++ public static final byte TYPE_COMPLEX_L2OFF = 0x40; ++ public static final byte TYPE_COMPLEX_L2D = (byte) 0xD1; ++ 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) ++ public static final byte TYPE_MULTILAYER_L2D = (byte) 0xD2; ++ ++ 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; ++ ++ // regions ++ 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; ++ ++ // global geodata ++ private static final int GEO_REGIONS_X = ((World.TILE_X_MAX - World.TILE_X_MIN) + 1); ++ private 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 +Index: java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java (nonexistent) +@@ -1,42 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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); +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/IGeoObject.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/IGeoObject.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/IGeoObject.java (working copy) +@@ -0,0 +1,53 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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 Hasha ++ */ ++public interface IGeoObject ++{ ++ /** ++ * Returns geodata X coordinate of the {@link IGeoObject}. ++ * @return int : Geodata X coordinate. ++ */ ++ int getGeoX(); ++ ++ /** ++ * Returns geodata Y coordinate of the {@link IGeoObject}. ++ * @return int : Geodata Y coordinate. ++ */ ++ int getGeoY(); ++ ++ /** ++ * Returns geodata Z coordinate of the {@link IGeoObject}. ++ * @return int : Geodata Z coordinate. ++ */ ++ int getGeoZ(); ++ ++ /** ++ * Returns height of the {@link IGeoObject}. ++ * @return int : Height. ++ */ ++ int getHeight(); ++ ++ /** ++ * Returns {@link IGeoObject} data. ++ * @return byte[][] : {@link IGeoObject} data. ++ */ ++ byte[][] getObjectGeoData(); ++} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java (nonexistent) +@@ -1,47 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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(); +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/MultilayerBlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/MultilayerBlock.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/MultilayerBlock.java (nonexistent) +@@ -1,183 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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; +- +-/** +- * @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); +- +- // 1 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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/NullRegion.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/NullRegion.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/NullRegion.java (nonexistent) +@@ -1,55 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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 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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/Region.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/Region.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/Region.java (nonexistent) +@@ -1,92 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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; +- +-/** +- * @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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java (nonexistent) +@@ -1,87 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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 T _loc; +- private AbstractNode _parent; +- +- public AbstractNode(T loc) +- { +- _loc = loc; +- } +- +- public void setParent(AbstractNode p) +- { +- _parent = p; +- } +- +- public AbstractNode getParent() +- { +- return _parent; +- } +- +- public T getLoc() +- { +- return _loc; +- } +- +- public void setLoc(T l) +- { +- _loc = l; +- } +- +- @Override +- public int hashCode() +- { +- final int prime = 31; +- int result = 1; +- result = (prime * result) + ((_loc == null) ? 0 : _loc.hashCode()); +- return result; +- } +- +- @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 +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java (nonexistent) +@@ -1,33 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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 int getNodeX(); +- +- public abstract int getNodeY(); +-} +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNode.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNode.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNode.java (nonexistent) +@@ -1,67 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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 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 +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNodeBuffer.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNodeBuffer.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNodeBuffer.java (nonexistent) +@@ -1,348 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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.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 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) +- { +- _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(); +- } +- +- 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 void getNeighbors() +- { +- if (!_current.getLoc().canGoAll()) +- { +- 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); +- } +- +- // SouthEast +- if ((nodeE != null) && (nodeS != null)) +- { +- if (nodeE.getLoc().canGoSouth() && nodeS.getLoc().canGoEast()) +- { +- addNode(x + 1, y + 1, z, true); +- } +- } +- +- // SouthWest +- if ((nodeS != null) && (nodeW != null)) +- { +- if (nodeW.getLoc().canGoSouth() && nodeS.getLoc().canGoWest()) +- { +- addNode(x - 1, y + 1, z, true); +- } +- } +- +- // NorthEast +- if ((nodeN != null) && (nodeE != null)) +- { +- if (nodeE.getLoc().canGoNorth() && nodeN.getLoc().canGoEast()) +- { +- addNode(x + 1, y - 1, z, true); +- } +- } +- +- // NorthWest +- if ((nodeN != null) && (nodeW != null)) +- { +- if (nodeW.getLoc().canGoNorth() && nodeN.getLoc().canGoWest()) +- { +- addNode(x - 1, y - 1, z, true); +- } +- } +- } +- +- private 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(); +- // reinit node if needed +- if (result.getLoc() != null) +- { +- result.getLoc().set(x, y, z); +- } +- else +- { +- result.setLoc(new NodeLoc(x, y, z)); +- } +- } +- +- return result; +- } +- +- private 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 boolean isHighWeight(int x, int y, int z) +- { +- final CellNode result = getNode(x, y, z); +- if (result == null) +- { +- return true; +- } +- +- if (!result.getLoc().canGoAll()) +- { +- return true; +- } +- if (Math.abs(result.getLoc().getZ() - z) > 16) +- { +- return true; +- } +- +- return false; +- } +- +- private 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 +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java (working copy) +@@ -0,0 +1,87 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.geodata.GeoLocation; ++ ++/** ++ * @author Hasha ++ */ ++public class Node ++{ ++ // node coords and nswe flag ++ private GeoLocation _loc; ++ ++ // node parent (for reverse path construction) ++ private Node _parent; ++ // node child (for moving over nodes during iteration) ++ private Node _child; ++ ++ // node G cost (movement cost = parent movement cost + current movement cost) ++ private double _cost = -1000; ++ ++ public void setLoc(int x, int y, int z) ++ { ++ _loc = new GeoLocation(x, y, z); ++ } ++ ++ public GeoLocation getLoc() ++ { ++ return _loc; ++ } ++ ++ public void setParent(Node parent) ++ { ++ _parent = parent; ++ } ++ ++ public Node getParent() ++ { ++ return _parent; ++ } ++ ++ public void setChild(Node child) ++ { ++ _child = child; ++ } ++ ++ public Node getChild() ++ { ++ return _child; ++ } ++ ++ public void setCost(double cost) ++ { ++ _cost = cost; ++ } ++ ++ public double getCost() ++ { ++ return _cost; ++ } ++ ++ public void free() ++ { ++ // reset node location ++ _loc = null; ++ ++ // reset node parent, child and cost ++ _parent = null; ++ _child = null; ++ _cost = -1000; ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java (working copy) +@@ -0,0 +1,306 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.concurrent.locks.ReentrantLock; ++ ++import org.l2jmobius.Config; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; ++ ++/** ++ * @author DS, Hasha; Credits to Diamond ++ */ ++public class NodeBuffer ++{ ++ private final ReentrantLock _lock = new ReentrantLock(); ++ private final int _size; ++ private final Node[][] _buffer; ++ ++ // center coordinates ++ private int _cx = 0; ++ private int _cy = 0; ++ ++ // target coordinates ++ private int _gtx = 0; ++ private int _gty = 0; ++ private short _gtz = 0; ++ ++ private Node _current = null; ++ ++ /** ++ * Constructor of NodeBuffer. ++ * @param size : one dimension size of buffer ++ */ ++ public NodeBuffer(int size) ++ { ++ // set size ++ _size = size; ++ ++ // initialize buffer ++ _buffer = new Node[size][size]; ++ for (int x = 0; x < size; x++) ++ { ++ for (int y = 0; y < size; y++) ++ { ++ _buffer[x][y] = 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 Node : first node of path ++ */ ++ public Node findPath(int gox, int goy, short goz, int gtx, int gty, short gtz) ++ { ++ // set coordinates (middle of the line (gox,goy) - (gtx,gty), will be in the center of the buffer) ++ _cx = gox + ((gtx - gox - _size) / 2); ++ _cy = goy + ((gty - goy - _size) / 2); ++ ++ _gtx = gtx; ++ _gty = gty; ++ _gtz = gtz; ++ ++ _current = getNode(gox, goy, goz); ++ _current.setCost(getCostH(gox, goy, goz)); ++ ++ int count = 0; ++ do ++ { ++ // reached target? ++ if ((_current.getLoc().getGeoX() == _gtx) && (_current.getLoc().getGeoY() == _gty) && (Math.abs(_current.getLoc().getZ() - _gtz) < 8)) ++ { ++ return _current; ++ } ++ ++ // expand current node ++ expand(); ++ ++ // move pointer ++ _current = _current.getChild(); ++ } ++ while ((_current != null) && (count++ < Config.MAX_ITERATIONS)); ++ ++ return null; ++ } ++ ++ public boolean isLocked() ++ { ++ return _lock.tryLock(); ++ } ++ ++ public void free() ++ { ++ _current = null; ++ ++ for (Node[] nodes : _buffer) ++ { ++ for (Node node : nodes) ++ { ++ if (node.getLoc() != null) ++ { ++ node.free(); ++ } ++ } ++ } ++ ++ _lock.unlock(); ++ } ++ ++ /** ++ * Check _current Node and add its neighbors to the buffer. ++ */ ++ private final void expand() ++ { ++ // can't move anywhere, don't expand ++ final byte nswe = _current.getLoc().getNSWE(); ++ if (nswe == 0) ++ { ++ return; ++ } ++ ++ // get geo coords of the node to be expanded ++ final int x = _current.getLoc().getGeoX(); ++ final int y = _current.getLoc().getGeoY(); ++ final short z = (short) _current.getLoc().getZ(); ++ ++ // can move north, expand ++ if ((nswe & GeoStructure.CELL_FLAG_N) != 0) ++ { ++ addNode(x, y - 1, z, Config.BASE_WEIGHT); ++ } ++ ++ // can move south, expand ++ if ((nswe & GeoStructure.CELL_FLAG_S) != 0) ++ { ++ addNode(x, y + 1, z, Config.BASE_WEIGHT); ++ } ++ ++ // can move west, expand ++ if ((nswe & GeoStructure.CELL_FLAG_W) != 0) ++ { ++ addNode(x - 1, y, z, Config.BASE_WEIGHT); ++ } ++ ++ // can move east, expand ++ if ((nswe & GeoStructure.CELL_FLAG_E) != 0) ++ { ++ addNode(x + 1, y, z, Config.BASE_WEIGHT); ++ } ++ ++ // can move north-west, expand ++ if ((nswe & GeoStructure.CELL_FLAG_NW) != 0) ++ { ++ addNode(x - 1, y - 1, z, Config.DIAGONAL_WEIGHT); ++ } ++ ++ // can move north-east, expand ++ if ((nswe & GeoStructure.CELL_FLAG_NE) != 0) ++ { ++ addNode(x + 1, y - 1, z, Config.DIAGONAL_WEIGHT); ++ } ++ ++ // can move south-west, expand ++ if ((nswe & GeoStructure.CELL_FLAG_SW) != 0) ++ { ++ addNode(x - 1, y + 1, z, Config.DIAGONAL_WEIGHT); ++ } ++ ++ // can move south-east, expand ++ if ((nswe & GeoStructure.CELL_FLAG_SE) != 0) ++ { ++ addNode(x + 1, y + 1, z, Config.DIAGONAL_WEIGHT); ++ } ++ } ++ ++ /** ++ * Returns node, if it exists in buffer. ++ * @param x : node X coord ++ * @param y : node Y coord ++ * @param z : node Z coord ++ * @return Node : node, if exits in buffer ++ */ ++ private final Node getNode(int x, int y, short z) ++ { ++ // check node X out of coordinates ++ final int ix = x - _cx; ++ if ((ix < 0) || (ix >= _size)) ++ { ++ return null; ++ } ++ ++ // check node Y out of coordinates ++ final int iy = y - _cy; ++ if ((iy < 0) || (iy >= _size)) ++ { ++ return null; ++ } ++ ++ // get node ++ final Node result = _buffer[ix][iy]; ++ ++ // check and update ++ if (result.getLoc() == null) ++ { ++ result.setLoc(x, y, z); ++ } ++ ++ // return node ++ return result; ++ } ++ ++ /** ++ * Add node given by coordinates to the buffer. ++ * @param x : geo X coord ++ * @param y : geo Y coord ++ * @param z : geo Z coord ++ * @param weight : weight of movement to new node ++ */ ++ private final void addNode(int x, int y, short z, int weight) ++ { ++ // get node to be expanded ++ final Node node = getNode(x, y, z); ++ if (node == null) ++ { ++ return; ++ } ++ ++ // Z distance between nearby cells is higher than cell size ++ if (node.getLoc().getZ() > (z + (2 * GeoStructure.CELL_HEIGHT))) ++ { ++ return; ++ } ++ ++ // node was already expanded, return ++ if (node.getCost() >= 0) ++ { ++ return; ++ } ++ ++ node.setParent(_current); ++ if (node.getLoc().getNSWE() != (byte) 0xFF) ++ { ++ node.setCost(getCostH(x, y, node.getLoc().getZ()) + (weight * Config.OBSTACLE_MULTIPLIER)); ++ } ++ else ++ { ++ node.setCost(getCostH(x, y, node.getLoc().getZ()) + weight); ++ } ++ ++ Node current = _current; ++ int count = 0; ++ while ((current.getChild() != null) && (count < (Config.MAX_ITERATIONS * 4))) ++ { ++ count++; ++ if (current.getChild().getCost() > node.getCost()) ++ { ++ node.setChild(current.getChild()); ++ break; ++ } ++ current = current.getChild(); ++ } ++ ++ if (count >= (Config.MAX_ITERATIONS * 4)) ++ { ++ System.err.println("Pathfinding: too long loop detected, cost:" + node.getCost()); ++ } ++ ++ current.setChild(node); ++ } ++ ++ /** ++ * @param x : node X coord ++ * @param y : node Y coord ++ * @param i : node Z coord ++ * @return double : node cost ++ */ ++ private final double getCostH(int x, int y, int i) ++ { ++ final int dX = x - _gtx; ++ final int dY = y - _gty; ++ final int dZ = (i - _gtz) / GeoStructure.CELL_HEIGHT; ++ ++ // return (Math.abs(dX) + Math.abs(dY) + Math.abs(dZ)) * Config.HEURISTIC_WEIGHT; // Manhattan distance ++ return Math.sqrt((dX * dX) + (dY * dY) + (dZ * dZ)) * Config.HEURISTIC_WEIGHT; // Direct distance ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeLoc.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeLoc.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeLoc.java (nonexistent) +@@ -1,183 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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.Cell; +- +-/** +- * @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 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 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; +- // return super.hashCode(); +- } +- +- @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; +- if (_x != other._x) +- { +- return false; +- } +- if (_y != other._y) +- { +- return false; +- } +- if (_goNorth != other._goNorth) +- { +- return false; +- } +- if (_goEast != other._goEast) +- { +- return false; +- } +- if (_goSouth != other._goSouth) +- { +- return false; +- } +- if (_goWest != other._goWest) +- { +- return false; +- } +- if (_geoHeight != other._geoHeight) +- { +- return false; +- } +- return true; +- } +-} +Index: java/org/l2jmobius/gameserver/model/actor/Creature.java +=================================================================== +--- java/org/l2jmobius/gameserver/model/actor/Creature.java (revision 8399) ++++ java/org/l2jmobius/gameserver/model/actor/Creature.java (working copy) +@@ -66,8 +66,6 @@ + import org.l2jmobius.gameserver.enums.TeleportWhereType; + import org.l2jmobius.gameserver.enums.UserInfoType; + import org.l2jmobius.gameserver.geoengine.GeoEngine; +-import org.l2jmobius.gameserver.geoengine.GeoEnginePathfinding; +-import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; + import org.l2jmobius.gameserver.instancemanager.IdManager; + import org.l2jmobius.gameserver.instancemanager.MapRegionManager; + import org.l2jmobius.gameserver.instancemanager.QuestManager; +@@ -2577,7 +2575,7 @@ + + public boolean disregardingGeodata; + public int onGeodataPathIndex; +- public List geoPath; ++ public List geoPath; + public int geoPathAccurateTx; + public int geoPathAccurateTy; + public int geoPathGtx; +@@ -3466,7 +3464,7 @@ + if (((originalDistance - distance) > 30) && !isControlBlocked() && !isInVehicle) + { + // Path calculation -- overrides previous movement check +- m.geoPath = GeoEnginePathfinding.getInstance().findPath(curX, curY, curZ, originalX, originalY, originalZ, getInstanceWorld()); ++ m.geoPath = GeoEngine.getInstance().findPath(curX, curY, curZ, originalX, originalY, originalZ, getInstanceWorld()); + if ((m.geoPath == null) || (m.geoPath.size() < 2)) // No path found + { + if (isPlayer() && !_isFlying && !isInWater) +Index: java/org/l2jmobius/gameserver/model/actor/instance/PlayerInstance.java +=================================================================== +--- java/org/l2jmobius/gameserver/model/actor/instance/PlayerInstance.java (revision 8397) ++++ java/org/l2jmobius/gameserver/model/actor/instance/PlayerInstance.java (working copy) +@@ -12704,7 +12704,7 @@ + { + if (Config.CORRECT_PLAYER_Z) + { +- final int nearestZ = GeoEngine.getInstance().getHigherHeight(getX(), getY(), getZ()); ++ final int nearestZ = GeoEngine.getInstance().getHeightNearest(getX(), getY(), getZ()); + if (getZ() < nearestZ) + { + teleToLocation(new Location(getX(), getY(), nearestZ)); +Index: java/org/l2jmobius/gameserver/util/GeoUtils.java +=================================================================== +--- java/org/l2jmobius/gameserver/util/GeoUtils.java (revision 8397) ++++ java/org/l2jmobius/gameserver/util/GeoUtils.java (working copy) +@@ -19,7 +19,7 @@ + import java.awt.Color; + + import org.l2jmobius.gameserver.geoengine.GeoEngine; +-import org.l2jmobius.gameserver.geoengine.geodata.Cell; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; + import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance; + import org.l2jmobius.gameserver.network.serverpackets.ExServerPrimitive; + +@@ -26,25 +26,25 @@ + /** + * @author HorridoJoho + */ +-public final class GeoUtils ++public class GeoUtils + { + public static void debug2DLine(PlayerInstance player, int x, int y, int tx, int ty, int z) + { +- final int gx = GeoEngine.getInstance().getGeoX(x); +- final int gy = GeoEngine.getInstance().getGeoY(y); ++ final int gx = GeoEngine.getGeoX(x); ++ final int gy = GeoEngine.getGeoY(y); + +- final int tgx = GeoEngine.getInstance().getGeoX(tx); +- final int tgy = GeoEngine.getInstance().getGeoY(ty); ++ final int tgx = GeoEngine.getGeoX(tx); ++ final int tgy = GeoEngine.getGeoY(ty); + + final ExServerPrimitive prim = new ExServerPrimitive("Debug2DLine", x, y, z); +- prim.addLine(Color.BLUE, GeoEngine.getInstance().getWorldX(gx), GeoEngine.getInstance().getWorldY(gy), z, GeoEngine.getInstance().getWorldX(tgx), GeoEngine.getInstance().getWorldY(tgy), z); ++ prim.addLine(Color.BLUE, GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), z, GeoEngine.getWorldX(tgx), GeoEngine.getWorldY(tgy), z); + + final LinePointIterator iter = new LinePointIterator(gx, gy, tgx, tgy); + + while (iter.next()) + { +- final int wx = GeoEngine.getInstance().getWorldX(iter.x()); +- final int wy = GeoEngine.getInstance().getWorldY(iter.y()); ++ final int wx = GeoEngine.getWorldX(iter.x()); ++ final int wy = GeoEngine.getWorldY(iter.y()); + + prim.addPoint(Color.RED, wx, wy, z); + } +@@ -53,21 +53,21 @@ + + public static void debug3DLine(PlayerInstance player, int x, int y, int z, int tx, int ty, int tz) + { +- final int gx = GeoEngine.getInstance().getGeoX(x); +- final int gy = GeoEngine.getInstance().getGeoY(y); ++ final int gx = GeoEngine.getGeoX(x); ++ final int gy = GeoEngine.getGeoY(y); + +- final int tgx = GeoEngine.getInstance().getGeoX(tx); +- final int tgy = GeoEngine.getInstance().getGeoY(ty); ++ final int tgx = GeoEngine.getGeoX(tx); ++ final int tgy = GeoEngine.getGeoY(ty); + + final ExServerPrimitive prim = new ExServerPrimitive("Debug3DLine", x, y, z); +- prim.addLine(Color.BLUE, GeoEngine.getInstance().getWorldX(gx), GeoEngine.getInstance().getWorldY(gy), z, GeoEngine.getInstance().getWorldX(tgx), GeoEngine.getInstance().getWorldY(tgy), tz); ++ prim.addLine(Color.BLUE, GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), z, GeoEngine.getWorldX(tgx), GeoEngine.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.getInstance().getWorldX(prevX); +- int wy = GeoEngine.getInstance().getWorldY(prevY); ++ int wx = GeoEngine.getWorldX(prevX); ++ int wy = GeoEngine.getWorldY(prevY); + int wz = iter.z(); + prim.addPoint(Color.RED, wx, wy, wz); + +@@ -78,8 +78,8 @@ + + if ((curX != prevX) || (curY != prevY)) + { +- wx = GeoEngine.getInstance().getWorldX(curX); +- wy = GeoEngine.getInstance().getWorldY(curY); ++ wx = GeoEngine.getWorldX(curX); ++ wy = GeoEngine.getWorldY(curY); + wz = iter.z(); + + prim.addPoint(Color.RED, wx, wy, wz); +@@ -93,7 +93,7 @@ + + private static Color getDirectionColor(int x, int y, int z, int nswe) + { +- if (GeoEngine.getInstance().checkNearestNswe(x, y, z, nswe)) ++ if ((GeoEngine.getInstance().getNsweNearest(x, y, z) & nswe) == nswe) + { + return Color.GREEN; + } +@@ -109,9 +109,8 @@ + int iPacket = 0; + + ExServerPrimitive exsp = null; +- final GeoEngine ge = GeoEngine.getInstance(); +- final int playerGx = ge.getGeoX(player.getX()); +- final int playerGy = ge.getGeoY(player.getY()); ++ final int playerGx = GeoEngine.getGeoX(player.getX()); ++ final int playerGy = GeoEngine.getGeoY(player.getY()); + for (int dx = -geoRadius; dx <= geoRadius; ++dx) + { + for (int dy = -geoRadius; dy <= geoRadius; ++dy) +@@ -135,12 +134,12 @@ + final int gx = playerGx + dx; + final int gy = playerGy + dy; + +- final int x = ge.getWorldX(gx); +- final int y = ge.getWorldY(gy); +- final int z = ge.getNearestZ(gx, gy, player.getZ()); ++ final int x = GeoEngine.getWorldX(gx); ++ final int y = GeoEngine.getWorldY(gy); ++ final int z = GeoEngine.getInstance().getHeightNearest(gx, gy, player.getZ()); + + // north arrow +- Color col = getDirectionColor(gx, gy, z, Cell.NSWE_NORTH); ++ Color col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_N); + 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); +@@ -147,7 +146,7 @@ + exsp.addLine(col, x - 4, y - 4, z, x + 4, y - 4, z); + + // east arrow +- col = getDirectionColor(gx, gy, z, Cell.NSWE_EAST); ++ col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_E); + 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); +@@ -154,13 +153,13 @@ + exsp.addLine(col, x + 4, y - 4, z, x + 4, y + 4, z); + + // south arrow +- col = getDirectionColor(gx, gy, z, Cell.NSWE_SOUTH); ++ col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_S); + 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, Cell.NSWE_WEST); ++ col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_W); + 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); +@@ -188,15 +187,15 @@ + { + if (y > lastY) + { +- return Cell.NSWE_SOUTH_EAST; ++ return GeoStructure.CELL_FLAG_SE; // Direction.SOUTH_EAST; + } + else if (y < lastY) + { +- return Cell.NSWE_NORTH_EAST; ++ return GeoStructure.CELL_FLAG_NE; // Direction.NORTH_EAST; + } + else + { +- return Cell.NSWE_EAST; ++ return GeoStructure.CELL_FLAG_E; // Direction.EAST; + } + } + else if (x < lastX) // west +@@ -203,26 +202,27 @@ + { + if (y > lastY) + { +- return Cell.NSWE_SOUTH_WEST; ++ return GeoStructure.CELL_FLAG_SW; // Direction.SOUTH_WEST; + } + else if (y < lastY) + { +- return Cell.NSWE_NORTH_WEST; ++ return GeoStructure.CELL_FLAG_NW; // Direction.NORTH_WEST; + } + else + { +- return Cell.NSWE_WEST; ++ return GeoStructure.CELL_FLAG_W; // Direction.WEST; + } + } +- else // unchanged x ++ else ++ // unchanged x + { + if (y > lastY) + { +- return Cell.NSWE_SOUTH; ++ return GeoStructure.CELL_FLAG_S; // Direction.SOUTH; + } + else if (y < lastY) + { +- return Cell.NSWE_NORTH; ++ return GeoStructure.CELL_FLAG_N; // Direction.NORTH; + } + else + { +Index: java/org/l2jmobius/tools/geodataconverter/GeoDataConverter.java +=================================================================== +--- java/org/l2jmobius/tools/geodataconverter/GeoDataConverter.java (nonexistent) ++++ java/org/l2jmobius/tools/geodataconverter/GeoDataConverter.java (working copy) +@@ -0,0 +1,386 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ */ ++package org.l2jmobius.tools.geodataconverter; ++ ++import java.io.BufferedOutputStream; ++import java.io.File; ++import java.io.FileOutputStream; ++import java.io.RandomAccessFile; ++import java.nio.ByteOrder; ++import java.nio.MappedByteBuffer; ++import java.nio.channels.FileChannel; ++import java.util.Scanner; ++ ++import org.l2jmobius.Config; ++import org.l2jmobius.commons.util.PropertiesParser; ++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.GeoFormat; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; ++import org.l2jmobius.gameserver.model.World; ++ ++/** ++ * @author Hasha ++ */ ++public class GeoDataConverter ++{ ++ private static GeoFormat _format; ++ private static ABlock[][] _blocks; ++ ++ public static void main(String[] args) ++ { ++ // initialize config ++ loadGeoengineConfigs(); ++ ++ // get geodata format ++ String type = ""; ++ try (Scanner scn = new Scanner(System.in)) ++ { ++ while (!(type.equalsIgnoreCase("J") || type.equalsIgnoreCase("O") || type.equalsIgnoreCase("E"))) ++ { ++ System.out.println("GeoDataConverter: Select source geodata type:"); ++ System.out.println(" J: L2J (e.g. 23_22.l2j)"); ++ System.out.println(" O: L2OFF (e.g. 23_22_conv.dat)"); ++ System.out.println(" E: Exit"); ++ System.out.print("Choice: "); ++ type = scn.next(); ++ } ++ } ++ if (type.equalsIgnoreCase("E")) ++ { ++ System.exit(0); ++ } ++ ++ _format = type.equalsIgnoreCase("J") ? GeoFormat.L2J : GeoFormat.L2OFF; ++ ++ // start conversion ++ System.out.println("GeoDataConverter: Converting all " + _format + " files."); ++ ++ // initialize geodata container ++ _blocks = new ABlock[GeoStructure.REGION_BLOCKS_X][GeoStructure.REGION_BLOCKS_Y]; ++ ++ // initialize multilayer temporarily buffer ++ BlockMultilayer.initialize(); ++ ++ // load geo files ++ int converted = 0; ++ for (int rx = World.TILE_X_MIN; rx <= World.TILE_X_MAX; rx++) ++ { ++ for (int ry = World.TILE_Y_MIN; ry <= World.TILE_Y_MAX; ry++) ++ { ++ final String input = String.format(_format.getFilename(), rx, ry); ++ final String filepath = Config.GEODATA_PATH; ++ final File f = new File(filepath + input); ++ if (f.exists() && !f.isDirectory()) ++ { ++ // load geodata ++ if (!loadGeoBlocks(input)) ++ { ++ System.out.println("GeoDataConverter: Unable to load " + input + " region file."); ++ continue; ++ } ++ ++ // recalculate nswe ++ if (!recalculateNswe()) ++ { ++ System.out.println("GeoDataConverter: Unable to convert " + input + " region file."); ++ continue; ++ } ++ ++ // save geodata ++ final String output = String.format(GeoFormat.L2D.getFilename(), rx, ry); ++ if (!saveGeoBlocks(output)) ++ { ++ System.out.println("GeoDataConverter: Unable to save " + output + " region file."); ++ continue; ++ } ++ ++ converted++; ++ System.out.println("GeoDataConverter: Created " + output + " region file."); ++ } ++ } ++ } ++ System.out.println("GeoDataConverter: Converted " + converted + " " + _format + " to L2D region file(s)."); ++ ++ // release multilayer block temporarily buffer ++ BlockMultilayer.release(); ++ } ++ ++ /** ++ * Loads geo blocks from buffer of the region file. ++ * @param filename : The name of the to load. ++ * @return boolean : True when successful. ++ */ ++ private static boolean loadGeoBlocks(String filename) ++ { ++ // region file is load-able, try to load it ++ try (RandomAccessFile raf = new RandomAccessFile(Config.GEODATA_PATH + filename, "r"); ++ FileChannel fc = raf.getChannel()) ++ { ++ final MappedByteBuffer buffer = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()).load(); ++ buffer.order(ByteOrder.LITTLE_ENDIAN); ++ ++ // load 18B header for L2off geodata (1st and 2nd byte...region X and Y) ++ if (_format == GeoFormat.L2OFF) ++ { ++ for (int i = 0; i < 18; i++) ++ { ++ buffer.get(); ++ } ++ } ++ ++ // 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 (_format == GeoFormat.L2J) ++ { ++ // get block type ++ final byte type = buffer.get(); ++ ++ // load block according to block type ++ switch (type) ++ { ++ case GeoStructure.TYPE_FLAT_L2J_L2OFF: ++ { ++ _blocks[ix][iy] = new BlockFlat(buffer, _format); ++ break; ++ } ++ case GeoStructure.TYPE_COMPLEX_L2J: ++ { ++ _blocks[ix][iy] = new BlockComplex(buffer, _format); ++ break; ++ } ++ case GeoStructure.TYPE_MULTILAYER_L2J: ++ { ++ _blocks[ix][iy] = new BlockMultilayer(buffer, _format); ++ break; ++ } ++ default: ++ { ++ throw new IllegalArgumentException("Unknown block type: " + type); ++ } ++ } ++ } ++ else ++ { ++ // get block type ++ final short type = buffer.getShort(); ++ ++ // load block according to block type ++ switch (type) ++ { ++ case GeoStructure.TYPE_FLAT_L2J_L2OFF: ++ { ++ _blocks[ix][iy] = new BlockFlat(buffer, _format); ++ break; ++ } ++ case GeoStructure.TYPE_COMPLEX_L2OFF: ++ { ++ _blocks[ix][iy] = new BlockComplex(buffer, _format); ++ break; ++ } ++ default: ++ { ++ _blocks[ix][iy] = new BlockMultilayer(buffer, _format); ++ break; ++ } ++ } ++ } ++ } ++ } ++ ++ if (buffer.remaining() > 0) ++ { ++ System.out.println("GeoDataConverter: Region file " + filename + " can be corrupted, remaining " + buffer.remaining() + " bytes to read."); ++ return false; ++ } ++ ++ return true; ++ } ++ catch (Exception e) ++ { ++ System.out.println("GeoDataConverter: Error while loading " + filename + " region file."); ++ return false; ++ } ++ } ++ ++ /** ++ * Recalculate diagonal flags for the region file. ++ * @return boolean : True when successful. ++ */ ++ private static boolean recalculateNswe() ++ { ++ try ++ { ++ for (int x = 0; x < GeoStructure.REGION_CELLS_X; x++) ++ { ++ for (int y = 0; y < GeoStructure.REGION_CELLS_Y; y++) ++ { ++ // get block ++ final ABlock block = _blocks[x / GeoStructure.BLOCK_CELLS_X][y / GeoStructure.BLOCK_CELLS_Y]; ++ ++ // skip flat blocks ++ if (block instanceof BlockFlat) ++ { ++ continue; ++ } ++ ++ // for complex and multilayer blocks go though all layers ++ short height = Short.MAX_VALUE; ++ int index; ++ while ((index = block.getIndexBelow(x, y, height)) != -1) ++ { ++ // get height and nswe ++ height = block.getHeight(index); ++ byte nswe = block.getNswe(index); ++ ++ // update nswe with diagonal flags ++ nswe = updateNsweBelow(x, y, height, nswe); ++ ++ // set nswe of the cell ++ block.setNswe(index, nswe); ++ } ++ } ++ } ++ ++ return true; ++ } ++ catch (Exception e) ++ { ++ return false; ++ } ++ } ++ ++ /** ++ * Updates the NSWE flag with diagonal flags. ++ * @param x : Geodata X coordinate. ++ * @param y : Geodata Y coordinate. ++ * @param z : Geodata Z coordinate. ++ * @param nsweValue : NSWE flag to be updated. ++ * @return byte : Updated NSWE flag. ++ */ ++ private static byte updateNsweBelow(int x, int y, short z, byte nsweValue) ++ { ++ byte nswe = nsweValue; ++ ++ // calculate virtual layer height ++ final short height = (short) (z + GeoStructure.CELL_IGNORE_HEIGHT); ++ ++ // get NSWE of neighbor cells below virtual layer (NPC/PC can fall down of clif, but can not climb it -> NSWE of cell below) ++ final byte nsweN = getNsweBelow(x, y - 1, height); ++ final byte nsweS = getNsweBelow(x, y + 1, height); ++ final byte nsweW = getNsweBelow(x - 1, y, height); ++ final byte nsweE = getNsweBelow(x + 1, y, height); ++ ++ // north-west ++ if ((((nswe & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) || (((nswe & GeoStructure.CELL_FLAG_W) != 0) && ((nsweW & GeoStructure.CELL_FLAG_N) != 0))) ++ { ++ nswe |= GeoStructure.CELL_FLAG_NW; ++ } ++ ++ // north-east ++ if ((((nswe & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) || (((nswe & GeoStructure.CELL_FLAG_E) != 0) && ((nsweE & GeoStructure.CELL_FLAG_N) != 0))) ++ { ++ nswe |= GeoStructure.CELL_FLAG_NE; ++ } ++ ++ // south-west ++ if ((((nswe & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) || (((nswe & GeoStructure.CELL_FLAG_W) != 0) && ((nsweW & GeoStructure.CELL_FLAG_S) != 0))) ++ { ++ nswe |= GeoStructure.CELL_FLAG_SW; ++ } ++ ++ // south-east ++ if ((((nswe & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) || (((nswe & GeoStructure.CELL_FLAG_E) != 0) && ((nsweE & GeoStructure.CELL_FLAG_S) != 0))) ++ { ++ nswe |= GeoStructure.CELL_FLAG_SE; ++ } ++ ++ return nswe; ++ } ++ ++ private static byte getNsweBelow(int geoX, int geoY, short worldZ) ++ { ++ // out of geo coordinates ++ if ((geoX < 0) || (geoX >= GeoStructure.REGION_CELLS_X)) ++ { ++ return 0; ++ } ++ ++ // out of geo coordinates ++ if ((geoY < 0) || (geoY >= GeoStructure.REGION_CELLS_Y)) ++ { ++ return 0; ++ } ++ ++ // get block ++ final ABlock block = _blocks[geoX / GeoStructure.BLOCK_CELLS_X][geoY / GeoStructure.BLOCK_CELLS_Y]; ++ ++ // get index, when valid, return nswe ++ final int index = block.getIndexBelow(geoX, geoY, worldZ); ++ return index == -1 ? 0 : block.getNswe(index); ++ } ++ ++ /** ++ * Save region file to file. ++ * @param filename : The name of file to save. ++ * @return boolean : True when successful. ++ */ ++ private static boolean saveGeoBlocks(String filename) ++ { ++ try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(Config.GEODATA_PATH + filename), GeoStructure.REGION_BLOCKS * GeoStructure.BLOCK_CELLS * 3)) ++ { ++ // loop over region blocks and save each block ++ for (int ix = 0; ix < GeoStructure.REGION_BLOCKS_X; ix++) ++ { ++ for (int iy = 0; iy < GeoStructure.REGION_BLOCKS_Y; iy++) ++ { ++ _blocks[ix][iy].saveBlock(bos); ++ } ++ } ++ ++ // flush data to file ++ bos.flush(); ++ ++ return true; ++ } ++ catch (Exception e) ++ { ++ return false; ++ } ++ } ++ ++ private static void loadGeoengineConfigs() ++ { ++ final PropertiesParser geoData = new PropertiesParser(Config.GEOENGINE_CONFIG_FILE); ++ Config.GEODATA_PATH = geoData.getString("GeoDataPath", "./data/geodata/"); ++ Config.COORD_SYNCHRONIZE = geoData.getInt("CoordSynchronize", -1); ++ Config.PART_OF_CHARACTER_HEIGHT = geoData.getInt("PartOfCharacterHeight", 75); ++ Config.MAX_OBSTACLE_HEIGHT = geoData.getInt("MaxObstacleHeight", 32); ++ Config.PATHFINDING = geoData.getBoolean("PathFinding", true); ++ Config.PATHFIND_BUFFERS = geoData.getString("PathFindBuffers", "100x6;128x6;192x6;256x4;320x4;384x4;500x2"); ++ Config.BASE_WEIGHT = geoData.getInt("BaseWeight", 10); ++ Config.DIAGONAL_WEIGHT = geoData.getInt("DiagonalWeight", 14); ++ Config.OBSTACLE_MULTIPLIER = geoData.getInt("ObstacleMultiplier", 10); ++ Config.HEURISTIC_WEIGHT = geoData.getInt("HeuristicWeight", 20); ++ Config.MAX_ITERATIONS = geoData.getInt("MaxIterations", 3500); ++ } ++} +\ No newline at end of file diff --git a/L2J_Mobius_2.5_Underground/dist/game/data/geodata/L2D_GeoEngine.patch b/L2J_Mobius_2.5_Underground/dist/game/data/geodata/L2D_GeoEngine.patch new file mode 100644 index 0000000000..63e2faf0c9 --- /dev/null +++ b/L2J_Mobius_2.5_Underground/dist/game/data/geodata/L2D_GeoEngine.patch @@ -0,0 +1,6052 @@ +Index: dist/game/GeoDataConverter.bat +=================================================================== +--- dist/game/GeoDataConverter.bat (nonexistent) ++++ dist/game/GeoDataConverter.bat (working copy) +@@ -0,0 +1,6 @@ ++@echo off ++title L2D geodata converter ++ ++java -Xmx512m -cp ./../libs/* org.l2jmobius.tools.geodataconverter.GeoDataConverter ++ ++pause +Index: dist/game/GeoDataConverter.sh +=================================================================== +--- dist/game/GeoDataConverter.sh (nonexistent) ++++ dist/game/GeoDataConverter.sh (working copy) +@@ -0,0 +1,4 @@ ++#! /bin/sh ++ ++java -Xmx512m -cp ../libs/*: org.l2jmobius.tools.geodataconverter.GeoDataConverter > log/stdout.log 2>&1 ++ +Index: dist/game/config/GeoEngine.ini +=================================================================== +--- dist/game/config/GeoEngine.ini (revision 8397) ++++ dist/game/config/GeoEngine.ini (working copy) +@@ -1,6 +1,10 @@ + # ================================================================= + # Geodata + # ================================================================= ++# Because of real-time performance we are using geodata files only in ++# diagonal L2D format now (using filename e.g. 22_16.l2d). ++# L2D geodata can be obtained by conversion of existing L2J or L2OFF geodata. ++# Launch "GeoDataConverter.bat/sh" and follow instructions to start the conversion. + + # 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/ +@@ -13,6 +17,16 @@ + CoordSynchronize = 2 + + # ================================================================= ++# Path checking ++# ================================================================= ++ ++# 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 finding + # ================================================================= + +@@ -23,19 +37,23 @@ + # Pathfinding array buffers configuration, default: 100x6;128x6;192x6;256x4;320x4;384x4;500x2 + PathFindBuffers = 100x6;128x6;192x6;256x4;320x4;384x4;500x2 + +-# Weight for nodes without obstacles far from walls +-LowWeight = 0.5 ++# Base path weight, when moving from one node to another on axis direction, default: 10 ++BaseWeight = 10 + +-# Weight for nodes near walls +-MediumWeight = 2 ++# Path weight, when moving from one node to another on diagonal direction, default: BaseWeight * sqrt(2) = 14 ++DiagonalWeight = 14 + +-# Weight for nodes with obstacles +-HighWeight = 3 ++# When movement flags of target node is blocked to any direction, multiply movement weight by this multiplier. ++# This causes pathfinding algorithm to avoid path construction exactly near an obstacle, default: 10 ++ObstacleMultiplier = 10 + +-# Weight for diagonal movement. +-# Default: LowWeight * sqrt(2) +-DiagonalWeight = 0.707 ++# Weight of the heuristic algorithm, which is giving estimated cost from node to target, default: 20 ++# For proper function must be higher than BaseWeight and/or DiagonalWeight. ++HeuristicWeight = 20 + ++# Maximum number of generated nodes per one path-finding process, default 3500 ++MaxIterations = 3500 ++ + # ================================================================= + # Other + # ================================================================= +Index: dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java +=================================================================== +--- dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java (revision 8397) ++++ dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java (working copy) +@@ -55,9 +55,8 @@ + final int worldX = activeChar.getX(); + final int worldY = activeChar.getY(); + final int worldZ = activeChar.getZ(); +- final int geoX = GeoEngine.getInstance().getGeoX(worldX); +- final int geoY = GeoEngine.getInstance().getGeoY(worldY); +- ++ final int geoX = GeoEngine.getGeoX(worldX); ++ final int geoY = GeoEngine.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)); +@@ -73,9 +72,8 @@ + final int worldX = activeChar.getX(); + final int worldY = activeChar.getY(); + final int worldZ = activeChar.getZ(); +- final int geoX = GeoEngine.getInstance().getGeoX(worldX); +- final int geoY = GeoEngine.getInstance().getGeoY(worldY); +- ++ final int geoX = GeoEngine.getGeoX(worldX); ++ final int geoY = GeoEngine.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)); +Index: dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java +=================================================================== +--- dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java (revision 8397) ++++ dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java (working copy) +@@ -18,11 +18,11 @@ + + import java.util.List; + +-import org.l2jmobius.Config; +-import org.l2jmobius.gameserver.geoengine.GeoEnginePathfinding; +-import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; ++import org.l2jmobius.gameserver.geoengine.GeoEngine; + import org.l2jmobius.gameserver.handler.IAdminCommandHandler; ++import org.l2jmobius.gameserver.model.Location; + import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance; ++import org.l2jmobius.gameserver.network.SystemMessageId; + import org.l2jmobius.gameserver.util.BuilderUtil; + + public class AdminPathNode implements IAdminCommandHandler +@@ -37,29 +37,30 @@ + { + if (command.equals("admin_path_find")) + { +- if (!Config.PATHFINDING) +- { +- BuilderUtil.sendSysMessage(activeChar, "PathFinding is disabled."); +- return true; +- } + if (activeChar.getTarget() != null) + { +- final List path = GeoEnginePathfinding.getInstance().findPath(activeChar.getX(), activeChar.getY(), (short) activeChar.getZ(), activeChar.getTarget().getX(), activeChar.getTarget().getY(), (short) activeChar.getTarget().getZ(), activeChar.getInstanceWorld()); ++ 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()); + if (path == null) + { +- BuilderUtil.sendSysMessage(activeChar, "No Route!"); +- return true; ++ BuilderUtil.sendSysMessage(activeChar, "No route found or pathfinding disabled."); + } +- for (AbstractNodeLoc a : path) ++ else + { +- BuilderUtil.sendSysMessage(activeChar, "x:" + a.getX() + " y:" + a.getY() + " z:" + a.getZ()); ++ for (Location point : path) ++ { ++ BuilderUtil.sendSysMessage(activeChar, "x:" + point.getX() + " y:" + point.getY() + " z:" + point.getZ()); ++ } + } + } + else + { +- BuilderUtil.sendSysMessage(activeChar, "No Target!"); ++ activeChar.sendPacket(SystemMessageId.INVALID_TARGET); + } + } ++ else ++ { ++ return false; ++ } + return true; + } + +Index: java/org/l2jmobius/Config.java +=================================================================== +--- java/org/l2jmobius/Config.java (revision 8397) ++++ java/org/l2jmobius/Config.java (working copy) +@@ -32,7 +32,6 @@ + import java.net.UnknownHostException; + import java.nio.charset.StandardCharsets; + import java.nio.file.Files; +-import java.nio.file.Path; + import java.nio.file.Paths; + import java.time.Duration; + import java.util.ArrayList; +@@ -927,14 +926,17 @@ + // -------------------------------------------------- + // GeoEngine + // -------------------------------------------------- +- public static Path GEODATA_PATH; ++ public static String GEODATA_PATH; ++ public static int COORD_SYNCHRONIZE; ++ public static int PART_OF_CHARACTER_HEIGHT; ++ public static int MAX_OBSTACLE_HEIGHT; + public static boolean PATHFINDING; + public static String PATHFIND_BUFFERS; +- public static float LOW_WEIGHT; +- public static float MEDIUM_WEIGHT; +- public static float HIGH_WEIGHT; +- public static float DIAGONAL_WEIGHT; +- public static int COORD_SYNCHRONIZE; ++ public static int BASE_WEIGHT; ++ public static int DIAGONAL_WEIGHT; ++ public static int HEURISTIC_WEIGHT; ++ public static int OBSTACLE_MULTIPLIER; ++ public static int MAX_ITERATIONS; + public static boolean CORRECT_PLAYER_Z; + + /** Attribute System */ +@@ -2468,14 +2470,17 @@ + + // Load GeoEngine config file (if exists) + final PropertiesParser GeoEngine = new PropertiesParser(GEOENGINE_CONFIG_FILE); +- GEODATA_PATH = Paths.get(GeoEngine.getString("GeoDataPath", "./data/geodata")); ++ GEODATA_PATH = GeoEngine.getString("GeoDataPath", "./data/geodata/"); ++ COORD_SYNCHRONIZE = GeoEngine.getInt("CoordSynchronize", -1); ++ PART_OF_CHARACTER_HEIGHT = GeoEngine.getInt("PartOfCharacterHeight", 75); ++ MAX_OBSTACLE_HEIGHT = GeoEngine.getInt("MaxObstacleHeight", 32); + PATHFINDING = GeoEngine.getBoolean("PathFinding", true); + PATHFIND_BUFFERS = GeoEngine.getString("PathFindBuffers", "100x6;128x6;192x6;256x4;320x4;384x4;500x2"); +- LOW_WEIGHT = GeoEngine.getFloat("LowWeight", 0.5f); +- MEDIUM_WEIGHT = GeoEngine.getFloat("MediumWeight", 2); +- HIGH_WEIGHT = GeoEngine.getFloat("HighWeight", 3); +- DIAGONAL_WEIGHT = GeoEngine.getFloat("DiagonalWeight", 0.707f); +- COORD_SYNCHRONIZE = GeoEngine.getInt("CoordSynchronize", -1); ++ BASE_WEIGHT = GeoEngine.getInt("BaseWeight", 10); ++ DIAGONAL_WEIGHT = GeoEngine.getInt("DiagonalWeight", 14); ++ OBSTACLE_MULTIPLIER = GeoEngine.getInt("ObstacleMultiplier", 10); ++ HEURISTIC_WEIGHT = GeoEngine.getInt("HeuristicWeight", 20); ++ MAX_ITERATIONS = GeoEngine.getInt("MaxIterations", 3500); + CORRECT_PLAYER_Z = GeoEngine.getBoolean("CorrectPlayerZ", false); + + // Load AllowedPlayerRaces config file (if exists) +Index: java/org/l2jmobius/gameserver/geoengine/GeoEngine.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/GeoEngine.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/GeoEngine.java (working copy) +@@ -16,101 +16,91 @@ + */ + package org.l2jmobius.gameserver.geoengine; + +-import java.io.IOException; ++import java.io.File; + import java.io.RandomAccessFile; + import java.nio.ByteOrder; +-import java.nio.channels.FileChannel.MapMode; +-import java.nio.file.Files; +-import java.nio.file.Path; +-import java.util.concurrent.atomic.AtomicReferenceArray; +-import java.util.logging.Level; ++import java.nio.MappedByteBuffer; ++import java.nio.channels.FileChannel; ++import java.util.List; + 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.geoengine.geodata.Cell; +-import org.l2jmobius.gameserver.geoengine.geodata.IRegion; +-import org.l2jmobius.gameserver.geoengine.geodata.NullRegion; +-import org.l2jmobius.gameserver.geoengine.geodata.Region; ++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.GeoFormat; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoLocation; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; + import org.l2jmobius.gameserver.instancemanager.WarpedSpaceManager; + 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; ++import org.l2jmobius.gameserver.util.MathUtil; + + /** +- * @author -Nemesiss-, HorridoJoho ++ * @author Hasha + */ + public class GeoEngine + { +- private static final Logger LOGGER = Logger.getLogger(GeoEngine.class.getName()); ++ protected static final Logger LOGGER = Logger.getLogger(GeoEngine.class.getName()); + +- private static final int WORLD_MIN_X = -655360; +- private static final int WORLD_MIN_Y = -589824; +- private static final int WORLD_MIN_Z = -16384; ++ private final ABlock[][] _blocks; ++ private final BlockNull _nullBlock; + +- /** 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; +- +- /** The regions array */ +- private final AtomicReferenceArray _regions = new AtomicReferenceArray<>(GEO_REGIONS); +- +- 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; +- +- protected GeoEngine() ++ /** ++ * GeoEngine constructor. Loads all geodata files of chosen geodata format. ++ */ ++ public GeoEngine() + { + LOGGER.info("GeoEngine: Initializing..."); +- for (int i = 0; i < _regions.length(); i++) +- { +- _regions.set(i, NullRegion.INSTANCE); +- } + ++ // 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; +- try ++ long fileSize = 0; ++ for (int rx = World.TILE_X_MIN; rx <= World.TILE_X_MAX; rx++) + { +- for (int regionX = World.TILE_X_MIN; regionX <= World.TILE_X_MAX; regionX++) ++ for (int ry = World.TILE_Y_MIN; ry <= World.TILE_Y_MAX; ry++) + { +- for (int regionY = World.TILE_Y_MIN; regionY <= World.TILE_Y_MAX; regionY++) ++ final File f = new File(Config.GEODATA_PATH + String.format(GeoFormat.L2D.getFilename(), rx, ry)); ++ if (f.exists() && !f.isDirectory()) + { +- 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(rx, ry)) + { +- try (RandomAccessFile raf = new RandomAccessFile(geoFilePath.toFile(), "r")) +- { +- _regions.set((regionX * GEO_REGIONS_Y) + regionY, new Region(raf.getChannel().map(MapMode.READ_ONLY, 0, raf.length()).load().order(ByteOrder.LITTLE_ENDIAN))); +- loaded++; +- } ++ loaded++; ++ fileSize += f.length(); + } + } ++ else ++ { ++ // region file is not load-able, load null blocks ++ loadNullBlocks(rx, ry); ++ } + } + } +- catch (Exception e) ++ ++ LOGGER.info("GeoEngine: Loaded " + loaded + " geodata files."); ++ if (loaded > 0) + { +- LOGGER.log(Level.SEVERE, "GeoEngine: Failed to load geodata!", e); +- System.exit(1); ++ LOGGER.info("GeoEngine: Total geodata file size " + (fileSize / 1024 / 1024) + " MB."); + } + +- LOGGER.info("GeoEngine: Loaded " + loaded + " geodata files."); +- +- // Avoid wrong configs when no files are loaded. ++ // avoid wrong configs when no files are loaded + if (loaded == 0) + { + if (Config.PATHFINDING) +@@ -124,627 +114,832 @@ + LOGGER.info("GeoEngine: Forcing CoordSynchronize setting to -1."); + } + } ++ ++ // release multilayer block temporarily buffer ++ BlockMultilayer.release(); + } + + /** +- * @param geoX +- * @param geoY +- * @return the region ++ * 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 IRegion getRegion(int geoX, int geoY) ++ private final boolean loadGeoBlocks(int regionX, int regionY) + { +- final int region = ((geoX / IRegion.REGION_CELLS_X) * GEO_REGIONS_Y) + (geoY / IRegion.REGION_CELLS_Y); +- if ((region < 0) || (region >= _regions.length())) ++ final String filename = String.format(GeoFormat.L2D.getFilename(), regionX, regionY); ++ ++ // standard load ++ try (RandomAccessFile raf = new RandomAccessFile(Config.GEODATA_PATH + filename, "r"); ++ FileChannel fc = raf.getChannel()) + { +- return null; ++ // initialize file buffer ++ final MappedByteBuffer buffer = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()).load(); ++ buffer.order(ByteOrder.LITTLE_ENDIAN); ++ ++ // 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++) ++ { ++ // get block type ++ final byte type = buffer.get(); ++ ++ // load block according to block type ++ switch (type) ++ { ++ case GeoStructure.TYPE_FLAT_L2D: ++ { ++ _blocks[blockX + ix][blockY + iy] = new BlockFlat(buffer, GeoFormat.L2D); ++ break; ++ } ++ case GeoStructure.TYPE_COMPLEX_L2D: ++ { ++ _blocks[blockX + ix][blockY + iy] = new BlockComplex(buffer, GeoFormat.L2D); ++ break; ++ } ++ case GeoStructure.TYPE_MULTILAYER_L2D: ++ { ++ _blocks[blockX + ix][blockY + iy] = new BlockMultilayer(buffer, GeoFormat.L2D); ++ break; ++ } ++ default: ++ { ++ throw new IllegalArgumentException("Unknown block type: " + type); ++ } ++ } ++ } ++ } ++ ++ // check data consistency ++ if (buffer.remaining() > 0) ++ { ++ LOGGER.warning("GeoEngine: Region file " + filename + " can be corrupted, remaining " + buffer.remaining() + " bytes to read."); ++ } ++ ++ // loading was successful ++ return true; + } +- return _regions.get(region); +- } +- +- /** +- * @param filePath +- * @param regionX +- * @param regionY +- * @throws IOException +- */ +- 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")) ++ catch (Exception e) + { +- _regions.set(regionOffset, new Region(raf.getChannel().map(MapMode.READ_ONLY, 0, raf.length()).load().order(ByteOrder.LITTLE_ENDIAN))); ++ // an error occured while loading, load null blocks ++ LOGGER.warning("GeoEngine: Error while loading " + filename + " region file."); ++ LOGGER.warning(e.getMessage()); ++ ++ // replace whole region file with null blocks ++ loadNullBlocks(regionX, regionY); ++ ++ // loading was not successful ++ return false; + } + } + + /** +- * @param regionX +- * @param regionY ++ * 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. + */ +- public void unloadRegion(int regionX, int regionY) ++ private final void loadNullBlocks(int regionX, int regionY) + { +- _regions.set((regionX * GEO_REGIONS_Y) + regionY, NullRegion.INSTANCE); +- } +- +- /** +- * @param geoX +- * @param geoY +- * @return if geodata exist +- */ +- public boolean hasGeoPos(int geoX, int geoY) +- { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) ++ // 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++) + { +- return false; ++ for (int iy = 0; iy < GeoStructure.REGION_BLOCKS_Y; iy++) ++ { ++ _blocks[blockX + ix][blockY + iy] = _nullBlock; ++ } + } +- return region.hasGeo(); + } + ++ // GEODATA - GENERAL ++ + /** +- * Checks the specified position for available geodata. +- * @param x the world x +- * @param y the world y +- * @return {@code true} if there is geodata for the given coordinates, {@code false} otherwise ++ * Converts world X to geodata X. ++ * @param worldX ++ * @return int : Geo X + */ +- public boolean hasGeo(int x, int y) ++ public static int getGeoX(int worldX) + { +- return hasGeoPos(getGeoX(x), getGeoY(y)); ++ return (MathUtil.limit(worldX, World.MAP_MIN_X, World.MAP_MAX_X) - World.MAP_MIN_X) >> 4; + } + + /** +- * @param geoX +- * @param geoY +- * @param worldZ +- * @param nswe +- * @return the nearest nswe check ++ * Converts world Y to geodata Y. ++ * @param worldY ++ * @return int : Geo Y + */ +- public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) ++ public static int getGeoY(int worldY) + { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) +- { +- return true; +- } +- return region.checkNearestNswe(geoX, geoY, worldZ, nswe); ++ return (MathUtil.limit(worldY, World.MAP_MIN_Y, World.MAP_MAX_Y) - World.MAP_MIN_Y) >> 4; + } + + /** ++ * Converts geodata X to world X. + * @param geoX +- * @param geoY +- * @param worldZ +- * @param nswe +- * @return the nearest nswe anti-corner cut ++ * @return int : World X + */ +- public boolean checkNearestNsweAntiCornerCut(int geoX, int geoY, int worldZ, int nswe) ++ public static int getWorldX(int geoX) + { +- boolean can = true; +- if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) +- { +- can = checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_EAST) && checkNearestNswe(geoX + 1, geoY, worldZ, Cell.NSWE_NORTH); +- } +- if (can && ((nswe & Cell.NSWE_NORTH_WEST) == Cell.NSWE_NORTH_WEST)) +- { +- 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 = 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 = checkNearestNswe(geoX, geoY + 1, worldZ, Cell.NSWE_WEST) && checkNearestNswe(geoX - 1, geoY, worldZ, Cell.NSWE_SOUTH); +- } +- return can && checkNearestNswe(geoX, geoY, worldZ, nswe); ++ return (MathUtil.limit(geoX, 0, GeoStructure.GEO_CELLS_X) << 4) + World.MAP_MIN_X + 8; + } + + /** +- * @param geoX ++ * Converts geodata Y to world Y. + * @param geoY +- * @param worldZ +- * @return the nearest Z value ++ * @return int : World Y + */ +- public int getNearestZ(int geoX, int geoY, int worldZ) ++ public static int getWorldY(int geoY) + { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) +- { +- return worldZ; +- } +- return region.getNearestZ(geoX, geoY, worldZ); ++ return (MathUtil.limit(geoY, 0, GeoStructure.GEO_CELLS_Y) << 4) + World.MAP_MIN_Y + 8; + } + + /** +- * @param geoX +- * @param geoY +- * @param worldZ +- * @return the next lower Z value ++ * Returns block of geodata on given coordinates. ++ * @param geoX : Geodata X ++ * @param geoY : Geodata Y ++ * @return {@link ABlock} : Block of geodata. + */ +- public int getNextLowerZ(int geoX, int geoY, int worldZ) ++ private final ABlock getBlock(int geoX, int geoY) + { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) ++ final int x = geoX / GeoStructure.BLOCK_CELLS_X; ++ final int y = geoY / GeoStructure.BLOCK_CELLS_Y; ++ ++ // if x or y is out of array return null ++ if ((x > -1) && (y > -1) && (x < GeoStructure.GEO_BLOCKS_X) && (y < GeoStructure.GEO_BLOCKS_Y)) + { +- return worldZ; ++ return _blocks[x][y]; + } +- return region.getNextLowerZ(geoX, geoY, worldZ); ++ return null; + } + + /** +- * @param geoX +- * @param geoY +- * @param worldZ +- * @return the next higher Z value ++ * Check if geo coordinates has geo. ++ * @param geoX : Geodata X ++ * @param geoY : Geodata Y ++ * @return boolean : True, if given geo coordinates have geodata + */ +- public int getNextHigherZ(int geoX, int geoY, int worldZ) ++ public boolean hasGeoPos(int geoX, int geoY) + { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) ++ final ABlock block = getBlock(geoX, geoY); ++ if (block == null) // null block check + { +- return worldZ; ++ // TODO: Find when this can be null. (Bad geodata? Check World getRegion method.) ++ // LOGGER.warning("Could not find geodata block at " + getWorldX(geoX) + ", " + getWorldY(geoY) + "."); ++ return false; + } +- return region.getNextHigherZ(geoX, geoY, worldZ); ++ return block.hasGeoPos(); + } + + /** +- * Gets the Z height. +- * @param x the world x +- * @param y the world y +- * @param z the world z +- * @return the nearest Z height ++ * 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 int getHeight(int x, int y, int z) ++ public short getHeightNearest(int geoX, int geoY, int worldZ) + { +- return getNearestZ(getGeoX(x), getGeoY(y), z); ++ final ABlock block = getBlock(geoX, geoY); ++ return block != null ? block.getHeightNearest(geoX, geoY, worldZ) : (short) worldZ; + } + + /** +- * Gets the next lower Z height. +- * @param x the world x +- * @param y the world y +- * @param z the world z +- * @return the nearest Z height ++ * 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 int getLowerHeight(int x, int y, int z) ++ public byte getNsweNearest(int geoX, int geoY, int worldZ) + { +- return getNextLowerZ(getGeoX(x), getGeoY(y), z); ++ final ABlock block = getBlock(geoX, geoY); ++ return block != null ? block.getNsweNearest(geoX, geoY, worldZ) : (byte) 0xFF; + } + + /** +- * Gets the next higher Z height. +- * @param x the world x +- * @param y the world y +- * @param z the world z +- * @return the nearest Z height ++ * Check if world coordinates has geo. ++ * @param worldX : World X ++ * @param worldY : World Y ++ * @return boolean : True, if given world coordinates have geodata + */ +- public int getHigherHeight(int x, int y, int z) ++ public boolean hasGeo(int worldX, int worldY) + { +- return getNextHigherZ(getGeoX(x), getGeoY(y), z); ++ return hasGeoPos(getGeoX(worldX), getGeoY(worldY)); + } + + /** +- * @param worldX +- * @return the geo X ++ * 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 int getGeoX(int worldX) ++ public short getHeight(int worldX, int worldY, int worldZ) + { +- return (worldX - WORLD_MIN_X) / 16; ++ return getHeightNearest(getGeoX(worldX), getGeoY(worldY), worldZ); + } + +- /** +- * @param worldY +- * @return the geo Y +- */ +- public int getGeoY(int worldY) +- { +- return (worldY - WORLD_MIN_Y) / 16; +- } ++ // PATHFINDING + + /** +- * @param worldZ +- * @return the geo Z ++ * Check line of sight from {@link WorldObject} to {@link WorldObject}. ++ * @param origin : The origin object. ++ * @param target : The target object. ++ * @return {@code boolean} : True if origin can see target + */ +- public int getGeoZ(int worldZ) ++ public boolean canSeeTarget(WorldObject origin, WorldObject target) + { +- return (worldZ - WORLD_MIN_Z) / 16; +- } +- +- /** +- * @param geoX +- * @return the world X +- */ +- public int getWorldX(int geoX) +- { +- return (geoX * 16) + WORLD_MIN_X + 8; +- } +- +- /** +- * @param geoY +- * @return the world Y +- */ +- public int getWorldY(int geoY) +- { +- return (geoY * 16) + WORLD_MIN_Y + 8; +- } +- +- /** +- * @param geoZ +- * @return the world Z +- */ +- public int getWorldZ(int geoZ) +- { +- return (geoZ * 16) + WORLD_MIN_Z + 8; +- } +- +- /** +- * 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) +- { +- if (target.isDoor()) ++ if (target.isDoor() || target.isArtefact() || (target.isCreature() && ((Creature) target).isFlying())) + { +- // Can always see doors. + return true; + } +- return 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(), cha.getInstanceWorld(), worldPosition.getX(), worldPosition.getY(), worldPosition.getZ()); +- } +- +- /** +- * Can see target. Checks doors between. +- * @param x the x coordinate +- * @param y the y coordinate +- * @param z the z coordinate +- * @param world +- * @param tx the target's x coordinate +- * @param ty the target's y coordinate +- * @param tz the target's z coordinate +- * @param tworld the target's instanceId +- * @return +- */ +- public boolean canSeeTarget(int x, int y, int z, Instance world, int tx, int ty, int tz, Instance tworld) +- { +- return (world != tworld) ? false : canSeeTarget(x, y, z, world, tx, ty, tz); +- } +- +- /** +- * 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 +- * @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, Instance instance, int tx, int ty, int tz) +- { +- if (DoorData.getInstance().checkIfDoorsBetween(x, y, z, tx, ty, tz, instance, true)) ++ ++ // get origin and target world coordinates ++ final int ox = origin.getX(); ++ final int oy = origin.getY(); ++ final int oz = origin.getZ(); ++ final int tx = target.getX(); ++ final int ty = target.getY(); ++ final int tz = target.getZ(); ++ ++ if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld(), true)) + { + return false; + } +- return canSeeTarget(x, y, z, tx, ty, tz); +- } +- +- /** +- * @param prevX +- * @param prevY +- * @param prevGeoZ +- * @param curX +- * @param curY +- * @param nswe +- * @return the LOS Z value +- */ +- 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))) ++ if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld())) + { +- throw new RuntimeException("Multiple directions!"); ++ return false; + } ++ if (WarpedSpaceManager.getInstance().checkForWarpedSpace(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld())) ++ { ++ return false; ++ } + +- if (checkNearestNsweAntiCornerCut(prevX, prevY, prevGeoZ, nswe)) ++ // get origin and check existing geo coordinates ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) + { +- return getNearestZ(curX, curY, prevGeoZ); ++ return true; + } + +- return getNextHigherZ(curX, curY, prevGeoZ); ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coordinates ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) ++ { ++ return true; ++ } ++ ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // origin and target coordinates are same ++ if ((gox == gtx) && (goy == gty)) ++ { ++ return goz == gtz; ++ } ++ ++ // get origin and target height, real height = collision height * 2 ++ double oheight = 0; ++ if (origin.isCreature()) ++ { ++ oheight = ((Creature) origin).getCollisionHeight() * 2; ++ } ++ ++ double theight = 0; ++ if (target.isCreature()) ++ { ++ theight = ((Creature) target).getCollisionHeight() * 2; ++ } ++ ++ // perform geodata check ++ return checkSee(gox, goy, goz, oheight, gtx, gty, gtz, theight, origin.getInstanceWorld()); + } + + /** +- * Can see target. Does not check doors between. +- * @param xValue the x coordinate +- * @param yValue the y coordinate +- * @param zValue the z coordinate +- * @param txValue the target's x coordinate +- * @param tyValue the target's y coordinate +- * @param tzValue the target's z coordinate +- * @return {@code true} if there is line of sight between the given coordinate sets, {@code false} otherwise ++ * Check line of sight from {@link WorldObject} to {@link Location}. ++ * @param origin : The origin object. ++ * @param position : The target position. ++ * @return {@code boolean} : True if object can see position + */ +- public boolean canSeeTarget(int xValue, int yValue, int zValue, int txValue, int tyValue, int tzValue) ++ public boolean canSeeTarget(WorldObject origin, Location position) + { +- int x = xValue; +- int y = yValue; +- int tx = txValue; +- int ty = tyValue; ++ // get origin and target world coordinates ++ final int ox = origin.getX(); ++ final int oy = origin.getY(); ++ final int oz = origin.getZ(); ++ final int tx = position.getX(); ++ final int ty = position.getY(); ++ final int tz = position.getZ(); + +- int geoX = getGeoX(x); +- int geoY = getGeoY(y); +- int tGeoX = getGeoX(tx); +- int tGeoY = getGeoY(ty); ++ if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld(), true)) ++ { ++ return false; ++ } ++ if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld())) ++ { ++ return false; ++ } ++ if (WarpedSpaceManager.getInstance().checkForWarpedSpace(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld())) ++ { ++ return false; ++ } + +- int z = getNearestZ(geoX, geoY, zValue); +- int tz = getNearestZ(tGeoX, tGeoY, tzValue); ++ // get origin and check existing geo coordinates ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) ++ { ++ return true; ++ } + +- // Fastpath. +- if ((geoX == tGeoX) && (geoY == tGeoY)) ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coordinates ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) + { +- if (hasGeoPos(tGeoX, tGeoY)) +- { +- return z == tz; +- } + return true; + } + +- if (tz > z) ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // origin and target coordinates are same ++ if ((gox == gtx) && (goy == gty)) + { +- int tmp = tx; +- tx = x; +- x = tmp; +- +- tmp = ty; +- ty = y; +- y = tmp; +- +- tmp = tz; +- tz = z; +- z = tmp; +- +- tmp = tGeoX; +- tGeoX = geoX; +- geoX = tmp; +- +- tmp = tGeoY; +- tGeoY = geoY; +- geoY = tmp; ++ return goz == gtz; + } + +- final LinePointIterator3D pointIter = new LinePointIterator3D(geoX, geoY, z, tGeoX, tGeoY, tz); +- // 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()) ++ // get origin and target height, real height = collision height * 2 ++ double oheight = 0; ++ if (origin.isCreature()) + { +- final int curX = pointIter.x(); +- final int curY = pointIter.y(); ++ oheight = ((Creature) origin).getTemplate().getCollisionHeight(); ++ } ++ ++ // perform geodata check ++ return checkSee(gox, goy, goz, oheight, gtx, gty, gtz, 0, origin.getInstanceWorld()); ++ } ++ ++ /** ++ * Simple check for origin to target visibility. ++ * @param goxValue : origin X geodata coordinate ++ * @param goyValue : origin Y geodata coordinate ++ * @param gozValue : origin Z geodata coordinate ++ * @param oheight : origin height (if instance of {@link Character}) ++ * @param gtxValue : target X geodata coordinate ++ * @param gtyValue : target Y geodata coordinate ++ * @param gtzValue : target Z geodata coordinate ++ * @param theight : target height (if instance of {@link Character}) ++ * @param instance ++ * @return {@code boolean} : True, when target can be seen. ++ */ ++ private final boolean checkSee(int goxValue, int goyValue, int gozValue, double oheight, int gtxValue, int gtyValue, int gtzValue, double theight, Instance instance) ++ { ++ int goz = gozValue; ++ int gtz = gtzValue; ++ int gox = goxValue; ++ int goy = goyValue; ++ int gtx = gtxValue; ++ int gty = gtyValue; ++ ++ // get line of sight Z coordinates ++ double losoz = goz + ((oheight * Config.PART_OF_CHARACTER_HEIGHT) / 100); ++ double lostz = gtz + ((theight * Config.PART_OF_CHARACTER_HEIGHT) / 100); ++ ++ // get X delta and signum ++ final int dx = Math.abs(gtx - gox); ++ final int sx = gox < gtx ? 1 : -1; ++ final byte dirox = sx > 0 ? GeoStructure.CELL_FLAG_E : GeoStructure.CELL_FLAG_W; ++ final byte dirtx = sx > 0 ? GeoStructure.CELL_FLAG_W : GeoStructure.CELL_FLAG_E; ++ ++ // get Y delta and signum ++ final int dy = Math.abs(gty - goy); ++ final int sy = goy < gty ? 1 : -1; ++ final byte diroy = sy > 0 ? GeoStructure.CELL_FLAG_S : GeoStructure.CELL_FLAG_N; ++ final byte dirty = sy > 0 ? GeoStructure.CELL_FLAG_N : GeoStructure.CELL_FLAG_S; ++ ++ // get Z delta ++ final int dm = Math.max(dx, dy); ++ final double dz = (lostz - losoz) / dm; ++ ++ // get direction flag for diagonal movement ++ final byte diroxy = getDirXY(dirox, diroy); ++ final byte dirtxy = getDirXY(dirtx, dirty); ++ ++ // delta, determines axis to move on (+..X axis, -..Y axis) ++ int d = dx - dy; ++ ++ // NSWE direction of movement ++ byte diro; ++ byte dirt; ++ ++ // initialize node values ++ int nox = gox; ++ int noy = goy; ++ int ntx = gtx; ++ int nty = gty; ++ byte nsweo = getNsweNearest(gox, goy, goz); ++ byte nswet = getNsweNearest(gtx, gty, gtz); ++ ++ // loop ++ ABlock block; ++ int index; ++ for (int i = 0; i < ((dm + 1) / 2); i++) ++ { ++ // reset direction flag ++ diro = 0; ++ dirt = 0; + +- if ((curX == prevX) && (curY == prevY)) ++ // calculate next point coordinates ++ final int e2 = 2 * d; ++ if ((e2 > -dy) && (e2 < dx)) + { +- continue; ++ // calculate next point XY coordinates ++ d -= dy; ++ d += dx; ++ nox += sx; ++ ntx -= sx; ++ noy += sy; ++ nty -= sy; ++ diro |= diroxy; ++ dirt |= dirtxy; + } ++ else if (e2 > -dy) ++ { ++ // calculate next point X coordinate ++ d -= dy; ++ nox += sx; ++ ntx -= sx; ++ diro |= dirox; ++ dirt |= dirtx; ++ } ++ else if (e2 < dx) ++ { ++ // calculate next point Y coordinate ++ d += dx; ++ noy += sy; ++ nty -= sy; ++ diro |= diroy; ++ dirt |= dirty; ++ } + +- final int beeCurZ = pointIter.z(); +- int curGeoZ = prevGeoZ; +- +- // Check if the position has geodata. +- if (hasGeoPos(curX, curY)) + { +- final int beeCurGeoZ = getNearestZ(curX, curY, beeCurZ); +- final int nswe = GeoUtils.computeNswe(prevX, prevY, curX, curY); // .computeDirection(prevX, prevY, curX, curY); +- curGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, curX, curY, nswe); +- int maxHeight; +- if (ptIndex < ELEVATED_SEE_OVER_DISTANCE) ++ // get block of the next cell ++ block = getBlock(nox, noy); ++ ++ // get index of particular layer, based on movement conditions ++ if ((nsweo & diro) == 0) + { +- maxHeight = z + MAX_SEE_OVER_HEIGHT; ++ index = block.getIndexAbove(nox, noy, goz - GeoStructure.CELL_IGNORE_HEIGHT); + } + else + { +- maxHeight = beeCurZ + MAX_SEE_OVER_HEIGHT; ++ index = block.getIndexBelow(nox, noy, goz + GeoStructure.CELL_IGNORE_HEIGHT); + } + +- boolean canSeeThrough = false; +- if ((curGeoZ <= maxHeight) && (curGeoZ <= beeCurGeoZ)) ++ // layer does not exist, return ++ if (index == -1) + { +- if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) +- { +- 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 +- { +- canSeeThrough = true; +- } ++ return false; + } + +- if (!canSeeThrough) ++ // get layer and next line of sight Z coordinate ++ goz = block.getHeight(index); ++ losoz += dz; ++ ++ // perform line of sight check, return when fails ++ if ((goz - losoz) > Config.MAX_OBSTACLE_HEIGHT) + { + return false; + } ++ ++ // get layer nswe ++ nsweo = block.getNswe(index); + } ++ { ++ // get block of the next cell ++ block = getBlock(ntx, nty); ++ ++ // get index of particular layer, based on movement conditions ++ if ((nswet & dirt) == 0) ++ { ++ index = block.getIndexAbove(ntx, nty, gtz - GeoStructure.CELL_IGNORE_HEIGHT); ++ } ++ else ++ { ++ index = block.getIndexBelow(ntx, nty, gtz + GeoStructure.CELL_IGNORE_HEIGHT); ++ } ++ ++ // layer does not exist, return ++ if (index == -1) ++ { ++ return false; ++ } ++ ++ // get layer and next line of sight Z coordinate ++ gtz = block.getHeight(index); ++ lostz -= dz; ++ ++ // perform line of sight check, return when fails ++ if ((gtz - lostz) > Config.MAX_OBSTACLE_HEIGHT) ++ { ++ return false; ++ } ++ ++ // get layer nswe ++ nswet = block.getNswe(index); ++ } + +- prevX = curX; +- prevY = curY; +- prevGeoZ = curGeoZ; +- ++ptIndex; ++ // update coords ++ gox = nox; ++ goy = noy; ++ gtx = ntx; ++ gty = nty; + } + +- return true; ++ // when iteration is completed, compare final Z coordinates ++ return Math.abs(goz - gtz) < (GeoStructure.CELL_HEIGHT * 4); + } + + /** +- * Move check. +- * @param x the x coordinate +- * @param y the y coordinate +- * @param zValue the z coordinate +- * @param tx the target's x coordinate +- * @param ty the target's y coordinate +- * @param tzValue the target's z coordinate +- * @param instance the instance +- * @return the last Location (x,y,z) where player can walk - just before wall ++ * Check movement from coordinates to 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 {code boolean} : True if target coordinates are reachable from origin coordinates + */ +- public Location canMoveToTargetLoc(int x, int y, int zValue, int tx, int ty, int tzValue, Instance instance) ++ public boolean canMoveToTarget(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + { +- final int geoX = getGeoX(x); +- final int geoY = getGeoY(y); +- final int z = getNearestZ(geoX, geoY, zValue); +- final int tGeoX = getGeoX(tx); +- final int tGeoY = getGeoY(ty); +- final int tz = getNearestZ(tGeoX, tGeoY, tzValue); ++ // get origin and check existing geo coordinates ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) ++ { ++ return true; ++ } + +- if (DoorData.getInstance().checkIfDoorsBetween(x, y, z, tx, ty, tz, instance, false)) ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coordinates ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) + { +- return new Location(x, y, getHeight(x, y, z)); ++ return true; + } +- if (FenceData.getInstance().checkIfFenceBetween(x, y, z, tx, ty, tz, instance)) ++ ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // target coordinates reached ++ if ((gox == gtx) && (goy == gty) && (goz == gtz)) + { +- return new Location(x, y, getHeight(x, y, z)); ++ return true; + } +- if (WarpedSpaceManager.getInstance().checkForWarpedSpace(x, y, z, tx, ty, tz, instance)) ++ ++ // perform geodata check ++ final GeoLocation loc = checkMove(gox, goy, goz, gtx, gty, gtz, instance); ++ return (loc.getGeoX() == gtx) && (loc.getGeoY() == gty); ++ } ++ ++ /** ++ * Check movement from origin to target. Returns last available point in the checked path. ++ * @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 {@link Location} : Last point where object can walk (just before wall) ++ */ ++ public Location canMoveToTargetLoc(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) ++ { ++ // Mobius: Double check for doors before normal checkMove to avoid exploiting key movement. ++ if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, instance, false)) + { +- return new Location(x, y, getHeight(x, y, z)); ++ return new Location(ox, oy, oz); + } ++ if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, instance)) ++ { ++ return new Location(ox, oy, oz); ++ } + +- 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 = z; ++ // get origin and check existing geo coordinates ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) ++ { ++ return new Location(tx, ty, tz); ++ } + +- while (pointIter.next()) ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coordinates ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) + { +- final int curX = pointIter.x(); +- final int curY = pointIter.y(); +- final int curZ = getNearestZ(curX, curY, prevZ); +- +- if (hasGeoPos(prevX, prevY)) +- { +- final int nswe = GeoUtils.computeNswe(prevX, prevY, curX, curY); +- if (!checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, nswe)) +- { +- // Can't move, return previous location. +- return new Location(getWorldX(prevX), getWorldY(prevY), prevZ); +- } +- } +- +- prevX = curX; +- prevY = curY; +- prevZ = curZ; ++ return new Location(tx, ty, tz); + } + +- if (hasGeoPos(prevX, prevY) && (prevZ != tz)) ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // target coordinates reached ++ if ((gox == gtx) && (goy == gty) && (goz == gtz)) + { +- // Different floors, return start location. +- return new Location(x, y, z); ++ return new Location(tx, ty, tz); + } + +- return new Location(tx, ty, tz); ++ // perform geodata check ++ return checkMove(gox, goy, goz, gtx, gty, gtz, instance); + } + + /** +- * 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 fromZvalue 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 toZvalue 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 ++ * With this method you can check if a position is visible or can be reached by beeline movement.
++ * 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 gox : origin X geodata coordinate ++ * @param goy : origin Y geodata coordinate ++ * @param goz : origin Z geodata coordinate ++ * @param gtx : target X geodata coordinate ++ * @param gty : target Y geodata coordinate ++ * @param gtz : target Z geodata coordinate ++ * @param instance ++ * @return {@link GeoLocation} : The last allowed point of movement. + */ +- public boolean canMoveToTarget(int fromX, int fromY, int fromZvalue, int toX, int toY, int toZvalue, Instance instance) ++ protected final GeoLocation checkMove(int gox, int goy, int goz, int gtx, int gty, int gtz, Instance instance) + { +- final int geoX = getGeoX(fromX); +- final int geoY = getGeoY(fromY); +- final int fromZ = getNearestZ(geoX, geoY, fromZvalue); +- final int tGeoX = getGeoX(toX); +- final int tGeoY = getGeoY(toY); +- final int toZ = getNearestZ(tGeoX, tGeoY, toZvalue); +- +- if (DoorData.getInstance().checkIfDoorsBetween(fromX, fromY, fromZ, toX, toY, toZ, instance, false)) ++ if (DoorData.getInstance().checkIfDoorsBetween(gox, goy, goz, gtx, gty, gtz, instance, false)) + { +- return false; ++ return new GeoLocation(gox, goy, goz); + } +- if (FenceData.getInstance().checkIfFenceBetween(fromX, fromY, fromZ, toX, toY, toZ, instance)) ++ if (FenceData.getInstance().checkIfFenceBetween(gox, goy, goz, gtx, gty, gtz, instance)) + { +- return false; ++ return new GeoLocation(gox, goy, goz); + } +- if (WarpedSpaceManager.getInstance().checkForWarpedSpace(fromX, fromY, fromZ, toX, toY, toZ, instance)) ++ if (WarpedSpaceManager.getInstance().checkForWarpedSpace(gox, goy, goz, gtx, gty, gtz, instance)) + { +- return false; ++ return new GeoLocation(gox, goy, goz); + } + +- 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 = fromZ; ++ // get X delta, signum and direction flag ++ final int dx = Math.abs(gtx - gox); ++ final int sx = gox < gtx ? 1 : -1; ++ final byte dirX = sx > 0 ? GeoStructure.CELL_FLAG_E : GeoStructure.CELL_FLAG_W; + +- while (pointIter.next()) ++ // get Y delta, signum and direction flag ++ final int dy = Math.abs(gty - goy); ++ final int sy = goy < gty ? 1 : -1; ++ final byte dirY = sy > 0 ? GeoStructure.CELL_FLAG_S : GeoStructure.CELL_FLAG_N; ++ ++ // get direction flag for diagonal movement ++ final byte dirXY = getDirXY(dirX, dirY); ++ ++ // delta, determines axis to move on (+..X axis, -..Y axis) ++ int d = dx - dy; ++ ++ // NSWE direction of movement ++ byte direction; ++ ++ // load pointer coordinates ++ int gpx = gox; ++ int gpy = goy; ++ int gpz = goz; ++ ++ // load next pointer ++ int nx = gpx; ++ int ny = gpy; ++ ++ // loop ++ int count = 0; ++ while (count++ < Config.MAX_ITERATIONS) + { +- final int curX = pointIter.x(); +- final int curY = pointIter.y(); +- final int curZ = getNearestZ(curX, curY, prevZ); ++ direction = 0; + +- if (hasGeoPos(prevX, prevY)) ++ // calculate next point coordinates ++ final int e2 = 2 * d; ++ if ((e2 > -dy) && (e2 < dx)) + { +- final int nswe = GeoUtils.computeNswe(prevX, prevY, curX, curY); +- if (!checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, nswe)) ++ d -= dy; ++ d += dx; ++ nx += sx; ++ ny += sy; ++ direction |= dirXY; ++ } ++ else if (e2 > -dy) ++ { ++ d -= dy; ++ nx += sx; ++ direction |= dirX; ++ } ++ else if (e2 < dx) ++ { ++ d += dx; ++ ny += sy; ++ direction |= dirY; ++ } ++ ++ // obstacle found, return ++ if ((getNsweNearest(gpx, gpy, gpz) & direction) == 0) ++ { ++ return new GeoLocation(gpx, gpy, gpz); ++ } ++ ++ // update pointer coordinates ++ gpx = nx; ++ gpy = ny; ++ gpz = getHeightNearest(nx, ny, gpz); ++ ++ // target coordinates reached ++ if ((gpx == gtx) && (gpy == gty)) ++ { ++ if (gpz == gtz) + { +- return false; ++ // path found, Z coordinates are okay, return target point ++ return new GeoLocation(gtx, gty, gtz); + } ++ ++ // path found, Z coordinates are not okay, return last good point ++ return new GeoLocation(gpx, gpy, gpz); + } ++ } ++ ++ return new GeoLocation(gox, goy, goz); ++ } ++ ++ /** ++ * Returns diagonal NSWE flag format of combined two NSWE flags. ++ * @param dirX : X direction NSWE flag ++ * @param dirY : Y direction NSWE flag ++ * @return byte : NSWE flag of combined direction ++ */ ++ private static byte getDirXY(byte dirX, byte dirY) ++ { ++ // check axis directions ++ if (dirY == GeoStructure.CELL_FLAG_N) ++ { ++ if (dirX == GeoStructure.CELL_FLAG_W) ++ { ++ return GeoStructure.CELL_FLAG_NW; ++ } + +- prevX = curX; +- prevY = curY; +- prevZ = curZ; ++ return GeoStructure.CELL_FLAG_NE; + } + +- if (hasGeoPos(prevX, prevY) && (prevZ != toZ)) ++ if (dirX == GeoStructure.CELL_FLAG_W) + { +- // Different floors. +- return false; ++ return GeoStructure.CELL_FLAG_SW; + } + +- return true; ++ return GeoStructure.CELL_FLAG_SE; + } + ++ /** ++ * 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 ++ */ ++ public List findPath(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) ++ { ++ return null; ++ } ++ ++ /** ++ * Returns the instance of the {@link GeoEngine}. ++ * @return {@link GeoEngine} : The instance. ++ */ + public static GeoEngine getInstance() + { + return SingletonHolder.INSTANCE; +@@ -752,6 +947,6 @@ + + private static class SingletonHolder + { +- protected static final GeoEngine INSTANCE = new GeoEngine(); ++ protected static final GeoEngine INSTANCE = Config.PATHFINDING ? new GeoEnginePathfinding() : new GeoEngine(); + } +-} ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/GeoEnginePathfinding.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/GeoEnginePathfinding.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/GeoEnginePathfinding.java (working copy) +@@ -20,88 +20,84 @@ + import java.util.LinkedList; + import java.util.List; + import java.util.ListIterator; +-import java.util.logging.Level; +-import java.util.logging.Logger; + + import org.l2jmobius.Config; +-import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; +-import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +-import org.l2jmobius.gameserver.geoengine.pathfinding.CellNode; +-import org.l2jmobius.gameserver.geoengine.pathfinding.CellNodeBuffer; +-import org.l2jmobius.gameserver.geoengine.pathfinding.NodeLoc; +-import org.l2jmobius.gameserver.model.World; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoLocation; ++import org.l2jmobius.gameserver.geoengine.pathfinding.Node; ++import org.l2jmobius.gameserver.geoengine.pathfinding.NodeBuffer; ++import org.l2jmobius.gameserver.model.Location; + import org.l2jmobius.gameserver.model.instancezone.Instance; + + /** +- * @author -Nemesiss- ++ * @author Hasha + */ +-public class GeoEnginePathfinding ++final class GeoEnginePathfinding extends GeoEngine + { +- private static final Logger LOGGER = Logger.getLogger(GeoEnginePathfinding.class.getName()); ++ // pre-allocated buffers ++ private final BufferHolder[] _buffers; + +- private BufferInfo[] _buffers; +- + protected GeoEnginePathfinding() + { +- try ++ super(); ++ ++ final String[] array = Config.PATHFIND_BUFFERS.split(";"); ++ _buffers = new BufferHolder[array.length]; ++ int count = 0; ++ for (int i = 0; i < array.length; i++) + { +- final String[] array = Config.PATHFIND_BUFFERS.split(";"); ++ final String buf = array[i]; ++ final String[] args = buf.split("x"); + +- _buffers = new BufferInfo[array.length]; +- +- String buf; +- String[] args; +- for (int i = 0; i < array.length; i++) ++ try + { +- buf = array[i]; +- args = buf.split("x"); +- if (args.length != 2) +- { +- throw new Exception("Invalid buffer definition: " + buf); +- } +- +- _buffers[i] = new BufferInfo(Integer.parseInt(args[0]), Integer.parseInt(args[1])); ++ final int size = Integer.parseInt(args[1]); ++ count += size; ++ _buffers[i] = new BufferHolder(Integer.parseInt(args[0]), size); + } ++ catch (Exception e) ++ { ++ LOGGER.warning("GeoEnginePathfinding: Can not load buffer setting: " + buf); ++ } + } +- catch (Exception e) +- { +- LOGGER.log(Level.WARNING, "CellPathFinding: Problem during buffer init: " + e.getMessage(), e); +- throw new Error("CellPathFinding: load aborted"); +- } ++ ++ LOGGER.info("GeoEnginePathfinding: Loaded " + count + " node buffers."); + } + +- public boolean pathNodesExist(short regionoffset) ++ @Override ++ public List findPath(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + { +- return false; +- } +- +- public List findPath(int x, int y, int z, int tx, int ty, int tz, Instance instance) +- { +- final int gx = GeoEngine.getInstance().getGeoX(x); +- final int gy = GeoEngine.getInstance().getGeoY(y); +- if (!GeoEngine.getInstance().hasGeo(x, y)) ++ // get origin and check existing geo coords ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) + { + 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)) ++ ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coords ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) + { + 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)))); ++ ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // Prepare buffer for pathfinding calculations ++ final NodeBuffer buffer = getBuffer(64 + (2 * Math.max(Math.abs(gox - gtx), Math.abs(goy - gty)))); + if (buffer == null) + { + return null; + } + +- List path = null; ++ // find path ++ List path = null; + try + { +- final CellNode result = buffer.findPath(gx, gy, gz, gtx, gty, gtz); +- ++ final Node result = buffer.findPath(gox, goy, goz, gtx, gty, gtz); + if (result == null) + { + return null; +@@ -125,33 +121,45 @@ + return path; + } + +- int currentX, currentY, currentZ; +- ListIterator middlePoint; ++ // get path list iterator ++ final ListIterator point = path.listIterator(); + +- middlePoint = path.listIterator(); +- currentX = x; +- currentY = y; +- currentZ = z; ++ // get node A (origin) ++ int nodeAx = gox; ++ int nodeAy = goy; ++ short nodeAz = goz; + +- while (middlePoint.hasNext()) ++ // get node B ++ GeoLocation nodeB = (GeoLocation) point.next(); ++ ++ // iterate thought the path to optimize it ++ int count = 0; ++ while (point.hasNext() && (count++ < Config.MAX_ITERATIONS)) + { +- final AbstractNodeLoc locMiddle = middlePoint.next(); +- if (!middlePoint.hasNext()) +- { +- break; +- } ++ // get node C ++ final GeoLocation nodeC = (GeoLocation) path.get(point.nextIndex()); + +- final AbstractNodeLoc locEnd = path.get(middlePoint.nextIndex()); +- if (GeoEngine.getInstance().canMoveToTarget(currentX, currentY, currentZ, locEnd.getX(), locEnd.getY(), locEnd.getZ(), instance)) ++ // check movement from node A to node C ++ final GeoLocation loc = checkMove(nodeAx, nodeAy, nodeAz, nodeC.getGeoX(), nodeC.getGeoY(), nodeC.getZ(), instance); ++ if ((loc.getGeoX() == nodeC.getGeoX()) && (loc.getGeoY() == nodeC.getGeoY())) + { +- middlePoint.remove(); ++ // can move from node A to node C ++ ++ // remove node B ++ point.remove(); + } + else + { +- currentX = locMiddle.getX(); +- currentY = locMiddle.getY(); +- currentZ = locMiddle.getZ(); ++ // can not move from node A to node C ++ ++ // set node A (node B is part of path, update A coordinates) ++ nodeAx = nodeB.getGeoX(); ++ nodeAy = nodeB.getGeoY(); ++ nodeAz = (short) nodeB.getZ(); + } ++ ++ // set node B ++ nodeB = (GeoLocation) point.next(); + } + + return path; +@@ -158,144 +166,102 @@ + } + + /** +- * Convert geodata position to pathnode position +- * @param geo_pos +- * @return pathnode position ++ * Create list of node locations as result of calculated buffer node tree. ++ * @param node : the entry point ++ * @return List : list of node location + */ +- public short getNodePos(int geo_pos) ++ private static List constructPath(Node node) + { +- return (short) (geo_pos >> 3); // OK? +- } +- +- /** +- * Convert node position to pathnode block position +- * @param node_pos +- * @return pathnode block position (0...255) +- */ +- public short getNodeBlock(int node_pos) +- { +- return (short) (node_pos % 256); +- } +- +- public byte getRegionX(int node_pos) +- { +- return (byte) ((node_pos >> 8) + World.TILE_X_MIN); +- } +- +- public byte getRegionY(int node_pos) +- { +- return (byte) ((node_pos >> 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 node_x rx +- * @return +- */ +- public int calculateWorldX(short node_x) +- { +- return World.MAP_MIN_X + (node_x * 128) + 48; +- } +- +- /** +- * Convert pathnode y to World y position +- * @param node_y +- * @return +- */ +- public int calculateWorldY(short node_y) +- { +- return World.MAP_MIN_Y + (node_y * 128) + 48; +- } +- +- private List constructPath(AbstractNode nodeValue) +- { +- final LinkedList path = new LinkedList<>(); +- int previousDirectionX = Integer.MIN_VALUE; +- int previousDirectionY = Integer.MIN_VALUE; +- int directionX, directionY; ++ // create empty list ++ final LinkedList list = new LinkedList<>(); + +- AbstractNode node = nodeValue; +- while (node.getParent() != null) ++ // set direction X/Y ++ int dx = 0; ++ int dy = 0; ++ ++ // get target parent ++ Node target = node; ++ Node parent = target.getParent(); ++ ++ // while parent exists ++ int count = 0; ++ while ((parent != null) && (count++ < Config.MAX_ITERATIONS)) + { +- directionX = node.getLoc().getNodeX() - node.getParent().getLoc().getNodeX(); +- directionY = node.getLoc().getNodeY() - node.getParent().getLoc().getNodeY(); ++ // get parent <> target direction X/Y ++ final int nx = parent.getLoc().getGeoX() - target.getLoc().getGeoX(); ++ final int ny = parent.getLoc().getGeoY() - target.getLoc().getGeoY(); + +- // only add a new route point if moving direction changes +- if ((directionX != previousDirectionX) || (directionY != previousDirectionY)) ++ // direction has changed? ++ if ((dx != nx) || (dy != ny)) + { +- previousDirectionX = directionX; +- previousDirectionY = directionY; ++ // add node to the beginning of the list ++ list.addFirst(target.getLoc()); + +- path.addFirst(node.getLoc()); +- node.setLoc(null); ++ // update direction X/Y ++ dx = nx; ++ dy = ny; + } + +- node = node.getParent(); ++ // move to next node, set target and get its parent ++ target = parent; ++ parent = target.getParent(); + } + +- return path; ++ // return list ++ return list; + } + +- private CellNodeBuffer alloc(int size) ++ /** ++ * Provides optimize selection of the buffer. When all pre-initialized buffer are locked, creates new buffer. ++ * @param size : pre-calculated minimal required size ++ * @return NodeBuffer : buffer ++ */ ++ private final NodeBuffer getBuffer(int size) + { +- CellNodeBuffer current = null; +- for (BufferInfo i : _buffers) ++ NodeBuffer current = null; ++ for (BufferHolder holder : _buffers) + { +- if (i.mapSize >= size) ++ // Find proper size of buffer ++ if (holder._size < size) + { +- for (CellNodeBuffer buf : i.buffer) ++ continue; ++ } ++ ++ // Find unlocked NodeBuffer ++ for (NodeBuffer buffer : holder._buffer) ++ { ++ if (!buffer.isLocked()) + { +- if (buf.lock()) +- { +- current = buf; +- break; +- } ++ continue; + } +- if (current != null) +- { +- break; +- } + +- // not found, allocate temporary buffer +- current = new CellNodeBuffer(i.mapSize); +- current.lock(); +- if (i.buffer.size() < i.count) +- { +- i.buffer.add(current); +- break; +- } ++ return buffer; + } ++ ++ // NodeBuffer not found, allocate temporary buffer ++ current = new NodeBuffer(holder._size); ++ current.isLocked(); + } + + return current; + } + +- private static final class BufferInfo ++ /** ++ * NodeBuffer container with specified size and count of separate buffers. ++ */ ++ private static final class BufferHolder + { +- final int mapSize; +- final int count; +- ArrayList buffer; ++ final int _size; ++ List _buffer; + +- public BufferInfo(int size, int cnt) ++ public BufferHolder(int size, int count) + { +- mapSize = size; +- count = cnt; +- buffer = new ArrayList<>(count); ++ _size = size; ++ _buffer = new ArrayList<>(count); ++ for (int i = 0; i < count; i++) ++ { ++ _buffer.add(new NodeBuffer(size)); ++ } + } + } +- +- public static GeoEnginePathfinding getInstance() +- { +- return SingletonHolder.INSTANCE; +- } +- +- private static class SingletonHolder +- { +- protected static final GeoEnginePathfinding INSTANCE = new GeoEnginePathfinding(); +- } +-} ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java (working copy) +@@ -0,0 +1,197 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++import java.io.IOException; ++ ++/** ++ * @author Hasha ++ */ ++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 height of cell, which is closest to given coordinates.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @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 getHeightNearestOriginal(int geoX, int geoY, int worldZ); ++ ++ /** ++ * Returns the height of cell, which is first above 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, above given coordinates. ++ */ ++ public abstract short getHeightAbove(int geoX, int geoY, int worldZ); ++ ++ /** ++ * Returns the height of cell, which is first below 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, below given coordinates. ++ */ ++ public abstract short getHeightBelow(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 the NSWE flag byte of cell, which is closest to given coordinates.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @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 getNsweNearestOriginal(int geoX, int geoY, int worldZ); ++ ++ /** ++ * Returns the NSWE flag byte of cell, which is first above 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 getNsweAbove(int geoX, int geoY, int worldZ); ++ ++ /** ++ * Returns the NSWE flag byte of cell, which is first below 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 getNsweBelow(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 above given coordinates.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @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 getIndexAboveOriginal(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 index to data of the cell, which is first layer below given coordinates.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @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 getIndexBelowOriginal(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 height of cell given by cell index.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @param index : Index of the cell. ++ * @return short : Cell geodata Z coordinate, below given coordinates. ++ */ ++ public abstract short getHeightOriginal(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); ++ ++ /** ++ * Returns the NSWE flag byte of cell given by cell index.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @param index : Index of the cell. ++ * @return short : Cell geodata Z coordinate, below given coordinates. ++ */ ++ public abstract byte getNsweOriginal(int index); ++ ++ /** ++ * Sets the NSWE flag byte of cell given by cell index. ++ * @param index : Index of the cell. ++ * @param nswe : New NSWE flag byte. ++ */ ++ public abstract void setNswe(int index, byte nswe); ++ ++ /** ++ * Saves the block in L2D format to {@link BufferedOutputStream}. Used only for L2D geodata conversion. ++ * @param stream : The stream. ++ * @throws IOException : Can't save the block to steam. ++ */ ++ public abstract void saveBlock(BufferedOutputStream stream) throws IOException; ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java (working copy) +@@ -0,0 +1,252 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++import java.io.IOException; ++import java.nio.ByteBuffer; ++ ++/** ++ * @author Hasha ++ */ ++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. ++ * @param format : GeoFormat specifying format of loaded data. ++ */ ++ public BlockComplex(ByteBuffer bb, GeoFormat format) ++ { ++ // initialize buffer ++ _buffer = new byte[GeoStructure.BLOCK_CELLS * 3]; ++ ++ // load data ++ for (int i = 0; i < GeoStructure.BLOCK_CELLS; i++) ++ { ++ if (format != GeoFormat.L2D) ++ { ++ // 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); ++ } ++ else ++ { ++ // get nswe ++ final byte nswe = bb.get(); ++ _buffer[i * 3] = nswe; ++ ++ // get height ++ final short height = bb.getShort(); ++ _buffer[(i * 3) + 1] = (byte) (height & 0x00FF); ++ _buffer[(i * 3) + 2] = (byte) (height >> 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 short getHeightNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getHeightNearest(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeightAbove(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 short height = (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ ++ // check and return height ++ return height > worldZ ? height : Short.MIN_VALUE; ++ } ++ ++ @Override ++ public short getHeightBelow(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 short height = (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ ++ // check and return height ++ return height < worldZ ? height : Short.MAX_VALUE; ++ } ++ ++ @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 byte getNsweNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getNsweNearest(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public byte getNsweAbove(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 ? _buffer[index] : 0; ++ } ++ ++ @Override ++ public byte getNsweBelow(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 ? _buffer[index] : 0; ++ } ++ ++ @Override ++ public 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 getIndexAboveOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexAbove(geoX, geoY, worldZ); ++ } ++ ++ @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 int getIndexBelowOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexBelow(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeight(int index) ++ { ++ return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ } ++ ++ @Override ++ public short getHeightOriginal(int index) ++ { ++ return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ } ++ ++ @Override ++ public byte getNswe(int index) ++ { ++ return _buffer[index]; ++ } ++ ++ @Override ++ public byte getNsweOriginal(int index) ++ { ++ return _buffer[index]; ++ } ++ ++ @Override ++ public void setNswe(int index, byte nswe) ++ { ++ _buffer[index] = nswe; ++ } ++ ++ @Override ++ public void saveBlock(BufferedOutputStream stream) throws IOException ++ { ++ // write block type ++ stream.write(GeoStructure.TYPE_COMPLEX_L2D); ++ ++ // write block data ++ stream.write(_buffer, 0, GeoStructure.BLOCK_CELLS * 3); ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java (working copy) +@@ -0,0 +1,176 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++import java.io.IOException; ++import java.nio.ByteBuffer; ++ ++/** ++ * @author Hasha ++ */ ++public class BlockFlat extends ABlock ++{ ++ protected final short _height; ++ protected byte _nswe; ++ ++ /** ++ * Creates FlatBlock. ++ * @param bb : Input byte buffer. ++ * @param format : GeoFormat specifying format of loaded data. ++ */ ++ public BlockFlat(ByteBuffer bb, GeoFormat format) ++ { ++ _height = bb.getShort(); ++ _nswe = format != GeoFormat.L2D ? 0x0F : (byte) (0xFF); ++ if (format == GeoFormat.L2OFF) ++ { ++ bb.getShort(); ++ } ++ } ++ ++ @Override ++ public boolean hasGeoPos() ++ { ++ return true; ++ } ++ ++ @Override ++ public short getHeightNearest(int geoX, int geoY, int worldZ) ++ { ++ return _height; ++ } ++ ++ @Override ++ public short getHeightNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return _height; ++ } ++ ++ @Override ++ public short getHeightAbove(int geoX, int geoY, int worldZ) ++ { ++ // check and return height ++ return _height > worldZ ? _height : Short.MIN_VALUE; ++ } ++ ++ @Override ++ public short getHeightBelow(int geoX, int geoY, int worldZ) ++ { ++ // check and return height ++ return _height < worldZ ? _height : Short.MAX_VALUE; ++ } ++ ++ @Override ++ public byte getNsweNearest(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweAbove(int geoX, int geoY, int worldZ) ++ { ++ // check height and return nswe ++ return _height > worldZ ? _nswe : 0; ++ } ++ ++ @Override ++ public byte getNsweBelow(int geoX, int geoY, int worldZ) ++ { ++ // check height and return nswe ++ return _height < worldZ ? _nswe : 0; ++ } ++ ++ @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 getIndexAboveOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexAbove(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public int getIndexBelow(int geoX, int geoY, int worldZ) ++ { ++ // check height and return index ++ return _height < worldZ ? 0 : -1; ++ } ++ ++ @Override ++ public int getIndexBelowOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexBelow(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeight(int index) ++ { ++ return _height; ++ } ++ ++ @Override ++ public short getHeightOriginal(int index) ++ { ++ return _height; ++ } ++ ++ @Override ++ public byte getNswe(int index) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweOriginal(int index) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public void setNswe(int index, byte nswe) ++ { ++ _nswe = nswe; ++ } ++ ++ @Override ++ public void saveBlock(BufferedOutputStream stream) throws IOException ++ { ++ // write block type ++ stream.write(GeoStructure.TYPE_FLAT_L2D); ++ ++ // write height ++ stream.write((byte) (_height & 0x00FF)); ++ stream.write((byte) (_height >> 8)); ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java (working copy) +@@ -0,0 +1,465 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++import java.io.IOException; ++import java.nio.ByteBuffer; ++import java.nio.ByteOrder; ++import java.util.Arrays; ++ ++/** ++ * @author Hasha ++ */ ++public class BlockMultilayer extends ABlock ++{ ++ private static final int MAX_LAYERS = Byte.MAX_VALUE; ++ ++ private static ByteBuffer _temp; ++ ++ /** ++ * Initializes the temporarily buffer. ++ */ ++ public static void initialize() ++ { ++ // initialize temporarily buffer and sorting mechanism ++ _temp = ByteBuffer.allocate(GeoStructure.BLOCK_CELLS * MAX_LAYERS * 3); ++ _temp.order(ByteOrder.LITTLE_ENDIAN); ++ } ++ ++ /** ++ * Releases temporarily buffer. ++ */ ++ public static void release() ++ { ++ _temp = null; ++ } ++ ++ protected byte[] _buffer; ++ ++ /** ++ * Implicit constructor for children class. ++ */ ++ protected BlockMultilayer() ++ { ++ _buffer = null; ++ } ++ ++ /** ++ * Creates MultilayerBlock. ++ * @param bb : Input byte buffer. ++ * @param format : GeoFormat specifying format of loaded data. ++ */ ++ public BlockMultilayer(ByteBuffer bb, GeoFormat format) ++ { ++ // 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 = format != GeoFormat.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++) ++ { ++ if (format != GeoFormat.L2D) ++ { ++ // get data ++ final short data = bb.getShort(); ++ ++ // add nswe and height ++ _temp.put((byte) (data & 0x000F)); ++ _temp.putShort((short) ((short) (data & 0xFFF0) >> 1)); ++ } ++ else ++ { ++ // add nswe ++ _temp.put(bb.get()); ++ ++ // add height ++ _temp.putShort(bb.getShort()); ++ } ++ } ++ } ++ ++ // 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 short getHeightNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getHeightNearest(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeightAbove(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 height ++ if (height > worldZ) ++ { ++ return (short) height; ++ } ++ ++ // move index to next layer ++ index -= 3; ++ } ++ ++ // none layer found, return minimum value ++ return Short.MIN_VALUE; ++ } ++ ++ @Override ++ public short getHeightBelow(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 height ++ if (height < worldZ) ++ { ++ return (short) height; ++ } ++ ++ // move index to next layer ++ index += 3; ++ } ++ ++ // none layer found, return maximum value ++ return Short.MAX_VALUE; ++ } ++ ++ @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 byte getNsweNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getNsweNearest(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public byte getNsweAbove(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 nswe ++ if (height > worldZ) ++ { ++ return _buffer[index]; ++ } ++ ++ // move index to next layer ++ index -= 3; ++ } ++ ++ // none layer found, block movement ++ return 0; ++ } ++ ++ @Override ++ public byte getNsweBelow(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 nswe ++ if (height < worldZ) ++ { ++ return _buffer[index]; ++ } ++ ++ // move index to next layer ++ index += 3; ++ } ++ ++ // none layer found, block movement ++ return 0; ++ } ++ ++ @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 first layer data (first from bottom) ++ byte layers = _buffer[index++]; ++ ++ // loop though all cell layers, find closest layer ++ 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; ++ } ++ ++ // none layer found ++ return -1; ++ } ++ ++ @Override ++ public int getIndexAboveOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexAbove(geoX, geoY, worldZ); ++ } ++ ++ @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; ++ } ++ ++ // none layer found ++ return -1; ++ } ++ ++ @Override ++ public int getIndexBelowOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexBelow(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeight(int index) ++ { ++ // get height ++ return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ } ++ ++ @Override ++ public short getHeightOriginal(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]; ++ } ++ ++ @Override ++ public byte getNsweOriginal(int index) ++ { ++ // get nswe ++ return _buffer[index]; ++ } ++ ++ @Override ++ public void setNswe(int index, byte nswe) ++ { ++ // set nswe ++ _buffer[index] = nswe; ++ } ++ ++ @Override ++ public void saveBlock(BufferedOutputStream stream) throws IOException ++ { ++ // write block type ++ stream.write(GeoStructure.TYPE_MULTILAYER_L2D); ++ ++ // for each cell ++ int index = 0; ++ for (int i = 0; i < GeoStructure.BLOCK_CELLS; i++) ++ { ++ // write layers count ++ final byte layers = _buffer[index++]; ++ stream.write(layers); ++ ++ // write cell data ++ stream.write(_buffer, index, layers * 3); ++ ++ // move index to next cell ++ index += layers * 3; ++ } ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java (working copy) +@@ -0,0 +1,150 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++ ++/** ++ * @author Hasha ++ */ ++public class BlockNull extends ABlock ++{ ++ private final byte _nswe; ++ ++ public BlockNull() ++ { ++ _nswe = (byte) 0xFF; ++ } ++ ++ @Override ++ public boolean hasGeoPos() ++ { ++ return false; ++ } ++ ++ @Override ++ public short getHeightNearest(int geoX, int geoY, int worldZ) ++ { ++ return (short) worldZ; ++ } ++ ++ @Override ++ public short getHeightNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return (short) worldZ; ++ } ++ ++ @Override ++ public short getHeightAbove(int geoX, int geoY, int worldZ) ++ { ++ return (short) worldZ; ++ } ++ ++ @Override ++ public short getHeightBelow(int geoX, int geoY, int worldZ) ++ { ++ return (short) worldZ; ++ } ++ ++ @Override ++ public byte getNsweNearest(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweAbove(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweBelow(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) ++ { ++ return 0; ++ } ++ ++ @Override ++ public int getIndexAboveOriginal(int geoX, int geoY, int worldZ) ++ { ++ return 0; ++ } ++ ++ @Override ++ public int getIndexBelow(int geoX, int geoY, int worldZ) ++ { ++ return 0; ++ } ++ ++ @Override ++ public int getIndexBelowOriginal(int geoX, int geoY, int worldZ) ++ { ++ return 0; ++ } ++ ++ @Override ++ public short getHeight(int index) ++ { ++ return 0; ++ } ++ ++ @Override ++ public short getHeightOriginal(int index) ++ { ++ return 0; ++ } ++ ++ @Override ++ public byte getNswe(int index) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweOriginal(int index) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public void setNswe(int index, byte nswe) ++ { ++ } ++ ++ @Override ++ public void saveBlock(BufferedOutputStream stream) ++ { ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java (nonexistent) +@@ -1,48 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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() +- { +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/ComplexBlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/ComplexBlock.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/ComplexBlock.java (nonexistent) +@@ -1,77 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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; +- +-/** +- * @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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/FlatBlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/FlatBlock.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/FlatBlock.java (nonexistent) +@@ -1,56 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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; +- +-/** +- * @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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/GeoFormat.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/GeoFormat.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/GeoFormat.java (working copy) +@@ -0,0 +1,39 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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 Hasha ++ */ ++public enum GeoFormat ++{ ++ L2J("%d_%d.l2j"), ++ L2OFF("%d_%d_conv.dat"), ++ L2D("%d_%d.l2d"); ++ ++ private final String _filename; ++ ++ private GeoFormat(String filename) ++ { ++ _filename = filename; ++ } ++ ++ public String getFilename() ++ { ++ return _filename; ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/GeoLocation.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/GeoLocation.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/GeoLocation.java (working copy) +@@ -0,0 +1,67 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.geoengine.GeoEngine; ++import org.l2jmobius.gameserver.model.Location; ++ ++/** ++ * @author Hasha ++ */ ++public class GeoLocation extends Location ++{ ++ private byte _nswe; ++ ++ public GeoLocation(int x, int y, int z) ++ { ++ super(x, y, GeoEngine.getInstance().getHeightNearest(x, y, z)); ++ _nswe = GeoEngine.getInstance().getNsweNearest(x, y, z); ++ } ++ ++ public void set(int x, int y, short z) ++ { ++ super.setXYZ(x, y, GeoEngine.getInstance().getHeightNearest(x, y, z)); ++ _nswe = GeoEngine.getInstance().getNsweNearest(x, y, z); ++ } ++ ++ public int getGeoX() ++ { ++ return _x; ++ } ++ ++ public int getGeoY() ++ { ++ return _y; ++ } ++ ++ @Override ++ public int getX() ++ { ++ return GeoEngine.getWorldX(_x); ++ } ++ ++ @Override ++ public int getY() ++ { ++ return GeoEngine.getWorldY(_y); ++ } ++ ++ public byte getNSWE() ++ { ++ return _nswe; ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java (working copy) +@@ -0,0 +1,70 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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; ++ ++/** ++ * @author Hasha ++ */ ++public class GeoStructure ++{ ++ // cells ++ public static final byte CELL_FLAG_E = 1 << 0; ++ public static final byte CELL_FLAG_W = 1 << 1; ++ public static final byte CELL_FLAG_S = 1 << 2; ++ public static final byte CELL_FLAG_N = 1 << 3; ++ public static final byte CELL_FLAG_SE = 1 << 4; ++ public static final byte CELL_FLAG_SW = 1 << 5; ++ public static final byte CELL_FLAG_NE = 1 << 6; ++ public static final byte CELL_FLAG_NW = (byte) (1 << 7); ++ ++ public static final int CELL_HEIGHT = 8; ++ public static final int CELL_IGNORE_HEIGHT = CELL_HEIGHT * 6; ++ ++ // blocks ++ public static final byte TYPE_FLAT_L2J_L2OFF = 0; ++ public static final byte TYPE_FLAT_L2D = (byte) 0xD0; ++ public static final byte TYPE_COMPLEX_L2J = 1; ++ public static final byte TYPE_COMPLEX_L2OFF = 0x40; ++ public static final byte TYPE_COMPLEX_L2D = (byte) 0xD1; ++ 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) ++ public static final byte TYPE_MULTILAYER_L2D = (byte) 0xD2; ++ ++ 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; ++ ++ // regions ++ 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; ++ ++ // global geodata ++ private static final int GEO_REGIONS_X = ((World.TILE_X_MAX - World.TILE_X_MIN) + 1); ++ private 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 +Index: java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java (nonexistent) +@@ -1,42 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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); +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/IGeoObject.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/IGeoObject.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/IGeoObject.java (working copy) +@@ -0,0 +1,53 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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 Hasha ++ */ ++public interface IGeoObject ++{ ++ /** ++ * Returns geodata X coordinate of the {@link IGeoObject}. ++ * @return int : Geodata X coordinate. ++ */ ++ int getGeoX(); ++ ++ /** ++ * Returns geodata Y coordinate of the {@link IGeoObject}. ++ * @return int : Geodata Y coordinate. ++ */ ++ int getGeoY(); ++ ++ /** ++ * Returns geodata Z coordinate of the {@link IGeoObject}. ++ * @return int : Geodata Z coordinate. ++ */ ++ int getGeoZ(); ++ ++ /** ++ * Returns height of the {@link IGeoObject}. ++ * @return int : Height. ++ */ ++ int getHeight(); ++ ++ /** ++ * Returns {@link IGeoObject} data. ++ * @return byte[][] : {@link IGeoObject} data. ++ */ ++ byte[][] getObjectGeoData(); ++} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java (nonexistent) +@@ -1,47 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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(); +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/MultilayerBlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/MultilayerBlock.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/MultilayerBlock.java (nonexistent) +@@ -1,183 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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; +- +-/** +- * @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); +- +- // 1 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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/NullRegion.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/NullRegion.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/NullRegion.java (nonexistent) +@@ -1,55 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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 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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/Region.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/Region.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/Region.java (nonexistent) +@@ -1,92 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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; +- +-/** +- * @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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java (nonexistent) +@@ -1,87 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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 T _loc; +- private AbstractNode _parent; +- +- public AbstractNode(T loc) +- { +- _loc = loc; +- } +- +- public void setParent(AbstractNode p) +- { +- _parent = p; +- } +- +- public AbstractNode getParent() +- { +- return _parent; +- } +- +- public T getLoc() +- { +- return _loc; +- } +- +- public void setLoc(T l) +- { +- _loc = l; +- } +- +- @Override +- public int hashCode() +- { +- final int prime = 31; +- int result = 1; +- result = (prime * result) + ((_loc == null) ? 0 : _loc.hashCode()); +- return result; +- } +- +- @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 +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java (nonexistent) +@@ -1,33 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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 int getNodeX(); +- +- public abstract int getNodeY(); +-} +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNode.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNode.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNode.java (nonexistent) +@@ -1,67 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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 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 +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNodeBuffer.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNodeBuffer.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNodeBuffer.java (nonexistent) +@@ -1,348 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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.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 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) +- { +- _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(); +- } +- +- 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 void getNeighbors() +- { +- if (!_current.getLoc().canGoAll()) +- { +- 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); +- } +- +- // SouthEast +- if ((nodeE != null) && (nodeS != null)) +- { +- if (nodeE.getLoc().canGoSouth() && nodeS.getLoc().canGoEast()) +- { +- addNode(x + 1, y + 1, z, true); +- } +- } +- +- // SouthWest +- if ((nodeS != null) && (nodeW != null)) +- { +- if (nodeW.getLoc().canGoSouth() && nodeS.getLoc().canGoWest()) +- { +- addNode(x - 1, y + 1, z, true); +- } +- } +- +- // NorthEast +- if ((nodeN != null) && (nodeE != null)) +- { +- if (nodeE.getLoc().canGoNorth() && nodeN.getLoc().canGoEast()) +- { +- addNode(x + 1, y - 1, z, true); +- } +- } +- +- // NorthWest +- if ((nodeN != null) && (nodeW != null)) +- { +- if (nodeW.getLoc().canGoNorth() && nodeN.getLoc().canGoWest()) +- { +- addNode(x - 1, y - 1, z, true); +- } +- } +- } +- +- private 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(); +- // reinit node if needed +- if (result.getLoc() != null) +- { +- result.getLoc().set(x, y, z); +- } +- else +- { +- result.setLoc(new NodeLoc(x, y, z)); +- } +- } +- +- return result; +- } +- +- private 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 boolean isHighWeight(int x, int y, int z) +- { +- final CellNode result = getNode(x, y, z); +- if (result == null) +- { +- return true; +- } +- +- if (!result.getLoc().canGoAll()) +- { +- return true; +- } +- if (Math.abs(result.getLoc().getZ() - z) > 16) +- { +- return true; +- } +- +- return false; +- } +- +- private 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 +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java (working copy) +@@ -0,0 +1,87 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.geodata.GeoLocation; ++ ++/** ++ * @author Hasha ++ */ ++public class Node ++{ ++ // node coords and nswe flag ++ private GeoLocation _loc; ++ ++ // node parent (for reverse path construction) ++ private Node _parent; ++ // node child (for moving over nodes during iteration) ++ private Node _child; ++ ++ // node G cost (movement cost = parent movement cost + current movement cost) ++ private double _cost = -1000; ++ ++ public void setLoc(int x, int y, int z) ++ { ++ _loc = new GeoLocation(x, y, z); ++ } ++ ++ public GeoLocation getLoc() ++ { ++ return _loc; ++ } ++ ++ public void setParent(Node parent) ++ { ++ _parent = parent; ++ } ++ ++ public Node getParent() ++ { ++ return _parent; ++ } ++ ++ public void setChild(Node child) ++ { ++ _child = child; ++ } ++ ++ public Node getChild() ++ { ++ return _child; ++ } ++ ++ public void setCost(double cost) ++ { ++ _cost = cost; ++ } ++ ++ public double getCost() ++ { ++ return _cost; ++ } ++ ++ public void free() ++ { ++ // reset node location ++ _loc = null; ++ ++ // reset node parent, child and cost ++ _parent = null; ++ _child = null; ++ _cost = -1000; ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java (working copy) +@@ -0,0 +1,306 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.concurrent.locks.ReentrantLock; ++ ++import org.l2jmobius.Config; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; ++ ++/** ++ * @author DS, Hasha; Credits to Diamond ++ */ ++public class NodeBuffer ++{ ++ private final ReentrantLock _lock = new ReentrantLock(); ++ private final int _size; ++ private final Node[][] _buffer; ++ ++ // center coordinates ++ private int _cx = 0; ++ private int _cy = 0; ++ ++ // target coordinates ++ private int _gtx = 0; ++ private int _gty = 0; ++ private short _gtz = 0; ++ ++ private Node _current = null; ++ ++ /** ++ * Constructor of NodeBuffer. ++ * @param size : one dimension size of buffer ++ */ ++ public NodeBuffer(int size) ++ { ++ // set size ++ _size = size; ++ ++ // initialize buffer ++ _buffer = new Node[size][size]; ++ for (int x = 0; x < size; x++) ++ { ++ for (int y = 0; y < size; y++) ++ { ++ _buffer[x][y] = 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 Node : first node of path ++ */ ++ public Node findPath(int gox, int goy, short goz, int gtx, int gty, short gtz) ++ { ++ // set coordinates (middle of the line (gox,goy) - (gtx,gty), will be in the center of the buffer) ++ _cx = gox + ((gtx - gox - _size) / 2); ++ _cy = goy + ((gty - goy - _size) / 2); ++ ++ _gtx = gtx; ++ _gty = gty; ++ _gtz = gtz; ++ ++ _current = getNode(gox, goy, goz); ++ _current.setCost(getCostH(gox, goy, goz)); ++ ++ int count = 0; ++ do ++ { ++ // reached target? ++ if ((_current.getLoc().getGeoX() == _gtx) && (_current.getLoc().getGeoY() == _gty) && (Math.abs(_current.getLoc().getZ() - _gtz) < 8)) ++ { ++ return _current; ++ } ++ ++ // expand current node ++ expand(); ++ ++ // move pointer ++ _current = _current.getChild(); ++ } ++ while ((_current != null) && (count++ < Config.MAX_ITERATIONS)); ++ ++ return null; ++ } ++ ++ public boolean isLocked() ++ { ++ return _lock.tryLock(); ++ } ++ ++ public void free() ++ { ++ _current = null; ++ ++ for (Node[] nodes : _buffer) ++ { ++ for (Node node : nodes) ++ { ++ if (node.getLoc() != null) ++ { ++ node.free(); ++ } ++ } ++ } ++ ++ _lock.unlock(); ++ } ++ ++ /** ++ * Check _current Node and add its neighbors to the buffer. ++ */ ++ private final void expand() ++ { ++ // can't move anywhere, don't expand ++ final byte nswe = _current.getLoc().getNSWE(); ++ if (nswe == 0) ++ { ++ return; ++ } ++ ++ // get geo coords of the node to be expanded ++ final int x = _current.getLoc().getGeoX(); ++ final int y = _current.getLoc().getGeoY(); ++ final short z = (short) _current.getLoc().getZ(); ++ ++ // can move north, expand ++ if ((nswe & GeoStructure.CELL_FLAG_N) != 0) ++ { ++ addNode(x, y - 1, z, Config.BASE_WEIGHT); ++ } ++ ++ // can move south, expand ++ if ((nswe & GeoStructure.CELL_FLAG_S) != 0) ++ { ++ addNode(x, y + 1, z, Config.BASE_WEIGHT); ++ } ++ ++ // can move west, expand ++ if ((nswe & GeoStructure.CELL_FLAG_W) != 0) ++ { ++ addNode(x - 1, y, z, Config.BASE_WEIGHT); ++ } ++ ++ // can move east, expand ++ if ((nswe & GeoStructure.CELL_FLAG_E) != 0) ++ { ++ addNode(x + 1, y, z, Config.BASE_WEIGHT); ++ } ++ ++ // can move north-west, expand ++ if ((nswe & GeoStructure.CELL_FLAG_NW) != 0) ++ { ++ addNode(x - 1, y - 1, z, Config.DIAGONAL_WEIGHT); ++ } ++ ++ // can move north-east, expand ++ if ((nswe & GeoStructure.CELL_FLAG_NE) != 0) ++ { ++ addNode(x + 1, y - 1, z, Config.DIAGONAL_WEIGHT); ++ } ++ ++ // can move south-west, expand ++ if ((nswe & GeoStructure.CELL_FLAG_SW) != 0) ++ { ++ addNode(x - 1, y + 1, z, Config.DIAGONAL_WEIGHT); ++ } ++ ++ // can move south-east, expand ++ if ((nswe & GeoStructure.CELL_FLAG_SE) != 0) ++ { ++ addNode(x + 1, y + 1, z, Config.DIAGONAL_WEIGHT); ++ } ++ } ++ ++ /** ++ * Returns node, if it exists in buffer. ++ * @param x : node X coord ++ * @param y : node Y coord ++ * @param z : node Z coord ++ * @return Node : node, if exits in buffer ++ */ ++ private final Node getNode(int x, int y, short z) ++ { ++ // check node X out of coordinates ++ final int ix = x - _cx; ++ if ((ix < 0) || (ix >= _size)) ++ { ++ return null; ++ } ++ ++ // check node Y out of coordinates ++ final int iy = y - _cy; ++ if ((iy < 0) || (iy >= _size)) ++ { ++ return null; ++ } ++ ++ // get node ++ final Node result = _buffer[ix][iy]; ++ ++ // check and update ++ if (result.getLoc() == null) ++ { ++ result.setLoc(x, y, z); ++ } ++ ++ // return node ++ return result; ++ } ++ ++ /** ++ * Add node given by coordinates to the buffer. ++ * @param x : geo X coord ++ * @param y : geo Y coord ++ * @param z : geo Z coord ++ * @param weight : weight of movement to new node ++ */ ++ private final void addNode(int x, int y, short z, int weight) ++ { ++ // get node to be expanded ++ final Node node = getNode(x, y, z); ++ if (node == null) ++ { ++ return; ++ } ++ ++ // Z distance between nearby cells is higher than cell size ++ if (node.getLoc().getZ() > (z + (2 * GeoStructure.CELL_HEIGHT))) ++ { ++ return; ++ } ++ ++ // node was already expanded, return ++ if (node.getCost() >= 0) ++ { ++ return; ++ } ++ ++ node.setParent(_current); ++ if (node.getLoc().getNSWE() != (byte) 0xFF) ++ { ++ node.setCost(getCostH(x, y, node.getLoc().getZ()) + (weight * Config.OBSTACLE_MULTIPLIER)); ++ } ++ else ++ { ++ node.setCost(getCostH(x, y, node.getLoc().getZ()) + weight); ++ } ++ ++ Node current = _current; ++ int count = 0; ++ while ((current.getChild() != null) && (count < (Config.MAX_ITERATIONS * 4))) ++ { ++ count++; ++ if (current.getChild().getCost() > node.getCost()) ++ { ++ node.setChild(current.getChild()); ++ break; ++ } ++ current = current.getChild(); ++ } ++ ++ if (count >= (Config.MAX_ITERATIONS * 4)) ++ { ++ System.err.println("Pathfinding: too long loop detected, cost:" + node.getCost()); ++ } ++ ++ current.setChild(node); ++ } ++ ++ /** ++ * @param x : node X coord ++ * @param y : node Y coord ++ * @param i : node Z coord ++ * @return double : node cost ++ */ ++ private final double getCostH(int x, int y, int i) ++ { ++ final int dX = x - _gtx; ++ final int dY = y - _gty; ++ final int dZ = (i - _gtz) / GeoStructure.CELL_HEIGHT; ++ ++ // return (Math.abs(dX) + Math.abs(dY) + Math.abs(dZ)) * Config.HEURISTIC_WEIGHT; // Manhattan distance ++ return Math.sqrt((dX * dX) + (dY * dY) + (dZ * dZ)) * Config.HEURISTIC_WEIGHT; // Direct distance ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeLoc.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeLoc.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeLoc.java (nonexistent) +@@ -1,183 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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.Cell; +- +-/** +- * @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 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 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; +- // return super.hashCode(); +- } +- +- @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; +- if (_x != other._x) +- { +- return false; +- } +- if (_y != other._y) +- { +- return false; +- } +- if (_goNorth != other._goNorth) +- { +- return false; +- } +- if (_goEast != other._goEast) +- { +- return false; +- } +- if (_goSouth != other._goSouth) +- { +- return false; +- } +- if (_goWest != other._goWest) +- { +- return false; +- } +- if (_geoHeight != other._geoHeight) +- { +- return false; +- } +- return true; +- } +-} +Index: java/org/l2jmobius/gameserver/model/actor/Creature.java +=================================================================== +--- java/org/l2jmobius/gameserver/model/actor/Creature.java (revision 8399) ++++ java/org/l2jmobius/gameserver/model/actor/Creature.java (working copy) +@@ -66,8 +66,6 @@ + import org.l2jmobius.gameserver.enums.TeleportWhereType; + import org.l2jmobius.gameserver.enums.UserInfoType; + import org.l2jmobius.gameserver.geoengine.GeoEngine; +-import org.l2jmobius.gameserver.geoengine.GeoEnginePathfinding; +-import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; + import org.l2jmobius.gameserver.instancemanager.IdManager; + import org.l2jmobius.gameserver.instancemanager.MapRegionManager; + import org.l2jmobius.gameserver.instancemanager.QuestManager; +@@ -2577,7 +2575,7 @@ + + public boolean disregardingGeodata; + public int onGeodataPathIndex; +- public List geoPath; ++ public List geoPath; + public int geoPathAccurateTx; + public int geoPathAccurateTy; + public int geoPathGtx; +@@ -3466,7 +3464,7 @@ + if (((originalDistance - distance) > 30) && !isControlBlocked() && !isInVehicle) + { + // Path calculation -- overrides previous movement check +- m.geoPath = GeoEnginePathfinding.getInstance().findPath(curX, curY, curZ, originalX, originalY, originalZ, getInstanceWorld()); ++ m.geoPath = GeoEngine.getInstance().findPath(curX, curY, curZ, originalX, originalY, originalZ, getInstanceWorld()); + if ((m.geoPath == null) || (m.geoPath.size() < 2)) // No path found + { + if (isPlayer() && !_isFlying && !isInWater) +Index: java/org/l2jmobius/gameserver/model/actor/instance/PlayerInstance.java +=================================================================== +--- java/org/l2jmobius/gameserver/model/actor/instance/PlayerInstance.java (revision 8397) ++++ java/org/l2jmobius/gameserver/model/actor/instance/PlayerInstance.java (working copy) +@@ -12704,7 +12704,7 @@ + { + if (Config.CORRECT_PLAYER_Z) + { +- final int nearestZ = GeoEngine.getInstance().getHigherHeight(getX(), getY(), getZ()); ++ final int nearestZ = GeoEngine.getInstance().getHeightNearest(getX(), getY(), getZ()); + if (getZ() < nearestZ) + { + teleToLocation(new Location(getX(), getY(), nearestZ)); +Index: java/org/l2jmobius/gameserver/util/GeoUtils.java +=================================================================== +--- java/org/l2jmobius/gameserver/util/GeoUtils.java (revision 8397) ++++ java/org/l2jmobius/gameserver/util/GeoUtils.java (working copy) +@@ -19,7 +19,7 @@ + import java.awt.Color; + + import org.l2jmobius.gameserver.geoengine.GeoEngine; +-import org.l2jmobius.gameserver.geoengine.geodata.Cell; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; + import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance; + import org.l2jmobius.gameserver.network.serverpackets.ExServerPrimitive; + +@@ -26,25 +26,25 @@ + /** + * @author HorridoJoho + */ +-public final class GeoUtils ++public class GeoUtils + { + public static void debug2DLine(PlayerInstance player, int x, int y, int tx, int ty, int z) + { +- final int gx = GeoEngine.getInstance().getGeoX(x); +- final int gy = GeoEngine.getInstance().getGeoY(y); ++ final int gx = GeoEngine.getGeoX(x); ++ final int gy = GeoEngine.getGeoY(y); + +- final int tgx = GeoEngine.getInstance().getGeoX(tx); +- final int tgy = GeoEngine.getInstance().getGeoY(ty); ++ final int tgx = GeoEngine.getGeoX(tx); ++ final int tgy = GeoEngine.getGeoY(ty); + + final ExServerPrimitive prim = new ExServerPrimitive("Debug2DLine", x, y, z); +- prim.addLine(Color.BLUE, GeoEngine.getInstance().getWorldX(gx), GeoEngine.getInstance().getWorldY(gy), z, GeoEngine.getInstance().getWorldX(tgx), GeoEngine.getInstance().getWorldY(tgy), z); ++ prim.addLine(Color.BLUE, GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), z, GeoEngine.getWorldX(tgx), GeoEngine.getWorldY(tgy), z); + + final LinePointIterator iter = new LinePointIterator(gx, gy, tgx, tgy); + + while (iter.next()) + { +- final int wx = GeoEngine.getInstance().getWorldX(iter.x()); +- final int wy = GeoEngine.getInstance().getWorldY(iter.y()); ++ final int wx = GeoEngine.getWorldX(iter.x()); ++ final int wy = GeoEngine.getWorldY(iter.y()); + + prim.addPoint(Color.RED, wx, wy, z); + } +@@ -53,21 +53,21 @@ + + public static void debug3DLine(PlayerInstance player, int x, int y, int z, int tx, int ty, int tz) + { +- final int gx = GeoEngine.getInstance().getGeoX(x); +- final int gy = GeoEngine.getInstance().getGeoY(y); ++ final int gx = GeoEngine.getGeoX(x); ++ final int gy = GeoEngine.getGeoY(y); + +- final int tgx = GeoEngine.getInstance().getGeoX(tx); +- final int tgy = GeoEngine.getInstance().getGeoY(ty); ++ final int tgx = GeoEngine.getGeoX(tx); ++ final int tgy = GeoEngine.getGeoY(ty); + + final ExServerPrimitive prim = new ExServerPrimitive("Debug3DLine", x, y, z); +- prim.addLine(Color.BLUE, GeoEngine.getInstance().getWorldX(gx), GeoEngine.getInstance().getWorldY(gy), z, GeoEngine.getInstance().getWorldX(tgx), GeoEngine.getInstance().getWorldY(tgy), tz); ++ prim.addLine(Color.BLUE, GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), z, GeoEngine.getWorldX(tgx), GeoEngine.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.getInstance().getWorldX(prevX); +- int wy = GeoEngine.getInstance().getWorldY(prevY); ++ int wx = GeoEngine.getWorldX(prevX); ++ int wy = GeoEngine.getWorldY(prevY); + int wz = iter.z(); + prim.addPoint(Color.RED, wx, wy, wz); + +@@ -78,8 +78,8 @@ + + if ((curX != prevX) || (curY != prevY)) + { +- wx = GeoEngine.getInstance().getWorldX(curX); +- wy = GeoEngine.getInstance().getWorldY(curY); ++ wx = GeoEngine.getWorldX(curX); ++ wy = GeoEngine.getWorldY(curY); + wz = iter.z(); + + prim.addPoint(Color.RED, wx, wy, wz); +@@ -93,7 +93,7 @@ + + private static Color getDirectionColor(int x, int y, int z, int nswe) + { +- if (GeoEngine.getInstance().checkNearestNswe(x, y, z, nswe)) ++ if ((GeoEngine.getInstance().getNsweNearest(x, y, z) & nswe) == nswe) + { + return Color.GREEN; + } +@@ -109,9 +109,8 @@ + int iPacket = 0; + + ExServerPrimitive exsp = null; +- final GeoEngine ge = GeoEngine.getInstance(); +- final int playerGx = ge.getGeoX(player.getX()); +- final int playerGy = ge.getGeoY(player.getY()); ++ final int playerGx = GeoEngine.getGeoX(player.getX()); ++ final int playerGy = GeoEngine.getGeoY(player.getY()); + for (int dx = -geoRadius; dx <= geoRadius; ++dx) + { + for (int dy = -geoRadius; dy <= geoRadius; ++dy) +@@ -135,12 +134,12 @@ + final int gx = playerGx + dx; + final int gy = playerGy + dy; + +- final int x = ge.getWorldX(gx); +- final int y = ge.getWorldY(gy); +- final int z = ge.getNearestZ(gx, gy, player.getZ()); ++ final int x = GeoEngine.getWorldX(gx); ++ final int y = GeoEngine.getWorldY(gy); ++ final int z = GeoEngine.getInstance().getHeightNearest(gx, gy, player.getZ()); + + // north arrow +- Color col = getDirectionColor(gx, gy, z, Cell.NSWE_NORTH); ++ Color col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_N); + 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); +@@ -147,7 +146,7 @@ + exsp.addLine(col, x - 4, y - 4, z, x + 4, y - 4, z); + + // east arrow +- col = getDirectionColor(gx, gy, z, Cell.NSWE_EAST); ++ col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_E); + 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); +@@ -154,13 +153,13 @@ + exsp.addLine(col, x + 4, y - 4, z, x + 4, y + 4, z); + + // south arrow +- col = getDirectionColor(gx, gy, z, Cell.NSWE_SOUTH); ++ col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_S); + 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, Cell.NSWE_WEST); ++ col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_W); + 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); +@@ -188,15 +187,15 @@ + { + if (y > lastY) + { +- return Cell.NSWE_SOUTH_EAST; ++ return GeoStructure.CELL_FLAG_SE; // Direction.SOUTH_EAST; + } + else if (y < lastY) + { +- return Cell.NSWE_NORTH_EAST; ++ return GeoStructure.CELL_FLAG_NE; // Direction.NORTH_EAST; + } + else + { +- return Cell.NSWE_EAST; ++ return GeoStructure.CELL_FLAG_E; // Direction.EAST; + } + } + else if (x < lastX) // west +@@ -203,26 +202,27 @@ + { + if (y > lastY) + { +- return Cell.NSWE_SOUTH_WEST; ++ return GeoStructure.CELL_FLAG_SW; // Direction.SOUTH_WEST; + } + else if (y < lastY) + { +- return Cell.NSWE_NORTH_WEST; ++ return GeoStructure.CELL_FLAG_NW; // Direction.NORTH_WEST; + } + else + { +- return Cell.NSWE_WEST; ++ return GeoStructure.CELL_FLAG_W; // Direction.WEST; + } + } +- else // unchanged x ++ else ++ // unchanged x + { + if (y > lastY) + { +- return Cell.NSWE_SOUTH; ++ return GeoStructure.CELL_FLAG_S; // Direction.SOUTH; + } + else if (y < lastY) + { +- return Cell.NSWE_NORTH; ++ return GeoStructure.CELL_FLAG_N; // Direction.NORTH; + } + else + { +Index: java/org/l2jmobius/tools/geodataconverter/GeoDataConverter.java +=================================================================== +--- java/org/l2jmobius/tools/geodataconverter/GeoDataConverter.java (nonexistent) ++++ java/org/l2jmobius/tools/geodataconverter/GeoDataConverter.java (working copy) +@@ -0,0 +1,386 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ */ ++package org.l2jmobius.tools.geodataconverter; ++ ++import java.io.BufferedOutputStream; ++import java.io.File; ++import java.io.FileOutputStream; ++import java.io.RandomAccessFile; ++import java.nio.ByteOrder; ++import java.nio.MappedByteBuffer; ++import java.nio.channels.FileChannel; ++import java.util.Scanner; ++ ++import org.l2jmobius.Config; ++import org.l2jmobius.commons.util.PropertiesParser; ++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.GeoFormat; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; ++import org.l2jmobius.gameserver.model.World; ++ ++/** ++ * @author Hasha ++ */ ++public class GeoDataConverter ++{ ++ private static GeoFormat _format; ++ private static ABlock[][] _blocks; ++ ++ public static void main(String[] args) ++ { ++ // initialize config ++ loadGeoengineConfigs(); ++ ++ // get geodata format ++ String type = ""; ++ try (Scanner scn = new Scanner(System.in)) ++ { ++ while (!(type.equalsIgnoreCase("J") || type.equalsIgnoreCase("O") || type.equalsIgnoreCase("E"))) ++ { ++ System.out.println("GeoDataConverter: Select source geodata type:"); ++ System.out.println(" J: L2J (e.g. 23_22.l2j)"); ++ System.out.println(" O: L2OFF (e.g. 23_22_conv.dat)"); ++ System.out.println(" E: Exit"); ++ System.out.print("Choice: "); ++ type = scn.next(); ++ } ++ } ++ if (type.equalsIgnoreCase("E")) ++ { ++ System.exit(0); ++ } ++ ++ _format = type.equalsIgnoreCase("J") ? GeoFormat.L2J : GeoFormat.L2OFF; ++ ++ // start conversion ++ System.out.println("GeoDataConverter: Converting all " + _format + " files."); ++ ++ // initialize geodata container ++ _blocks = new ABlock[GeoStructure.REGION_BLOCKS_X][GeoStructure.REGION_BLOCKS_Y]; ++ ++ // initialize multilayer temporarily buffer ++ BlockMultilayer.initialize(); ++ ++ // load geo files ++ int converted = 0; ++ for (int rx = World.TILE_X_MIN; rx <= World.TILE_X_MAX; rx++) ++ { ++ for (int ry = World.TILE_Y_MIN; ry <= World.TILE_Y_MAX; ry++) ++ { ++ final String input = String.format(_format.getFilename(), rx, ry); ++ final String filepath = Config.GEODATA_PATH; ++ final File f = new File(filepath + input); ++ if (f.exists() && !f.isDirectory()) ++ { ++ // load geodata ++ if (!loadGeoBlocks(input)) ++ { ++ System.out.println("GeoDataConverter: Unable to load " + input + " region file."); ++ continue; ++ } ++ ++ // recalculate nswe ++ if (!recalculateNswe()) ++ { ++ System.out.println("GeoDataConverter: Unable to convert " + input + " region file."); ++ continue; ++ } ++ ++ // save geodata ++ final String output = String.format(GeoFormat.L2D.getFilename(), rx, ry); ++ if (!saveGeoBlocks(output)) ++ { ++ System.out.println("GeoDataConverter: Unable to save " + output + " region file."); ++ continue; ++ } ++ ++ converted++; ++ System.out.println("GeoDataConverter: Created " + output + " region file."); ++ } ++ } ++ } ++ System.out.println("GeoDataConverter: Converted " + converted + " " + _format + " to L2D region file(s)."); ++ ++ // release multilayer block temporarily buffer ++ BlockMultilayer.release(); ++ } ++ ++ /** ++ * Loads geo blocks from buffer of the region file. ++ * @param filename : The name of the to load. ++ * @return boolean : True when successful. ++ */ ++ private static boolean loadGeoBlocks(String filename) ++ { ++ // region file is load-able, try to load it ++ try (RandomAccessFile raf = new RandomAccessFile(Config.GEODATA_PATH + filename, "r"); ++ FileChannel fc = raf.getChannel()) ++ { ++ final MappedByteBuffer buffer = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()).load(); ++ buffer.order(ByteOrder.LITTLE_ENDIAN); ++ ++ // load 18B header for L2off geodata (1st and 2nd byte...region X and Y) ++ if (_format == GeoFormat.L2OFF) ++ { ++ for (int i = 0; i < 18; i++) ++ { ++ buffer.get(); ++ } ++ } ++ ++ // 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 (_format == GeoFormat.L2J) ++ { ++ // get block type ++ final byte type = buffer.get(); ++ ++ // load block according to block type ++ switch (type) ++ { ++ case GeoStructure.TYPE_FLAT_L2J_L2OFF: ++ { ++ _blocks[ix][iy] = new BlockFlat(buffer, _format); ++ break; ++ } ++ case GeoStructure.TYPE_COMPLEX_L2J: ++ { ++ _blocks[ix][iy] = new BlockComplex(buffer, _format); ++ break; ++ } ++ case GeoStructure.TYPE_MULTILAYER_L2J: ++ { ++ _blocks[ix][iy] = new BlockMultilayer(buffer, _format); ++ break; ++ } ++ default: ++ { ++ throw new IllegalArgumentException("Unknown block type: " + type); ++ } ++ } ++ } ++ else ++ { ++ // get block type ++ final short type = buffer.getShort(); ++ ++ // load block according to block type ++ switch (type) ++ { ++ case GeoStructure.TYPE_FLAT_L2J_L2OFF: ++ { ++ _blocks[ix][iy] = new BlockFlat(buffer, _format); ++ break; ++ } ++ case GeoStructure.TYPE_COMPLEX_L2OFF: ++ { ++ _blocks[ix][iy] = new BlockComplex(buffer, _format); ++ break; ++ } ++ default: ++ { ++ _blocks[ix][iy] = new BlockMultilayer(buffer, _format); ++ break; ++ } ++ } ++ } ++ } ++ } ++ ++ if (buffer.remaining() > 0) ++ { ++ System.out.println("GeoDataConverter: Region file " + filename + " can be corrupted, remaining " + buffer.remaining() + " bytes to read."); ++ return false; ++ } ++ ++ return true; ++ } ++ catch (Exception e) ++ { ++ System.out.println("GeoDataConverter: Error while loading " + filename + " region file."); ++ return false; ++ } ++ } ++ ++ /** ++ * Recalculate diagonal flags for the region file. ++ * @return boolean : True when successful. ++ */ ++ private static boolean recalculateNswe() ++ { ++ try ++ { ++ for (int x = 0; x < GeoStructure.REGION_CELLS_X; x++) ++ { ++ for (int y = 0; y < GeoStructure.REGION_CELLS_Y; y++) ++ { ++ // get block ++ final ABlock block = _blocks[x / GeoStructure.BLOCK_CELLS_X][y / GeoStructure.BLOCK_CELLS_Y]; ++ ++ // skip flat blocks ++ if (block instanceof BlockFlat) ++ { ++ continue; ++ } ++ ++ // for complex and multilayer blocks go though all layers ++ short height = Short.MAX_VALUE; ++ int index; ++ while ((index = block.getIndexBelow(x, y, height)) != -1) ++ { ++ // get height and nswe ++ height = block.getHeight(index); ++ byte nswe = block.getNswe(index); ++ ++ // update nswe with diagonal flags ++ nswe = updateNsweBelow(x, y, height, nswe); ++ ++ // set nswe of the cell ++ block.setNswe(index, nswe); ++ } ++ } ++ } ++ ++ return true; ++ } ++ catch (Exception e) ++ { ++ return false; ++ } ++ } ++ ++ /** ++ * Updates the NSWE flag with diagonal flags. ++ * @param x : Geodata X coordinate. ++ * @param y : Geodata Y coordinate. ++ * @param z : Geodata Z coordinate. ++ * @param nsweValue : NSWE flag to be updated. ++ * @return byte : Updated NSWE flag. ++ */ ++ private static byte updateNsweBelow(int x, int y, short z, byte nsweValue) ++ { ++ byte nswe = nsweValue; ++ ++ // calculate virtual layer height ++ final short height = (short) (z + GeoStructure.CELL_IGNORE_HEIGHT); ++ ++ // get NSWE of neighbor cells below virtual layer (NPC/PC can fall down of clif, but can not climb it -> NSWE of cell below) ++ final byte nsweN = getNsweBelow(x, y - 1, height); ++ final byte nsweS = getNsweBelow(x, y + 1, height); ++ final byte nsweW = getNsweBelow(x - 1, y, height); ++ final byte nsweE = getNsweBelow(x + 1, y, height); ++ ++ // north-west ++ if ((((nswe & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) || (((nswe & GeoStructure.CELL_FLAG_W) != 0) && ((nsweW & GeoStructure.CELL_FLAG_N) != 0))) ++ { ++ nswe |= GeoStructure.CELL_FLAG_NW; ++ } ++ ++ // north-east ++ if ((((nswe & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) || (((nswe & GeoStructure.CELL_FLAG_E) != 0) && ((nsweE & GeoStructure.CELL_FLAG_N) != 0))) ++ { ++ nswe |= GeoStructure.CELL_FLAG_NE; ++ } ++ ++ // south-west ++ if ((((nswe & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) || (((nswe & GeoStructure.CELL_FLAG_W) != 0) && ((nsweW & GeoStructure.CELL_FLAG_S) != 0))) ++ { ++ nswe |= GeoStructure.CELL_FLAG_SW; ++ } ++ ++ // south-east ++ if ((((nswe & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) || (((nswe & GeoStructure.CELL_FLAG_E) != 0) && ((nsweE & GeoStructure.CELL_FLAG_S) != 0))) ++ { ++ nswe |= GeoStructure.CELL_FLAG_SE; ++ } ++ ++ return nswe; ++ } ++ ++ private static byte getNsweBelow(int geoX, int geoY, short worldZ) ++ { ++ // out of geo coordinates ++ if ((geoX < 0) || (geoX >= GeoStructure.REGION_CELLS_X)) ++ { ++ return 0; ++ } ++ ++ // out of geo coordinates ++ if ((geoY < 0) || (geoY >= GeoStructure.REGION_CELLS_Y)) ++ { ++ return 0; ++ } ++ ++ // get block ++ final ABlock block = _blocks[geoX / GeoStructure.BLOCK_CELLS_X][geoY / GeoStructure.BLOCK_CELLS_Y]; ++ ++ // get index, when valid, return nswe ++ final int index = block.getIndexBelow(geoX, geoY, worldZ); ++ return index == -1 ? 0 : block.getNswe(index); ++ } ++ ++ /** ++ * Save region file to file. ++ * @param filename : The name of file to save. ++ * @return boolean : True when successful. ++ */ ++ private static boolean saveGeoBlocks(String filename) ++ { ++ try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(Config.GEODATA_PATH + filename), GeoStructure.REGION_BLOCKS * GeoStructure.BLOCK_CELLS * 3)) ++ { ++ // loop over region blocks and save each block ++ for (int ix = 0; ix < GeoStructure.REGION_BLOCKS_X; ix++) ++ { ++ for (int iy = 0; iy < GeoStructure.REGION_BLOCKS_Y; iy++) ++ { ++ _blocks[ix][iy].saveBlock(bos); ++ } ++ } ++ ++ // flush data to file ++ bos.flush(); ++ ++ return true; ++ } ++ catch (Exception e) ++ { ++ return false; ++ } ++ } ++ ++ private static void loadGeoengineConfigs() ++ { ++ final PropertiesParser geoData = new PropertiesParser(Config.GEOENGINE_CONFIG_FILE); ++ Config.GEODATA_PATH = geoData.getString("GeoDataPath", "./data/geodata/"); ++ Config.COORD_SYNCHRONIZE = geoData.getInt("CoordSynchronize", -1); ++ Config.PART_OF_CHARACTER_HEIGHT = geoData.getInt("PartOfCharacterHeight", 75); ++ Config.MAX_OBSTACLE_HEIGHT = geoData.getInt("MaxObstacleHeight", 32); ++ Config.PATHFINDING = geoData.getBoolean("PathFinding", true); ++ Config.PATHFIND_BUFFERS = geoData.getString("PathFindBuffers", "100x6;128x6;192x6;256x4;320x4;384x4;500x2"); ++ Config.BASE_WEIGHT = geoData.getInt("BaseWeight", 10); ++ Config.DIAGONAL_WEIGHT = geoData.getInt("DiagonalWeight", 14); ++ Config.OBSTACLE_MULTIPLIER = geoData.getInt("ObstacleMultiplier", 10); ++ Config.HEURISTIC_WEIGHT = geoData.getInt("HeuristicWeight", 20); ++ Config.MAX_ITERATIONS = geoData.getInt("MaxIterations", 3500); ++ } ++} +\ No newline at end of file diff --git a/L2J_Mobius_3.0_Helios/dist/game/data/geodata/L2D_GeoEngine.patch b/L2J_Mobius_3.0_Helios/dist/game/data/geodata/L2D_GeoEngine.patch new file mode 100644 index 0000000000..63e2faf0c9 --- /dev/null +++ b/L2J_Mobius_3.0_Helios/dist/game/data/geodata/L2D_GeoEngine.patch @@ -0,0 +1,6052 @@ +Index: dist/game/GeoDataConverter.bat +=================================================================== +--- dist/game/GeoDataConverter.bat (nonexistent) ++++ dist/game/GeoDataConverter.bat (working copy) +@@ -0,0 +1,6 @@ ++@echo off ++title L2D geodata converter ++ ++java -Xmx512m -cp ./../libs/* org.l2jmobius.tools.geodataconverter.GeoDataConverter ++ ++pause +Index: dist/game/GeoDataConverter.sh +=================================================================== +--- dist/game/GeoDataConverter.sh (nonexistent) ++++ dist/game/GeoDataConverter.sh (working copy) +@@ -0,0 +1,4 @@ ++#! /bin/sh ++ ++java -Xmx512m -cp ../libs/*: org.l2jmobius.tools.geodataconverter.GeoDataConverter > log/stdout.log 2>&1 ++ +Index: dist/game/config/GeoEngine.ini +=================================================================== +--- dist/game/config/GeoEngine.ini (revision 8397) ++++ dist/game/config/GeoEngine.ini (working copy) +@@ -1,6 +1,10 @@ + # ================================================================= + # Geodata + # ================================================================= ++# Because of real-time performance we are using geodata files only in ++# diagonal L2D format now (using filename e.g. 22_16.l2d). ++# L2D geodata can be obtained by conversion of existing L2J or L2OFF geodata. ++# Launch "GeoDataConverter.bat/sh" and follow instructions to start the conversion. + + # 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/ +@@ -13,6 +17,16 @@ + CoordSynchronize = 2 + + # ================================================================= ++# Path checking ++# ================================================================= ++ ++# 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 finding + # ================================================================= + +@@ -23,19 +37,23 @@ + # Pathfinding array buffers configuration, default: 100x6;128x6;192x6;256x4;320x4;384x4;500x2 + PathFindBuffers = 100x6;128x6;192x6;256x4;320x4;384x4;500x2 + +-# Weight for nodes without obstacles far from walls +-LowWeight = 0.5 ++# Base path weight, when moving from one node to another on axis direction, default: 10 ++BaseWeight = 10 + +-# Weight for nodes near walls +-MediumWeight = 2 ++# Path weight, when moving from one node to another on diagonal direction, default: BaseWeight * sqrt(2) = 14 ++DiagonalWeight = 14 + +-# Weight for nodes with obstacles +-HighWeight = 3 ++# When movement flags of target node is blocked to any direction, multiply movement weight by this multiplier. ++# This causes pathfinding algorithm to avoid path construction exactly near an obstacle, default: 10 ++ObstacleMultiplier = 10 + +-# Weight for diagonal movement. +-# Default: LowWeight * sqrt(2) +-DiagonalWeight = 0.707 ++# Weight of the heuristic algorithm, which is giving estimated cost from node to target, default: 20 ++# For proper function must be higher than BaseWeight and/or DiagonalWeight. ++HeuristicWeight = 20 + ++# Maximum number of generated nodes per one path-finding process, default 3500 ++MaxIterations = 3500 ++ + # ================================================================= + # Other + # ================================================================= +Index: dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java +=================================================================== +--- dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java (revision 8397) ++++ dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java (working copy) +@@ -55,9 +55,8 @@ + final int worldX = activeChar.getX(); + final int worldY = activeChar.getY(); + final int worldZ = activeChar.getZ(); +- final int geoX = GeoEngine.getInstance().getGeoX(worldX); +- final int geoY = GeoEngine.getInstance().getGeoY(worldY); +- ++ final int geoX = GeoEngine.getGeoX(worldX); ++ final int geoY = GeoEngine.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)); +@@ -73,9 +72,8 @@ + final int worldX = activeChar.getX(); + final int worldY = activeChar.getY(); + final int worldZ = activeChar.getZ(); +- final int geoX = GeoEngine.getInstance().getGeoX(worldX); +- final int geoY = GeoEngine.getInstance().getGeoY(worldY); +- ++ final int geoX = GeoEngine.getGeoX(worldX); ++ final int geoY = GeoEngine.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)); +Index: dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java +=================================================================== +--- dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java (revision 8397) ++++ dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java (working copy) +@@ -18,11 +18,11 @@ + + import java.util.List; + +-import org.l2jmobius.Config; +-import org.l2jmobius.gameserver.geoengine.GeoEnginePathfinding; +-import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; ++import org.l2jmobius.gameserver.geoengine.GeoEngine; + import org.l2jmobius.gameserver.handler.IAdminCommandHandler; ++import org.l2jmobius.gameserver.model.Location; + import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance; ++import org.l2jmobius.gameserver.network.SystemMessageId; + import org.l2jmobius.gameserver.util.BuilderUtil; + + public class AdminPathNode implements IAdminCommandHandler +@@ -37,29 +37,30 @@ + { + if (command.equals("admin_path_find")) + { +- if (!Config.PATHFINDING) +- { +- BuilderUtil.sendSysMessage(activeChar, "PathFinding is disabled."); +- return true; +- } + if (activeChar.getTarget() != null) + { +- final List path = GeoEnginePathfinding.getInstance().findPath(activeChar.getX(), activeChar.getY(), (short) activeChar.getZ(), activeChar.getTarget().getX(), activeChar.getTarget().getY(), (short) activeChar.getTarget().getZ(), activeChar.getInstanceWorld()); ++ 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()); + if (path == null) + { +- BuilderUtil.sendSysMessage(activeChar, "No Route!"); +- return true; ++ BuilderUtil.sendSysMessage(activeChar, "No route found or pathfinding disabled."); + } +- for (AbstractNodeLoc a : path) ++ else + { +- BuilderUtil.sendSysMessage(activeChar, "x:" + a.getX() + " y:" + a.getY() + " z:" + a.getZ()); ++ for (Location point : path) ++ { ++ BuilderUtil.sendSysMessage(activeChar, "x:" + point.getX() + " y:" + point.getY() + " z:" + point.getZ()); ++ } + } + } + else + { +- BuilderUtil.sendSysMessage(activeChar, "No Target!"); ++ activeChar.sendPacket(SystemMessageId.INVALID_TARGET); + } + } ++ else ++ { ++ return false; ++ } + return true; + } + +Index: java/org/l2jmobius/Config.java +=================================================================== +--- java/org/l2jmobius/Config.java (revision 8397) ++++ java/org/l2jmobius/Config.java (working copy) +@@ -32,7 +32,6 @@ + import java.net.UnknownHostException; + import java.nio.charset.StandardCharsets; + import java.nio.file.Files; +-import java.nio.file.Path; + import java.nio.file.Paths; + import java.time.Duration; + import java.util.ArrayList; +@@ -927,14 +926,17 @@ + // -------------------------------------------------- + // GeoEngine + // -------------------------------------------------- +- public static Path GEODATA_PATH; ++ public static String GEODATA_PATH; ++ public static int COORD_SYNCHRONIZE; ++ public static int PART_OF_CHARACTER_HEIGHT; ++ public static int MAX_OBSTACLE_HEIGHT; + public static boolean PATHFINDING; + public static String PATHFIND_BUFFERS; +- public static float LOW_WEIGHT; +- public static float MEDIUM_WEIGHT; +- public static float HIGH_WEIGHT; +- public static float DIAGONAL_WEIGHT; +- public static int COORD_SYNCHRONIZE; ++ public static int BASE_WEIGHT; ++ public static int DIAGONAL_WEIGHT; ++ public static int HEURISTIC_WEIGHT; ++ public static int OBSTACLE_MULTIPLIER; ++ public static int MAX_ITERATIONS; + public static boolean CORRECT_PLAYER_Z; + + /** Attribute System */ +@@ -2468,14 +2470,17 @@ + + // Load GeoEngine config file (if exists) + final PropertiesParser GeoEngine = new PropertiesParser(GEOENGINE_CONFIG_FILE); +- GEODATA_PATH = Paths.get(GeoEngine.getString("GeoDataPath", "./data/geodata")); ++ GEODATA_PATH = GeoEngine.getString("GeoDataPath", "./data/geodata/"); ++ COORD_SYNCHRONIZE = GeoEngine.getInt("CoordSynchronize", -1); ++ PART_OF_CHARACTER_HEIGHT = GeoEngine.getInt("PartOfCharacterHeight", 75); ++ MAX_OBSTACLE_HEIGHT = GeoEngine.getInt("MaxObstacleHeight", 32); + PATHFINDING = GeoEngine.getBoolean("PathFinding", true); + PATHFIND_BUFFERS = GeoEngine.getString("PathFindBuffers", "100x6;128x6;192x6;256x4;320x4;384x4;500x2"); +- LOW_WEIGHT = GeoEngine.getFloat("LowWeight", 0.5f); +- MEDIUM_WEIGHT = GeoEngine.getFloat("MediumWeight", 2); +- HIGH_WEIGHT = GeoEngine.getFloat("HighWeight", 3); +- DIAGONAL_WEIGHT = GeoEngine.getFloat("DiagonalWeight", 0.707f); +- COORD_SYNCHRONIZE = GeoEngine.getInt("CoordSynchronize", -1); ++ BASE_WEIGHT = GeoEngine.getInt("BaseWeight", 10); ++ DIAGONAL_WEIGHT = GeoEngine.getInt("DiagonalWeight", 14); ++ OBSTACLE_MULTIPLIER = GeoEngine.getInt("ObstacleMultiplier", 10); ++ HEURISTIC_WEIGHT = GeoEngine.getInt("HeuristicWeight", 20); ++ MAX_ITERATIONS = GeoEngine.getInt("MaxIterations", 3500); + CORRECT_PLAYER_Z = GeoEngine.getBoolean("CorrectPlayerZ", false); + + // Load AllowedPlayerRaces config file (if exists) +Index: java/org/l2jmobius/gameserver/geoengine/GeoEngine.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/GeoEngine.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/GeoEngine.java (working copy) +@@ -16,101 +16,91 @@ + */ + package org.l2jmobius.gameserver.geoengine; + +-import java.io.IOException; ++import java.io.File; + import java.io.RandomAccessFile; + import java.nio.ByteOrder; +-import java.nio.channels.FileChannel.MapMode; +-import java.nio.file.Files; +-import java.nio.file.Path; +-import java.util.concurrent.atomic.AtomicReferenceArray; +-import java.util.logging.Level; ++import java.nio.MappedByteBuffer; ++import java.nio.channels.FileChannel; ++import java.util.List; + 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.geoengine.geodata.Cell; +-import org.l2jmobius.gameserver.geoengine.geodata.IRegion; +-import org.l2jmobius.gameserver.geoengine.geodata.NullRegion; +-import org.l2jmobius.gameserver.geoengine.geodata.Region; ++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.GeoFormat; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoLocation; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; + import org.l2jmobius.gameserver.instancemanager.WarpedSpaceManager; + 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; ++import org.l2jmobius.gameserver.util.MathUtil; + + /** +- * @author -Nemesiss-, HorridoJoho ++ * @author Hasha + */ + public class GeoEngine + { +- private static final Logger LOGGER = Logger.getLogger(GeoEngine.class.getName()); ++ protected static final Logger LOGGER = Logger.getLogger(GeoEngine.class.getName()); + +- private static final int WORLD_MIN_X = -655360; +- private static final int WORLD_MIN_Y = -589824; +- private static final int WORLD_MIN_Z = -16384; ++ private final ABlock[][] _blocks; ++ private final BlockNull _nullBlock; + +- /** 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; +- +- /** The regions array */ +- private final AtomicReferenceArray _regions = new AtomicReferenceArray<>(GEO_REGIONS); +- +- 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; +- +- protected GeoEngine() ++ /** ++ * GeoEngine constructor. Loads all geodata files of chosen geodata format. ++ */ ++ public GeoEngine() + { + LOGGER.info("GeoEngine: Initializing..."); +- for (int i = 0; i < _regions.length(); i++) +- { +- _regions.set(i, NullRegion.INSTANCE); +- } + ++ // 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; +- try ++ long fileSize = 0; ++ for (int rx = World.TILE_X_MIN; rx <= World.TILE_X_MAX; rx++) + { +- for (int regionX = World.TILE_X_MIN; regionX <= World.TILE_X_MAX; regionX++) ++ for (int ry = World.TILE_Y_MIN; ry <= World.TILE_Y_MAX; ry++) + { +- for (int regionY = World.TILE_Y_MIN; regionY <= World.TILE_Y_MAX; regionY++) ++ final File f = new File(Config.GEODATA_PATH + String.format(GeoFormat.L2D.getFilename(), rx, ry)); ++ if (f.exists() && !f.isDirectory()) + { +- 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(rx, ry)) + { +- try (RandomAccessFile raf = new RandomAccessFile(geoFilePath.toFile(), "r")) +- { +- _regions.set((regionX * GEO_REGIONS_Y) + regionY, new Region(raf.getChannel().map(MapMode.READ_ONLY, 0, raf.length()).load().order(ByteOrder.LITTLE_ENDIAN))); +- loaded++; +- } ++ loaded++; ++ fileSize += f.length(); + } + } ++ else ++ { ++ // region file is not load-able, load null blocks ++ loadNullBlocks(rx, ry); ++ } + } + } +- catch (Exception e) ++ ++ LOGGER.info("GeoEngine: Loaded " + loaded + " geodata files."); ++ if (loaded > 0) + { +- LOGGER.log(Level.SEVERE, "GeoEngine: Failed to load geodata!", e); +- System.exit(1); ++ LOGGER.info("GeoEngine: Total geodata file size " + (fileSize / 1024 / 1024) + " MB."); + } + +- LOGGER.info("GeoEngine: Loaded " + loaded + " geodata files."); +- +- // Avoid wrong configs when no files are loaded. ++ // avoid wrong configs when no files are loaded + if (loaded == 0) + { + if (Config.PATHFINDING) +@@ -124,627 +114,832 @@ + LOGGER.info("GeoEngine: Forcing CoordSynchronize setting to -1."); + } + } ++ ++ // release multilayer block temporarily buffer ++ BlockMultilayer.release(); + } + + /** +- * @param geoX +- * @param geoY +- * @return the region ++ * 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 IRegion getRegion(int geoX, int geoY) ++ private final boolean loadGeoBlocks(int regionX, int regionY) + { +- final int region = ((geoX / IRegion.REGION_CELLS_X) * GEO_REGIONS_Y) + (geoY / IRegion.REGION_CELLS_Y); +- if ((region < 0) || (region >= _regions.length())) ++ final String filename = String.format(GeoFormat.L2D.getFilename(), regionX, regionY); ++ ++ // standard load ++ try (RandomAccessFile raf = new RandomAccessFile(Config.GEODATA_PATH + filename, "r"); ++ FileChannel fc = raf.getChannel()) + { +- return null; ++ // initialize file buffer ++ final MappedByteBuffer buffer = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()).load(); ++ buffer.order(ByteOrder.LITTLE_ENDIAN); ++ ++ // 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++) ++ { ++ // get block type ++ final byte type = buffer.get(); ++ ++ // load block according to block type ++ switch (type) ++ { ++ case GeoStructure.TYPE_FLAT_L2D: ++ { ++ _blocks[blockX + ix][blockY + iy] = new BlockFlat(buffer, GeoFormat.L2D); ++ break; ++ } ++ case GeoStructure.TYPE_COMPLEX_L2D: ++ { ++ _blocks[blockX + ix][blockY + iy] = new BlockComplex(buffer, GeoFormat.L2D); ++ break; ++ } ++ case GeoStructure.TYPE_MULTILAYER_L2D: ++ { ++ _blocks[blockX + ix][blockY + iy] = new BlockMultilayer(buffer, GeoFormat.L2D); ++ break; ++ } ++ default: ++ { ++ throw new IllegalArgumentException("Unknown block type: " + type); ++ } ++ } ++ } ++ } ++ ++ // check data consistency ++ if (buffer.remaining() > 0) ++ { ++ LOGGER.warning("GeoEngine: Region file " + filename + " can be corrupted, remaining " + buffer.remaining() + " bytes to read."); ++ } ++ ++ // loading was successful ++ return true; + } +- return _regions.get(region); +- } +- +- /** +- * @param filePath +- * @param regionX +- * @param regionY +- * @throws IOException +- */ +- 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")) ++ catch (Exception e) + { +- _regions.set(regionOffset, new Region(raf.getChannel().map(MapMode.READ_ONLY, 0, raf.length()).load().order(ByteOrder.LITTLE_ENDIAN))); ++ // an error occured while loading, load null blocks ++ LOGGER.warning("GeoEngine: Error while loading " + filename + " region file."); ++ LOGGER.warning(e.getMessage()); ++ ++ // replace whole region file with null blocks ++ loadNullBlocks(regionX, regionY); ++ ++ // loading was not successful ++ return false; + } + } + + /** +- * @param regionX +- * @param regionY ++ * 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. + */ +- public void unloadRegion(int regionX, int regionY) ++ private final void loadNullBlocks(int regionX, int regionY) + { +- _regions.set((regionX * GEO_REGIONS_Y) + regionY, NullRegion.INSTANCE); +- } +- +- /** +- * @param geoX +- * @param geoY +- * @return if geodata exist +- */ +- public boolean hasGeoPos(int geoX, int geoY) +- { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) ++ // 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++) + { +- return false; ++ for (int iy = 0; iy < GeoStructure.REGION_BLOCKS_Y; iy++) ++ { ++ _blocks[blockX + ix][blockY + iy] = _nullBlock; ++ } + } +- return region.hasGeo(); + } + ++ // GEODATA - GENERAL ++ + /** +- * Checks the specified position for available geodata. +- * @param x the world x +- * @param y the world y +- * @return {@code true} if there is geodata for the given coordinates, {@code false} otherwise ++ * Converts world X to geodata X. ++ * @param worldX ++ * @return int : Geo X + */ +- public boolean hasGeo(int x, int y) ++ public static int getGeoX(int worldX) + { +- return hasGeoPos(getGeoX(x), getGeoY(y)); ++ return (MathUtil.limit(worldX, World.MAP_MIN_X, World.MAP_MAX_X) - World.MAP_MIN_X) >> 4; + } + + /** +- * @param geoX +- * @param geoY +- * @param worldZ +- * @param nswe +- * @return the nearest nswe check ++ * Converts world Y to geodata Y. ++ * @param worldY ++ * @return int : Geo Y + */ +- public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) ++ public static int getGeoY(int worldY) + { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) +- { +- return true; +- } +- return region.checkNearestNswe(geoX, geoY, worldZ, nswe); ++ return (MathUtil.limit(worldY, World.MAP_MIN_Y, World.MAP_MAX_Y) - World.MAP_MIN_Y) >> 4; + } + + /** ++ * Converts geodata X to world X. + * @param geoX +- * @param geoY +- * @param worldZ +- * @param nswe +- * @return the nearest nswe anti-corner cut ++ * @return int : World X + */ +- public boolean checkNearestNsweAntiCornerCut(int geoX, int geoY, int worldZ, int nswe) ++ public static int getWorldX(int geoX) + { +- boolean can = true; +- if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) +- { +- can = checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_EAST) && checkNearestNswe(geoX + 1, geoY, worldZ, Cell.NSWE_NORTH); +- } +- if (can && ((nswe & Cell.NSWE_NORTH_WEST) == Cell.NSWE_NORTH_WEST)) +- { +- 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 = 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 = checkNearestNswe(geoX, geoY + 1, worldZ, Cell.NSWE_WEST) && checkNearestNswe(geoX - 1, geoY, worldZ, Cell.NSWE_SOUTH); +- } +- return can && checkNearestNswe(geoX, geoY, worldZ, nswe); ++ return (MathUtil.limit(geoX, 0, GeoStructure.GEO_CELLS_X) << 4) + World.MAP_MIN_X + 8; + } + + /** +- * @param geoX ++ * Converts geodata Y to world Y. + * @param geoY +- * @param worldZ +- * @return the nearest Z value ++ * @return int : World Y + */ +- public int getNearestZ(int geoX, int geoY, int worldZ) ++ public static int getWorldY(int geoY) + { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) +- { +- return worldZ; +- } +- return region.getNearestZ(geoX, geoY, worldZ); ++ return (MathUtil.limit(geoY, 0, GeoStructure.GEO_CELLS_Y) << 4) + World.MAP_MIN_Y + 8; + } + + /** +- * @param geoX +- * @param geoY +- * @param worldZ +- * @return the next lower Z value ++ * Returns block of geodata on given coordinates. ++ * @param geoX : Geodata X ++ * @param geoY : Geodata Y ++ * @return {@link ABlock} : Block of geodata. + */ +- public int getNextLowerZ(int geoX, int geoY, int worldZ) ++ private final ABlock getBlock(int geoX, int geoY) + { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) ++ final int x = geoX / GeoStructure.BLOCK_CELLS_X; ++ final int y = geoY / GeoStructure.BLOCK_CELLS_Y; ++ ++ // if x or y is out of array return null ++ if ((x > -1) && (y > -1) && (x < GeoStructure.GEO_BLOCKS_X) && (y < GeoStructure.GEO_BLOCKS_Y)) + { +- return worldZ; ++ return _blocks[x][y]; + } +- return region.getNextLowerZ(geoX, geoY, worldZ); ++ return null; + } + + /** +- * @param geoX +- * @param geoY +- * @param worldZ +- * @return the next higher Z value ++ * Check if geo coordinates has geo. ++ * @param geoX : Geodata X ++ * @param geoY : Geodata Y ++ * @return boolean : True, if given geo coordinates have geodata + */ +- public int getNextHigherZ(int geoX, int geoY, int worldZ) ++ public boolean hasGeoPos(int geoX, int geoY) + { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) ++ final ABlock block = getBlock(geoX, geoY); ++ if (block == null) // null block check + { +- return worldZ; ++ // TODO: Find when this can be null. (Bad geodata? Check World getRegion method.) ++ // LOGGER.warning("Could not find geodata block at " + getWorldX(geoX) + ", " + getWorldY(geoY) + "."); ++ return false; + } +- return region.getNextHigherZ(geoX, geoY, worldZ); ++ return block.hasGeoPos(); + } + + /** +- * Gets the Z height. +- * @param x the world x +- * @param y the world y +- * @param z the world z +- * @return the nearest Z height ++ * 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 int getHeight(int x, int y, int z) ++ public short getHeightNearest(int geoX, int geoY, int worldZ) + { +- return getNearestZ(getGeoX(x), getGeoY(y), z); ++ final ABlock block = getBlock(geoX, geoY); ++ return block != null ? block.getHeightNearest(geoX, geoY, worldZ) : (short) worldZ; + } + + /** +- * Gets the next lower Z height. +- * @param x the world x +- * @param y the world y +- * @param z the world z +- * @return the nearest Z height ++ * 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 int getLowerHeight(int x, int y, int z) ++ public byte getNsweNearest(int geoX, int geoY, int worldZ) + { +- return getNextLowerZ(getGeoX(x), getGeoY(y), z); ++ final ABlock block = getBlock(geoX, geoY); ++ return block != null ? block.getNsweNearest(geoX, geoY, worldZ) : (byte) 0xFF; + } + + /** +- * Gets the next higher Z height. +- * @param x the world x +- * @param y the world y +- * @param z the world z +- * @return the nearest Z height ++ * Check if world coordinates has geo. ++ * @param worldX : World X ++ * @param worldY : World Y ++ * @return boolean : True, if given world coordinates have geodata + */ +- public int getHigherHeight(int x, int y, int z) ++ public boolean hasGeo(int worldX, int worldY) + { +- return getNextHigherZ(getGeoX(x), getGeoY(y), z); ++ return hasGeoPos(getGeoX(worldX), getGeoY(worldY)); + } + + /** +- * @param worldX +- * @return the geo X ++ * 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 int getGeoX(int worldX) ++ public short getHeight(int worldX, int worldY, int worldZ) + { +- return (worldX - WORLD_MIN_X) / 16; ++ return getHeightNearest(getGeoX(worldX), getGeoY(worldY), worldZ); + } + +- /** +- * @param worldY +- * @return the geo Y +- */ +- public int getGeoY(int worldY) +- { +- return (worldY - WORLD_MIN_Y) / 16; +- } ++ // PATHFINDING + + /** +- * @param worldZ +- * @return the geo Z ++ * Check line of sight from {@link WorldObject} to {@link WorldObject}. ++ * @param origin : The origin object. ++ * @param target : The target object. ++ * @return {@code boolean} : True if origin can see target + */ +- public int getGeoZ(int worldZ) ++ public boolean canSeeTarget(WorldObject origin, WorldObject target) + { +- return (worldZ - WORLD_MIN_Z) / 16; +- } +- +- /** +- * @param geoX +- * @return the world X +- */ +- public int getWorldX(int geoX) +- { +- return (geoX * 16) + WORLD_MIN_X + 8; +- } +- +- /** +- * @param geoY +- * @return the world Y +- */ +- public int getWorldY(int geoY) +- { +- return (geoY * 16) + WORLD_MIN_Y + 8; +- } +- +- /** +- * @param geoZ +- * @return the world Z +- */ +- public int getWorldZ(int geoZ) +- { +- return (geoZ * 16) + WORLD_MIN_Z + 8; +- } +- +- /** +- * 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) +- { +- if (target.isDoor()) ++ if (target.isDoor() || target.isArtefact() || (target.isCreature() && ((Creature) target).isFlying())) + { +- // Can always see doors. + return true; + } +- return 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(), cha.getInstanceWorld(), worldPosition.getX(), worldPosition.getY(), worldPosition.getZ()); +- } +- +- /** +- * Can see target. Checks doors between. +- * @param x the x coordinate +- * @param y the y coordinate +- * @param z the z coordinate +- * @param world +- * @param tx the target's x coordinate +- * @param ty the target's y coordinate +- * @param tz the target's z coordinate +- * @param tworld the target's instanceId +- * @return +- */ +- public boolean canSeeTarget(int x, int y, int z, Instance world, int tx, int ty, int tz, Instance tworld) +- { +- return (world != tworld) ? false : canSeeTarget(x, y, z, world, tx, ty, tz); +- } +- +- /** +- * 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 +- * @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, Instance instance, int tx, int ty, int tz) +- { +- if (DoorData.getInstance().checkIfDoorsBetween(x, y, z, tx, ty, tz, instance, true)) ++ ++ // get origin and target world coordinates ++ final int ox = origin.getX(); ++ final int oy = origin.getY(); ++ final int oz = origin.getZ(); ++ final int tx = target.getX(); ++ final int ty = target.getY(); ++ final int tz = target.getZ(); ++ ++ if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld(), true)) + { + return false; + } +- return canSeeTarget(x, y, z, tx, ty, tz); +- } +- +- /** +- * @param prevX +- * @param prevY +- * @param prevGeoZ +- * @param curX +- * @param curY +- * @param nswe +- * @return the LOS Z value +- */ +- 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))) ++ if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld())) + { +- throw new RuntimeException("Multiple directions!"); ++ return false; + } ++ if (WarpedSpaceManager.getInstance().checkForWarpedSpace(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld())) ++ { ++ return false; ++ } + +- if (checkNearestNsweAntiCornerCut(prevX, prevY, prevGeoZ, nswe)) ++ // get origin and check existing geo coordinates ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) + { +- return getNearestZ(curX, curY, prevGeoZ); ++ return true; + } + +- return getNextHigherZ(curX, curY, prevGeoZ); ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coordinates ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) ++ { ++ return true; ++ } ++ ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // origin and target coordinates are same ++ if ((gox == gtx) && (goy == gty)) ++ { ++ return goz == gtz; ++ } ++ ++ // get origin and target height, real height = collision height * 2 ++ double oheight = 0; ++ if (origin.isCreature()) ++ { ++ oheight = ((Creature) origin).getCollisionHeight() * 2; ++ } ++ ++ double theight = 0; ++ if (target.isCreature()) ++ { ++ theight = ((Creature) target).getCollisionHeight() * 2; ++ } ++ ++ // perform geodata check ++ return checkSee(gox, goy, goz, oheight, gtx, gty, gtz, theight, origin.getInstanceWorld()); + } + + /** +- * Can see target. Does not check doors between. +- * @param xValue the x coordinate +- * @param yValue the y coordinate +- * @param zValue the z coordinate +- * @param txValue the target's x coordinate +- * @param tyValue the target's y coordinate +- * @param tzValue the target's z coordinate +- * @return {@code true} if there is line of sight between the given coordinate sets, {@code false} otherwise ++ * Check line of sight from {@link WorldObject} to {@link Location}. ++ * @param origin : The origin object. ++ * @param position : The target position. ++ * @return {@code boolean} : True if object can see position + */ +- public boolean canSeeTarget(int xValue, int yValue, int zValue, int txValue, int tyValue, int tzValue) ++ public boolean canSeeTarget(WorldObject origin, Location position) + { +- int x = xValue; +- int y = yValue; +- int tx = txValue; +- int ty = tyValue; ++ // get origin and target world coordinates ++ final int ox = origin.getX(); ++ final int oy = origin.getY(); ++ final int oz = origin.getZ(); ++ final int tx = position.getX(); ++ final int ty = position.getY(); ++ final int tz = position.getZ(); + +- int geoX = getGeoX(x); +- int geoY = getGeoY(y); +- int tGeoX = getGeoX(tx); +- int tGeoY = getGeoY(ty); ++ if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld(), true)) ++ { ++ return false; ++ } ++ if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld())) ++ { ++ return false; ++ } ++ if (WarpedSpaceManager.getInstance().checkForWarpedSpace(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld())) ++ { ++ return false; ++ } + +- int z = getNearestZ(geoX, geoY, zValue); +- int tz = getNearestZ(tGeoX, tGeoY, tzValue); ++ // get origin and check existing geo coordinates ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) ++ { ++ return true; ++ } + +- // Fastpath. +- if ((geoX == tGeoX) && (geoY == tGeoY)) ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coordinates ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) + { +- if (hasGeoPos(tGeoX, tGeoY)) +- { +- return z == tz; +- } + return true; + } + +- if (tz > z) ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // origin and target coordinates are same ++ if ((gox == gtx) && (goy == gty)) + { +- int tmp = tx; +- tx = x; +- x = tmp; +- +- tmp = ty; +- ty = y; +- y = tmp; +- +- tmp = tz; +- tz = z; +- z = tmp; +- +- tmp = tGeoX; +- tGeoX = geoX; +- geoX = tmp; +- +- tmp = tGeoY; +- tGeoY = geoY; +- geoY = tmp; ++ return goz == gtz; + } + +- final LinePointIterator3D pointIter = new LinePointIterator3D(geoX, geoY, z, tGeoX, tGeoY, tz); +- // 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()) ++ // get origin and target height, real height = collision height * 2 ++ double oheight = 0; ++ if (origin.isCreature()) + { +- final int curX = pointIter.x(); +- final int curY = pointIter.y(); ++ oheight = ((Creature) origin).getTemplate().getCollisionHeight(); ++ } ++ ++ // perform geodata check ++ return checkSee(gox, goy, goz, oheight, gtx, gty, gtz, 0, origin.getInstanceWorld()); ++ } ++ ++ /** ++ * Simple check for origin to target visibility. ++ * @param goxValue : origin X geodata coordinate ++ * @param goyValue : origin Y geodata coordinate ++ * @param gozValue : origin Z geodata coordinate ++ * @param oheight : origin height (if instance of {@link Character}) ++ * @param gtxValue : target X geodata coordinate ++ * @param gtyValue : target Y geodata coordinate ++ * @param gtzValue : target Z geodata coordinate ++ * @param theight : target height (if instance of {@link Character}) ++ * @param instance ++ * @return {@code boolean} : True, when target can be seen. ++ */ ++ private final boolean checkSee(int goxValue, int goyValue, int gozValue, double oheight, int gtxValue, int gtyValue, int gtzValue, double theight, Instance instance) ++ { ++ int goz = gozValue; ++ int gtz = gtzValue; ++ int gox = goxValue; ++ int goy = goyValue; ++ int gtx = gtxValue; ++ int gty = gtyValue; ++ ++ // get line of sight Z coordinates ++ double losoz = goz + ((oheight * Config.PART_OF_CHARACTER_HEIGHT) / 100); ++ double lostz = gtz + ((theight * Config.PART_OF_CHARACTER_HEIGHT) / 100); ++ ++ // get X delta and signum ++ final int dx = Math.abs(gtx - gox); ++ final int sx = gox < gtx ? 1 : -1; ++ final byte dirox = sx > 0 ? GeoStructure.CELL_FLAG_E : GeoStructure.CELL_FLAG_W; ++ final byte dirtx = sx > 0 ? GeoStructure.CELL_FLAG_W : GeoStructure.CELL_FLAG_E; ++ ++ // get Y delta and signum ++ final int dy = Math.abs(gty - goy); ++ final int sy = goy < gty ? 1 : -1; ++ final byte diroy = sy > 0 ? GeoStructure.CELL_FLAG_S : GeoStructure.CELL_FLAG_N; ++ final byte dirty = sy > 0 ? GeoStructure.CELL_FLAG_N : GeoStructure.CELL_FLAG_S; ++ ++ // get Z delta ++ final int dm = Math.max(dx, dy); ++ final double dz = (lostz - losoz) / dm; ++ ++ // get direction flag for diagonal movement ++ final byte diroxy = getDirXY(dirox, diroy); ++ final byte dirtxy = getDirXY(dirtx, dirty); ++ ++ // delta, determines axis to move on (+..X axis, -..Y axis) ++ int d = dx - dy; ++ ++ // NSWE direction of movement ++ byte diro; ++ byte dirt; ++ ++ // initialize node values ++ int nox = gox; ++ int noy = goy; ++ int ntx = gtx; ++ int nty = gty; ++ byte nsweo = getNsweNearest(gox, goy, goz); ++ byte nswet = getNsweNearest(gtx, gty, gtz); ++ ++ // loop ++ ABlock block; ++ int index; ++ for (int i = 0; i < ((dm + 1) / 2); i++) ++ { ++ // reset direction flag ++ diro = 0; ++ dirt = 0; + +- if ((curX == prevX) && (curY == prevY)) ++ // calculate next point coordinates ++ final int e2 = 2 * d; ++ if ((e2 > -dy) && (e2 < dx)) + { +- continue; ++ // calculate next point XY coordinates ++ d -= dy; ++ d += dx; ++ nox += sx; ++ ntx -= sx; ++ noy += sy; ++ nty -= sy; ++ diro |= diroxy; ++ dirt |= dirtxy; + } ++ else if (e2 > -dy) ++ { ++ // calculate next point X coordinate ++ d -= dy; ++ nox += sx; ++ ntx -= sx; ++ diro |= dirox; ++ dirt |= dirtx; ++ } ++ else if (e2 < dx) ++ { ++ // calculate next point Y coordinate ++ d += dx; ++ noy += sy; ++ nty -= sy; ++ diro |= diroy; ++ dirt |= dirty; ++ } + +- final int beeCurZ = pointIter.z(); +- int curGeoZ = prevGeoZ; +- +- // Check if the position has geodata. +- if (hasGeoPos(curX, curY)) + { +- final int beeCurGeoZ = getNearestZ(curX, curY, beeCurZ); +- final int nswe = GeoUtils.computeNswe(prevX, prevY, curX, curY); // .computeDirection(prevX, prevY, curX, curY); +- curGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, curX, curY, nswe); +- int maxHeight; +- if (ptIndex < ELEVATED_SEE_OVER_DISTANCE) ++ // get block of the next cell ++ block = getBlock(nox, noy); ++ ++ // get index of particular layer, based on movement conditions ++ if ((nsweo & diro) == 0) + { +- maxHeight = z + MAX_SEE_OVER_HEIGHT; ++ index = block.getIndexAbove(nox, noy, goz - GeoStructure.CELL_IGNORE_HEIGHT); + } + else + { +- maxHeight = beeCurZ + MAX_SEE_OVER_HEIGHT; ++ index = block.getIndexBelow(nox, noy, goz + GeoStructure.CELL_IGNORE_HEIGHT); + } + +- boolean canSeeThrough = false; +- if ((curGeoZ <= maxHeight) && (curGeoZ <= beeCurGeoZ)) ++ // layer does not exist, return ++ if (index == -1) + { +- if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) +- { +- 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 +- { +- canSeeThrough = true; +- } ++ return false; + } + +- if (!canSeeThrough) ++ // get layer and next line of sight Z coordinate ++ goz = block.getHeight(index); ++ losoz += dz; ++ ++ // perform line of sight check, return when fails ++ if ((goz - losoz) > Config.MAX_OBSTACLE_HEIGHT) + { + return false; + } ++ ++ // get layer nswe ++ nsweo = block.getNswe(index); + } ++ { ++ // get block of the next cell ++ block = getBlock(ntx, nty); ++ ++ // get index of particular layer, based on movement conditions ++ if ((nswet & dirt) == 0) ++ { ++ index = block.getIndexAbove(ntx, nty, gtz - GeoStructure.CELL_IGNORE_HEIGHT); ++ } ++ else ++ { ++ index = block.getIndexBelow(ntx, nty, gtz + GeoStructure.CELL_IGNORE_HEIGHT); ++ } ++ ++ // layer does not exist, return ++ if (index == -1) ++ { ++ return false; ++ } ++ ++ // get layer and next line of sight Z coordinate ++ gtz = block.getHeight(index); ++ lostz -= dz; ++ ++ // perform line of sight check, return when fails ++ if ((gtz - lostz) > Config.MAX_OBSTACLE_HEIGHT) ++ { ++ return false; ++ } ++ ++ // get layer nswe ++ nswet = block.getNswe(index); ++ } + +- prevX = curX; +- prevY = curY; +- prevGeoZ = curGeoZ; +- ++ptIndex; ++ // update coords ++ gox = nox; ++ goy = noy; ++ gtx = ntx; ++ gty = nty; + } + +- return true; ++ // when iteration is completed, compare final Z coordinates ++ return Math.abs(goz - gtz) < (GeoStructure.CELL_HEIGHT * 4); + } + + /** +- * Move check. +- * @param x the x coordinate +- * @param y the y coordinate +- * @param zValue the z coordinate +- * @param tx the target's x coordinate +- * @param ty the target's y coordinate +- * @param tzValue the target's z coordinate +- * @param instance the instance +- * @return the last Location (x,y,z) where player can walk - just before wall ++ * Check movement from coordinates to 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 {code boolean} : True if target coordinates are reachable from origin coordinates + */ +- public Location canMoveToTargetLoc(int x, int y, int zValue, int tx, int ty, int tzValue, Instance instance) ++ public boolean canMoveToTarget(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + { +- final int geoX = getGeoX(x); +- final int geoY = getGeoY(y); +- final int z = getNearestZ(geoX, geoY, zValue); +- final int tGeoX = getGeoX(tx); +- final int tGeoY = getGeoY(ty); +- final int tz = getNearestZ(tGeoX, tGeoY, tzValue); ++ // get origin and check existing geo coordinates ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) ++ { ++ return true; ++ } + +- if (DoorData.getInstance().checkIfDoorsBetween(x, y, z, tx, ty, tz, instance, false)) ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coordinates ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) + { +- return new Location(x, y, getHeight(x, y, z)); ++ return true; + } +- if (FenceData.getInstance().checkIfFenceBetween(x, y, z, tx, ty, tz, instance)) ++ ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // target coordinates reached ++ if ((gox == gtx) && (goy == gty) && (goz == gtz)) + { +- return new Location(x, y, getHeight(x, y, z)); ++ return true; + } +- if (WarpedSpaceManager.getInstance().checkForWarpedSpace(x, y, z, tx, ty, tz, instance)) ++ ++ // perform geodata check ++ final GeoLocation loc = checkMove(gox, goy, goz, gtx, gty, gtz, instance); ++ return (loc.getGeoX() == gtx) && (loc.getGeoY() == gty); ++ } ++ ++ /** ++ * Check movement from origin to target. Returns last available point in the checked path. ++ * @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 {@link Location} : Last point where object can walk (just before wall) ++ */ ++ public Location canMoveToTargetLoc(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) ++ { ++ // Mobius: Double check for doors before normal checkMove to avoid exploiting key movement. ++ if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, instance, false)) + { +- return new Location(x, y, getHeight(x, y, z)); ++ return new Location(ox, oy, oz); + } ++ if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, instance)) ++ { ++ return new Location(ox, oy, oz); ++ } + +- 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 = z; ++ // get origin and check existing geo coordinates ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) ++ { ++ return new Location(tx, ty, tz); ++ } + +- while (pointIter.next()) ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coordinates ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) + { +- final int curX = pointIter.x(); +- final int curY = pointIter.y(); +- final int curZ = getNearestZ(curX, curY, prevZ); +- +- if (hasGeoPos(prevX, prevY)) +- { +- final int nswe = GeoUtils.computeNswe(prevX, prevY, curX, curY); +- if (!checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, nswe)) +- { +- // Can't move, return previous location. +- return new Location(getWorldX(prevX), getWorldY(prevY), prevZ); +- } +- } +- +- prevX = curX; +- prevY = curY; +- prevZ = curZ; ++ return new Location(tx, ty, tz); + } + +- if (hasGeoPos(prevX, prevY) && (prevZ != tz)) ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // target coordinates reached ++ if ((gox == gtx) && (goy == gty) && (goz == gtz)) + { +- // Different floors, return start location. +- return new Location(x, y, z); ++ return new Location(tx, ty, tz); + } + +- return new Location(tx, ty, tz); ++ // perform geodata check ++ return checkMove(gox, goy, goz, gtx, gty, gtz, instance); + } + + /** +- * 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 fromZvalue 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 toZvalue 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 ++ * With this method you can check if a position is visible or can be reached by beeline movement.
++ * 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 gox : origin X geodata coordinate ++ * @param goy : origin Y geodata coordinate ++ * @param goz : origin Z geodata coordinate ++ * @param gtx : target X geodata coordinate ++ * @param gty : target Y geodata coordinate ++ * @param gtz : target Z geodata coordinate ++ * @param instance ++ * @return {@link GeoLocation} : The last allowed point of movement. + */ +- public boolean canMoveToTarget(int fromX, int fromY, int fromZvalue, int toX, int toY, int toZvalue, Instance instance) ++ protected final GeoLocation checkMove(int gox, int goy, int goz, int gtx, int gty, int gtz, Instance instance) + { +- final int geoX = getGeoX(fromX); +- final int geoY = getGeoY(fromY); +- final int fromZ = getNearestZ(geoX, geoY, fromZvalue); +- final int tGeoX = getGeoX(toX); +- final int tGeoY = getGeoY(toY); +- final int toZ = getNearestZ(tGeoX, tGeoY, toZvalue); +- +- if (DoorData.getInstance().checkIfDoorsBetween(fromX, fromY, fromZ, toX, toY, toZ, instance, false)) ++ if (DoorData.getInstance().checkIfDoorsBetween(gox, goy, goz, gtx, gty, gtz, instance, false)) + { +- return false; ++ return new GeoLocation(gox, goy, goz); + } +- if (FenceData.getInstance().checkIfFenceBetween(fromX, fromY, fromZ, toX, toY, toZ, instance)) ++ if (FenceData.getInstance().checkIfFenceBetween(gox, goy, goz, gtx, gty, gtz, instance)) + { +- return false; ++ return new GeoLocation(gox, goy, goz); + } +- if (WarpedSpaceManager.getInstance().checkForWarpedSpace(fromX, fromY, fromZ, toX, toY, toZ, instance)) ++ if (WarpedSpaceManager.getInstance().checkForWarpedSpace(gox, goy, goz, gtx, gty, gtz, instance)) + { +- return false; ++ return new GeoLocation(gox, goy, goz); + } + +- 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 = fromZ; ++ // get X delta, signum and direction flag ++ final int dx = Math.abs(gtx - gox); ++ final int sx = gox < gtx ? 1 : -1; ++ final byte dirX = sx > 0 ? GeoStructure.CELL_FLAG_E : GeoStructure.CELL_FLAG_W; + +- while (pointIter.next()) ++ // get Y delta, signum and direction flag ++ final int dy = Math.abs(gty - goy); ++ final int sy = goy < gty ? 1 : -1; ++ final byte dirY = sy > 0 ? GeoStructure.CELL_FLAG_S : GeoStructure.CELL_FLAG_N; ++ ++ // get direction flag for diagonal movement ++ final byte dirXY = getDirXY(dirX, dirY); ++ ++ // delta, determines axis to move on (+..X axis, -..Y axis) ++ int d = dx - dy; ++ ++ // NSWE direction of movement ++ byte direction; ++ ++ // load pointer coordinates ++ int gpx = gox; ++ int gpy = goy; ++ int gpz = goz; ++ ++ // load next pointer ++ int nx = gpx; ++ int ny = gpy; ++ ++ // loop ++ int count = 0; ++ while (count++ < Config.MAX_ITERATIONS) + { +- final int curX = pointIter.x(); +- final int curY = pointIter.y(); +- final int curZ = getNearestZ(curX, curY, prevZ); ++ direction = 0; + +- if (hasGeoPos(prevX, prevY)) ++ // calculate next point coordinates ++ final int e2 = 2 * d; ++ if ((e2 > -dy) && (e2 < dx)) + { +- final int nswe = GeoUtils.computeNswe(prevX, prevY, curX, curY); +- if (!checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, nswe)) ++ d -= dy; ++ d += dx; ++ nx += sx; ++ ny += sy; ++ direction |= dirXY; ++ } ++ else if (e2 > -dy) ++ { ++ d -= dy; ++ nx += sx; ++ direction |= dirX; ++ } ++ else if (e2 < dx) ++ { ++ d += dx; ++ ny += sy; ++ direction |= dirY; ++ } ++ ++ // obstacle found, return ++ if ((getNsweNearest(gpx, gpy, gpz) & direction) == 0) ++ { ++ return new GeoLocation(gpx, gpy, gpz); ++ } ++ ++ // update pointer coordinates ++ gpx = nx; ++ gpy = ny; ++ gpz = getHeightNearest(nx, ny, gpz); ++ ++ // target coordinates reached ++ if ((gpx == gtx) && (gpy == gty)) ++ { ++ if (gpz == gtz) + { +- return false; ++ // path found, Z coordinates are okay, return target point ++ return new GeoLocation(gtx, gty, gtz); + } ++ ++ // path found, Z coordinates are not okay, return last good point ++ return new GeoLocation(gpx, gpy, gpz); + } ++ } ++ ++ return new GeoLocation(gox, goy, goz); ++ } ++ ++ /** ++ * Returns diagonal NSWE flag format of combined two NSWE flags. ++ * @param dirX : X direction NSWE flag ++ * @param dirY : Y direction NSWE flag ++ * @return byte : NSWE flag of combined direction ++ */ ++ private static byte getDirXY(byte dirX, byte dirY) ++ { ++ // check axis directions ++ if (dirY == GeoStructure.CELL_FLAG_N) ++ { ++ if (dirX == GeoStructure.CELL_FLAG_W) ++ { ++ return GeoStructure.CELL_FLAG_NW; ++ } + +- prevX = curX; +- prevY = curY; +- prevZ = curZ; ++ return GeoStructure.CELL_FLAG_NE; + } + +- if (hasGeoPos(prevX, prevY) && (prevZ != toZ)) ++ if (dirX == GeoStructure.CELL_FLAG_W) + { +- // Different floors. +- return false; ++ return GeoStructure.CELL_FLAG_SW; + } + +- return true; ++ return GeoStructure.CELL_FLAG_SE; + } + ++ /** ++ * 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 ++ */ ++ public List findPath(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) ++ { ++ return null; ++ } ++ ++ /** ++ * Returns the instance of the {@link GeoEngine}. ++ * @return {@link GeoEngine} : The instance. ++ */ + public static GeoEngine getInstance() + { + return SingletonHolder.INSTANCE; +@@ -752,6 +947,6 @@ + + private static class SingletonHolder + { +- protected static final GeoEngine INSTANCE = new GeoEngine(); ++ protected static final GeoEngine INSTANCE = Config.PATHFINDING ? new GeoEnginePathfinding() : new GeoEngine(); + } +-} ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/GeoEnginePathfinding.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/GeoEnginePathfinding.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/GeoEnginePathfinding.java (working copy) +@@ -20,88 +20,84 @@ + import java.util.LinkedList; + import java.util.List; + import java.util.ListIterator; +-import java.util.logging.Level; +-import java.util.logging.Logger; + + import org.l2jmobius.Config; +-import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; +-import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +-import org.l2jmobius.gameserver.geoengine.pathfinding.CellNode; +-import org.l2jmobius.gameserver.geoengine.pathfinding.CellNodeBuffer; +-import org.l2jmobius.gameserver.geoengine.pathfinding.NodeLoc; +-import org.l2jmobius.gameserver.model.World; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoLocation; ++import org.l2jmobius.gameserver.geoengine.pathfinding.Node; ++import org.l2jmobius.gameserver.geoengine.pathfinding.NodeBuffer; ++import org.l2jmobius.gameserver.model.Location; + import org.l2jmobius.gameserver.model.instancezone.Instance; + + /** +- * @author -Nemesiss- ++ * @author Hasha + */ +-public class GeoEnginePathfinding ++final class GeoEnginePathfinding extends GeoEngine + { +- private static final Logger LOGGER = Logger.getLogger(GeoEnginePathfinding.class.getName()); ++ // pre-allocated buffers ++ private final BufferHolder[] _buffers; + +- private BufferInfo[] _buffers; +- + protected GeoEnginePathfinding() + { +- try ++ super(); ++ ++ final String[] array = Config.PATHFIND_BUFFERS.split(";"); ++ _buffers = new BufferHolder[array.length]; ++ int count = 0; ++ for (int i = 0; i < array.length; i++) + { +- final String[] array = Config.PATHFIND_BUFFERS.split(";"); ++ final String buf = array[i]; ++ final String[] args = buf.split("x"); + +- _buffers = new BufferInfo[array.length]; +- +- String buf; +- String[] args; +- for (int i = 0; i < array.length; i++) ++ try + { +- buf = array[i]; +- args = buf.split("x"); +- if (args.length != 2) +- { +- throw new Exception("Invalid buffer definition: " + buf); +- } +- +- _buffers[i] = new BufferInfo(Integer.parseInt(args[0]), Integer.parseInt(args[1])); ++ final int size = Integer.parseInt(args[1]); ++ count += size; ++ _buffers[i] = new BufferHolder(Integer.parseInt(args[0]), size); + } ++ catch (Exception e) ++ { ++ LOGGER.warning("GeoEnginePathfinding: Can not load buffer setting: " + buf); ++ } + } +- catch (Exception e) +- { +- LOGGER.log(Level.WARNING, "CellPathFinding: Problem during buffer init: " + e.getMessage(), e); +- throw new Error("CellPathFinding: load aborted"); +- } ++ ++ LOGGER.info("GeoEnginePathfinding: Loaded " + count + " node buffers."); + } + +- public boolean pathNodesExist(short regionoffset) ++ @Override ++ public List findPath(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + { +- return false; +- } +- +- public List findPath(int x, int y, int z, int tx, int ty, int tz, Instance instance) +- { +- final int gx = GeoEngine.getInstance().getGeoX(x); +- final int gy = GeoEngine.getInstance().getGeoY(y); +- if (!GeoEngine.getInstance().hasGeo(x, y)) ++ // get origin and check existing geo coords ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) + { + 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)) ++ ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coords ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) + { + 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)))); ++ ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // Prepare buffer for pathfinding calculations ++ final NodeBuffer buffer = getBuffer(64 + (2 * Math.max(Math.abs(gox - gtx), Math.abs(goy - gty)))); + if (buffer == null) + { + return null; + } + +- List path = null; ++ // find path ++ List path = null; + try + { +- final CellNode result = buffer.findPath(gx, gy, gz, gtx, gty, gtz); +- ++ final Node result = buffer.findPath(gox, goy, goz, gtx, gty, gtz); + if (result == null) + { + return null; +@@ -125,33 +121,45 @@ + return path; + } + +- int currentX, currentY, currentZ; +- ListIterator middlePoint; ++ // get path list iterator ++ final ListIterator point = path.listIterator(); + +- middlePoint = path.listIterator(); +- currentX = x; +- currentY = y; +- currentZ = z; ++ // get node A (origin) ++ int nodeAx = gox; ++ int nodeAy = goy; ++ short nodeAz = goz; + +- while (middlePoint.hasNext()) ++ // get node B ++ GeoLocation nodeB = (GeoLocation) point.next(); ++ ++ // iterate thought the path to optimize it ++ int count = 0; ++ while (point.hasNext() && (count++ < Config.MAX_ITERATIONS)) + { +- final AbstractNodeLoc locMiddle = middlePoint.next(); +- if (!middlePoint.hasNext()) +- { +- break; +- } ++ // get node C ++ final GeoLocation nodeC = (GeoLocation) path.get(point.nextIndex()); + +- final AbstractNodeLoc locEnd = path.get(middlePoint.nextIndex()); +- if (GeoEngine.getInstance().canMoveToTarget(currentX, currentY, currentZ, locEnd.getX(), locEnd.getY(), locEnd.getZ(), instance)) ++ // check movement from node A to node C ++ final GeoLocation loc = checkMove(nodeAx, nodeAy, nodeAz, nodeC.getGeoX(), nodeC.getGeoY(), nodeC.getZ(), instance); ++ if ((loc.getGeoX() == nodeC.getGeoX()) && (loc.getGeoY() == nodeC.getGeoY())) + { +- middlePoint.remove(); ++ // can move from node A to node C ++ ++ // remove node B ++ point.remove(); + } + else + { +- currentX = locMiddle.getX(); +- currentY = locMiddle.getY(); +- currentZ = locMiddle.getZ(); ++ // can not move from node A to node C ++ ++ // set node A (node B is part of path, update A coordinates) ++ nodeAx = nodeB.getGeoX(); ++ nodeAy = nodeB.getGeoY(); ++ nodeAz = (short) nodeB.getZ(); + } ++ ++ // set node B ++ nodeB = (GeoLocation) point.next(); + } + + return path; +@@ -158,144 +166,102 @@ + } + + /** +- * Convert geodata position to pathnode position +- * @param geo_pos +- * @return pathnode position ++ * Create list of node locations as result of calculated buffer node tree. ++ * @param node : the entry point ++ * @return List : list of node location + */ +- public short getNodePos(int geo_pos) ++ private static List constructPath(Node node) + { +- return (short) (geo_pos >> 3); // OK? +- } +- +- /** +- * Convert node position to pathnode block position +- * @param node_pos +- * @return pathnode block position (0...255) +- */ +- public short getNodeBlock(int node_pos) +- { +- return (short) (node_pos % 256); +- } +- +- public byte getRegionX(int node_pos) +- { +- return (byte) ((node_pos >> 8) + World.TILE_X_MIN); +- } +- +- public byte getRegionY(int node_pos) +- { +- return (byte) ((node_pos >> 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 node_x rx +- * @return +- */ +- public int calculateWorldX(short node_x) +- { +- return World.MAP_MIN_X + (node_x * 128) + 48; +- } +- +- /** +- * Convert pathnode y to World y position +- * @param node_y +- * @return +- */ +- public int calculateWorldY(short node_y) +- { +- return World.MAP_MIN_Y + (node_y * 128) + 48; +- } +- +- private List constructPath(AbstractNode nodeValue) +- { +- final LinkedList path = new LinkedList<>(); +- int previousDirectionX = Integer.MIN_VALUE; +- int previousDirectionY = Integer.MIN_VALUE; +- int directionX, directionY; ++ // create empty list ++ final LinkedList list = new LinkedList<>(); + +- AbstractNode node = nodeValue; +- while (node.getParent() != null) ++ // set direction X/Y ++ int dx = 0; ++ int dy = 0; ++ ++ // get target parent ++ Node target = node; ++ Node parent = target.getParent(); ++ ++ // while parent exists ++ int count = 0; ++ while ((parent != null) && (count++ < Config.MAX_ITERATIONS)) + { +- directionX = node.getLoc().getNodeX() - node.getParent().getLoc().getNodeX(); +- directionY = node.getLoc().getNodeY() - node.getParent().getLoc().getNodeY(); ++ // get parent <> target direction X/Y ++ final int nx = parent.getLoc().getGeoX() - target.getLoc().getGeoX(); ++ final int ny = parent.getLoc().getGeoY() - target.getLoc().getGeoY(); + +- // only add a new route point if moving direction changes +- if ((directionX != previousDirectionX) || (directionY != previousDirectionY)) ++ // direction has changed? ++ if ((dx != nx) || (dy != ny)) + { +- previousDirectionX = directionX; +- previousDirectionY = directionY; ++ // add node to the beginning of the list ++ list.addFirst(target.getLoc()); + +- path.addFirst(node.getLoc()); +- node.setLoc(null); ++ // update direction X/Y ++ dx = nx; ++ dy = ny; + } + +- node = node.getParent(); ++ // move to next node, set target and get its parent ++ target = parent; ++ parent = target.getParent(); + } + +- return path; ++ // return list ++ return list; + } + +- private CellNodeBuffer alloc(int size) ++ /** ++ * Provides optimize selection of the buffer. When all pre-initialized buffer are locked, creates new buffer. ++ * @param size : pre-calculated minimal required size ++ * @return NodeBuffer : buffer ++ */ ++ private final NodeBuffer getBuffer(int size) + { +- CellNodeBuffer current = null; +- for (BufferInfo i : _buffers) ++ NodeBuffer current = null; ++ for (BufferHolder holder : _buffers) + { +- if (i.mapSize >= size) ++ // Find proper size of buffer ++ if (holder._size < size) + { +- for (CellNodeBuffer buf : i.buffer) ++ continue; ++ } ++ ++ // Find unlocked NodeBuffer ++ for (NodeBuffer buffer : holder._buffer) ++ { ++ if (!buffer.isLocked()) + { +- if (buf.lock()) +- { +- current = buf; +- break; +- } ++ continue; + } +- if (current != null) +- { +- break; +- } + +- // not found, allocate temporary buffer +- current = new CellNodeBuffer(i.mapSize); +- current.lock(); +- if (i.buffer.size() < i.count) +- { +- i.buffer.add(current); +- break; +- } ++ return buffer; + } ++ ++ // NodeBuffer not found, allocate temporary buffer ++ current = new NodeBuffer(holder._size); ++ current.isLocked(); + } + + return current; + } + +- private static final class BufferInfo ++ /** ++ * NodeBuffer container with specified size and count of separate buffers. ++ */ ++ private static final class BufferHolder + { +- final int mapSize; +- final int count; +- ArrayList buffer; ++ final int _size; ++ List _buffer; + +- public BufferInfo(int size, int cnt) ++ public BufferHolder(int size, int count) + { +- mapSize = size; +- count = cnt; +- buffer = new ArrayList<>(count); ++ _size = size; ++ _buffer = new ArrayList<>(count); ++ for (int i = 0; i < count; i++) ++ { ++ _buffer.add(new NodeBuffer(size)); ++ } + } + } +- +- public static GeoEnginePathfinding getInstance() +- { +- return SingletonHolder.INSTANCE; +- } +- +- private static class SingletonHolder +- { +- protected static final GeoEnginePathfinding INSTANCE = new GeoEnginePathfinding(); +- } +-} ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java (working copy) +@@ -0,0 +1,197 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++import java.io.IOException; ++ ++/** ++ * @author Hasha ++ */ ++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 height of cell, which is closest to given coordinates.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @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 getHeightNearestOriginal(int geoX, int geoY, int worldZ); ++ ++ /** ++ * Returns the height of cell, which is first above 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, above given coordinates. ++ */ ++ public abstract short getHeightAbove(int geoX, int geoY, int worldZ); ++ ++ /** ++ * Returns the height of cell, which is first below 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, below given coordinates. ++ */ ++ public abstract short getHeightBelow(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 the NSWE flag byte of cell, which is closest to given coordinates.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @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 getNsweNearestOriginal(int geoX, int geoY, int worldZ); ++ ++ /** ++ * Returns the NSWE flag byte of cell, which is first above 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 getNsweAbove(int geoX, int geoY, int worldZ); ++ ++ /** ++ * Returns the NSWE flag byte of cell, which is first below 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 getNsweBelow(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 above given coordinates.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @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 getIndexAboveOriginal(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 index to data of the cell, which is first layer below given coordinates.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @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 getIndexBelowOriginal(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 height of cell given by cell index.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @param index : Index of the cell. ++ * @return short : Cell geodata Z coordinate, below given coordinates. ++ */ ++ public abstract short getHeightOriginal(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); ++ ++ /** ++ * Returns the NSWE flag byte of cell given by cell index.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @param index : Index of the cell. ++ * @return short : Cell geodata Z coordinate, below given coordinates. ++ */ ++ public abstract byte getNsweOriginal(int index); ++ ++ /** ++ * Sets the NSWE flag byte of cell given by cell index. ++ * @param index : Index of the cell. ++ * @param nswe : New NSWE flag byte. ++ */ ++ public abstract void setNswe(int index, byte nswe); ++ ++ /** ++ * Saves the block in L2D format to {@link BufferedOutputStream}. Used only for L2D geodata conversion. ++ * @param stream : The stream. ++ * @throws IOException : Can't save the block to steam. ++ */ ++ public abstract void saveBlock(BufferedOutputStream stream) throws IOException; ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java (working copy) +@@ -0,0 +1,252 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++import java.io.IOException; ++import java.nio.ByteBuffer; ++ ++/** ++ * @author Hasha ++ */ ++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. ++ * @param format : GeoFormat specifying format of loaded data. ++ */ ++ public BlockComplex(ByteBuffer bb, GeoFormat format) ++ { ++ // initialize buffer ++ _buffer = new byte[GeoStructure.BLOCK_CELLS * 3]; ++ ++ // load data ++ for (int i = 0; i < GeoStructure.BLOCK_CELLS; i++) ++ { ++ if (format != GeoFormat.L2D) ++ { ++ // 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); ++ } ++ else ++ { ++ // get nswe ++ final byte nswe = bb.get(); ++ _buffer[i * 3] = nswe; ++ ++ // get height ++ final short height = bb.getShort(); ++ _buffer[(i * 3) + 1] = (byte) (height & 0x00FF); ++ _buffer[(i * 3) + 2] = (byte) (height >> 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 short getHeightNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getHeightNearest(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeightAbove(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 short height = (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ ++ // check and return height ++ return height > worldZ ? height : Short.MIN_VALUE; ++ } ++ ++ @Override ++ public short getHeightBelow(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 short height = (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ ++ // check and return height ++ return height < worldZ ? height : Short.MAX_VALUE; ++ } ++ ++ @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 byte getNsweNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getNsweNearest(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public byte getNsweAbove(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 ? _buffer[index] : 0; ++ } ++ ++ @Override ++ public byte getNsweBelow(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 ? _buffer[index] : 0; ++ } ++ ++ @Override ++ public 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 getIndexAboveOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexAbove(geoX, geoY, worldZ); ++ } ++ ++ @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 int getIndexBelowOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexBelow(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeight(int index) ++ { ++ return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ } ++ ++ @Override ++ public short getHeightOriginal(int index) ++ { ++ return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ } ++ ++ @Override ++ public byte getNswe(int index) ++ { ++ return _buffer[index]; ++ } ++ ++ @Override ++ public byte getNsweOriginal(int index) ++ { ++ return _buffer[index]; ++ } ++ ++ @Override ++ public void setNswe(int index, byte nswe) ++ { ++ _buffer[index] = nswe; ++ } ++ ++ @Override ++ public void saveBlock(BufferedOutputStream stream) throws IOException ++ { ++ // write block type ++ stream.write(GeoStructure.TYPE_COMPLEX_L2D); ++ ++ // write block data ++ stream.write(_buffer, 0, GeoStructure.BLOCK_CELLS * 3); ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java (working copy) +@@ -0,0 +1,176 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++import java.io.IOException; ++import java.nio.ByteBuffer; ++ ++/** ++ * @author Hasha ++ */ ++public class BlockFlat extends ABlock ++{ ++ protected final short _height; ++ protected byte _nswe; ++ ++ /** ++ * Creates FlatBlock. ++ * @param bb : Input byte buffer. ++ * @param format : GeoFormat specifying format of loaded data. ++ */ ++ public BlockFlat(ByteBuffer bb, GeoFormat format) ++ { ++ _height = bb.getShort(); ++ _nswe = format != GeoFormat.L2D ? 0x0F : (byte) (0xFF); ++ if (format == GeoFormat.L2OFF) ++ { ++ bb.getShort(); ++ } ++ } ++ ++ @Override ++ public boolean hasGeoPos() ++ { ++ return true; ++ } ++ ++ @Override ++ public short getHeightNearest(int geoX, int geoY, int worldZ) ++ { ++ return _height; ++ } ++ ++ @Override ++ public short getHeightNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return _height; ++ } ++ ++ @Override ++ public short getHeightAbove(int geoX, int geoY, int worldZ) ++ { ++ // check and return height ++ return _height > worldZ ? _height : Short.MIN_VALUE; ++ } ++ ++ @Override ++ public short getHeightBelow(int geoX, int geoY, int worldZ) ++ { ++ // check and return height ++ return _height < worldZ ? _height : Short.MAX_VALUE; ++ } ++ ++ @Override ++ public byte getNsweNearest(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweAbove(int geoX, int geoY, int worldZ) ++ { ++ // check height and return nswe ++ return _height > worldZ ? _nswe : 0; ++ } ++ ++ @Override ++ public byte getNsweBelow(int geoX, int geoY, int worldZ) ++ { ++ // check height and return nswe ++ return _height < worldZ ? _nswe : 0; ++ } ++ ++ @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 getIndexAboveOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexAbove(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public int getIndexBelow(int geoX, int geoY, int worldZ) ++ { ++ // check height and return index ++ return _height < worldZ ? 0 : -1; ++ } ++ ++ @Override ++ public int getIndexBelowOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexBelow(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeight(int index) ++ { ++ return _height; ++ } ++ ++ @Override ++ public short getHeightOriginal(int index) ++ { ++ return _height; ++ } ++ ++ @Override ++ public byte getNswe(int index) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweOriginal(int index) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public void setNswe(int index, byte nswe) ++ { ++ _nswe = nswe; ++ } ++ ++ @Override ++ public void saveBlock(BufferedOutputStream stream) throws IOException ++ { ++ // write block type ++ stream.write(GeoStructure.TYPE_FLAT_L2D); ++ ++ // write height ++ stream.write((byte) (_height & 0x00FF)); ++ stream.write((byte) (_height >> 8)); ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java (working copy) +@@ -0,0 +1,465 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++import java.io.IOException; ++import java.nio.ByteBuffer; ++import java.nio.ByteOrder; ++import java.util.Arrays; ++ ++/** ++ * @author Hasha ++ */ ++public class BlockMultilayer extends ABlock ++{ ++ private static final int MAX_LAYERS = Byte.MAX_VALUE; ++ ++ private static ByteBuffer _temp; ++ ++ /** ++ * Initializes the temporarily buffer. ++ */ ++ public static void initialize() ++ { ++ // initialize temporarily buffer and sorting mechanism ++ _temp = ByteBuffer.allocate(GeoStructure.BLOCK_CELLS * MAX_LAYERS * 3); ++ _temp.order(ByteOrder.LITTLE_ENDIAN); ++ } ++ ++ /** ++ * Releases temporarily buffer. ++ */ ++ public static void release() ++ { ++ _temp = null; ++ } ++ ++ protected byte[] _buffer; ++ ++ /** ++ * Implicit constructor for children class. ++ */ ++ protected BlockMultilayer() ++ { ++ _buffer = null; ++ } ++ ++ /** ++ * Creates MultilayerBlock. ++ * @param bb : Input byte buffer. ++ * @param format : GeoFormat specifying format of loaded data. ++ */ ++ public BlockMultilayer(ByteBuffer bb, GeoFormat format) ++ { ++ // 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 = format != GeoFormat.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++) ++ { ++ if (format != GeoFormat.L2D) ++ { ++ // get data ++ final short data = bb.getShort(); ++ ++ // add nswe and height ++ _temp.put((byte) (data & 0x000F)); ++ _temp.putShort((short) ((short) (data & 0xFFF0) >> 1)); ++ } ++ else ++ { ++ // add nswe ++ _temp.put(bb.get()); ++ ++ // add height ++ _temp.putShort(bb.getShort()); ++ } ++ } ++ } ++ ++ // 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 short getHeightNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getHeightNearest(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeightAbove(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 height ++ if (height > worldZ) ++ { ++ return (short) height; ++ } ++ ++ // move index to next layer ++ index -= 3; ++ } ++ ++ // none layer found, return minimum value ++ return Short.MIN_VALUE; ++ } ++ ++ @Override ++ public short getHeightBelow(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 height ++ if (height < worldZ) ++ { ++ return (short) height; ++ } ++ ++ // move index to next layer ++ index += 3; ++ } ++ ++ // none layer found, return maximum value ++ return Short.MAX_VALUE; ++ } ++ ++ @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 byte getNsweNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getNsweNearest(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public byte getNsweAbove(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 nswe ++ if (height > worldZ) ++ { ++ return _buffer[index]; ++ } ++ ++ // move index to next layer ++ index -= 3; ++ } ++ ++ // none layer found, block movement ++ return 0; ++ } ++ ++ @Override ++ public byte getNsweBelow(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 nswe ++ if (height < worldZ) ++ { ++ return _buffer[index]; ++ } ++ ++ // move index to next layer ++ index += 3; ++ } ++ ++ // none layer found, block movement ++ return 0; ++ } ++ ++ @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 first layer data (first from bottom) ++ byte layers = _buffer[index++]; ++ ++ // loop though all cell layers, find closest layer ++ 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; ++ } ++ ++ // none layer found ++ return -1; ++ } ++ ++ @Override ++ public int getIndexAboveOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexAbove(geoX, geoY, worldZ); ++ } ++ ++ @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; ++ } ++ ++ // none layer found ++ return -1; ++ } ++ ++ @Override ++ public int getIndexBelowOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexBelow(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeight(int index) ++ { ++ // get height ++ return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ } ++ ++ @Override ++ public short getHeightOriginal(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]; ++ } ++ ++ @Override ++ public byte getNsweOriginal(int index) ++ { ++ // get nswe ++ return _buffer[index]; ++ } ++ ++ @Override ++ public void setNswe(int index, byte nswe) ++ { ++ // set nswe ++ _buffer[index] = nswe; ++ } ++ ++ @Override ++ public void saveBlock(BufferedOutputStream stream) throws IOException ++ { ++ // write block type ++ stream.write(GeoStructure.TYPE_MULTILAYER_L2D); ++ ++ // for each cell ++ int index = 0; ++ for (int i = 0; i < GeoStructure.BLOCK_CELLS; i++) ++ { ++ // write layers count ++ final byte layers = _buffer[index++]; ++ stream.write(layers); ++ ++ // write cell data ++ stream.write(_buffer, index, layers * 3); ++ ++ // move index to next cell ++ index += layers * 3; ++ } ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java (working copy) +@@ -0,0 +1,150 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++ ++/** ++ * @author Hasha ++ */ ++public class BlockNull extends ABlock ++{ ++ private final byte _nswe; ++ ++ public BlockNull() ++ { ++ _nswe = (byte) 0xFF; ++ } ++ ++ @Override ++ public boolean hasGeoPos() ++ { ++ return false; ++ } ++ ++ @Override ++ public short getHeightNearest(int geoX, int geoY, int worldZ) ++ { ++ return (short) worldZ; ++ } ++ ++ @Override ++ public short getHeightNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return (short) worldZ; ++ } ++ ++ @Override ++ public short getHeightAbove(int geoX, int geoY, int worldZ) ++ { ++ return (short) worldZ; ++ } ++ ++ @Override ++ public short getHeightBelow(int geoX, int geoY, int worldZ) ++ { ++ return (short) worldZ; ++ } ++ ++ @Override ++ public byte getNsweNearest(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweAbove(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweBelow(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) ++ { ++ return 0; ++ } ++ ++ @Override ++ public int getIndexAboveOriginal(int geoX, int geoY, int worldZ) ++ { ++ return 0; ++ } ++ ++ @Override ++ public int getIndexBelow(int geoX, int geoY, int worldZ) ++ { ++ return 0; ++ } ++ ++ @Override ++ public int getIndexBelowOriginal(int geoX, int geoY, int worldZ) ++ { ++ return 0; ++ } ++ ++ @Override ++ public short getHeight(int index) ++ { ++ return 0; ++ } ++ ++ @Override ++ public short getHeightOriginal(int index) ++ { ++ return 0; ++ } ++ ++ @Override ++ public byte getNswe(int index) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweOriginal(int index) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public void setNswe(int index, byte nswe) ++ { ++ } ++ ++ @Override ++ public void saveBlock(BufferedOutputStream stream) ++ { ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java (nonexistent) +@@ -1,48 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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() +- { +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/ComplexBlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/ComplexBlock.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/ComplexBlock.java (nonexistent) +@@ -1,77 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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; +- +-/** +- * @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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/FlatBlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/FlatBlock.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/FlatBlock.java (nonexistent) +@@ -1,56 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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; +- +-/** +- * @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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/GeoFormat.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/GeoFormat.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/GeoFormat.java (working copy) +@@ -0,0 +1,39 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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 Hasha ++ */ ++public enum GeoFormat ++{ ++ L2J("%d_%d.l2j"), ++ L2OFF("%d_%d_conv.dat"), ++ L2D("%d_%d.l2d"); ++ ++ private final String _filename; ++ ++ private GeoFormat(String filename) ++ { ++ _filename = filename; ++ } ++ ++ public String getFilename() ++ { ++ return _filename; ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/GeoLocation.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/GeoLocation.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/GeoLocation.java (working copy) +@@ -0,0 +1,67 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.geoengine.GeoEngine; ++import org.l2jmobius.gameserver.model.Location; ++ ++/** ++ * @author Hasha ++ */ ++public class GeoLocation extends Location ++{ ++ private byte _nswe; ++ ++ public GeoLocation(int x, int y, int z) ++ { ++ super(x, y, GeoEngine.getInstance().getHeightNearest(x, y, z)); ++ _nswe = GeoEngine.getInstance().getNsweNearest(x, y, z); ++ } ++ ++ public void set(int x, int y, short z) ++ { ++ super.setXYZ(x, y, GeoEngine.getInstance().getHeightNearest(x, y, z)); ++ _nswe = GeoEngine.getInstance().getNsweNearest(x, y, z); ++ } ++ ++ public int getGeoX() ++ { ++ return _x; ++ } ++ ++ public int getGeoY() ++ { ++ return _y; ++ } ++ ++ @Override ++ public int getX() ++ { ++ return GeoEngine.getWorldX(_x); ++ } ++ ++ @Override ++ public int getY() ++ { ++ return GeoEngine.getWorldY(_y); ++ } ++ ++ public byte getNSWE() ++ { ++ return _nswe; ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java (working copy) +@@ -0,0 +1,70 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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; ++ ++/** ++ * @author Hasha ++ */ ++public class GeoStructure ++{ ++ // cells ++ public static final byte CELL_FLAG_E = 1 << 0; ++ public static final byte CELL_FLAG_W = 1 << 1; ++ public static final byte CELL_FLAG_S = 1 << 2; ++ public static final byte CELL_FLAG_N = 1 << 3; ++ public static final byte CELL_FLAG_SE = 1 << 4; ++ public static final byte CELL_FLAG_SW = 1 << 5; ++ public static final byte CELL_FLAG_NE = 1 << 6; ++ public static final byte CELL_FLAG_NW = (byte) (1 << 7); ++ ++ public static final int CELL_HEIGHT = 8; ++ public static final int CELL_IGNORE_HEIGHT = CELL_HEIGHT * 6; ++ ++ // blocks ++ public static final byte TYPE_FLAT_L2J_L2OFF = 0; ++ public static final byte TYPE_FLAT_L2D = (byte) 0xD0; ++ public static final byte TYPE_COMPLEX_L2J = 1; ++ public static final byte TYPE_COMPLEX_L2OFF = 0x40; ++ public static final byte TYPE_COMPLEX_L2D = (byte) 0xD1; ++ 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) ++ public static final byte TYPE_MULTILAYER_L2D = (byte) 0xD2; ++ ++ 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; ++ ++ // regions ++ 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; ++ ++ // global geodata ++ private static final int GEO_REGIONS_X = ((World.TILE_X_MAX - World.TILE_X_MIN) + 1); ++ private 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 +Index: java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java (nonexistent) +@@ -1,42 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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); +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/IGeoObject.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/IGeoObject.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/IGeoObject.java (working copy) +@@ -0,0 +1,53 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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 Hasha ++ */ ++public interface IGeoObject ++{ ++ /** ++ * Returns geodata X coordinate of the {@link IGeoObject}. ++ * @return int : Geodata X coordinate. ++ */ ++ int getGeoX(); ++ ++ /** ++ * Returns geodata Y coordinate of the {@link IGeoObject}. ++ * @return int : Geodata Y coordinate. ++ */ ++ int getGeoY(); ++ ++ /** ++ * Returns geodata Z coordinate of the {@link IGeoObject}. ++ * @return int : Geodata Z coordinate. ++ */ ++ int getGeoZ(); ++ ++ /** ++ * Returns height of the {@link IGeoObject}. ++ * @return int : Height. ++ */ ++ int getHeight(); ++ ++ /** ++ * Returns {@link IGeoObject} data. ++ * @return byte[][] : {@link IGeoObject} data. ++ */ ++ byte[][] getObjectGeoData(); ++} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java (nonexistent) +@@ -1,47 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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(); +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/MultilayerBlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/MultilayerBlock.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/MultilayerBlock.java (nonexistent) +@@ -1,183 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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; +- +-/** +- * @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); +- +- // 1 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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/NullRegion.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/NullRegion.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/NullRegion.java (nonexistent) +@@ -1,55 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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 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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/Region.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/Region.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/Region.java (nonexistent) +@@ -1,92 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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; +- +-/** +- * @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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java (nonexistent) +@@ -1,87 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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 T _loc; +- private AbstractNode _parent; +- +- public AbstractNode(T loc) +- { +- _loc = loc; +- } +- +- public void setParent(AbstractNode p) +- { +- _parent = p; +- } +- +- public AbstractNode getParent() +- { +- return _parent; +- } +- +- public T getLoc() +- { +- return _loc; +- } +- +- public void setLoc(T l) +- { +- _loc = l; +- } +- +- @Override +- public int hashCode() +- { +- final int prime = 31; +- int result = 1; +- result = (prime * result) + ((_loc == null) ? 0 : _loc.hashCode()); +- return result; +- } +- +- @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 +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java (nonexistent) +@@ -1,33 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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 int getNodeX(); +- +- public abstract int getNodeY(); +-} +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNode.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNode.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNode.java (nonexistent) +@@ -1,67 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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 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 +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNodeBuffer.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNodeBuffer.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNodeBuffer.java (nonexistent) +@@ -1,348 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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.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 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) +- { +- _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(); +- } +- +- 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 void getNeighbors() +- { +- if (!_current.getLoc().canGoAll()) +- { +- 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); +- } +- +- // SouthEast +- if ((nodeE != null) && (nodeS != null)) +- { +- if (nodeE.getLoc().canGoSouth() && nodeS.getLoc().canGoEast()) +- { +- addNode(x + 1, y + 1, z, true); +- } +- } +- +- // SouthWest +- if ((nodeS != null) && (nodeW != null)) +- { +- if (nodeW.getLoc().canGoSouth() && nodeS.getLoc().canGoWest()) +- { +- addNode(x - 1, y + 1, z, true); +- } +- } +- +- // NorthEast +- if ((nodeN != null) && (nodeE != null)) +- { +- if (nodeE.getLoc().canGoNorth() && nodeN.getLoc().canGoEast()) +- { +- addNode(x + 1, y - 1, z, true); +- } +- } +- +- // NorthWest +- if ((nodeN != null) && (nodeW != null)) +- { +- if (nodeW.getLoc().canGoNorth() && nodeN.getLoc().canGoWest()) +- { +- addNode(x - 1, y - 1, z, true); +- } +- } +- } +- +- private 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(); +- // reinit node if needed +- if (result.getLoc() != null) +- { +- result.getLoc().set(x, y, z); +- } +- else +- { +- result.setLoc(new NodeLoc(x, y, z)); +- } +- } +- +- return result; +- } +- +- private 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 boolean isHighWeight(int x, int y, int z) +- { +- final CellNode result = getNode(x, y, z); +- if (result == null) +- { +- return true; +- } +- +- if (!result.getLoc().canGoAll()) +- { +- return true; +- } +- if (Math.abs(result.getLoc().getZ() - z) > 16) +- { +- return true; +- } +- +- return false; +- } +- +- private 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 +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java (working copy) +@@ -0,0 +1,87 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.geodata.GeoLocation; ++ ++/** ++ * @author Hasha ++ */ ++public class Node ++{ ++ // node coords and nswe flag ++ private GeoLocation _loc; ++ ++ // node parent (for reverse path construction) ++ private Node _parent; ++ // node child (for moving over nodes during iteration) ++ private Node _child; ++ ++ // node G cost (movement cost = parent movement cost + current movement cost) ++ private double _cost = -1000; ++ ++ public void setLoc(int x, int y, int z) ++ { ++ _loc = new GeoLocation(x, y, z); ++ } ++ ++ public GeoLocation getLoc() ++ { ++ return _loc; ++ } ++ ++ public void setParent(Node parent) ++ { ++ _parent = parent; ++ } ++ ++ public Node getParent() ++ { ++ return _parent; ++ } ++ ++ public void setChild(Node child) ++ { ++ _child = child; ++ } ++ ++ public Node getChild() ++ { ++ return _child; ++ } ++ ++ public void setCost(double cost) ++ { ++ _cost = cost; ++ } ++ ++ public double getCost() ++ { ++ return _cost; ++ } ++ ++ public void free() ++ { ++ // reset node location ++ _loc = null; ++ ++ // reset node parent, child and cost ++ _parent = null; ++ _child = null; ++ _cost = -1000; ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java (working copy) +@@ -0,0 +1,306 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.concurrent.locks.ReentrantLock; ++ ++import org.l2jmobius.Config; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; ++ ++/** ++ * @author DS, Hasha; Credits to Diamond ++ */ ++public class NodeBuffer ++{ ++ private final ReentrantLock _lock = new ReentrantLock(); ++ private final int _size; ++ private final Node[][] _buffer; ++ ++ // center coordinates ++ private int _cx = 0; ++ private int _cy = 0; ++ ++ // target coordinates ++ private int _gtx = 0; ++ private int _gty = 0; ++ private short _gtz = 0; ++ ++ private Node _current = null; ++ ++ /** ++ * Constructor of NodeBuffer. ++ * @param size : one dimension size of buffer ++ */ ++ public NodeBuffer(int size) ++ { ++ // set size ++ _size = size; ++ ++ // initialize buffer ++ _buffer = new Node[size][size]; ++ for (int x = 0; x < size; x++) ++ { ++ for (int y = 0; y < size; y++) ++ { ++ _buffer[x][y] = 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 Node : first node of path ++ */ ++ public Node findPath(int gox, int goy, short goz, int gtx, int gty, short gtz) ++ { ++ // set coordinates (middle of the line (gox,goy) - (gtx,gty), will be in the center of the buffer) ++ _cx = gox + ((gtx - gox - _size) / 2); ++ _cy = goy + ((gty - goy - _size) / 2); ++ ++ _gtx = gtx; ++ _gty = gty; ++ _gtz = gtz; ++ ++ _current = getNode(gox, goy, goz); ++ _current.setCost(getCostH(gox, goy, goz)); ++ ++ int count = 0; ++ do ++ { ++ // reached target? ++ if ((_current.getLoc().getGeoX() == _gtx) && (_current.getLoc().getGeoY() == _gty) && (Math.abs(_current.getLoc().getZ() - _gtz) < 8)) ++ { ++ return _current; ++ } ++ ++ // expand current node ++ expand(); ++ ++ // move pointer ++ _current = _current.getChild(); ++ } ++ while ((_current != null) && (count++ < Config.MAX_ITERATIONS)); ++ ++ return null; ++ } ++ ++ public boolean isLocked() ++ { ++ return _lock.tryLock(); ++ } ++ ++ public void free() ++ { ++ _current = null; ++ ++ for (Node[] nodes : _buffer) ++ { ++ for (Node node : nodes) ++ { ++ if (node.getLoc() != null) ++ { ++ node.free(); ++ } ++ } ++ } ++ ++ _lock.unlock(); ++ } ++ ++ /** ++ * Check _current Node and add its neighbors to the buffer. ++ */ ++ private final void expand() ++ { ++ // can't move anywhere, don't expand ++ final byte nswe = _current.getLoc().getNSWE(); ++ if (nswe == 0) ++ { ++ return; ++ } ++ ++ // get geo coords of the node to be expanded ++ final int x = _current.getLoc().getGeoX(); ++ final int y = _current.getLoc().getGeoY(); ++ final short z = (short) _current.getLoc().getZ(); ++ ++ // can move north, expand ++ if ((nswe & GeoStructure.CELL_FLAG_N) != 0) ++ { ++ addNode(x, y - 1, z, Config.BASE_WEIGHT); ++ } ++ ++ // can move south, expand ++ if ((nswe & GeoStructure.CELL_FLAG_S) != 0) ++ { ++ addNode(x, y + 1, z, Config.BASE_WEIGHT); ++ } ++ ++ // can move west, expand ++ if ((nswe & GeoStructure.CELL_FLAG_W) != 0) ++ { ++ addNode(x - 1, y, z, Config.BASE_WEIGHT); ++ } ++ ++ // can move east, expand ++ if ((nswe & GeoStructure.CELL_FLAG_E) != 0) ++ { ++ addNode(x + 1, y, z, Config.BASE_WEIGHT); ++ } ++ ++ // can move north-west, expand ++ if ((nswe & GeoStructure.CELL_FLAG_NW) != 0) ++ { ++ addNode(x - 1, y - 1, z, Config.DIAGONAL_WEIGHT); ++ } ++ ++ // can move north-east, expand ++ if ((nswe & GeoStructure.CELL_FLAG_NE) != 0) ++ { ++ addNode(x + 1, y - 1, z, Config.DIAGONAL_WEIGHT); ++ } ++ ++ // can move south-west, expand ++ if ((nswe & GeoStructure.CELL_FLAG_SW) != 0) ++ { ++ addNode(x - 1, y + 1, z, Config.DIAGONAL_WEIGHT); ++ } ++ ++ // can move south-east, expand ++ if ((nswe & GeoStructure.CELL_FLAG_SE) != 0) ++ { ++ addNode(x + 1, y + 1, z, Config.DIAGONAL_WEIGHT); ++ } ++ } ++ ++ /** ++ * Returns node, if it exists in buffer. ++ * @param x : node X coord ++ * @param y : node Y coord ++ * @param z : node Z coord ++ * @return Node : node, if exits in buffer ++ */ ++ private final Node getNode(int x, int y, short z) ++ { ++ // check node X out of coordinates ++ final int ix = x - _cx; ++ if ((ix < 0) || (ix >= _size)) ++ { ++ return null; ++ } ++ ++ // check node Y out of coordinates ++ final int iy = y - _cy; ++ if ((iy < 0) || (iy >= _size)) ++ { ++ return null; ++ } ++ ++ // get node ++ final Node result = _buffer[ix][iy]; ++ ++ // check and update ++ if (result.getLoc() == null) ++ { ++ result.setLoc(x, y, z); ++ } ++ ++ // return node ++ return result; ++ } ++ ++ /** ++ * Add node given by coordinates to the buffer. ++ * @param x : geo X coord ++ * @param y : geo Y coord ++ * @param z : geo Z coord ++ * @param weight : weight of movement to new node ++ */ ++ private final void addNode(int x, int y, short z, int weight) ++ { ++ // get node to be expanded ++ final Node node = getNode(x, y, z); ++ if (node == null) ++ { ++ return; ++ } ++ ++ // Z distance between nearby cells is higher than cell size ++ if (node.getLoc().getZ() > (z + (2 * GeoStructure.CELL_HEIGHT))) ++ { ++ return; ++ } ++ ++ // node was already expanded, return ++ if (node.getCost() >= 0) ++ { ++ return; ++ } ++ ++ node.setParent(_current); ++ if (node.getLoc().getNSWE() != (byte) 0xFF) ++ { ++ node.setCost(getCostH(x, y, node.getLoc().getZ()) + (weight * Config.OBSTACLE_MULTIPLIER)); ++ } ++ else ++ { ++ node.setCost(getCostH(x, y, node.getLoc().getZ()) + weight); ++ } ++ ++ Node current = _current; ++ int count = 0; ++ while ((current.getChild() != null) && (count < (Config.MAX_ITERATIONS * 4))) ++ { ++ count++; ++ if (current.getChild().getCost() > node.getCost()) ++ { ++ node.setChild(current.getChild()); ++ break; ++ } ++ current = current.getChild(); ++ } ++ ++ if (count >= (Config.MAX_ITERATIONS * 4)) ++ { ++ System.err.println("Pathfinding: too long loop detected, cost:" + node.getCost()); ++ } ++ ++ current.setChild(node); ++ } ++ ++ /** ++ * @param x : node X coord ++ * @param y : node Y coord ++ * @param i : node Z coord ++ * @return double : node cost ++ */ ++ private final double getCostH(int x, int y, int i) ++ { ++ final int dX = x - _gtx; ++ final int dY = y - _gty; ++ final int dZ = (i - _gtz) / GeoStructure.CELL_HEIGHT; ++ ++ // return (Math.abs(dX) + Math.abs(dY) + Math.abs(dZ)) * Config.HEURISTIC_WEIGHT; // Manhattan distance ++ return Math.sqrt((dX * dX) + (dY * dY) + (dZ * dZ)) * Config.HEURISTIC_WEIGHT; // Direct distance ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeLoc.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeLoc.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeLoc.java (nonexistent) +@@ -1,183 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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.Cell; +- +-/** +- * @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 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 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; +- // return super.hashCode(); +- } +- +- @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; +- if (_x != other._x) +- { +- return false; +- } +- if (_y != other._y) +- { +- return false; +- } +- if (_goNorth != other._goNorth) +- { +- return false; +- } +- if (_goEast != other._goEast) +- { +- return false; +- } +- if (_goSouth != other._goSouth) +- { +- return false; +- } +- if (_goWest != other._goWest) +- { +- return false; +- } +- if (_geoHeight != other._geoHeight) +- { +- return false; +- } +- return true; +- } +-} +Index: java/org/l2jmobius/gameserver/model/actor/Creature.java +=================================================================== +--- java/org/l2jmobius/gameserver/model/actor/Creature.java (revision 8399) ++++ java/org/l2jmobius/gameserver/model/actor/Creature.java (working copy) +@@ -66,8 +66,6 @@ + import org.l2jmobius.gameserver.enums.TeleportWhereType; + import org.l2jmobius.gameserver.enums.UserInfoType; + import org.l2jmobius.gameserver.geoengine.GeoEngine; +-import org.l2jmobius.gameserver.geoengine.GeoEnginePathfinding; +-import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; + import org.l2jmobius.gameserver.instancemanager.IdManager; + import org.l2jmobius.gameserver.instancemanager.MapRegionManager; + import org.l2jmobius.gameserver.instancemanager.QuestManager; +@@ -2577,7 +2575,7 @@ + + public boolean disregardingGeodata; + public int onGeodataPathIndex; +- public List geoPath; ++ public List geoPath; + public int geoPathAccurateTx; + public int geoPathAccurateTy; + public int geoPathGtx; +@@ -3466,7 +3464,7 @@ + if (((originalDistance - distance) > 30) && !isControlBlocked() && !isInVehicle) + { + // Path calculation -- overrides previous movement check +- m.geoPath = GeoEnginePathfinding.getInstance().findPath(curX, curY, curZ, originalX, originalY, originalZ, getInstanceWorld()); ++ m.geoPath = GeoEngine.getInstance().findPath(curX, curY, curZ, originalX, originalY, originalZ, getInstanceWorld()); + if ((m.geoPath == null) || (m.geoPath.size() < 2)) // No path found + { + if (isPlayer() && !_isFlying && !isInWater) +Index: java/org/l2jmobius/gameserver/model/actor/instance/PlayerInstance.java +=================================================================== +--- java/org/l2jmobius/gameserver/model/actor/instance/PlayerInstance.java (revision 8397) ++++ java/org/l2jmobius/gameserver/model/actor/instance/PlayerInstance.java (working copy) +@@ -12704,7 +12704,7 @@ + { + if (Config.CORRECT_PLAYER_Z) + { +- final int nearestZ = GeoEngine.getInstance().getHigherHeight(getX(), getY(), getZ()); ++ final int nearestZ = GeoEngine.getInstance().getHeightNearest(getX(), getY(), getZ()); + if (getZ() < nearestZ) + { + teleToLocation(new Location(getX(), getY(), nearestZ)); +Index: java/org/l2jmobius/gameserver/util/GeoUtils.java +=================================================================== +--- java/org/l2jmobius/gameserver/util/GeoUtils.java (revision 8397) ++++ java/org/l2jmobius/gameserver/util/GeoUtils.java (working copy) +@@ -19,7 +19,7 @@ + import java.awt.Color; + + import org.l2jmobius.gameserver.geoengine.GeoEngine; +-import org.l2jmobius.gameserver.geoengine.geodata.Cell; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; + import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance; + import org.l2jmobius.gameserver.network.serverpackets.ExServerPrimitive; + +@@ -26,25 +26,25 @@ + /** + * @author HorridoJoho + */ +-public final class GeoUtils ++public class GeoUtils + { + public static void debug2DLine(PlayerInstance player, int x, int y, int tx, int ty, int z) + { +- final int gx = GeoEngine.getInstance().getGeoX(x); +- final int gy = GeoEngine.getInstance().getGeoY(y); ++ final int gx = GeoEngine.getGeoX(x); ++ final int gy = GeoEngine.getGeoY(y); + +- final int tgx = GeoEngine.getInstance().getGeoX(tx); +- final int tgy = GeoEngine.getInstance().getGeoY(ty); ++ final int tgx = GeoEngine.getGeoX(tx); ++ final int tgy = GeoEngine.getGeoY(ty); + + final ExServerPrimitive prim = new ExServerPrimitive("Debug2DLine", x, y, z); +- prim.addLine(Color.BLUE, GeoEngine.getInstance().getWorldX(gx), GeoEngine.getInstance().getWorldY(gy), z, GeoEngine.getInstance().getWorldX(tgx), GeoEngine.getInstance().getWorldY(tgy), z); ++ prim.addLine(Color.BLUE, GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), z, GeoEngine.getWorldX(tgx), GeoEngine.getWorldY(tgy), z); + + final LinePointIterator iter = new LinePointIterator(gx, gy, tgx, tgy); + + while (iter.next()) + { +- final int wx = GeoEngine.getInstance().getWorldX(iter.x()); +- final int wy = GeoEngine.getInstance().getWorldY(iter.y()); ++ final int wx = GeoEngine.getWorldX(iter.x()); ++ final int wy = GeoEngine.getWorldY(iter.y()); + + prim.addPoint(Color.RED, wx, wy, z); + } +@@ -53,21 +53,21 @@ + + public static void debug3DLine(PlayerInstance player, int x, int y, int z, int tx, int ty, int tz) + { +- final int gx = GeoEngine.getInstance().getGeoX(x); +- final int gy = GeoEngine.getInstance().getGeoY(y); ++ final int gx = GeoEngine.getGeoX(x); ++ final int gy = GeoEngine.getGeoY(y); + +- final int tgx = GeoEngine.getInstance().getGeoX(tx); +- final int tgy = GeoEngine.getInstance().getGeoY(ty); ++ final int tgx = GeoEngine.getGeoX(tx); ++ final int tgy = GeoEngine.getGeoY(ty); + + final ExServerPrimitive prim = new ExServerPrimitive("Debug3DLine", x, y, z); +- prim.addLine(Color.BLUE, GeoEngine.getInstance().getWorldX(gx), GeoEngine.getInstance().getWorldY(gy), z, GeoEngine.getInstance().getWorldX(tgx), GeoEngine.getInstance().getWorldY(tgy), tz); ++ prim.addLine(Color.BLUE, GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), z, GeoEngine.getWorldX(tgx), GeoEngine.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.getInstance().getWorldX(prevX); +- int wy = GeoEngine.getInstance().getWorldY(prevY); ++ int wx = GeoEngine.getWorldX(prevX); ++ int wy = GeoEngine.getWorldY(prevY); + int wz = iter.z(); + prim.addPoint(Color.RED, wx, wy, wz); + +@@ -78,8 +78,8 @@ + + if ((curX != prevX) || (curY != prevY)) + { +- wx = GeoEngine.getInstance().getWorldX(curX); +- wy = GeoEngine.getInstance().getWorldY(curY); ++ wx = GeoEngine.getWorldX(curX); ++ wy = GeoEngine.getWorldY(curY); + wz = iter.z(); + + prim.addPoint(Color.RED, wx, wy, wz); +@@ -93,7 +93,7 @@ + + private static Color getDirectionColor(int x, int y, int z, int nswe) + { +- if (GeoEngine.getInstance().checkNearestNswe(x, y, z, nswe)) ++ if ((GeoEngine.getInstance().getNsweNearest(x, y, z) & nswe) == nswe) + { + return Color.GREEN; + } +@@ -109,9 +109,8 @@ + int iPacket = 0; + + ExServerPrimitive exsp = null; +- final GeoEngine ge = GeoEngine.getInstance(); +- final int playerGx = ge.getGeoX(player.getX()); +- final int playerGy = ge.getGeoY(player.getY()); ++ final int playerGx = GeoEngine.getGeoX(player.getX()); ++ final int playerGy = GeoEngine.getGeoY(player.getY()); + for (int dx = -geoRadius; dx <= geoRadius; ++dx) + { + for (int dy = -geoRadius; dy <= geoRadius; ++dy) +@@ -135,12 +134,12 @@ + final int gx = playerGx + dx; + final int gy = playerGy + dy; + +- final int x = ge.getWorldX(gx); +- final int y = ge.getWorldY(gy); +- final int z = ge.getNearestZ(gx, gy, player.getZ()); ++ final int x = GeoEngine.getWorldX(gx); ++ final int y = GeoEngine.getWorldY(gy); ++ final int z = GeoEngine.getInstance().getHeightNearest(gx, gy, player.getZ()); + + // north arrow +- Color col = getDirectionColor(gx, gy, z, Cell.NSWE_NORTH); ++ Color col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_N); + 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); +@@ -147,7 +146,7 @@ + exsp.addLine(col, x - 4, y - 4, z, x + 4, y - 4, z); + + // east arrow +- col = getDirectionColor(gx, gy, z, Cell.NSWE_EAST); ++ col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_E); + 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); +@@ -154,13 +153,13 @@ + exsp.addLine(col, x + 4, y - 4, z, x + 4, y + 4, z); + + // south arrow +- col = getDirectionColor(gx, gy, z, Cell.NSWE_SOUTH); ++ col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_S); + 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, Cell.NSWE_WEST); ++ col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_W); + 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); +@@ -188,15 +187,15 @@ + { + if (y > lastY) + { +- return Cell.NSWE_SOUTH_EAST; ++ return GeoStructure.CELL_FLAG_SE; // Direction.SOUTH_EAST; + } + else if (y < lastY) + { +- return Cell.NSWE_NORTH_EAST; ++ return GeoStructure.CELL_FLAG_NE; // Direction.NORTH_EAST; + } + else + { +- return Cell.NSWE_EAST; ++ return GeoStructure.CELL_FLAG_E; // Direction.EAST; + } + } + else if (x < lastX) // west +@@ -203,26 +202,27 @@ + { + if (y > lastY) + { +- return Cell.NSWE_SOUTH_WEST; ++ return GeoStructure.CELL_FLAG_SW; // Direction.SOUTH_WEST; + } + else if (y < lastY) + { +- return Cell.NSWE_NORTH_WEST; ++ return GeoStructure.CELL_FLAG_NW; // Direction.NORTH_WEST; + } + else + { +- return Cell.NSWE_WEST; ++ return GeoStructure.CELL_FLAG_W; // Direction.WEST; + } + } +- else // unchanged x ++ else ++ // unchanged x + { + if (y > lastY) + { +- return Cell.NSWE_SOUTH; ++ return GeoStructure.CELL_FLAG_S; // Direction.SOUTH; + } + else if (y < lastY) + { +- return Cell.NSWE_NORTH; ++ return GeoStructure.CELL_FLAG_N; // Direction.NORTH; + } + else + { +Index: java/org/l2jmobius/tools/geodataconverter/GeoDataConverter.java +=================================================================== +--- java/org/l2jmobius/tools/geodataconverter/GeoDataConverter.java (nonexistent) ++++ java/org/l2jmobius/tools/geodataconverter/GeoDataConverter.java (working copy) +@@ -0,0 +1,386 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ */ ++package org.l2jmobius.tools.geodataconverter; ++ ++import java.io.BufferedOutputStream; ++import java.io.File; ++import java.io.FileOutputStream; ++import java.io.RandomAccessFile; ++import java.nio.ByteOrder; ++import java.nio.MappedByteBuffer; ++import java.nio.channels.FileChannel; ++import java.util.Scanner; ++ ++import org.l2jmobius.Config; ++import org.l2jmobius.commons.util.PropertiesParser; ++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.GeoFormat; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; ++import org.l2jmobius.gameserver.model.World; ++ ++/** ++ * @author Hasha ++ */ ++public class GeoDataConverter ++{ ++ private static GeoFormat _format; ++ private static ABlock[][] _blocks; ++ ++ public static void main(String[] args) ++ { ++ // initialize config ++ loadGeoengineConfigs(); ++ ++ // get geodata format ++ String type = ""; ++ try (Scanner scn = new Scanner(System.in)) ++ { ++ while (!(type.equalsIgnoreCase("J") || type.equalsIgnoreCase("O") || type.equalsIgnoreCase("E"))) ++ { ++ System.out.println("GeoDataConverter: Select source geodata type:"); ++ System.out.println(" J: L2J (e.g. 23_22.l2j)"); ++ System.out.println(" O: L2OFF (e.g. 23_22_conv.dat)"); ++ System.out.println(" E: Exit"); ++ System.out.print("Choice: "); ++ type = scn.next(); ++ } ++ } ++ if (type.equalsIgnoreCase("E")) ++ { ++ System.exit(0); ++ } ++ ++ _format = type.equalsIgnoreCase("J") ? GeoFormat.L2J : GeoFormat.L2OFF; ++ ++ // start conversion ++ System.out.println("GeoDataConverter: Converting all " + _format + " files."); ++ ++ // initialize geodata container ++ _blocks = new ABlock[GeoStructure.REGION_BLOCKS_X][GeoStructure.REGION_BLOCKS_Y]; ++ ++ // initialize multilayer temporarily buffer ++ BlockMultilayer.initialize(); ++ ++ // load geo files ++ int converted = 0; ++ for (int rx = World.TILE_X_MIN; rx <= World.TILE_X_MAX; rx++) ++ { ++ for (int ry = World.TILE_Y_MIN; ry <= World.TILE_Y_MAX; ry++) ++ { ++ final String input = String.format(_format.getFilename(), rx, ry); ++ final String filepath = Config.GEODATA_PATH; ++ final File f = new File(filepath + input); ++ if (f.exists() && !f.isDirectory()) ++ { ++ // load geodata ++ if (!loadGeoBlocks(input)) ++ { ++ System.out.println("GeoDataConverter: Unable to load " + input + " region file."); ++ continue; ++ } ++ ++ // recalculate nswe ++ if (!recalculateNswe()) ++ { ++ System.out.println("GeoDataConverter: Unable to convert " + input + " region file."); ++ continue; ++ } ++ ++ // save geodata ++ final String output = String.format(GeoFormat.L2D.getFilename(), rx, ry); ++ if (!saveGeoBlocks(output)) ++ { ++ System.out.println("GeoDataConverter: Unable to save " + output + " region file."); ++ continue; ++ } ++ ++ converted++; ++ System.out.println("GeoDataConverter: Created " + output + " region file."); ++ } ++ } ++ } ++ System.out.println("GeoDataConverter: Converted " + converted + " " + _format + " to L2D region file(s)."); ++ ++ // release multilayer block temporarily buffer ++ BlockMultilayer.release(); ++ } ++ ++ /** ++ * Loads geo blocks from buffer of the region file. ++ * @param filename : The name of the to load. ++ * @return boolean : True when successful. ++ */ ++ private static boolean loadGeoBlocks(String filename) ++ { ++ // region file is load-able, try to load it ++ try (RandomAccessFile raf = new RandomAccessFile(Config.GEODATA_PATH + filename, "r"); ++ FileChannel fc = raf.getChannel()) ++ { ++ final MappedByteBuffer buffer = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()).load(); ++ buffer.order(ByteOrder.LITTLE_ENDIAN); ++ ++ // load 18B header for L2off geodata (1st and 2nd byte...region X and Y) ++ if (_format == GeoFormat.L2OFF) ++ { ++ for (int i = 0; i < 18; i++) ++ { ++ buffer.get(); ++ } ++ } ++ ++ // 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 (_format == GeoFormat.L2J) ++ { ++ // get block type ++ final byte type = buffer.get(); ++ ++ // load block according to block type ++ switch (type) ++ { ++ case GeoStructure.TYPE_FLAT_L2J_L2OFF: ++ { ++ _blocks[ix][iy] = new BlockFlat(buffer, _format); ++ break; ++ } ++ case GeoStructure.TYPE_COMPLEX_L2J: ++ { ++ _blocks[ix][iy] = new BlockComplex(buffer, _format); ++ break; ++ } ++ case GeoStructure.TYPE_MULTILAYER_L2J: ++ { ++ _blocks[ix][iy] = new BlockMultilayer(buffer, _format); ++ break; ++ } ++ default: ++ { ++ throw new IllegalArgumentException("Unknown block type: " + type); ++ } ++ } ++ } ++ else ++ { ++ // get block type ++ final short type = buffer.getShort(); ++ ++ // load block according to block type ++ switch (type) ++ { ++ case GeoStructure.TYPE_FLAT_L2J_L2OFF: ++ { ++ _blocks[ix][iy] = new BlockFlat(buffer, _format); ++ break; ++ } ++ case GeoStructure.TYPE_COMPLEX_L2OFF: ++ { ++ _blocks[ix][iy] = new BlockComplex(buffer, _format); ++ break; ++ } ++ default: ++ { ++ _blocks[ix][iy] = new BlockMultilayer(buffer, _format); ++ break; ++ } ++ } ++ } ++ } ++ } ++ ++ if (buffer.remaining() > 0) ++ { ++ System.out.println("GeoDataConverter: Region file " + filename + " can be corrupted, remaining " + buffer.remaining() + " bytes to read."); ++ return false; ++ } ++ ++ return true; ++ } ++ catch (Exception e) ++ { ++ System.out.println("GeoDataConverter: Error while loading " + filename + " region file."); ++ return false; ++ } ++ } ++ ++ /** ++ * Recalculate diagonal flags for the region file. ++ * @return boolean : True when successful. ++ */ ++ private static boolean recalculateNswe() ++ { ++ try ++ { ++ for (int x = 0; x < GeoStructure.REGION_CELLS_X; x++) ++ { ++ for (int y = 0; y < GeoStructure.REGION_CELLS_Y; y++) ++ { ++ // get block ++ final ABlock block = _blocks[x / GeoStructure.BLOCK_CELLS_X][y / GeoStructure.BLOCK_CELLS_Y]; ++ ++ // skip flat blocks ++ if (block instanceof BlockFlat) ++ { ++ continue; ++ } ++ ++ // for complex and multilayer blocks go though all layers ++ short height = Short.MAX_VALUE; ++ int index; ++ while ((index = block.getIndexBelow(x, y, height)) != -1) ++ { ++ // get height and nswe ++ height = block.getHeight(index); ++ byte nswe = block.getNswe(index); ++ ++ // update nswe with diagonal flags ++ nswe = updateNsweBelow(x, y, height, nswe); ++ ++ // set nswe of the cell ++ block.setNswe(index, nswe); ++ } ++ } ++ } ++ ++ return true; ++ } ++ catch (Exception e) ++ { ++ return false; ++ } ++ } ++ ++ /** ++ * Updates the NSWE flag with diagonal flags. ++ * @param x : Geodata X coordinate. ++ * @param y : Geodata Y coordinate. ++ * @param z : Geodata Z coordinate. ++ * @param nsweValue : NSWE flag to be updated. ++ * @return byte : Updated NSWE flag. ++ */ ++ private static byte updateNsweBelow(int x, int y, short z, byte nsweValue) ++ { ++ byte nswe = nsweValue; ++ ++ // calculate virtual layer height ++ final short height = (short) (z + GeoStructure.CELL_IGNORE_HEIGHT); ++ ++ // get NSWE of neighbor cells below virtual layer (NPC/PC can fall down of clif, but can not climb it -> NSWE of cell below) ++ final byte nsweN = getNsweBelow(x, y - 1, height); ++ final byte nsweS = getNsweBelow(x, y + 1, height); ++ final byte nsweW = getNsweBelow(x - 1, y, height); ++ final byte nsweE = getNsweBelow(x + 1, y, height); ++ ++ // north-west ++ if ((((nswe & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) || (((nswe & GeoStructure.CELL_FLAG_W) != 0) && ((nsweW & GeoStructure.CELL_FLAG_N) != 0))) ++ { ++ nswe |= GeoStructure.CELL_FLAG_NW; ++ } ++ ++ // north-east ++ if ((((nswe & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) || (((nswe & GeoStructure.CELL_FLAG_E) != 0) && ((nsweE & GeoStructure.CELL_FLAG_N) != 0))) ++ { ++ nswe |= GeoStructure.CELL_FLAG_NE; ++ } ++ ++ // south-west ++ if ((((nswe & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) || (((nswe & GeoStructure.CELL_FLAG_W) != 0) && ((nsweW & GeoStructure.CELL_FLAG_S) != 0))) ++ { ++ nswe |= GeoStructure.CELL_FLAG_SW; ++ } ++ ++ // south-east ++ if ((((nswe & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) || (((nswe & GeoStructure.CELL_FLAG_E) != 0) && ((nsweE & GeoStructure.CELL_FLAG_S) != 0))) ++ { ++ nswe |= GeoStructure.CELL_FLAG_SE; ++ } ++ ++ return nswe; ++ } ++ ++ private static byte getNsweBelow(int geoX, int geoY, short worldZ) ++ { ++ // out of geo coordinates ++ if ((geoX < 0) || (geoX >= GeoStructure.REGION_CELLS_X)) ++ { ++ return 0; ++ } ++ ++ // out of geo coordinates ++ if ((geoY < 0) || (geoY >= GeoStructure.REGION_CELLS_Y)) ++ { ++ return 0; ++ } ++ ++ // get block ++ final ABlock block = _blocks[geoX / GeoStructure.BLOCK_CELLS_X][geoY / GeoStructure.BLOCK_CELLS_Y]; ++ ++ // get index, when valid, return nswe ++ final int index = block.getIndexBelow(geoX, geoY, worldZ); ++ return index == -1 ? 0 : block.getNswe(index); ++ } ++ ++ /** ++ * Save region file to file. ++ * @param filename : The name of file to save. ++ * @return boolean : True when successful. ++ */ ++ private static boolean saveGeoBlocks(String filename) ++ { ++ try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(Config.GEODATA_PATH + filename), GeoStructure.REGION_BLOCKS * GeoStructure.BLOCK_CELLS * 3)) ++ { ++ // loop over region blocks and save each block ++ for (int ix = 0; ix < GeoStructure.REGION_BLOCKS_X; ix++) ++ { ++ for (int iy = 0; iy < GeoStructure.REGION_BLOCKS_Y; iy++) ++ { ++ _blocks[ix][iy].saveBlock(bos); ++ } ++ } ++ ++ // flush data to file ++ bos.flush(); ++ ++ return true; ++ } ++ catch (Exception e) ++ { ++ return false; ++ } ++ } ++ ++ private static void loadGeoengineConfigs() ++ { ++ final PropertiesParser geoData = new PropertiesParser(Config.GEOENGINE_CONFIG_FILE); ++ Config.GEODATA_PATH = geoData.getString("GeoDataPath", "./data/geodata/"); ++ Config.COORD_SYNCHRONIZE = geoData.getInt("CoordSynchronize", -1); ++ Config.PART_OF_CHARACTER_HEIGHT = geoData.getInt("PartOfCharacterHeight", 75); ++ Config.MAX_OBSTACLE_HEIGHT = geoData.getInt("MaxObstacleHeight", 32); ++ Config.PATHFINDING = geoData.getBoolean("PathFinding", true); ++ Config.PATHFIND_BUFFERS = geoData.getString("PathFindBuffers", "100x6;128x6;192x6;256x4;320x4;384x4;500x2"); ++ Config.BASE_WEIGHT = geoData.getInt("BaseWeight", 10); ++ Config.DIAGONAL_WEIGHT = geoData.getInt("DiagonalWeight", 14); ++ Config.OBSTACLE_MULTIPLIER = geoData.getInt("ObstacleMultiplier", 10); ++ Config.HEURISTIC_WEIGHT = geoData.getInt("HeuristicWeight", 20); ++ Config.MAX_ITERATIONS = geoData.getInt("MaxIterations", 3500); ++ } ++} +\ No newline at end of file diff --git a/L2J_Mobius_4.0_GrandCrusade/dist/game/data/geodata/L2D_GeoEngine.patch b/L2J_Mobius_4.0_GrandCrusade/dist/game/data/geodata/L2D_GeoEngine.patch new file mode 100644 index 0000000000..63e2faf0c9 --- /dev/null +++ b/L2J_Mobius_4.0_GrandCrusade/dist/game/data/geodata/L2D_GeoEngine.patch @@ -0,0 +1,6052 @@ +Index: dist/game/GeoDataConverter.bat +=================================================================== +--- dist/game/GeoDataConverter.bat (nonexistent) ++++ dist/game/GeoDataConverter.bat (working copy) +@@ -0,0 +1,6 @@ ++@echo off ++title L2D geodata converter ++ ++java -Xmx512m -cp ./../libs/* org.l2jmobius.tools.geodataconverter.GeoDataConverter ++ ++pause +Index: dist/game/GeoDataConverter.sh +=================================================================== +--- dist/game/GeoDataConverter.sh (nonexistent) ++++ dist/game/GeoDataConverter.sh (working copy) +@@ -0,0 +1,4 @@ ++#! /bin/sh ++ ++java -Xmx512m -cp ../libs/*: org.l2jmobius.tools.geodataconverter.GeoDataConverter > log/stdout.log 2>&1 ++ +Index: dist/game/config/GeoEngine.ini +=================================================================== +--- dist/game/config/GeoEngine.ini (revision 8397) ++++ dist/game/config/GeoEngine.ini (working copy) +@@ -1,6 +1,10 @@ + # ================================================================= + # Geodata + # ================================================================= ++# Because of real-time performance we are using geodata files only in ++# diagonal L2D format now (using filename e.g. 22_16.l2d). ++# L2D geodata can be obtained by conversion of existing L2J or L2OFF geodata. ++# Launch "GeoDataConverter.bat/sh" and follow instructions to start the conversion. + + # 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/ +@@ -13,6 +17,16 @@ + CoordSynchronize = 2 + + # ================================================================= ++# Path checking ++# ================================================================= ++ ++# 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 finding + # ================================================================= + +@@ -23,19 +37,23 @@ + # Pathfinding array buffers configuration, default: 100x6;128x6;192x6;256x4;320x4;384x4;500x2 + PathFindBuffers = 100x6;128x6;192x6;256x4;320x4;384x4;500x2 + +-# Weight for nodes without obstacles far from walls +-LowWeight = 0.5 ++# Base path weight, when moving from one node to another on axis direction, default: 10 ++BaseWeight = 10 + +-# Weight for nodes near walls +-MediumWeight = 2 ++# Path weight, when moving from one node to another on diagonal direction, default: BaseWeight * sqrt(2) = 14 ++DiagonalWeight = 14 + +-# Weight for nodes with obstacles +-HighWeight = 3 ++# When movement flags of target node is blocked to any direction, multiply movement weight by this multiplier. ++# This causes pathfinding algorithm to avoid path construction exactly near an obstacle, default: 10 ++ObstacleMultiplier = 10 + +-# Weight for diagonal movement. +-# Default: LowWeight * sqrt(2) +-DiagonalWeight = 0.707 ++# Weight of the heuristic algorithm, which is giving estimated cost from node to target, default: 20 ++# For proper function must be higher than BaseWeight and/or DiagonalWeight. ++HeuristicWeight = 20 + ++# Maximum number of generated nodes per one path-finding process, default 3500 ++MaxIterations = 3500 ++ + # ================================================================= + # Other + # ================================================================= +Index: dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java +=================================================================== +--- dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java (revision 8397) ++++ dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java (working copy) +@@ -55,9 +55,8 @@ + final int worldX = activeChar.getX(); + final int worldY = activeChar.getY(); + final int worldZ = activeChar.getZ(); +- final int geoX = GeoEngine.getInstance().getGeoX(worldX); +- final int geoY = GeoEngine.getInstance().getGeoY(worldY); +- ++ final int geoX = GeoEngine.getGeoX(worldX); ++ final int geoY = GeoEngine.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)); +@@ -73,9 +72,8 @@ + final int worldX = activeChar.getX(); + final int worldY = activeChar.getY(); + final int worldZ = activeChar.getZ(); +- final int geoX = GeoEngine.getInstance().getGeoX(worldX); +- final int geoY = GeoEngine.getInstance().getGeoY(worldY); +- ++ final int geoX = GeoEngine.getGeoX(worldX); ++ final int geoY = GeoEngine.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)); +Index: dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java +=================================================================== +--- dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java (revision 8397) ++++ dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java (working copy) +@@ -18,11 +18,11 @@ + + import java.util.List; + +-import org.l2jmobius.Config; +-import org.l2jmobius.gameserver.geoengine.GeoEnginePathfinding; +-import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; ++import org.l2jmobius.gameserver.geoengine.GeoEngine; + import org.l2jmobius.gameserver.handler.IAdminCommandHandler; ++import org.l2jmobius.gameserver.model.Location; + import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance; ++import org.l2jmobius.gameserver.network.SystemMessageId; + import org.l2jmobius.gameserver.util.BuilderUtil; + + public class AdminPathNode implements IAdminCommandHandler +@@ -37,29 +37,30 @@ + { + if (command.equals("admin_path_find")) + { +- if (!Config.PATHFINDING) +- { +- BuilderUtil.sendSysMessage(activeChar, "PathFinding is disabled."); +- return true; +- } + if (activeChar.getTarget() != null) + { +- final List path = GeoEnginePathfinding.getInstance().findPath(activeChar.getX(), activeChar.getY(), (short) activeChar.getZ(), activeChar.getTarget().getX(), activeChar.getTarget().getY(), (short) activeChar.getTarget().getZ(), activeChar.getInstanceWorld()); ++ 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()); + if (path == null) + { +- BuilderUtil.sendSysMessage(activeChar, "No Route!"); +- return true; ++ BuilderUtil.sendSysMessage(activeChar, "No route found or pathfinding disabled."); + } +- for (AbstractNodeLoc a : path) ++ else + { +- BuilderUtil.sendSysMessage(activeChar, "x:" + a.getX() + " y:" + a.getY() + " z:" + a.getZ()); ++ for (Location point : path) ++ { ++ BuilderUtil.sendSysMessage(activeChar, "x:" + point.getX() + " y:" + point.getY() + " z:" + point.getZ()); ++ } + } + } + else + { +- BuilderUtil.sendSysMessage(activeChar, "No Target!"); ++ activeChar.sendPacket(SystemMessageId.INVALID_TARGET); + } + } ++ else ++ { ++ return false; ++ } + return true; + } + +Index: java/org/l2jmobius/Config.java +=================================================================== +--- java/org/l2jmobius/Config.java (revision 8397) ++++ java/org/l2jmobius/Config.java (working copy) +@@ -32,7 +32,6 @@ + import java.net.UnknownHostException; + import java.nio.charset.StandardCharsets; + import java.nio.file.Files; +-import java.nio.file.Path; + import java.nio.file.Paths; + import java.time.Duration; + import java.util.ArrayList; +@@ -927,14 +926,17 @@ + // -------------------------------------------------- + // GeoEngine + // -------------------------------------------------- +- public static Path GEODATA_PATH; ++ public static String GEODATA_PATH; ++ public static int COORD_SYNCHRONIZE; ++ public static int PART_OF_CHARACTER_HEIGHT; ++ public static int MAX_OBSTACLE_HEIGHT; + public static boolean PATHFINDING; + public static String PATHFIND_BUFFERS; +- public static float LOW_WEIGHT; +- public static float MEDIUM_WEIGHT; +- public static float HIGH_WEIGHT; +- public static float DIAGONAL_WEIGHT; +- public static int COORD_SYNCHRONIZE; ++ public static int BASE_WEIGHT; ++ public static int DIAGONAL_WEIGHT; ++ public static int HEURISTIC_WEIGHT; ++ public static int OBSTACLE_MULTIPLIER; ++ public static int MAX_ITERATIONS; + public static boolean CORRECT_PLAYER_Z; + + /** Attribute System */ +@@ -2468,14 +2470,17 @@ + + // Load GeoEngine config file (if exists) + final PropertiesParser GeoEngine = new PropertiesParser(GEOENGINE_CONFIG_FILE); +- GEODATA_PATH = Paths.get(GeoEngine.getString("GeoDataPath", "./data/geodata")); ++ GEODATA_PATH = GeoEngine.getString("GeoDataPath", "./data/geodata/"); ++ COORD_SYNCHRONIZE = GeoEngine.getInt("CoordSynchronize", -1); ++ PART_OF_CHARACTER_HEIGHT = GeoEngine.getInt("PartOfCharacterHeight", 75); ++ MAX_OBSTACLE_HEIGHT = GeoEngine.getInt("MaxObstacleHeight", 32); + PATHFINDING = GeoEngine.getBoolean("PathFinding", true); + PATHFIND_BUFFERS = GeoEngine.getString("PathFindBuffers", "100x6;128x6;192x6;256x4;320x4;384x4;500x2"); +- LOW_WEIGHT = GeoEngine.getFloat("LowWeight", 0.5f); +- MEDIUM_WEIGHT = GeoEngine.getFloat("MediumWeight", 2); +- HIGH_WEIGHT = GeoEngine.getFloat("HighWeight", 3); +- DIAGONAL_WEIGHT = GeoEngine.getFloat("DiagonalWeight", 0.707f); +- COORD_SYNCHRONIZE = GeoEngine.getInt("CoordSynchronize", -1); ++ BASE_WEIGHT = GeoEngine.getInt("BaseWeight", 10); ++ DIAGONAL_WEIGHT = GeoEngine.getInt("DiagonalWeight", 14); ++ OBSTACLE_MULTIPLIER = GeoEngine.getInt("ObstacleMultiplier", 10); ++ HEURISTIC_WEIGHT = GeoEngine.getInt("HeuristicWeight", 20); ++ MAX_ITERATIONS = GeoEngine.getInt("MaxIterations", 3500); + CORRECT_PLAYER_Z = GeoEngine.getBoolean("CorrectPlayerZ", false); + + // Load AllowedPlayerRaces config file (if exists) +Index: java/org/l2jmobius/gameserver/geoengine/GeoEngine.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/GeoEngine.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/GeoEngine.java (working copy) +@@ -16,101 +16,91 @@ + */ + package org.l2jmobius.gameserver.geoengine; + +-import java.io.IOException; ++import java.io.File; + import java.io.RandomAccessFile; + import java.nio.ByteOrder; +-import java.nio.channels.FileChannel.MapMode; +-import java.nio.file.Files; +-import java.nio.file.Path; +-import java.util.concurrent.atomic.AtomicReferenceArray; +-import java.util.logging.Level; ++import java.nio.MappedByteBuffer; ++import java.nio.channels.FileChannel; ++import java.util.List; + 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.geoengine.geodata.Cell; +-import org.l2jmobius.gameserver.geoengine.geodata.IRegion; +-import org.l2jmobius.gameserver.geoengine.geodata.NullRegion; +-import org.l2jmobius.gameserver.geoengine.geodata.Region; ++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.GeoFormat; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoLocation; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; + import org.l2jmobius.gameserver.instancemanager.WarpedSpaceManager; + 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; ++import org.l2jmobius.gameserver.util.MathUtil; + + /** +- * @author -Nemesiss-, HorridoJoho ++ * @author Hasha + */ + public class GeoEngine + { +- private static final Logger LOGGER = Logger.getLogger(GeoEngine.class.getName()); ++ protected static final Logger LOGGER = Logger.getLogger(GeoEngine.class.getName()); + +- private static final int WORLD_MIN_X = -655360; +- private static final int WORLD_MIN_Y = -589824; +- private static final int WORLD_MIN_Z = -16384; ++ private final ABlock[][] _blocks; ++ private final BlockNull _nullBlock; + +- /** 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; +- +- /** The regions array */ +- private final AtomicReferenceArray _regions = new AtomicReferenceArray<>(GEO_REGIONS); +- +- 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; +- +- protected GeoEngine() ++ /** ++ * GeoEngine constructor. Loads all geodata files of chosen geodata format. ++ */ ++ public GeoEngine() + { + LOGGER.info("GeoEngine: Initializing..."); +- for (int i = 0; i < _regions.length(); i++) +- { +- _regions.set(i, NullRegion.INSTANCE); +- } + ++ // 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; +- try ++ long fileSize = 0; ++ for (int rx = World.TILE_X_MIN; rx <= World.TILE_X_MAX; rx++) + { +- for (int regionX = World.TILE_X_MIN; regionX <= World.TILE_X_MAX; regionX++) ++ for (int ry = World.TILE_Y_MIN; ry <= World.TILE_Y_MAX; ry++) + { +- for (int regionY = World.TILE_Y_MIN; regionY <= World.TILE_Y_MAX; regionY++) ++ final File f = new File(Config.GEODATA_PATH + String.format(GeoFormat.L2D.getFilename(), rx, ry)); ++ if (f.exists() && !f.isDirectory()) + { +- 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(rx, ry)) + { +- try (RandomAccessFile raf = new RandomAccessFile(geoFilePath.toFile(), "r")) +- { +- _regions.set((regionX * GEO_REGIONS_Y) + regionY, new Region(raf.getChannel().map(MapMode.READ_ONLY, 0, raf.length()).load().order(ByteOrder.LITTLE_ENDIAN))); +- loaded++; +- } ++ loaded++; ++ fileSize += f.length(); + } + } ++ else ++ { ++ // region file is not load-able, load null blocks ++ loadNullBlocks(rx, ry); ++ } + } + } +- catch (Exception e) ++ ++ LOGGER.info("GeoEngine: Loaded " + loaded + " geodata files."); ++ if (loaded > 0) + { +- LOGGER.log(Level.SEVERE, "GeoEngine: Failed to load geodata!", e); +- System.exit(1); ++ LOGGER.info("GeoEngine: Total geodata file size " + (fileSize / 1024 / 1024) + " MB."); + } + +- LOGGER.info("GeoEngine: Loaded " + loaded + " geodata files."); +- +- // Avoid wrong configs when no files are loaded. ++ // avoid wrong configs when no files are loaded + if (loaded == 0) + { + if (Config.PATHFINDING) +@@ -124,627 +114,832 @@ + LOGGER.info("GeoEngine: Forcing CoordSynchronize setting to -1."); + } + } ++ ++ // release multilayer block temporarily buffer ++ BlockMultilayer.release(); + } + + /** +- * @param geoX +- * @param geoY +- * @return the region ++ * 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 IRegion getRegion(int geoX, int geoY) ++ private final boolean loadGeoBlocks(int regionX, int regionY) + { +- final int region = ((geoX / IRegion.REGION_CELLS_X) * GEO_REGIONS_Y) + (geoY / IRegion.REGION_CELLS_Y); +- if ((region < 0) || (region >= _regions.length())) ++ final String filename = String.format(GeoFormat.L2D.getFilename(), regionX, regionY); ++ ++ // standard load ++ try (RandomAccessFile raf = new RandomAccessFile(Config.GEODATA_PATH + filename, "r"); ++ FileChannel fc = raf.getChannel()) + { +- return null; ++ // initialize file buffer ++ final MappedByteBuffer buffer = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()).load(); ++ buffer.order(ByteOrder.LITTLE_ENDIAN); ++ ++ // 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++) ++ { ++ // get block type ++ final byte type = buffer.get(); ++ ++ // load block according to block type ++ switch (type) ++ { ++ case GeoStructure.TYPE_FLAT_L2D: ++ { ++ _blocks[blockX + ix][blockY + iy] = new BlockFlat(buffer, GeoFormat.L2D); ++ break; ++ } ++ case GeoStructure.TYPE_COMPLEX_L2D: ++ { ++ _blocks[blockX + ix][blockY + iy] = new BlockComplex(buffer, GeoFormat.L2D); ++ break; ++ } ++ case GeoStructure.TYPE_MULTILAYER_L2D: ++ { ++ _blocks[blockX + ix][blockY + iy] = new BlockMultilayer(buffer, GeoFormat.L2D); ++ break; ++ } ++ default: ++ { ++ throw new IllegalArgumentException("Unknown block type: " + type); ++ } ++ } ++ } ++ } ++ ++ // check data consistency ++ if (buffer.remaining() > 0) ++ { ++ LOGGER.warning("GeoEngine: Region file " + filename + " can be corrupted, remaining " + buffer.remaining() + " bytes to read."); ++ } ++ ++ // loading was successful ++ return true; + } +- return _regions.get(region); +- } +- +- /** +- * @param filePath +- * @param regionX +- * @param regionY +- * @throws IOException +- */ +- 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")) ++ catch (Exception e) + { +- _regions.set(regionOffset, new Region(raf.getChannel().map(MapMode.READ_ONLY, 0, raf.length()).load().order(ByteOrder.LITTLE_ENDIAN))); ++ // an error occured while loading, load null blocks ++ LOGGER.warning("GeoEngine: Error while loading " + filename + " region file."); ++ LOGGER.warning(e.getMessage()); ++ ++ // replace whole region file with null blocks ++ loadNullBlocks(regionX, regionY); ++ ++ // loading was not successful ++ return false; + } + } + + /** +- * @param regionX +- * @param regionY ++ * 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. + */ +- public void unloadRegion(int regionX, int regionY) ++ private final void loadNullBlocks(int regionX, int regionY) + { +- _regions.set((regionX * GEO_REGIONS_Y) + regionY, NullRegion.INSTANCE); +- } +- +- /** +- * @param geoX +- * @param geoY +- * @return if geodata exist +- */ +- public boolean hasGeoPos(int geoX, int geoY) +- { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) ++ // 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++) + { +- return false; ++ for (int iy = 0; iy < GeoStructure.REGION_BLOCKS_Y; iy++) ++ { ++ _blocks[blockX + ix][blockY + iy] = _nullBlock; ++ } + } +- return region.hasGeo(); + } + ++ // GEODATA - GENERAL ++ + /** +- * Checks the specified position for available geodata. +- * @param x the world x +- * @param y the world y +- * @return {@code true} if there is geodata for the given coordinates, {@code false} otherwise ++ * Converts world X to geodata X. ++ * @param worldX ++ * @return int : Geo X + */ +- public boolean hasGeo(int x, int y) ++ public static int getGeoX(int worldX) + { +- return hasGeoPos(getGeoX(x), getGeoY(y)); ++ return (MathUtil.limit(worldX, World.MAP_MIN_X, World.MAP_MAX_X) - World.MAP_MIN_X) >> 4; + } + + /** +- * @param geoX +- * @param geoY +- * @param worldZ +- * @param nswe +- * @return the nearest nswe check ++ * Converts world Y to geodata Y. ++ * @param worldY ++ * @return int : Geo Y + */ +- public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) ++ public static int getGeoY(int worldY) + { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) +- { +- return true; +- } +- return region.checkNearestNswe(geoX, geoY, worldZ, nswe); ++ return (MathUtil.limit(worldY, World.MAP_MIN_Y, World.MAP_MAX_Y) - World.MAP_MIN_Y) >> 4; + } + + /** ++ * Converts geodata X to world X. + * @param geoX +- * @param geoY +- * @param worldZ +- * @param nswe +- * @return the nearest nswe anti-corner cut ++ * @return int : World X + */ +- public boolean checkNearestNsweAntiCornerCut(int geoX, int geoY, int worldZ, int nswe) ++ public static int getWorldX(int geoX) + { +- boolean can = true; +- if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) +- { +- can = checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_EAST) && checkNearestNswe(geoX + 1, geoY, worldZ, Cell.NSWE_NORTH); +- } +- if (can && ((nswe & Cell.NSWE_NORTH_WEST) == Cell.NSWE_NORTH_WEST)) +- { +- 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 = 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 = checkNearestNswe(geoX, geoY + 1, worldZ, Cell.NSWE_WEST) && checkNearestNswe(geoX - 1, geoY, worldZ, Cell.NSWE_SOUTH); +- } +- return can && checkNearestNswe(geoX, geoY, worldZ, nswe); ++ return (MathUtil.limit(geoX, 0, GeoStructure.GEO_CELLS_X) << 4) + World.MAP_MIN_X + 8; + } + + /** +- * @param geoX ++ * Converts geodata Y to world Y. + * @param geoY +- * @param worldZ +- * @return the nearest Z value ++ * @return int : World Y + */ +- public int getNearestZ(int geoX, int geoY, int worldZ) ++ public static int getWorldY(int geoY) + { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) +- { +- return worldZ; +- } +- return region.getNearestZ(geoX, geoY, worldZ); ++ return (MathUtil.limit(geoY, 0, GeoStructure.GEO_CELLS_Y) << 4) + World.MAP_MIN_Y + 8; + } + + /** +- * @param geoX +- * @param geoY +- * @param worldZ +- * @return the next lower Z value ++ * Returns block of geodata on given coordinates. ++ * @param geoX : Geodata X ++ * @param geoY : Geodata Y ++ * @return {@link ABlock} : Block of geodata. + */ +- public int getNextLowerZ(int geoX, int geoY, int worldZ) ++ private final ABlock getBlock(int geoX, int geoY) + { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) ++ final int x = geoX / GeoStructure.BLOCK_CELLS_X; ++ final int y = geoY / GeoStructure.BLOCK_CELLS_Y; ++ ++ // if x or y is out of array return null ++ if ((x > -1) && (y > -1) && (x < GeoStructure.GEO_BLOCKS_X) && (y < GeoStructure.GEO_BLOCKS_Y)) + { +- return worldZ; ++ return _blocks[x][y]; + } +- return region.getNextLowerZ(geoX, geoY, worldZ); ++ return null; + } + + /** +- * @param geoX +- * @param geoY +- * @param worldZ +- * @return the next higher Z value ++ * Check if geo coordinates has geo. ++ * @param geoX : Geodata X ++ * @param geoY : Geodata Y ++ * @return boolean : True, if given geo coordinates have geodata + */ +- public int getNextHigherZ(int geoX, int geoY, int worldZ) ++ public boolean hasGeoPos(int geoX, int geoY) + { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) ++ final ABlock block = getBlock(geoX, geoY); ++ if (block == null) // null block check + { +- return worldZ; ++ // TODO: Find when this can be null. (Bad geodata? Check World getRegion method.) ++ // LOGGER.warning("Could not find geodata block at " + getWorldX(geoX) + ", " + getWorldY(geoY) + "."); ++ return false; + } +- return region.getNextHigherZ(geoX, geoY, worldZ); ++ return block.hasGeoPos(); + } + + /** +- * Gets the Z height. +- * @param x the world x +- * @param y the world y +- * @param z the world z +- * @return the nearest Z height ++ * 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 int getHeight(int x, int y, int z) ++ public short getHeightNearest(int geoX, int geoY, int worldZ) + { +- return getNearestZ(getGeoX(x), getGeoY(y), z); ++ final ABlock block = getBlock(geoX, geoY); ++ return block != null ? block.getHeightNearest(geoX, geoY, worldZ) : (short) worldZ; + } + + /** +- * Gets the next lower Z height. +- * @param x the world x +- * @param y the world y +- * @param z the world z +- * @return the nearest Z height ++ * 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 int getLowerHeight(int x, int y, int z) ++ public byte getNsweNearest(int geoX, int geoY, int worldZ) + { +- return getNextLowerZ(getGeoX(x), getGeoY(y), z); ++ final ABlock block = getBlock(geoX, geoY); ++ return block != null ? block.getNsweNearest(geoX, geoY, worldZ) : (byte) 0xFF; + } + + /** +- * Gets the next higher Z height. +- * @param x the world x +- * @param y the world y +- * @param z the world z +- * @return the nearest Z height ++ * Check if world coordinates has geo. ++ * @param worldX : World X ++ * @param worldY : World Y ++ * @return boolean : True, if given world coordinates have geodata + */ +- public int getHigherHeight(int x, int y, int z) ++ public boolean hasGeo(int worldX, int worldY) + { +- return getNextHigherZ(getGeoX(x), getGeoY(y), z); ++ return hasGeoPos(getGeoX(worldX), getGeoY(worldY)); + } + + /** +- * @param worldX +- * @return the geo X ++ * 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 int getGeoX(int worldX) ++ public short getHeight(int worldX, int worldY, int worldZ) + { +- return (worldX - WORLD_MIN_X) / 16; ++ return getHeightNearest(getGeoX(worldX), getGeoY(worldY), worldZ); + } + +- /** +- * @param worldY +- * @return the geo Y +- */ +- public int getGeoY(int worldY) +- { +- return (worldY - WORLD_MIN_Y) / 16; +- } ++ // PATHFINDING + + /** +- * @param worldZ +- * @return the geo Z ++ * Check line of sight from {@link WorldObject} to {@link WorldObject}. ++ * @param origin : The origin object. ++ * @param target : The target object. ++ * @return {@code boolean} : True if origin can see target + */ +- public int getGeoZ(int worldZ) ++ public boolean canSeeTarget(WorldObject origin, WorldObject target) + { +- return (worldZ - WORLD_MIN_Z) / 16; +- } +- +- /** +- * @param geoX +- * @return the world X +- */ +- public int getWorldX(int geoX) +- { +- return (geoX * 16) + WORLD_MIN_X + 8; +- } +- +- /** +- * @param geoY +- * @return the world Y +- */ +- public int getWorldY(int geoY) +- { +- return (geoY * 16) + WORLD_MIN_Y + 8; +- } +- +- /** +- * @param geoZ +- * @return the world Z +- */ +- public int getWorldZ(int geoZ) +- { +- return (geoZ * 16) + WORLD_MIN_Z + 8; +- } +- +- /** +- * 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) +- { +- if (target.isDoor()) ++ if (target.isDoor() || target.isArtefact() || (target.isCreature() && ((Creature) target).isFlying())) + { +- // Can always see doors. + return true; + } +- return 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(), cha.getInstanceWorld(), worldPosition.getX(), worldPosition.getY(), worldPosition.getZ()); +- } +- +- /** +- * Can see target. Checks doors between. +- * @param x the x coordinate +- * @param y the y coordinate +- * @param z the z coordinate +- * @param world +- * @param tx the target's x coordinate +- * @param ty the target's y coordinate +- * @param tz the target's z coordinate +- * @param tworld the target's instanceId +- * @return +- */ +- public boolean canSeeTarget(int x, int y, int z, Instance world, int tx, int ty, int tz, Instance tworld) +- { +- return (world != tworld) ? false : canSeeTarget(x, y, z, world, tx, ty, tz); +- } +- +- /** +- * 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 +- * @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, Instance instance, int tx, int ty, int tz) +- { +- if (DoorData.getInstance().checkIfDoorsBetween(x, y, z, tx, ty, tz, instance, true)) ++ ++ // get origin and target world coordinates ++ final int ox = origin.getX(); ++ final int oy = origin.getY(); ++ final int oz = origin.getZ(); ++ final int tx = target.getX(); ++ final int ty = target.getY(); ++ final int tz = target.getZ(); ++ ++ if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld(), true)) + { + return false; + } +- return canSeeTarget(x, y, z, tx, ty, tz); +- } +- +- /** +- * @param prevX +- * @param prevY +- * @param prevGeoZ +- * @param curX +- * @param curY +- * @param nswe +- * @return the LOS Z value +- */ +- 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))) ++ if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld())) + { +- throw new RuntimeException("Multiple directions!"); ++ return false; + } ++ if (WarpedSpaceManager.getInstance().checkForWarpedSpace(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld())) ++ { ++ return false; ++ } + +- if (checkNearestNsweAntiCornerCut(prevX, prevY, prevGeoZ, nswe)) ++ // get origin and check existing geo coordinates ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) + { +- return getNearestZ(curX, curY, prevGeoZ); ++ return true; + } + +- return getNextHigherZ(curX, curY, prevGeoZ); ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coordinates ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) ++ { ++ return true; ++ } ++ ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // origin and target coordinates are same ++ if ((gox == gtx) && (goy == gty)) ++ { ++ return goz == gtz; ++ } ++ ++ // get origin and target height, real height = collision height * 2 ++ double oheight = 0; ++ if (origin.isCreature()) ++ { ++ oheight = ((Creature) origin).getCollisionHeight() * 2; ++ } ++ ++ double theight = 0; ++ if (target.isCreature()) ++ { ++ theight = ((Creature) target).getCollisionHeight() * 2; ++ } ++ ++ // perform geodata check ++ return checkSee(gox, goy, goz, oheight, gtx, gty, gtz, theight, origin.getInstanceWorld()); + } + + /** +- * Can see target. Does not check doors between. +- * @param xValue the x coordinate +- * @param yValue the y coordinate +- * @param zValue the z coordinate +- * @param txValue the target's x coordinate +- * @param tyValue the target's y coordinate +- * @param tzValue the target's z coordinate +- * @return {@code true} if there is line of sight between the given coordinate sets, {@code false} otherwise ++ * Check line of sight from {@link WorldObject} to {@link Location}. ++ * @param origin : The origin object. ++ * @param position : The target position. ++ * @return {@code boolean} : True if object can see position + */ +- public boolean canSeeTarget(int xValue, int yValue, int zValue, int txValue, int tyValue, int tzValue) ++ public boolean canSeeTarget(WorldObject origin, Location position) + { +- int x = xValue; +- int y = yValue; +- int tx = txValue; +- int ty = tyValue; ++ // get origin and target world coordinates ++ final int ox = origin.getX(); ++ final int oy = origin.getY(); ++ final int oz = origin.getZ(); ++ final int tx = position.getX(); ++ final int ty = position.getY(); ++ final int tz = position.getZ(); + +- int geoX = getGeoX(x); +- int geoY = getGeoY(y); +- int tGeoX = getGeoX(tx); +- int tGeoY = getGeoY(ty); ++ if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld(), true)) ++ { ++ return false; ++ } ++ if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld())) ++ { ++ return false; ++ } ++ if (WarpedSpaceManager.getInstance().checkForWarpedSpace(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld())) ++ { ++ return false; ++ } + +- int z = getNearestZ(geoX, geoY, zValue); +- int tz = getNearestZ(tGeoX, tGeoY, tzValue); ++ // get origin and check existing geo coordinates ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) ++ { ++ return true; ++ } + +- // Fastpath. +- if ((geoX == tGeoX) && (geoY == tGeoY)) ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coordinates ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) + { +- if (hasGeoPos(tGeoX, tGeoY)) +- { +- return z == tz; +- } + return true; + } + +- if (tz > z) ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // origin and target coordinates are same ++ if ((gox == gtx) && (goy == gty)) + { +- int tmp = tx; +- tx = x; +- x = tmp; +- +- tmp = ty; +- ty = y; +- y = tmp; +- +- tmp = tz; +- tz = z; +- z = tmp; +- +- tmp = tGeoX; +- tGeoX = geoX; +- geoX = tmp; +- +- tmp = tGeoY; +- tGeoY = geoY; +- geoY = tmp; ++ return goz == gtz; + } + +- final LinePointIterator3D pointIter = new LinePointIterator3D(geoX, geoY, z, tGeoX, tGeoY, tz); +- // 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()) ++ // get origin and target height, real height = collision height * 2 ++ double oheight = 0; ++ if (origin.isCreature()) + { +- final int curX = pointIter.x(); +- final int curY = pointIter.y(); ++ oheight = ((Creature) origin).getTemplate().getCollisionHeight(); ++ } ++ ++ // perform geodata check ++ return checkSee(gox, goy, goz, oheight, gtx, gty, gtz, 0, origin.getInstanceWorld()); ++ } ++ ++ /** ++ * Simple check for origin to target visibility. ++ * @param goxValue : origin X geodata coordinate ++ * @param goyValue : origin Y geodata coordinate ++ * @param gozValue : origin Z geodata coordinate ++ * @param oheight : origin height (if instance of {@link Character}) ++ * @param gtxValue : target X geodata coordinate ++ * @param gtyValue : target Y geodata coordinate ++ * @param gtzValue : target Z geodata coordinate ++ * @param theight : target height (if instance of {@link Character}) ++ * @param instance ++ * @return {@code boolean} : True, when target can be seen. ++ */ ++ private final boolean checkSee(int goxValue, int goyValue, int gozValue, double oheight, int gtxValue, int gtyValue, int gtzValue, double theight, Instance instance) ++ { ++ int goz = gozValue; ++ int gtz = gtzValue; ++ int gox = goxValue; ++ int goy = goyValue; ++ int gtx = gtxValue; ++ int gty = gtyValue; ++ ++ // get line of sight Z coordinates ++ double losoz = goz + ((oheight * Config.PART_OF_CHARACTER_HEIGHT) / 100); ++ double lostz = gtz + ((theight * Config.PART_OF_CHARACTER_HEIGHT) / 100); ++ ++ // get X delta and signum ++ final int dx = Math.abs(gtx - gox); ++ final int sx = gox < gtx ? 1 : -1; ++ final byte dirox = sx > 0 ? GeoStructure.CELL_FLAG_E : GeoStructure.CELL_FLAG_W; ++ final byte dirtx = sx > 0 ? GeoStructure.CELL_FLAG_W : GeoStructure.CELL_FLAG_E; ++ ++ // get Y delta and signum ++ final int dy = Math.abs(gty - goy); ++ final int sy = goy < gty ? 1 : -1; ++ final byte diroy = sy > 0 ? GeoStructure.CELL_FLAG_S : GeoStructure.CELL_FLAG_N; ++ final byte dirty = sy > 0 ? GeoStructure.CELL_FLAG_N : GeoStructure.CELL_FLAG_S; ++ ++ // get Z delta ++ final int dm = Math.max(dx, dy); ++ final double dz = (lostz - losoz) / dm; ++ ++ // get direction flag for diagonal movement ++ final byte diroxy = getDirXY(dirox, diroy); ++ final byte dirtxy = getDirXY(dirtx, dirty); ++ ++ // delta, determines axis to move on (+..X axis, -..Y axis) ++ int d = dx - dy; ++ ++ // NSWE direction of movement ++ byte diro; ++ byte dirt; ++ ++ // initialize node values ++ int nox = gox; ++ int noy = goy; ++ int ntx = gtx; ++ int nty = gty; ++ byte nsweo = getNsweNearest(gox, goy, goz); ++ byte nswet = getNsweNearest(gtx, gty, gtz); ++ ++ // loop ++ ABlock block; ++ int index; ++ for (int i = 0; i < ((dm + 1) / 2); i++) ++ { ++ // reset direction flag ++ diro = 0; ++ dirt = 0; + +- if ((curX == prevX) && (curY == prevY)) ++ // calculate next point coordinates ++ final int e2 = 2 * d; ++ if ((e2 > -dy) && (e2 < dx)) + { +- continue; ++ // calculate next point XY coordinates ++ d -= dy; ++ d += dx; ++ nox += sx; ++ ntx -= sx; ++ noy += sy; ++ nty -= sy; ++ diro |= diroxy; ++ dirt |= dirtxy; + } ++ else if (e2 > -dy) ++ { ++ // calculate next point X coordinate ++ d -= dy; ++ nox += sx; ++ ntx -= sx; ++ diro |= dirox; ++ dirt |= dirtx; ++ } ++ else if (e2 < dx) ++ { ++ // calculate next point Y coordinate ++ d += dx; ++ noy += sy; ++ nty -= sy; ++ diro |= diroy; ++ dirt |= dirty; ++ } + +- final int beeCurZ = pointIter.z(); +- int curGeoZ = prevGeoZ; +- +- // Check if the position has geodata. +- if (hasGeoPos(curX, curY)) + { +- final int beeCurGeoZ = getNearestZ(curX, curY, beeCurZ); +- final int nswe = GeoUtils.computeNswe(prevX, prevY, curX, curY); // .computeDirection(prevX, prevY, curX, curY); +- curGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, curX, curY, nswe); +- int maxHeight; +- if (ptIndex < ELEVATED_SEE_OVER_DISTANCE) ++ // get block of the next cell ++ block = getBlock(nox, noy); ++ ++ // get index of particular layer, based on movement conditions ++ if ((nsweo & diro) == 0) + { +- maxHeight = z + MAX_SEE_OVER_HEIGHT; ++ index = block.getIndexAbove(nox, noy, goz - GeoStructure.CELL_IGNORE_HEIGHT); + } + else + { +- maxHeight = beeCurZ + MAX_SEE_OVER_HEIGHT; ++ index = block.getIndexBelow(nox, noy, goz + GeoStructure.CELL_IGNORE_HEIGHT); + } + +- boolean canSeeThrough = false; +- if ((curGeoZ <= maxHeight) && (curGeoZ <= beeCurGeoZ)) ++ // layer does not exist, return ++ if (index == -1) + { +- if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) +- { +- 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 +- { +- canSeeThrough = true; +- } ++ return false; + } + +- if (!canSeeThrough) ++ // get layer and next line of sight Z coordinate ++ goz = block.getHeight(index); ++ losoz += dz; ++ ++ // perform line of sight check, return when fails ++ if ((goz - losoz) > Config.MAX_OBSTACLE_HEIGHT) + { + return false; + } ++ ++ // get layer nswe ++ nsweo = block.getNswe(index); + } ++ { ++ // get block of the next cell ++ block = getBlock(ntx, nty); ++ ++ // get index of particular layer, based on movement conditions ++ if ((nswet & dirt) == 0) ++ { ++ index = block.getIndexAbove(ntx, nty, gtz - GeoStructure.CELL_IGNORE_HEIGHT); ++ } ++ else ++ { ++ index = block.getIndexBelow(ntx, nty, gtz + GeoStructure.CELL_IGNORE_HEIGHT); ++ } ++ ++ // layer does not exist, return ++ if (index == -1) ++ { ++ return false; ++ } ++ ++ // get layer and next line of sight Z coordinate ++ gtz = block.getHeight(index); ++ lostz -= dz; ++ ++ // perform line of sight check, return when fails ++ if ((gtz - lostz) > Config.MAX_OBSTACLE_HEIGHT) ++ { ++ return false; ++ } ++ ++ // get layer nswe ++ nswet = block.getNswe(index); ++ } + +- prevX = curX; +- prevY = curY; +- prevGeoZ = curGeoZ; +- ++ptIndex; ++ // update coords ++ gox = nox; ++ goy = noy; ++ gtx = ntx; ++ gty = nty; + } + +- return true; ++ // when iteration is completed, compare final Z coordinates ++ return Math.abs(goz - gtz) < (GeoStructure.CELL_HEIGHT * 4); + } + + /** +- * Move check. +- * @param x the x coordinate +- * @param y the y coordinate +- * @param zValue the z coordinate +- * @param tx the target's x coordinate +- * @param ty the target's y coordinate +- * @param tzValue the target's z coordinate +- * @param instance the instance +- * @return the last Location (x,y,z) where player can walk - just before wall ++ * Check movement from coordinates to 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 {code boolean} : True if target coordinates are reachable from origin coordinates + */ +- public Location canMoveToTargetLoc(int x, int y, int zValue, int tx, int ty, int tzValue, Instance instance) ++ public boolean canMoveToTarget(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + { +- final int geoX = getGeoX(x); +- final int geoY = getGeoY(y); +- final int z = getNearestZ(geoX, geoY, zValue); +- final int tGeoX = getGeoX(tx); +- final int tGeoY = getGeoY(ty); +- final int tz = getNearestZ(tGeoX, tGeoY, tzValue); ++ // get origin and check existing geo coordinates ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) ++ { ++ return true; ++ } + +- if (DoorData.getInstance().checkIfDoorsBetween(x, y, z, tx, ty, tz, instance, false)) ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coordinates ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) + { +- return new Location(x, y, getHeight(x, y, z)); ++ return true; + } +- if (FenceData.getInstance().checkIfFenceBetween(x, y, z, tx, ty, tz, instance)) ++ ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // target coordinates reached ++ if ((gox == gtx) && (goy == gty) && (goz == gtz)) + { +- return new Location(x, y, getHeight(x, y, z)); ++ return true; + } +- if (WarpedSpaceManager.getInstance().checkForWarpedSpace(x, y, z, tx, ty, tz, instance)) ++ ++ // perform geodata check ++ final GeoLocation loc = checkMove(gox, goy, goz, gtx, gty, gtz, instance); ++ return (loc.getGeoX() == gtx) && (loc.getGeoY() == gty); ++ } ++ ++ /** ++ * Check movement from origin to target. Returns last available point in the checked path. ++ * @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 {@link Location} : Last point where object can walk (just before wall) ++ */ ++ public Location canMoveToTargetLoc(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) ++ { ++ // Mobius: Double check for doors before normal checkMove to avoid exploiting key movement. ++ if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, instance, false)) + { +- return new Location(x, y, getHeight(x, y, z)); ++ return new Location(ox, oy, oz); + } ++ if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, instance)) ++ { ++ return new Location(ox, oy, oz); ++ } + +- 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 = z; ++ // get origin and check existing geo coordinates ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) ++ { ++ return new Location(tx, ty, tz); ++ } + +- while (pointIter.next()) ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coordinates ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) + { +- final int curX = pointIter.x(); +- final int curY = pointIter.y(); +- final int curZ = getNearestZ(curX, curY, prevZ); +- +- if (hasGeoPos(prevX, prevY)) +- { +- final int nswe = GeoUtils.computeNswe(prevX, prevY, curX, curY); +- if (!checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, nswe)) +- { +- // Can't move, return previous location. +- return new Location(getWorldX(prevX), getWorldY(prevY), prevZ); +- } +- } +- +- prevX = curX; +- prevY = curY; +- prevZ = curZ; ++ return new Location(tx, ty, tz); + } + +- if (hasGeoPos(prevX, prevY) && (prevZ != tz)) ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // target coordinates reached ++ if ((gox == gtx) && (goy == gty) && (goz == gtz)) + { +- // Different floors, return start location. +- return new Location(x, y, z); ++ return new Location(tx, ty, tz); + } + +- return new Location(tx, ty, tz); ++ // perform geodata check ++ return checkMove(gox, goy, goz, gtx, gty, gtz, instance); + } + + /** +- * 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 fromZvalue 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 toZvalue 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 ++ * With this method you can check if a position is visible or can be reached by beeline movement.
++ * 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 gox : origin X geodata coordinate ++ * @param goy : origin Y geodata coordinate ++ * @param goz : origin Z geodata coordinate ++ * @param gtx : target X geodata coordinate ++ * @param gty : target Y geodata coordinate ++ * @param gtz : target Z geodata coordinate ++ * @param instance ++ * @return {@link GeoLocation} : The last allowed point of movement. + */ +- public boolean canMoveToTarget(int fromX, int fromY, int fromZvalue, int toX, int toY, int toZvalue, Instance instance) ++ protected final GeoLocation checkMove(int gox, int goy, int goz, int gtx, int gty, int gtz, Instance instance) + { +- final int geoX = getGeoX(fromX); +- final int geoY = getGeoY(fromY); +- final int fromZ = getNearestZ(geoX, geoY, fromZvalue); +- final int tGeoX = getGeoX(toX); +- final int tGeoY = getGeoY(toY); +- final int toZ = getNearestZ(tGeoX, tGeoY, toZvalue); +- +- if (DoorData.getInstance().checkIfDoorsBetween(fromX, fromY, fromZ, toX, toY, toZ, instance, false)) ++ if (DoorData.getInstance().checkIfDoorsBetween(gox, goy, goz, gtx, gty, gtz, instance, false)) + { +- return false; ++ return new GeoLocation(gox, goy, goz); + } +- if (FenceData.getInstance().checkIfFenceBetween(fromX, fromY, fromZ, toX, toY, toZ, instance)) ++ if (FenceData.getInstance().checkIfFenceBetween(gox, goy, goz, gtx, gty, gtz, instance)) + { +- return false; ++ return new GeoLocation(gox, goy, goz); + } +- if (WarpedSpaceManager.getInstance().checkForWarpedSpace(fromX, fromY, fromZ, toX, toY, toZ, instance)) ++ if (WarpedSpaceManager.getInstance().checkForWarpedSpace(gox, goy, goz, gtx, gty, gtz, instance)) + { +- return false; ++ return new GeoLocation(gox, goy, goz); + } + +- 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 = fromZ; ++ // get X delta, signum and direction flag ++ final int dx = Math.abs(gtx - gox); ++ final int sx = gox < gtx ? 1 : -1; ++ final byte dirX = sx > 0 ? GeoStructure.CELL_FLAG_E : GeoStructure.CELL_FLAG_W; + +- while (pointIter.next()) ++ // get Y delta, signum and direction flag ++ final int dy = Math.abs(gty - goy); ++ final int sy = goy < gty ? 1 : -1; ++ final byte dirY = sy > 0 ? GeoStructure.CELL_FLAG_S : GeoStructure.CELL_FLAG_N; ++ ++ // get direction flag for diagonal movement ++ final byte dirXY = getDirXY(dirX, dirY); ++ ++ // delta, determines axis to move on (+..X axis, -..Y axis) ++ int d = dx - dy; ++ ++ // NSWE direction of movement ++ byte direction; ++ ++ // load pointer coordinates ++ int gpx = gox; ++ int gpy = goy; ++ int gpz = goz; ++ ++ // load next pointer ++ int nx = gpx; ++ int ny = gpy; ++ ++ // loop ++ int count = 0; ++ while (count++ < Config.MAX_ITERATIONS) + { +- final int curX = pointIter.x(); +- final int curY = pointIter.y(); +- final int curZ = getNearestZ(curX, curY, prevZ); ++ direction = 0; + +- if (hasGeoPos(prevX, prevY)) ++ // calculate next point coordinates ++ final int e2 = 2 * d; ++ if ((e2 > -dy) && (e2 < dx)) + { +- final int nswe = GeoUtils.computeNswe(prevX, prevY, curX, curY); +- if (!checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, nswe)) ++ d -= dy; ++ d += dx; ++ nx += sx; ++ ny += sy; ++ direction |= dirXY; ++ } ++ else if (e2 > -dy) ++ { ++ d -= dy; ++ nx += sx; ++ direction |= dirX; ++ } ++ else if (e2 < dx) ++ { ++ d += dx; ++ ny += sy; ++ direction |= dirY; ++ } ++ ++ // obstacle found, return ++ if ((getNsweNearest(gpx, gpy, gpz) & direction) == 0) ++ { ++ return new GeoLocation(gpx, gpy, gpz); ++ } ++ ++ // update pointer coordinates ++ gpx = nx; ++ gpy = ny; ++ gpz = getHeightNearest(nx, ny, gpz); ++ ++ // target coordinates reached ++ if ((gpx == gtx) && (gpy == gty)) ++ { ++ if (gpz == gtz) + { +- return false; ++ // path found, Z coordinates are okay, return target point ++ return new GeoLocation(gtx, gty, gtz); + } ++ ++ // path found, Z coordinates are not okay, return last good point ++ return new GeoLocation(gpx, gpy, gpz); + } ++ } ++ ++ return new GeoLocation(gox, goy, goz); ++ } ++ ++ /** ++ * Returns diagonal NSWE flag format of combined two NSWE flags. ++ * @param dirX : X direction NSWE flag ++ * @param dirY : Y direction NSWE flag ++ * @return byte : NSWE flag of combined direction ++ */ ++ private static byte getDirXY(byte dirX, byte dirY) ++ { ++ // check axis directions ++ if (dirY == GeoStructure.CELL_FLAG_N) ++ { ++ if (dirX == GeoStructure.CELL_FLAG_W) ++ { ++ return GeoStructure.CELL_FLAG_NW; ++ } + +- prevX = curX; +- prevY = curY; +- prevZ = curZ; ++ return GeoStructure.CELL_FLAG_NE; + } + +- if (hasGeoPos(prevX, prevY) && (prevZ != toZ)) ++ if (dirX == GeoStructure.CELL_FLAG_W) + { +- // Different floors. +- return false; ++ return GeoStructure.CELL_FLAG_SW; + } + +- return true; ++ return GeoStructure.CELL_FLAG_SE; + } + ++ /** ++ * 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 ++ */ ++ public List findPath(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) ++ { ++ return null; ++ } ++ ++ /** ++ * Returns the instance of the {@link GeoEngine}. ++ * @return {@link GeoEngine} : The instance. ++ */ + public static GeoEngine getInstance() + { + return SingletonHolder.INSTANCE; +@@ -752,6 +947,6 @@ + + private static class SingletonHolder + { +- protected static final GeoEngine INSTANCE = new GeoEngine(); ++ protected static final GeoEngine INSTANCE = Config.PATHFINDING ? new GeoEnginePathfinding() : new GeoEngine(); + } +-} ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/GeoEnginePathfinding.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/GeoEnginePathfinding.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/GeoEnginePathfinding.java (working copy) +@@ -20,88 +20,84 @@ + import java.util.LinkedList; + import java.util.List; + import java.util.ListIterator; +-import java.util.logging.Level; +-import java.util.logging.Logger; + + import org.l2jmobius.Config; +-import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; +-import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +-import org.l2jmobius.gameserver.geoengine.pathfinding.CellNode; +-import org.l2jmobius.gameserver.geoengine.pathfinding.CellNodeBuffer; +-import org.l2jmobius.gameserver.geoengine.pathfinding.NodeLoc; +-import org.l2jmobius.gameserver.model.World; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoLocation; ++import org.l2jmobius.gameserver.geoengine.pathfinding.Node; ++import org.l2jmobius.gameserver.geoengine.pathfinding.NodeBuffer; ++import org.l2jmobius.gameserver.model.Location; + import org.l2jmobius.gameserver.model.instancezone.Instance; + + /** +- * @author -Nemesiss- ++ * @author Hasha + */ +-public class GeoEnginePathfinding ++final class GeoEnginePathfinding extends GeoEngine + { +- private static final Logger LOGGER = Logger.getLogger(GeoEnginePathfinding.class.getName()); ++ // pre-allocated buffers ++ private final BufferHolder[] _buffers; + +- private BufferInfo[] _buffers; +- + protected GeoEnginePathfinding() + { +- try ++ super(); ++ ++ final String[] array = Config.PATHFIND_BUFFERS.split(";"); ++ _buffers = new BufferHolder[array.length]; ++ int count = 0; ++ for (int i = 0; i < array.length; i++) + { +- final String[] array = Config.PATHFIND_BUFFERS.split(";"); ++ final String buf = array[i]; ++ final String[] args = buf.split("x"); + +- _buffers = new BufferInfo[array.length]; +- +- String buf; +- String[] args; +- for (int i = 0; i < array.length; i++) ++ try + { +- buf = array[i]; +- args = buf.split("x"); +- if (args.length != 2) +- { +- throw new Exception("Invalid buffer definition: " + buf); +- } +- +- _buffers[i] = new BufferInfo(Integer.parseInt(args[0]), Integer.parseInt(args[1])); ++ final int size = Integer.parseInt(args[1]); ++ count += size; ++ _buffers[i] = new BufferHolder(Integer.parseInt(args[0]), size); + } ++ catch (Exception e) ++ { ++ LOGGER.warning("GeoEnginePathfinding: Can not load buffer setting: " + buf); ++ } + } +- catch (Exception e) +- { +- LOGGER.log(Level.WARNING, "CellPathFinding: Problem during buffer init: " + e.getMessage(), e); +- throw new Error("CellPathFinding: load aborted"); +- } ++ ++ LOGGER.info("GeoEnginePathfinding: Loaded " + count + " node buffers."); + } + +- public boolean pathNodesExist(short regionoffset) ++ @Override ++ public List findPath(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + { +- return false; +- } +- +- public List findPath(int x, int y, int z, int tx, int ty, int tz, Instance instance) +- { +- final int gx = GeoEngine.getInstance().getGeoX(x); +- final int gy = GeoEngine.getInstance().getGeoY(y); +- if (!GeoEngine.getInstance().hasGeo(x, y)) ++ // get origin and check existing geo coords ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) + { + 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)) ++ ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coords ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) + { + 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)))); ++ ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // Prepare buffer for pathfinding calculations ++ final NodeBuffer buffer = getBuffer(64 + (2 * Math.max(Math.abs(gox - gtx), Math.abs(goy - gty)))); + if (buffer == null) + { + return null; + } + +- List path = null; ++ // find path ++ List path = null; + try + { +- final CellNode result = buffer.findPath(gx, gy, gz, gtx, gty, gtz); +- ++ final Node result = buffer.findPath(gox, goy, goz, gtx, gty, gtz); + if (result == null) + { + return null; +@@ -125,33 +121,45 @@ + return path; + } + +- int currentX, currentY, currentZ; +- ListIterator middlePoint; ++ // get path list iterator ++ final ListIterator point = path.listIterator(); + +- middlePoint = path.listIterator(); +- currentX = x; +- currentY = y; +- currentZ = z; ++ // get node A (origin) ++ int nodeAx = gox; ++ int nodeAy = goy; ++ short nodeAz = goz; + +- while (middlePoint.hasNext()) ++ // get node B ++ GeoLocation nodeB = (GeoLocation) point.next(); ++ ++ // iterate thought the path to optimize it ++ int count = 0; ++ while (point.hasNext() && (count++ < Config.MAX_ITERATIONS)) + { +- final AbstractNodeLoc locMiddle = middlePoint.next(); +- if (!middlePoint.hasNext()) +- { +- break; +- } ++ // get node C ++ final GeoLocation nodeC = (GeoLocation) path.get(point.nextIndex()); + +- final AbstractNodeLoc locEnd = path.get(middlePoint.nextIndex()); +- if (GeoEngine.getInstance().canMoveToTarget(currentX, currentY, currentZ, locEnd.getX(), locEnd.getY(), locEnd.getZ(), instance)) ++ // check movement from node A to node C ++ final GeoLocation loc = checkMove(nodeAx, nodeAy, nodeAz, nodeC.getGeoX(), nodeC.getGeoY(), nodeC.getZ(), instance); ++ if ((loc.getGeoX() == nodeC.getGeoX()) && (loc.getGeoY() == nodeC.getGeoY())) + { +- middlePoint.remove(); ++ // can move from node A to node C ++ ++ // remove node B ++ point.remove(); + } + else + { +- currentX = locMiddle.getX(); +- currentY = locMiddle.getY(); +- currentZ = locMiddle.getZ(); ++ // can not move from node A to node C ++ ++ // set node A (node B is part of path, update A coordinates) ++ nodeAx = nodeB.getGeoX(); ++ nodeAy = nodeB.getGeoY(); ++ nodeAz = (short) nodeB.getZ(); + } ++ ++ // set node B ++ nodeB = (GeoLocation) point.next(); + } + + return path; +@@ -158,144 +166,102 @@ + } + + /** +- * Convert geodata position to pathnode position +- * @param geo_pos +- * @return pathnode position ++ * Create list of node locations as result of calculated buffer node tree. ++ * @param node : the entry point ++ * @return List : list of node location + */ +- public short getNodePos(int geo_pos) ++ private static List constructPath(Node node) + { +- return (short) (geo_pos >> 3); // OK? +- } +- +- /** +- * Convert node position to pathnode block position +- * @param node_pos +- * @return pathnode block position (0...255) +- */ +- public short getNodeBlock(int node_pos) +- { +- return (short) (node_pos % 256); +- } +- +- public byte getRegionX(int node_pos) +- { +- return (byte) ((node_pos >> 8) + World.TILE_X_MIN); +- } +- +- public byte getRegionY(int node_pos) +- { +- return (byte) ((node_pos >> 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 node_x rx +- * @return +- */ +- public int calculateWorldX(short node_x) +- { +- return World.MAP_MIN_X + (node_x * 128) + 48; +- } +- +- /** +- * Convert pathnode y to World y position +- * @param node_y +- * @return +- */ +- public int calculateWorldY(short node_y) +- { +- return World.MAP_MIN_Y + (node_y * 128) + 48; +- } +- +- private List constructPath(AbstractNode nodeValue) +- { +- final LinkedList path = new LinkedList<>(); +- int previousDirectionX = Integer.MIN_VALUE; +- int previousDirectionY = Integer.MIN_VALUE; +- int directionX, directionY; ++ // create empty list ++ final LinkedList list = new LinkedList<>(); + +- AbstractNode node = nodeValue; +- while (node.getParent() != null) ++ // set direction X/Y ++ int dx = 0; ++ int dy = 0; ++ ++ // get target parent ++ Node target = node; ++ Node parent = target.getParent(); ++ ++ // while parent exists ++ int count = 0; ++ while ((parent != null) && (count++ < Config.MAX_ITERATIONS)) + { +- directionX = node.getLoc().getNodeX() - node.getParent().getLoc().getNodeX(); +- directionY = node.getLoc().getNodeY() - node.getParent().getLoc().getNodeY(); ++ // get parent <> target direction X/Y ++ final int nx = parent.getLoc().getGeoX() - target.getLoc().getGeoX(); ++ final int ny = parent.getLoc().getGeoY() - target.getLoc().getGeoY(); + +- // only add a new route point if moving direction changes +- if ((directionX != previousDirectionX) || (directionY != previousDirectionY)) ++ // direction has changed? ++ if ((dx != nx) || (dy != ny)) + { +- previousDirectionX = directionX; +- previousDirectionY = directionY; ++ // add node to the beginning of the list ++ list.addFirst(target.getLoc()); + +- path.addFirst(node.getLoc()); +- node.setLoc(null); ++ // update direction X/Y ++ dx = nx; ++ dy = ny; + } + +- node = node.getParent(); ++ // move to next node, set target and get its parent ++ target = parent; ++ parent = target.getParent(); + } + +- return path; ++ // return list ++ return list; + } + +- private CellNodeBuffer alloc(int size) ++ /** ++ * Provides optimize selection of the buffer. When all pre-initialized buffer are locked, creates new buffer. ++ * @param size : pre-calculated minimal required size ++ * @return NodeBuffer : buffer ++ */ ++ private final NodeBuffer getBuffer(int size) + { +- CellNodeBuffer current = null; +- for (BufferInfo i : _buffers) ++ NodeBuffer current = null; ++ for (BufferHolder holder : _buffers) + { +- if (i.mapSize >= size) ++ // Find proper size of buffer ++ if (holder._size < size) + { +- for (CellNodeBuffer buf : i.buffer) ++ continue; ++ } ++ ++ // Find unlocked NodeBuffer ++ for (NodeBuffer buffer : holder._buffer) ++ { ++ if (!buffer.isLocked()) + { +- if (buf.lock()) +- { +- current = buf; +- break; +- } ++ continue; + } +- if (current != null) +- { +- break; +- } + +- // not found, allocate temporary buffer +- current = new CellNodeBuffer(i.mapSize); +- current.lock(); +- if (i.buffer.size() < i.count) +- { +- i.buffer.add(current); +- break; +- } ++ return buffer; + } ++ ++ // NodeBuffer not found, allocate temporary buffer ++ current = new NodeBuffer(holder._size); ++ current.isLocked(); + } + + return current; + } + +- private static final class BufferInfo ++ /** ++ * NodeBuffer container with specified size and count of separate buffers. ++ */ ++ private static final class BufferHolder + { +- final int mapSize; +- final int count; +- ArrayList buffer; ++ final int _size; ++ List _buffer; + +- public BufferInfo(int size, int cnt) ++ public BufferHolder(int size, int count) + { +- mapSize = size; +- count = cnt; +- buffer = new ArrayList<>(count); ++ _size = size; ++ _buffer = new ArrayList<>(count); ++ for (int i = 0; i < count; i++) ++ { ++ _buffer.add(new NodeBuffer(size)); ++ } + } + } +- +- public static GeoEnginePathfinding getInstance() +- { +- return SingletonHolder.INSTANCE; +- } +- +- private static class SingletonHolder +- { +- protected static final GeoEnginePathfinding INSTANCE = new GeoEnginePathfinding(); +- } +-} ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java (working copy) +@@ -0,0 +1,197 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++import java.io.IOException; ++ ++/** ++ * @author Hasha ++ */ ++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 height of cell, which is closest to given coordinates.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @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 getHeightNearestOriginal(int geoX, int geoY, int worldZ); ++ ++ /** ++ * Returns the height of cell, which is first above 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, above given coordinates. ++ */ ++ public abstract short getHeightAbove(int geoX, int geoY, int worldZ); ++ ++ /** ++ * Returns the height of cell, which is first below 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, below given coordinates. ++ */ ++ public abstract short getHeightBelow(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 the NSWE flag byte of cell, which is closest to given coordinates.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @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 getNsweNearestOriginal(int geoX, int geoY, int worldZ); ++ ++ /** ++ * Returns the NSWE flag byte of cell, which is first above 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 getNsweAbove(int geoX, int geoY, int worldZ); ++ ++ /** ++ * Returns the NSWE flag byte of cell, which is first below 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 getNsweBelow(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 above given coordinates.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @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 getIndexAboveOriginal(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 index to data of the cell, which is first layer below given coordinates.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @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 getIndexBelowOriginal(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 height of cell given by cell index.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @param index : Index of the cell. ++ * @return short : Cell geodata Z coordinate, below given coordinates. ++ */ ++ public abstract short getHeightOriginal(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); ++ ++ /** ++ * Returns the NSWE flag byte of cell given by cell index.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @param index : Index of the cell. ++ * @return short : Cell geodata Z coordinate, below given coordinates. ++ */ ++ public abstract byte getNsweOriginal(int index); ++ ++ /** ++ * Sets the NSWE flag byte of cell given by cell index. ++ * @param index : Index of the cell. ++ * @param nswe : New NSWE flag byte. ++ */ ++ public abstract void setNswe(int index, byte nswe); ++ ++ /** ++ * Saves the block in L2D format to {@link BufferedOutputStream}. Used only for L2D geodata conversion. ++ * @param stream : The stream. ++ * @throws IOException : Can't save the block to steam. ++ */ ++ public abstract void saveBlock(BufferedOutputStream stream) throws IOException; ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java (working copy) +@@ -0,0 +1,252 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++import java.io.IOException; ++import java.nio.ByteBuffer; ++ ++/** ++ * @author Hasha ++ */ ++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. ++ * @param format : GeoFormat specifying format of loaded data. ++ */ ++ public BlockComplex(ByteBuffer bb, GeoFormat format) ++ { ++ // initialize buffer ++ _buffer = new byte[GeoStructure.BLOCK_CELLS * 3]; ++ ++ // load data ++ for (int i = 0; i < GeoStructure.BLOCK_CELLS; i++) ++ { ++ if (format != GeoFormat.L2D) ++ { ++ // 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); ++ } ++ else ++ { ++ // get nswe ++ final byte nswe = bb.get(); ++ _buffer[i * 3] = nswe; ++ ++ // get height ++ final short height = bb.getShort(); ++ _buffer[(i * 3) + 1] = (byte) (height & 0x00FF); ++ _buffer[(i * 3) + 2] = (byte) (height >> 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 short getHeightNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getHeightNearest(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeightAbove(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 short height = (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ ++ // check and return height ++ return height > worldZ ? height : Short.MIN_VALUE; ++ } ++ ++ @Override ++ public short getHeightBelow(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 short height = (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ ++ // check and return height ++ return height < worldZ ? height : Short.MAX_VALUE; ++ } ++ ++ @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 byte getNsweNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getNsweNearest(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public byte getNsweAbove(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 ? _buffer[index] : 0; ++ } ++ ++ @Override ++ public byte getNsweBelow(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 ? _buffer[index] : 0; ++ } ++ ++ @Override ++ public 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 getIndexAboveOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexAbove(geoX, geoY, worldZ); ++ } ++ ++ @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 int getIndexBelowOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexBelow(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeight(int index) ++ { ++ return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ } ++ ++ @Override ++ public short getHeightOriginal(int index) ++ { ++ return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ } ++ ++ @Override ++ public byte getNswe(int index) ++ { ++ return _buffer[index]; ++ } ++ ++ @Override ++ public byte getNsweOriginal(int index) ++ { ++ return _buffer[index]; ++ } ++ ++ @Override ++ public void setNswe(int index, byte nswe) ++ { ++ _buffer[index] = nswe; ++ } ++ ++ @Override ++ public void saveBlock(BufferedOutputStream stream) throws IOException ++ { ++ // write block type ++ stream.write(GeoStructure.TYPE_COMPLEX_L2D); ++ ++ // write block data ++ stream.write(_buffer, 0, GeoStructure.BLOCK_CELLS * 3); ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java (working copy) +@@ -0,0 +1,176 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++import java.io.IOException; ++import java.nio.ByteBuffer; ++ ++/** ++ * @author Hasha ++ */ ++public class BlockFlat extends ABlock ++{ ++ protected final short _height; ++ protected byte _nswe; ++ ++ /** ++ * Creates FlatBlock. ++ * @param bb : Input byte buffer. ++ * @param format : GeoFormat specifying format of loaded data. ++ */ ++ public BlockFlat(ByteBuffer bb, GeoFormat format) ++ { ++ _height = bb.getShort(); ++ _nswe = format != GeoFormat.L2D ? 0x0F : (byte) (0xFF); ++ if (format == GeoFormat.L2OFF) ++ { ++ bb.getShort(); ++ } ++ } ++ ++ @Override ++ public boolean hasGeoPos() ++ { ++ return true; ++ } ++ ++ @Override ++ public short getHeightNearest(int geoX, int geoY, int worldZ) ++ { ++ return _height; ++ } ++ ++ @Override ++ public short getHeightNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return _height; ++ } ++ ++ @Override ++ public short getHeightAbove(int geoX, int geoY, int worldZ) ++ { ++ // check and return height ++ return _height > worldZ ? _height : Short.MIN_VALUE; ++ } ++ ++ @Override ++ public short getHeightBelow(int geoX, int geoY, int worldZ) ++ { ++ // check and return height ++ return _height < worldZ ? _height : Short.MAX_VALUE; ++ } ++ ++ @Override ++ public byte getNsweNearest(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweAbove(int geoX, int geoY, int worldZ) ++ { ++ // check height and return nswe ++ return _height > worldZ ? _nswe : 0; ++ } ++ ++ @Override ++ public byte getNsweBelow(int geoX, int geoY, int worldZ) ++ { ++ // check height and return nswe ++ return _height < worldZ ? _nswe : 0; ++ } ++ ++ @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 getIndexAboveOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexAbove(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public int getIndexBelow(int geoX, int geoY, int worldZ) ++ { ++ // check height and return index ++ return _height < worldZ ? 0 : -1; ++ } ++ ++ @Override ++ public int getIndexBelowOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexBelow(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeight(int index) ++ { ++ return _height; ++ } ++ ++ @Override ++ public short getHeightOriginal(int index) ++ { ++ return _height; ++ } ++ ++ @Override ++ public byte getNswe(int index) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweOriginal(int index) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public void setNswe(int index, byte nswe) ++ { ++ _nswe = nswe; ++ } ++ ++ @Override ++ public void saveBlock(BufferedOutputStream stream) throws IOException ++ { ++ // write block type ++ stream.write(GeoStructure.TYPE_FLAT_L2D); ++ ++ // write height ++ stream.write((byte) (_height & 0x00FF)); ++ stream.write((byte) (_height >> 8)); ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java (working copy) +@@ -0,0 +1,465 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++import java.io.IOException; ++import java.nio.ByteBuffer; ++import java.nio.ByteOrder; ++import java.util.Arrays; ++ ++/** ++ * @author Hasha ++ */ ++public class BlockMultilayer extends ABlock ++{ ++ private static final int MAX_LAYERS = Byte.MAX_VALUE; ++ ++ private static ByteBuffer _temp; ++ ++ /** ++ * Initializes the temporarily buffer. ++ */ ++ public static void initialize() ++ { ++ // initialize temporarily buffer and sorting mechanism ++ _temp = ByteBuffer.allocate(GeoStructure.BLOCK_CELLS * MAX_LAYERS * 3); ++ _temp.order(ByteOrder.LITTLE_ENDIAN); ++ } ++ ++ /** ++ * Releases temporarily buffer. ++ */ ++ public static void release() ++ { ++ _temp = null; ++ } ++ ++ protected byte[] _buffer; ++ ++ /** ++ * Implicit constructor for children class. ++ */ ++ protected BlockMultilayer() ++ { ++ _buffer = null; ++ } ++ ++ /** ++ * Creates MultilayerBlock. ++ * @param bb : Input byte buffer. ++ * @param format : GeoFormat specifying format of loaded data. ++ */ ++ public BlockMultilayer(ByteBuffer bb, GeoFormat format) ++ { ++ // 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 = format != GeoFormat.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++) ++ { ++ if (format != GeoFormat.L2D) ++ { ++ // get data ++ final short data = bb.getShort(); ++ ++ // add nswe and height ++ _temp.put((byte) (data & 0x000F)); ++ _temp.putShort((short) ((short) (data & 0xFFF0) >> 1)); ++ } ++ else ++ { ++ // add nswe ++ _temp.put(bb.get()); ++ ++ // add height ++ _temp.putShort(bb.getShort()); ++ } ++ } ++ } ++ ++ // 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 short getHeightNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getHeightNearest(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeightAbove(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 height ++ if (height > worldZ) ++ { ++ return (short) height; ++ } ++ ++ // move index to next layer ++ index -= 3; ++ } ++ ++ // none layer found, return minimum value ++ return Short.MIN_VALUE; ++ } ++ ++ @Override ++ public short getHeightBelow(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 height ++ if (height < worldZ) ++ { ++ return (short) height; ++ } ++ ++ // move index to next layer ++ index += 3; ++ } ++ ++ // none layer found, return maximum value ++ return Short.MAX_VALUE; ++ } ++ ++ @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 byte getNsweNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getNsweNearest(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public byte getNsweAbove(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 nswe ++ if (height > worldZ) ++ { ++ return _buffer[index]; ++ } ++ ++ // move index to next layer ++ index -= 3; ++ } ++ ++ // none layer found, block movement ++ return 0; ++ } ++ ++ @Override ++ public byte getNsweBelow(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 nswe ++ if (height < worldZ) ++ { ++ return _buffer[index]; ++ } ++ ++ // move index to next layer ++ index += 3; ++ } ++ ++ // none layer found, block movement ++ return 0; ++ } ++ ++ @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 first layer data (first from bottom) ++ byte layers = _buffer[index++]; ++ ++ // loop though all cell layers, find closest layer ++ 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; ++ } ++ ++ // none layer found ++ return -1; ++ } ++ ++ @Override ++ public int getIndexAboveOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexAbove(geoX, geoY, worldZ); ++ } ++ ++ @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; ++ } ++ ++ // none layer found ++ return -1; ++ } ++ ++ @Override ++ public int getIndexBelowOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexBelow(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeight(int index) ++ { ++ // get height ++ return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ } ++ ++ @Override ++ public short getHeightOriginal(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]; ++ } ++ ++ @Override ++ public byte getNsweOriginal(int index) ++ { ++ // get nswe ++ return _buffer[index]; ++ } ++ ++ @Override ++ public void setNswe(int index, byte nswe) ++ { ++ // set nswe ++ _buffer[index] = nswe; ++ } ++ ++ @Override ++ public void saveBlock(BufferedOutputStream stream) throws IOException ++ { ++ // write block type ++ stream.write(GeoStructure.TYPE_MULTILAYER_L2D); ++ ++ // for each cell ++ int index = 0; ++ for (int i = 0; i < GeoStructure.BLOCK_CELLS; i++) ++ { ++ // write layers count ++ final byte layers = _buffer[index++]; ++ stream.write(layers); ++ ++ // write cell data ++ stream.write(_buffer, index, layers * 3); ++ ++ // move index to next cell ++ index += layers * 3; ++ } ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java (working copy) +@@ -0,0 +1,150 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++ ++/** ++ * @author Hasha ++ */ ++public class BlockNull extends ABlock ++{ ++ private final byte _nswe; ++ ++ public BlockNull() ++ { ++ _nswe = (byte) 0xFF; ++ } ++ ++ @Override ++ public boolean hasGeoPos() ++ { ++ return false; ++ } ++ ++ @Override ++ public short getHeightNearest(int geoX, int geoY, int worldZ) ++ { ++ return (short) worldZ; ++ } ++ ++ @Override ++ public short getHeightNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return (short) worldZ; ++ } ++ ++ @Override ++ public short getHeightAbove(int geoX, int geoY, int worldZ) ++ { ++ return (short) worldZ; ++ } ++ ++ @Override ++ public short getHeightBelow(int geoX, int geoY, int worldZ) ++ { ++ return (short) worldZ; ++ } ++ ++ @Override ++ public byte getNsweNearest(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweAbove(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweBelow(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) ++ { ++ return 0; ++ } ++ ++ @Override ++ public int getIndexAboveOriginal(int geoX, int geoY, int worldZ) ++ { ++ return 0; ++ } ++ ++ @Override ++ public int getIndexBelow(int geoX, int geoY, int worldZ) ++ { ++ return 0; ++ } ++ ++ @Override ++ public int getIndexBelowOriginal(int geoX, int geoY, int worldZ) ++ { ++ return 0; ++ } ++ ++ @Override ++ public short getHeight(int index) ++ { ++ return 0; ++ } ++ ++ @Override ++ public short getHeightOriginal(int index) ++ { ++ return 0; ++ } ++ ++ @Override ++ public byte getNswe(int index) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweOriginal(int index) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public void setNswe(int index, byte nswe) ++ { ++ } ++ ++ @Override ++ public void saveBlock(BufferedOutputStream stream) ++ { ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java (nonexistent) +@@ -1,48 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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() +- { +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/ComplexBlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/ComplexBlock.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/ComplexBlock.java (nonexistent) +@@ -1,77 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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; +- +-/** +- * @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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/FlatBlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/FlatBlock.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/FlatBlock.java (nonexistent) +@@ -1,56 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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; +- +-/** +- * @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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/GeoFormat.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/GeoFormat.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/GeoFormat.java (working copy) +@@ -0,0 +1,39 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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 Hasha ++ */ ++public enum GeoFormat ++{ ++ L2J("%d_%d.l2j"), ++ L2OFF("%d_%d_conv.dat"), ++ L2D("%d_%d.l2d"); ++ ++ private final String _filename; ++ ++ private GeoFormat(String filename) ++ { ++ _filename = filename; ++ } ++ ++ public String getFilename() ++ { ++ return _filename; ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/GeoLocation.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/GeoLocation.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/GeoLocation.java (working copy) +@@ -0,0 +1,67 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.geoengine.GeoEngine; ++import org.l2jmobius.gameserver.model.Location; ++ ++/** ++ * @author Hasha ++ */ ++public class GeoLocation extends Location ++{ ++ private byte _nswe; ++ ++ public GeoLocation(int x, int y, int z) ++ { ++ super(x, y, GeoEngine.getInstance().getHeightNearest(x, y, z)); ++ _nswe = GeoEngine.getInstance().getNsweNearest(x, y, z); ++ } ++ ++ public void set(int x, int y, short z) ++ { ++ super.setXYZ(x, y, GeoEngine.getInstance().getHeightNearest(x, y, z)); ++ _nswe = GeoEngine.getInstance().getNsweNearest(x, y, z); ++ } ++ ++ public int getGeoX() ++ { ++ return _x; ++ } ++ ++ public int getGeoY() ++ { ++ return _y; ++ } ++ ++ @Override ++ public int getX() ++ { ++ return GeoEngine.getWorldX(_x); ++ } ++ ++ @Override ++ public int getY() ++ { ++ return GeoEngine.getWorldY(_y); ++ } ++ ++ public byte getNSWE() ++ { ++ return _nswe; ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java (working copy) +@@ -0,0 +1,70 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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; ++ ++/** ++ * @author Hasha ++ */ ++public class GeoStructure ++{ ++ // cells ++ public static final byte CELL_FLAG_E = 1 << 0; ++ public static final byte CELL_FLAG_W = 1 << 1; ++ public static final byte CELL_FLAG_S = 1 << 2; ++ public static final byte CELL_FLAG_N = 1 << 3; ++ public static final byte CELL_FLAG_SE = 1 << 4; ++ public static final byte CELL_FLAG_SW = 1 << 5; ++ public static final byte CELL_FLAG_NE = 1 << 6; ++ public static final byte CELL_FLAG_NW = (byte) (1 << 7); ++ ++ public static final int CELL_HEIGHT = 8; ++ public static final int CELL_IGNORE_HEIGHT = CELL_HEIGHT * 6; ++ ++ // blocks ++ public static final byte TYPE_FLAT_L2J_L2OFF = 0; ++ public static final byte TYPE_FLAT_L2D = (byte) 0xD0; ++ public static final byte TYPE_COMPLEX_L2J = 1; ++ public static final byte TYPE_COMPLEX_L2OFF = 0x40; ++ public static final byte TYPE_COMPLEX_L2D = (byte) 0xD1; ++ 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) ++ public static final byte TYPE_MULTILAYER_L2D = (byte) 0xD2; ++ ++ 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; ++ ++ // regions ++ 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; ++ ++ // global geodata ++ private static final int GEO_REGIONS_X = ((World.TILE_X_MAX - World.TILE_X_MIN) + 1); ++ private 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 +Index: java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java (nonexistent) +@@ -1,42 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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); +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/IGeoObject.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/IGeoObject.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/IGeoObject.java (working copy) +@@ -0,0 +1,53 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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 Hasha ++ */ ++public interface IGeoObject ++{ ++ /** ++ * Returns geodata X coordinate of the {@link IGeoObject}. ++ * @return int : Geodata X coordinate. ++ */ ++ int getGeoX(); ++ ++ /** ++ * Returns geodata Y coordinate of the {@link IGeoObject}. ++ * @return int : Geodata Y coordinate. ++ */ ++ int getGeoY(); ++ ++ /** ++ * Returns geodata Z coordinate of the {@link IGeoObject}. ++ * @return int : Geodata Z coordinate. ++ */ ++ int getGeoZ(); ++ ++ /** ++ * Returns height of the {@link IGeoObject}. ++ * @return int : Height. ++ */ ++ int getHeight(); ++ ++ /** ++ * Returns {@link IGeoObject} data. ++ * @return byte[][] : {@link IGeoObject} data. ++ */ ++ byte[][] getObjectGeoData(); ++} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java (nonexistent) +@@ -1,47 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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(); +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/MultilayerBlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/MultilayerBlock.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/MultilayerBlock.java (nonexistent) +@@ -1,183 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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; +- +-/** +- * @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); +- +- // 1 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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/NullRegion.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/NullRegion.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/NullRegion.java (nonexistent) +@@ -1,55 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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 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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/Region.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/Region.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/Region.java (nonexistent) +@@ -1,92 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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; +- +-/** +- * @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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java (nonexistent) +@@ -1,87 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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 T _loc; +- private AbstractNode _parent; +- +- public AbstractNode(T loc) +- { +- _loc = loc; +- } +- +- public void setParent(AbstractNode p) +- { +- _parent = p; +- } +- +- public AbstractNode getParent() +- { +- return _parent; +- } +- +- public T getLoc() +- { +- return _loc; +- } +- +- public void setLoc(T l) +- { +- _loc = l; +- } +- +- @Override +- public int hashCode() +- { +- final int prime = 31; +- int result = 1; +- result = (prime * result) + ((_loc == null) ? 0 : _loc.hashCode()); +- return result; +- } +- +- @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 +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java (nonexistent) +@@ -1,33 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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 int getNodeX(); +- +- public abstract int getNodeY(); +-} +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNode.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNode.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNode.java (nonexistent) +@@ -1,67 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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 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 +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNodeBuffer.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNodeBuffer.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNodeBuffer.java (nonexistent) +@@ -1,348 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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.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 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) +- { +- _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(); +- } +- +- 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 void getNeighbors() +- { +- if (!_current.getLoc().canGoAll()) +- { +- 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); +- } +- +- // SouthEast +- if ((nodeE != null) && (nodeS != null)) +- { +- if (nodeE.getLoc().canGoSouth() && nodeS.getLoc().canGoEast()) +- { +- addNode(x + 1, y + 1, z, true); +- } +- } +- +- // SouthWest +- if ((nodeS != null) && (nodeW != null)) +- { +- if (nodeW.getLoc().canGoSouth() && nodeS.getLoc().canGoWest()) +- { +- addNode(x - 1, y + 1, z, true); +- } +- } +- +- // NorthEast +- if ((nodeN != null) && (nodeE != null)) +- { +- if (nodeE.getLoc().canGoNorth() && nodeN.getLoc().canGoEast()) +- { +- addNode(x + 1, y - 1, z, true); +- } +- } +- +- // NorthWest +- if ((nodeN != null) && (nodeW != null)) +- { +- if (nodeW.getLoc().canGoNorth() && nodeN.getLoc().canGoWest()) +- { +- addNode(x - 1, y - 1, z, true); +- } +- } +- } +- +- private 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(); +- // reinit node if needed +- if (result.getLoc() != null) +- { +- result.getLoc().set(x, y, z); +- } +- else +- { +- result.setLoc(new NodeLoc(x, y, z)); +- } +- } +- +- return result; +- } +- +- private 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 boolean isHighWeight(int x, int y, int z) +- { +- final CellNode result = getNode(x, y, z); +- if (result == null) +- { +- return true; +- } +- +- if (!result.getLoc().canGoAll()) +- { +- return true; +- } +- if (Math.abs(result.getLoc().getZ() - z) > 16) +- { +- return true; +- } +- +- return false; +- } +- +- private 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 +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java (working copy) +@@ -0,0 +1,87 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.geodata.GeoLocation; ++ ++/** ++ * @author Hasha ++ */ ++public class Node ++{ ++ // node coords and nswe flag ++ private GeoLocation _loc; ++ ++ // node parent (for reverse path construction) ++ private Node _parent; ++ // node child (for moving over nodes during iteration) ++ private Node _child; ++ ++ // node G cost (movement cost = parent movement cost + current movement cost) ++ private double _cost = -1000; ++ ++ public void setLoc(int x, int y, int z) ++ { ++ _loc = new GeoLocation(x, y, z); ++ } ++ ++ public GeoLocation getLoc() ++ { ++ return _loc; ++ } ++ ++ public void setParent(Node parent) ++ { ++ _parent = parent; ++ } ++ ++ public Node getParent() ++ { ++ return _parent; ++ } ++ ++ public void setChild(Node child) ++ { ++ _child = child; ++ } ++ ++ public Node getChild() ++ { ++ return _child; ++ } ++ ++ public void setCost(double cost) ++ { ++ _cost = cost; ++ } ++ ++ public double getCost() ++ { ++ return _cost; ++ } ++ ++ public void free() ++ { ++ // reset node location ++ _loc = null; ++ ++ // reset node parent, child and cost ++ _parent = null; ++ _child = null; ++ _cost = -1000; ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java (working copy) +@@ -0,0 +1,306 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.concurrent.locks.ReentrantLock; ++ ++import org.l2jmobius.Config; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; ++ ++/** ++ * @author DS, Hasha; Credits to Diamond ++ */ ++public class NodeBuffer ++{ ++ private final ReentrantLock _lock = new ReentrantLock(); ++ private final int _size; ++ private final Node[][] _buffer; ++ ++ // center coordinates ++ private int _cx = 0; ++ private int _cy = 0; ++ ++ // target coordinates ++ private int _gtx = 0; ++ private int _gty = 0; ++ private short _gtz = 0; ++ ++ private Node _current = null; ++ ++ /** ++ * Constructor of NodeBuffer. ++ * @param size : one dimension size of buffer ++ */ ++ public NodeBuffer(int size) ++ { ++ // set size ++ _size = size; ++ ++ // initialize buffer ++ _buffer = new Node[size][size]; ++ for (int x = 0; x < size; x++) ++ { ++ for (int y = 0; y < size; y++) ++ { ++ _buffer[x][y] = 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 Node : first node of path ++ */ ++ public Node findPath(int gox, int goy, short goz, int gtx, int gty, short gtz) ++ { ++ // set coordinates (middle of the line (gox,goy) - (gtx,gty), will be in the center of the buffer) ++ _cx = gox + ((gtx - gox - _size) / 2); ++ _cy = goy + ((gty - goy - _size) / 2); ++ ++ _gtx = gtx; ++ _gty = gty; ++ _gtz = gtz; ++ ++ _current = getNode(gox, goy, goz); ++ _current.setCost(getCostH(gox, goy, goz)); ++ ++ int count = 0; ++ do ++ { ++ // reached target? ++ if ((_current.getLoc().getGeoX() == _gtx) && (_current.getLoc().getGeoY() == _gty) && (Math.abs(_current.getLoc().getZ() - _gtz) < 8)) ++ { ++ return _current; ++ } ++ ++ // expand current node ++ expand(); ++ ++ // move pointer ++ _current = _current.getChild(); ++ } ++ while ((_current != null) && (count++ < Config.MAX_ITERATIONS)); ++ ++ return null; ++ } ++ ++ public boolean isLocked() ++ { ++ return _lock.tryLock(); ++ } ++ ++ public void free() ++ { ++ _current = null; ++ ++ for (Node[] nodes : _buffer) ++ { ++ for (Node node : nodes) ++ { ++ if (node.getLoc() != null) ++ { ++ node.free(); ++ } ++ } ++ } ++ ++ _lock.unlock(); ++ } ++ ++ /** ++ * Check _current Node and add its neighbors to the buffer. ++ */ ++ private final void expand() ++ { ++ // can't move anywhere, don't expand ++ final byte nswe = _current.getLoc().getNSWE(); ++ if (nswe == 0) ++ { ++ return; ++ } ++ ++ // get geo coords of the node to be expanded ++ final int x = _current.getLoc().getGeoX(); ++ final int y = _current.getLoc().getGeoY(); ++ final short z = (short) _current.getLoc().getZ(); ++ ++ // can move north, expand ++ if ((nswe & GeoStructure.CELL_FLAG_N) != 0) ++ { ++ addNode(x, y - 1, z, Config.BASE_WEIGHT); ++ } ++ ++ // can move south, expand ++ if ((nswe & GeoStructure.CELL_FLAG_S) != 0) ++ { ++ addNode(x, y + 1, z, Config.BASE_WEIGHT); ++ } ++ ++ // can move west, expand ++ if ((nswe & GeoStructure.CELL_FLAG_W) != 0) ++ { ++ addNode(x - 1, y, z, Config.BASE_WEIGHT); ++ } ++ ++ // can move east, expand ++ if ((nswe & GeoStructure.CELL_FLAG_E) != 0) ++ { ++ addNode(x + 1, y, z, Config.BASE_WEIGHT); ++ } ++ ++ // can move north-west, expand ++ if ((nswe & GeoStructure.CELL_FLAG_NW) != 0) ++ { ++ addNode(x - 1, y - 1, z, Config.DIAGONAL_WEIGHT); ++ } ++ ++ // can move north-east, expand ++ if ((nswe & GeoStructure.CELL_FLAG_NE) != 0) ++ { ++ addNode(x + 1, y - 1, z, Config.DIAGONAL_WEIGHT); ++ } ++ ++ // can move south-west, expand ++ if ((nswe & GeoStructure.CELL_FLAG_SW) != 0) ++ { ++ addNode(x - 1, y + 1, z, Config.DIAGONAL_WEIGHT); ++ } ++ ++ // can move south-east, expand ++ if ((nswe & GeoStructure.CELL_FLAG_SE) != 0) ++ { ++ addNode(x + 1, y + 1, z, Config.DIAGONAL_WEIGHT); ++ } ++ } ++ ++ /** ++ * Returns node, if it exists in buffer. ++ * @param x : node X coord ++ * @param y : node Y coord ++ * @param z : node Z coord ++ * @return Node : node, if exits in buffer ++ */ ++ private final Node getNode(int x, int y, short z) ++ { ++ // check node X out of coordinates ++ final int ix = x - _cx; ++ if ((ix < 0) || (ix >= _size)) ++ { ++ return null; ++ } ++ ++ // check node Y out of coordinates ++ final int iy = y - _cy; ++ if ((iy < 0) || (iy >= _size)) ++ { ++ return null; ++ } ++ ++ // get node ++ final Node result = _buffer[ix][iy]; ++ ++ // check and update ++ if (result.getLoc() == null) ++ { ++ result.setLoc(x, y, z); ++ } ++ ++ // return node ++ return result; ++ } ++ ++ /** ++ * Add node given by coordinates to the buffer. ++ * @param x : geo X coord ++ * @param y : geo Y coord ++ * @param z : geo Z coord ++ * @param weight : weight of movement to new node ++ */ ++ private final void addNode(int x, int y, short z, int weight) ++ { ++ // get node to be expanded ++ final Node node = getNode(x, y, z); ++ if (node == null) ++ { ++ return; ++ } ++ ++ // Z distance between nearby cells is higher than cell size ++ if (node.getLoc().getZ() > (z + (2 * GeoStructure.CELL_HEIGHT))) ++ { ++ return; ++ } ++ ++ // node was already expanded, return ++ if (node.getCost() >= 0) ++ { ++ return; ++ } ++ ++ node.setParent(_current); ++ if (node.getLoc().getNSWE() != (byte) 0xFF) ++ { ++ node.setCost(getCostH(x, y, node.getLoc().getZ()) + (weight * Config.OBSTACLE_MULTIPLIER)); ++ } ++ else ++ { ++ node.setCost(getCostH(x, y, node.getLoc().getZ()) + weight); ++ } ++ ++ Node current = _current; ++ int count = 0; ++ while ((current.getChild() != null) && (count < (Config.MAX_ITERATIONS * 4))) ++ { ++ count++; ++ if (current.getChild().getCost() > node.getCost()) ++ { ++ node.setChild(current.getChild()); ++ break; ++ } ++ current = current.getChild(); ++ } ++ ++ if (count >= (Config.MAX_ITERATIONS * 4)) ++ { ++ System.err.println("Pathfinding: too long loop detected, cost:" + node.getCost()); ++ } ++ ++ current.setChild(node); ++ } ++ ++ /** ++ * @param x : node X coord ++ * @param y : node Y coord ++ * @param i : node Z coord ++ * @return double : node cost ++ */ ++ private final double getCostH(int x, int y, int i) ++ { ++ final int dX = x - _gtx; ++ final int dY = y - _gty; ++ final int dZ = (i - _gtz) / GeoStructure.CELL_HEIGHT; ++ ++ // return (Math.abs(dX) + Math.abs(dY) + Math.abs(dZ)) * Config.HEURISTIC_WEIGHT; // Manhattan distance ++ return Math.sqrt((dX * dX) + (dY * dY) + (dZ * dZ)) * Config.HEURISTIC_WEIGHT; // Direct distance ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeLoc.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeLoc.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeLoc.java (nonexistent) +@@ -1,183 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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.Cell; +- +-/** +- * @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 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 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; +- // return super.hashCode(); +- } +- +- @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; +- if (_x != other._x) +- { +- return false; +- } +- if (_y != other._y) +- { +- return false; +- } +- if (_goNorth != other._goNorth) +- { +- return false; +- } +- if (_goEast != other._goEast) +- { +- return false; +- } +- if (_goSouth != other._goSouth) +- { +- return false; +- } +- if (_goWest != other._goWest) +- { +- return false; +- } +- if (_geoHeight != other._geoHeight) +- { +- return false; +- } +- return true; +- } +-} +Index: java/org/l2jmobius/gameserver/model/actor/Creature.java +=================================================================== +--- java/org/l2jmobius/gameserver/model/actor/Creature.java (revision 8399) ++++ java/org/l2jmobius/gameserver/model/actor/Creature.java (working copy) +@@ -66,8 +66,6 @@ + import org.l2jmobius.gameserver.enums.TeleportWhereType; + import org.l2jmobius.gameserver.enums.UserInfoType; + import org.l2jmobius.gameserver.geoengine.GeoEngine; +-import org.l2jmobius.gameserver.geoengine.GeoEnginePathfinding; +-import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; + import org.l2jmobius.gameserver.instancemanager.IdManager; + import org.l2jmobius.gameserver.instancemanager.MapRegionManager; + import org.l2jmobius.gameserver.instancemanager.QuestManager; +@@ -2577,7 +2575,7 @@ + + public boolean disregardingGeodata; + public int onGeodataPathIndex; +- public List geoPath; ++ public List geoPath; + public int geoPathAccurateTx; + public int geoPathAccurateTy; + public int geoPathGtx; +@@ -3466,7 +3464,7 @@ + if (((originalDistance - distance) > 30) && !isControlBlocked() && !isInVehicle) + { + // Path calculation -- overrides previous movement check +- m.geoPath = GeoEnginePathfinding.getInstance().findPath(curX, curY, curZ, originalX, originalY, originalZ, getInstanceWorld()); ++ m.geoPath = GeoEngine.getInstance().findPath(curX, curY, curZ, originalX, originalY, originalZ, getInstanceWorld()); + if ((m.geoPath == null) || (m.geoPath.size() < 2)) // No path found + { + if (isPlayer() && !_isFlying && !isInWater) +Index: java/org/l2jmobius/gameserver/model/actor/instance/PlayerInstance.java +=================================================================== +--- java/org/l2jmobius/gameserver/model/actor/instance/PlayerInstance.java (revision 8397) ++++ java/org/l2jmobius/gameserver/model/actor/instance/PlayerInstance.java (working copy) +@@ -12704,7 +12704,7 @@ + { + if (Config.CORRECT_PLAYER_Z) + { +- final int nearestZ = GeoEngine.getInstance().getHigherHeight(getX(), getY(), getZ()); ++ final int nearestZ = GeoEngine.getInstance().getHeightNearest(getX(), getY(), getZ()); + if (getZ() < nearestZ) + { + teleToLocation(new Location(getX(), getY(), nearestZ)); +Index: java/org/l2jmobius/gameserver/util/GeoUtils.java +=================================================================== +--- java/org/l2jmobius/gameserver/util/GeoUtils.java (revision 8397) ++++ java/org/l2jmobius/gameserver/util/GeoUtils.java (working copy) +@@ -19,7 +19,7 @@ + import java.awt.Color; + + import org.l2jmobius.gameserver.geoengine.GeoEngine; +-import org.l2jmobius.gameserver.geoengine.geodata.Cell; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; + import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance; + import org.l2jmobius.gameserver.network.serverpackets.ExServerPrimitive; + +@@ -26,25 +26,25 @@ + /** + * @author HorridoJoho + */ +-public final class GeoUtils ++public class GeoUtils + { + public static void debug2DLine(PlayerInstance player, int x, int y, int tx, int ty, int z) + { +- final int gx = GeoEngine.getInstance().getGeoX(x); +- final int gy = GeoEngine.getInstance().getGeoY(y); ++ final int gx = GeoEngine.getGeoX(x); ++ final int gy = GeoEngine.getGeoY(y); + +- final int tgx = GeoEngine.getInstance().getGeoX(tx); +- final int tgy = GeoEngine.getInstance().getGeoY(ty); ++ final int tgx = GeoEngine.getGeoX(tx); ++ final int tgy = GeoEngine.getGeoY(ty); + + final ExServerPrimitive prim = new ExServerPrimitive("Debug2DLine", x, y, z); +- prim.addLine(Color.BLUE, GeoEngine.getInstance().getWorldX(gx), GeoEngine.getInstance().getWorldY(gy), z, GeoEngine.getInstance().getWorldX(tgx), GeoEngine.getInstance().getWorldY(tgy), z); ++ prim.addLine(Color.BLUE, GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), z, GeoEngine.getWorldX(tgx), GeoEngine.getWorldY(tgy), z); + + final LinePointIterator iter = new LinePointIterator(gx, gy, tgx, tgy); + + while (iter.next()) + { +- final int wx = GeoEngine.getInstance().getWorldX(iter.x()); +- final int wy = GeoEngine.getInstance().getWorldY(iter.y()); ++ final int wx = GeoEngine.getWorldX(iter.x()); ++ final int wy = GeoEngine.getWorldY(iter.y()); + + prim.addPoint(Color.RED, wx, wy, z); + } +@@ -53,21 +53,21 @@ + + public static void debug3DLine(PlayerInstance player, int x, int y, int z, int tx, int ty, int tz) + { +- final int gx = GeoEngine.getInstance().getGeoX(x); +- final int gy = GeoEngine.getInstance().getGeoY(y); ++ final int gx = GeoEngine.getGeoX(x); ++ final int gy = GeoEngine.getGeoY(y); + +- final int tgx = GeoEngine.getInstance().getGeoX(tx); +- final int tgy = GeoEngine.getInstance().getGeoY(ty); ++ final int tgx = GeoEngine.getGeoX(tx); ++ final int tgy = GeoEngine.getGeoY(ty); + + final ExServerPrimitive prim = new ExServerPrimitive("Debug3DLine", x, y, z); +- prim.addLine(Color.BLUE, GeoEngine.getInstance().getWorldX(gx), GeoEngine.getInstance().getWorldY(gy), z, GeoEngine.getInstance().getWorldX(tgx), GeoEngine.getInstance().getWorldY(tgy), tz); ++ prim.addLine(Color.BLUE, GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), z, GeoEngine.getWorldX(tgx), GeoEngine.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.getInstance().getWorldX(prevX); +- int wy = GeoEngine.getInstance().getWorldY(prevY); ++ int wx = GeoEngine.getWorldX(prevX); ++ int wy = GeoEngine.getWorldY(prevY); + int wz = iter.z(); + prim.addPoint(Color.RED, wx, wy, wz); + +@@ -78,8 +78,8 @@ + + if ((curX != prevX) || (curY != prevY)) + { +- wx = GeoEngine.getInstance().getWorldX(curX); +- wy = GeoEngine.getInstance().getWorldY(curY); ++ wx = GeoEngine.getWorldX(curX); ++ wy = GeoEngine.getWorldY(curY); + wz = iter.z(); + + prim.addPoint(Color.RED, wx, wy, wz); +@@ -93,7 +93,7 @@ + + private static Color getDirectionColor(int x, int y, int z, int nswe) + { +- if (GeoEngine.getInstance().checkNearestNswe(x, y, z, nswe)) ++ if ((GeoEngine.getInstance().getNsweNearest(x, y, z) & nswe) == nswe) + { + return Color.GREEN; + } +@@ -109,9 +109,8 @@ + int iPacket = 0; + + ExServerPrimitive exsp = null; +- final GeoEngine ge = GeoEngine.getInstance(); +- final int playerGx = ge.getGeoX(player.getX()); +- final int playerGy = ge.getGeoY(player.getY()); ++ final int playerGx = GeoEngine.getGeoX(player.getX()); ++ final int playerGy = GeoEngine.getGeoY(player.getY()); + for (int dx = -geoRadius; dx <= geoRadius; ++dx) + { + for (int dy = -geoRadius; dy <= geoRadius; ++dy) +@@ -135,12 +134,12 @@ + final int gx = playerGx + dx; + final int gy = playerGy + dy; + +- final int x = ge.getWorldX(gx); +- final int y = ge.getWorldY(gy); +- final int z = ge.getNearestZ(gx, gy, player.getZ()); ++ final int x = GeoEngine.getWorldX(gx); ++ final int y = GeoEngine.getWorldY(gy); ++ final int z = GeoEngine.getInstance().getHeightNearest(gx, gy, player.getZ()); + + // north arrow +- Color col = getDirectionColor(gx, gy, z, Cell.NSWE_NORTH); ++ Color col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_N); + 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); +@@ -147,7 +146,7 @@ + exsp.addLine(col, x - 4, y - 4, z, x + 4, y - 4, z); + + // east arrow +- col = getDirectionColor(gx, gy, z, Cell.NSWE_EAST); ++ col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_E); + 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); +@@ -154,13 +153,13 @@ + exsp.addLine(col, x + 4, y - 4, z, x + 4, y + 4, z); + + // south arrow +- col = getDirectionColor(gx, gy, z, Cell.NSWE_SOUTH); ++ col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_S); + 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, Cell.NSWE_WEST); ++ col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_W); + 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); +@@ -188,15 +187,15 @@ + { + if (y > lastY) + { +- return Cell.NSWE_SOUTH_EAST; ++ return GeoStructure.CELL_FLAG_SE; // Direction.SOUTH_EAST; + } + else if (y < lastY) + { +- return Cell.NSWE_NORTH_EAST; ++ return GeoStructure.CELL_FLAG_NE; // Direction.NORTH_EAST; + } + else + { +- return Cell.NSWE_EAST; ++ return GeoStructure.CELL_FLAG_E; // Direction.EAST; + } + } + else if (x < lastX) // west +@@ -203,26 +202,27 @@ + { + if (y > lastY) + { +- return Cell.NSWE_SOUTH_WEST; ++ return GeoStructure.CELL_FLAG_SW; // Direction.SOUTH_WEST; + } + else if (y < lastY) + { +- return Cell.NSWE_NORTH_WEST; ++ return GeoStructure.CELL_FLAG_NW; // Direction.NORTH_WEST; + } + else + { +- return Cell.NSWE_WEST; ++ return GeoStructure.CELL_FLAG_W; // Direction.WEST; + } + } +- else // unchanged x ++ else ++ // unchanged x + { + if (y > lastY) + { +- return Cell.NSWE_SOUTH; ++ return GeoStructure.CELL_FLAG_S; // Direction.SOUTH; + } + else if (y < lastY) + { +- return Cell.NSWE_NORTH; ++ return GeoStructure.CELL_FLAG_N; // Direction.NORTH; + } + else + { +Index: java/org/l2jmobius/tools/geodataconverter/GeoDataConverter.java +=================================================================== +--- java/org/l2jmobius/tools/geodataconverter/GeoDataConverter.java (nonexistent) ++++ java/org/l2jmobius/tools/geodataconverter/GeoDataConverter.java (working copy) +@@ -0,0 +1,386 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ */ ++package org.l2jmobius.tools.geodataconverter; ++ ++import java.io.BufferedOutputStream; ++import java.io.File; ++import java.io.FileOutputStream; ++import java.io.RandomAccessFile; ++import java.nio.ByteOrder; ++import java.nio.MappedByteBuffer; ++import java.nio.channels.FileChannel; ++import java.util.Scanner; ++ ++import org.l2jmobius.Config; ++import org.l2jmobius.commons.util.PropertiesParser; ++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.GeoFormat; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; ++import org.l2jmobius.gameserver.model.World; ++ ++/** ++ * @author Hasha ++ */ ++public class GeoDataConverter ++{ ++ private static GeoFormat _format; ++ private static ABlock[][] _blocks; ++ ++ public static void main(String[] args) ++ { ++ // initialize config ++ loadGeoengineConfigs(); ++ ++ // get geodata format ++ String type = ""; ++ try (Scanner scn = new Scanner(System.in)) ++ { ++ while (!(type.equalsIgnoreCase("J") || type.equalsIgnoreCase("O") || type.equalsIgnoreCase("E"))) ++ { ++ System.out.println("GeoDataConverter: Select source geodata type:"); ++ System.out.println(" J: L2J (e.g. 23_22.l2j)"); ++ System.out.println(" O: L2OFF (e.g. 23_22_conv.dat)"); ++ System.out.println(" E: Exit"); ++ System.out.print("Choice: "); ++ type = scn.next(); ++ } ++ } ++ if (type.equalsIgnoreCase("E")) ++ { ++ System.exit(0); ++ } ++ ++ _format = type.equalsIgnoreCase("J") ? GeoFormat.L2J : GeoFormat.L2OFF; ++ ++ // start conversion ++ System.out.println("GeoDataConverter: Converting all " + _format + " files."); ++ ++ // initialize geodata container ++ _blocks = new ABlock[GeoStructure.REGION_BLOCKS_X][GeoStructure.REGION_BLOCKS_Y]; ++ ++ // initialize multilayer temporarily buffer ++ BlockMultilayer.initialize(); ++ ++ // load geo files ++ int converted = 0; ++ for (int rx = World.TILE_X_MIN; rx <= World.TILE_X_MAX; rx++) ++ { ++ for (int ry = World.TILE_Y_MIN; ry <= World.TILE_Y_MAX; ry++) ++ { ++ final String input = String.format(_format.getFilename(), rx, ry); ++ final String filepath = Config.GEODATA_PATH; ++ final File f = new File(filepath + input); ++ if (f.exists() && !f.isDirectory()) ++ { ++ // load geodata ++ if (!loadGeoBlocks(input)) ++ { ++ System.out.println("GeoDataConverter: Unable to load " + input + " region file."); ++ continue; ++ } ++ ++ // recalculate nswe ++ if (!recalculateNswe()) ++ { ++ System.out.println("GeoDataConverter: Unable to convert " + input + " region file."); ++ continue; ++ } ++ ++ // save geodata ++ final String output = String.format(GeoFormat.L2D.getFilename(), rx, ry); ++ if (!saveGeoBlocks(output)) ++ { ++ System.out.println("GeoDataConverter: Unable to save " + output + " region file."); ++ continue; ++ } ++ ++ converted++; ++ System.out.println("GeoDataConverter: Created " + output + " region file."); ++ } ++ } ++ } ++ System.out.println("GeoDataConverter: Converted " + converted + " " + _format + " to L2D region file(s)."); ++ ++ // release multilayer block temporarily buffer ++ BlockMultilayer.release(); ++ } ++ ++ /** ++ * Loads geo blocks from buffer of the region file. ++ * @param filename : The name of the to load. ++ * @return boolean : True when successful. ++ */ ++ private static boolean loadGeoBlocks(String filename) ++ { ++ // region file is load-able, try to load it ++ try (RandomAccessFile raf = new RandomAccessFile(Config.GEODATA_PATH + filename, "r"); ++ FileChannel fc = raf.getChannel()) ++ { ++ final MappedByteBuffer buffer = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()).load(); ++ buffer.order(ByteOrder.LITTLE_ENDIAN); ++ ++ // load 18B header for L2off geodata (1st and 2nd byte...region X and Y) ++ if (_format == GeoFormat.L2OFF) ++ { ++ for (int i = 0; i < 18; i++) ++ { ++ buffer.get(); ++ } ++ } ++ ++ // 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 (_format == GeoFormat.L2J) ++ { ++ // get block type ++ final byte type = buffer.get(); ++ ++ // load block according to block type ++ switch (type) ++ { ++ case GeoStructure.TYPE_FLAT_L2J_L2OFF: ++ { ++ _blocks[ix][iy] = new BlockFlat(buffer, _format); ++ break; ++ } ++ case GeoStructure.TYPE_COMPLEX_L2J: ++ { ++ _blocks[ix][iy] = new BlockComplex(buffer, _format); ++ break; ++ } ++ case GeoStructure.TYPE_MULTILAYER_L2J: ++ { ++ _blocks[ix][iy] = new BlockMultilayer(buffer, _format); ++ break; ++ } ++ default: ++ { ++ throw new IllegalArgumentException("Unknown block type: " + type); ++ } ++ } ++ } ++ else ++ { ++ // get block type ++ final short type = buffer.getShort(); ++ ++ // load block according to block type ++ switch (type) ++ { ++ case GeoStructure.TYPE_FLAT_L2J_L2OFF: ++ { ++ _blocks[ix][iy] = new BlockFlat(buffer, _format); ++ break; ++ } ++ case GeoStructure.TYPE_COMPLEX_L2OFF: ++ { ++ _blocks[ix][iy] = new BlockComplex(buffer, _format); ++ break; ++ } ++ default: ++ { ++ _blocks[ix][iy] = new BlockMultilayer(buffer, _format); ++ break; ++ } ++ } ++ } ++ } ++ } ++ ++ if (buffer.remaining() > 0) ++ { ++ System.out.println("GeoDataConverter: Region file " + filename + " can be corrupted, remaining " + buffer.remaining() + " bytes to read."); ++ return false; ++ } ++ ++ return true; ++ } ++ catch (Exception e) ++ { ++ System.out.println("GeoDataConverter: Error while loading " + filename + " region file."); ++ return false; ++ } ++ } ++ ++ /** ++ * Recalculate diagonal flags for the region file. ++ * @return boolean : True when successful. ++ */ ++ private static boolean recalculateNswe() ++ { ++ try ++ { ++ for (int x = 0; x < GeoStructure.REGION_CELLS_X; x++) ++ { ++ for (int y = 0; y < GeoStructure.REGION_CELLS_Y; y++) ++ { ++ // get block ++ final ABlock block = _blocks[x / GeoStructure.BLOCK_CELLS_X][y / GeoStructure.BLOCK_CELLS_Y]; ++ ++ // skip flat blocks ++ if (block instanceof BlockFlat) ++ { ++ continue; ++ } ++ ++ // for complex and multilayer blocks go though all layers ++ short height = Short.MAX_VALUE; ++ int index; ++ while ((index = block.getIndexBelow(x, y, height)) != -1) ++ { ++ // get height and nswe ++ height = block.getHeight(index); ++ byte nswe = block.getNswe(index); ++ ++ // update nswe with diagonal flags ++ nswe = updateNsweBelow(x, y, height, nswe); ++ ++ // set nswe of the cell ++ block.setNswe(index, nswe); ++ } ++ } ++ } ++ ++ return true; ++ } ++ catch (Exception e) ++ { ++ return false; ++ } ++ } ++ ++ /** ++ * Updates the NSWE flag with diagonal flags. ++ * @param x : Geodata X coordinate. ++ * @param y : Geodata Y coordinate. ++ * @param z : Geodata Z coordinate. ++ * @param nsweValue : NSWE flag to be updated. ++ * @return byte : Updated NSWE flag. ++ */ ++ private static byte updateNsweBelow(int x, int y, short z, byte nsweValue) ++ { ++ byte nswe = nsweValue; ++ ++ // calculate virtual layer height ++ final short height = (short) (z + GeoStructure.CELL_IGNORE_HEIGHT); ++ ++ // get NSWE of neighbor cells below virtual layer (NPC/PC can fall down of clif, but can not climb it -> NSWE of cell below) ++ final byte nsweN = getNsweBelow(x, y - 1, height); ++ final byte nsweS = getNsweBelow(x, y + 1, height); ++ final byte nsweW = getNsweBelow(x - 1, y, height); ++ final byte nsweE = getNsweBelow(x + 1, y, height); ++ ++ // north-west ++ if ((((nswe & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) || (((nswe & GeoStructure.CELL_FLAG_W) != 0) && ((nsweW & GeoStructure.CELL_FLAG_N) != 0))) ++ { ++ nswe |= GeoStructure.CELL_FLAG_NW; ++ } ++ ++ // north-east ++ if ((((nswe & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) || (((nswe & GeoStructure.CELL_FLAG_E) != 0) && ((nsweE & GeoStructure.CELL_FLAG_N) != 0))) ++ { ++ nswe |= GeoStructure.CELL_FLAG_NE; ++ } ++ ++ // south-west ++ if ((((nswe & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) || (((nswe & GeoStructure.CELL_FLAG_W) != 0) && ((nsweW & GeoStructure.CELL_FLAG_S) != 0))) ++ { ++ nswe |= GeoStructure.CELL_FLAG_SW; ++ } ++ ++ // south-east ++ if ((((nswe & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) || (((nswe & GeoStructure.CELL_FLAG_E) != 0) && ((nsweE & GeoStructure.CELL_FLAG_S) != 0))) ++ { ++ nswe |= GeoStructure.CELL_FLAG_SE; ++ } ++ ++ return nswe; ++ } ++ ++ private static byte getNsweBelow(int geoX, int geoY, short worldZ) ++ { ++ // out of geo coordinates ++ if ((geoX < 0) || (geoX >= GeoStructure.REGION_CELLS_X)) ++ { ++ return 0; ++ } ++ ++ // out of geo coordinates ++ if ((geoY < 0) || (geoY >= GeoStructure.REGION_CELLS_Y)) ++ { ++ return 0; ++ } ++ ++ // get block ++ final ABlock block = _blocks[geoX / GeoStructure.BLOCK_CELLS_X][geoY / GeoStructure.BLOCK_CELLS_Y]; ++ ++ // get index, when valid, return nswe ++ final int index = block.getIndexBelow(geoX, geoY, worldZ); ++ return index == -1 ? 0 : block.getNswe(index); ++ } ++ ++ /** ++ * Save region file to file. ++ * @param filename : The name of file to save. ++ * @return boolean : True when successful. ++ */ ++ private static boolean saveGeoBlocks(String filename) ++ { ++ try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(Config.GEODATA_PATH + filename), GeoStructure.REGION_BLOCKS * GeoStructure.BLOCK_CELLS * 3)) ++ { ++ // loop over region blocks and save each block ++ for (int ix = 0; ix < GeoStructure.REGION_BLOCKS_X; ix++) ++ { ++ for (int iy = 0; iy < GeoStructure.REGION_BLOCKS_Y; iy++) ++ { ++ _blocks[ix][iy].saveBlock(bos); ++ } ++ } ++ ++ // flush data to file ++ bos.flush(); ++ ++ return true; ++ } ++ catch (Exception e) ++ { ++ return false; ++ } ++ } ++ ++ private static void loadGeoengineConfigs() ++ { ++ final PropertiesParser geoData = new PropertiesParser(Config.GEOENGINE_CONFIG_FILE); ++ Config.GEODATA_PATH = geoData.getString("GeoDataPath", "./data/geodata/"); ++ Config.COORD_SYNCHRONIZE = geoData.getInt("CoordSynchronize", -1); ++ Config.PART_OF_CHARACTER_HEIGHT = geoData.getInt("PartOfCharacterHeight", 75); ++ Config.MAX_OBSTACLE_HEIGHT = geoData.getInt("MaxObstacleHeight", 32); ++ Config.PATHFINDING = geoData.getBoolean("PathFinding", true); ++ Config.PATHFIND_BUFFERS = geoData.getString("PathFindBuffers", "100x6;128x6;192x6;256x4;320x4;384x4;500x2"); ++ Config.BASE_WEIGHT = geoData.getInt("BaseWeight", 10); ++ Config.DIAGONAL_WEIGHT = geoData.getInt("DiagonalWeight", 14); ++ Config.OBSTACLE_MULTIPLIER = geoData.getInt("ObstacleMultiplier", 10); ++ Config.HEURISTIC_WEIGHT = geoData.getInt("HeuristicWeight", 20); ++ Config.MAX_ITERATIONS = geoData.getInt("MaxIterations", 3500); ++ } ++} +\ No newline at end of file diff --git a/L2J_Mobius_5.0_Salvation/dist/game/data/geodata/L2D_GeoEngine.patch b/L2J_Mobius_5.0_Salvation/dist/game/data/geodata/L2D_GeoEngine.patch new file mode 100644 index 0000000000..63e2faf0c9 --- /dev/null +++ b/L2J_Mobius_5.0_Salvation/dist/game/data/geodata/L2D_GeoEngine.patch @@ -0,0 +1,6052 @@ +Index: dist/game/GeoDataConverter.bat +=================================================================== +--- dist/game/GeoDataConverter.bat (nonexistent) ++++ dist/game/GeoDataConverter.bat (working copy) +@@ -0,0 +1,6 @@ ++@echo off ++title L2D geodata converter ++ ++java -Xmx512m -cp ./../libs/* org.l2jmobius.tools.geodataconverter.GeoDataConverter ++ ++pause +Index: dist/game/GeoDataConverter.sh +=================================================================== +--- dist/game/GeoDataConverter.sh (nonexistent) ++++ dist/game/GeoDataConverter.sh (working copy) +@@ -0,0 +1,4 @@ ++#! /bin/sh ++ ++java -Xmx512m -cp ../libs/*: org.l2jmobius.tools.geodataconverter.GeoDataConverter > log/stdout.log 2>&1 ++ +Index: dist/game/config/GeoEngine.ini +=================================================================== +--- dist/game/config/GeoEngine.ini (revision 8397) ++++ dist/game/config/GeoEngine.ini (working copy) +@@ -1,6 +1,10 @@ + # ================================================================= + # Geodata + # ================================================================= ++# Because of real-time performance we are using geodata files only in ++# diagonal L2D format now (using filename e.g. 22_16.l2d). ++# L2D geodata can be obtained by conversion of existing L2J or L2OFF geodata. ++# Launch "GeoDataConverter.bat/sh" and follow instructions to start the conversion. + + # 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/ +@@ -13,6 +17,16 @@ + CoordSynchronize = 2 + + # ================================================================= ++# Path checking ++# ================================================================= ++ ++# 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 finding + # ================================================================= + +@@ -23,19 +37,23 @@ + # Pathfinding array buffers configuration, default: 100x6;128x6;192x6;256x4;320x4;384x4;500x2 + PathFindBuffers = 100x6;128x6;192x6;256x4;320x4;384x4;500x2 + +-# Weight for nodes without obstacles far from walls +-LowWeight = 0.5 ++# Base path weight, when moving from one node to another on axis direction, default: 10 ++BaseWeight = 10 + +-# Weight for nodes near walls +-MediumWeight = 2 ++# Path weight, when moving from one node to another on diagonal direction, default: BaseWeight * sqrt(2) = 14 ++DiagonalWeight = 14 + +-# Weight for nodes with obstacles +-HighWeight = 3 ++# When movement flags of target node is blocked to any direction, multiply movement weight by this multiplier. ++# This causes pathfinding algorithm to avoid path construction exactly near an obstacle, default: 10 ++ObstacleMultiplier = 10 + +-# Weight for diagonal movement. +-# Default: LowWeight * sqrt(2) +-DiagonalWeight = 0.707 ++# Weight of the heuristic algorithm, which is giving estimated cost from node to target, default: 20 ++# For proper function must be higher than BaseWeight and/or DiagonalWeight. ++HeuristicWeight = 20 + ++# Maximum number of generated nodes per one path-finding process, default 3500 ++MaxIterations = 3500 ++ + # ================================================================= + # Other + # ================================================================= +Index: dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java +=================================================================== +--- dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java (revision 8397) ++++ dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java (working copy) +@@ -55,9 +55,8 @@ + final int worldX = activeChar.getX(); + final int worldY = activeChar.getY(); + final int worldZ = activeChar.getZ(); +- final int geoX = GeoEngine.getInstance().getGeoX(worldX); +- final int geoY = GeoEngine.getInstance().getGeoY(worldY); +- ++ final int geoX = GeoEngine.getGeoX(worldX); ++ final int geoY = GeoEngine.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)); +@@ -73,9 +72,8 @@ + final int worldX = activeChar.getX(); + final int worldY = activeChar.getY(); + final int worldZ = activeChar.getZ(); +- final int geoX = GeoEngine.getInstance().getGeoX(worldX); +- final int geoY = GeoEngine.getInstance().getGeoY(worldY); +- ++ final int geoX = GeoEngine.getGeoX(worldX); ++ final int geoY = GeoEngine.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)); +Index: dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java +=================================================================== +--- dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java (revision 8397) ++++ dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java (working copy) +@@ -18,11 +18,11 @@ + + import java.util.List; + +-import org.l2jmobius.Config; +-import org.l2jmobius.gameserver.geoengine.GeoEnginePathfinding; +-import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; ++import org.l2jmobius.gameserver.geoengine.GeoEngine; + import org.l2jmobius.gameserver.handler.IAdminCommandHandler; ++import org.l2jmobius.gameserver.model.Location; + import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance; ++import org.l2jmobius.gameserver.network.SystemMessageId; + import org.l2jmobius.gameserver.util.BuilderUtil; + + public class AdminPathNode implements IAdminCommandHandler +@@ -37,29 +37,30 @@ + { + if (command.equals("admin_path_find")) + { +- if (!Config.PATHFINDING) +- { +- BuilderUtil.sendSysMessage(activeChar, "PathFinding is disabled."); +- return true; +- } + if (activeChar.getTarget() != null) + { +- final List path = GeoEnginePathfinding.getInstance().findPath(activeChar.getX(), activeChar.getY(), (short) activeChar.getZ(), activeChar.getTarget().getX(), activeChar.getTarget().getY(), (short) activeChar.getTarget().getZ(), activeChar.getInstanceWorld()); ++ 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()); + if (path == null) + { +- BuilderUtil.sendSysMessage(activeChar, "No Route!"); +- return true; ++ BuilderUtil.sendSysMessage(activeChar, "No route found or pathfinding disabled."); + } +- for (AbstractNodeLoc a : path) ++ else + { +- BuilderUtil.sendSysMessage(activeChar, "x:" + a.getX() + " y:" + a.getY() + " z:" + a.getZ()); ++ for (Location point : path) ++ { ++ BuilderUtil.sendSysMessage(activeChar, "x:" + point.getX() + " y:" + point.getY() + " z:" + point.getZ()); ++ } + } + } + else + { +- BuilderUtil.sendSysMessage(activeChar, "No Target!"); ++ activeChar.sendPacket(SystemMessageId.INVALID_TARGET); + } + } ++ else ++ { ++ return false; ++ } + return true; + } + +Index: java/org/l2jmobius/Config.java +=================================================================== +--- java/org/l2jmobius/Config.java (revision 8397) ++++ java/org/l2jmobius/Config.java (working copy) +@@ -32,7 +32,6 @@ + import java.net.UnknownHostException; + import java.nio.charset.StandardCharsets; + import java.nio.file.Files; +-import java.nio.file.Path; + import java.nio.file.Paths; + import java.time.Duration; + import java.util.ArrayList; +@@ -927,14 +926,17 @@ + // -------------------------------------------------- + // GeoEngine + // -------------------------------------------------- +- public static Path GEODATA_PATH; ++ public static String GEODATA_PATH; ++ public static int COORD_SYNCHRONIZE; ++ public static int PART_OF_CHARACTER_HEIGHT; ++ public static int MAX_OBSTACLE_HEIGHT; + public static boolean PATHFINDING; + public static String PATHFIND_BUFFERS; +- public static float LOW_WEIGHT; +- public static float MEDIUM_WEIGHT; +- public static float HIGH_WEIGHT; +- public static float DIAGONAL_WEIGHT; +- public static int COORD_SYNCHRONIZE; ++ public static int BASE_WEIGHT; ++ public static int DIAGONAL_WEIGHT; ++ public static int HEURISTIC_WEIGHT; ++ public static int OBSTACLE_MULTIPLIER; ++ public static int MAX_ITERATIONS; + public static boolean CORRECT_PLAYER_Z; + + /** Attribute System */ +@@ -2468,14 +2470,17 @@ + + // Load GeoEngine config file (if exists) + final PropertiesParser GeoEngine = new PropertiesParser(GEOENGINE_CONFIG_FILE); +- GEODATA_PATH = Paths.get(GeoEngine.getString("GeoDataPath", "./data/geodata")); ++ GEODATA_PATH = GeoEngine.getString("GeoDataPath", "./data/geodata/"); ++ COORD_SYNCHRONIZE = GeoEngine.getInt("CoordSynchronize", -1); ++ PART_OF_CHARACTER_HEIGHT = GeoEngine.getInt("PartOfCharacterHeight", 75); ++ MAX_OBSTACLE_HEIGHT = GeoEngine.getInt("MaxObstacleHeight", 32); + PATHFINDING = GeoEngine.getBoolean("PathFinding", true); + PATHFIND_BUFFERS = GeoEngine.getString("PathFindBuffers", "100x6;128x6;192x6;256x4;320x4;384x4;500x2"); +- LOW_WEIGHT = GeoEngine.getFloat("LowWeight", 0.5f); +- MEDIUM_WEIGHT = GeoEngine.getFloat("MediumWeight", 2); +- HIGH_WEIGHT = GeoEngine.getFloat("HighWeight", 3); +- DIAGONAL_WEIGHT = GeoEngine.getFloat("DiagonalWeight", 0.707f); +- COORD_SYNCHRONIZE = GeoEngine.getInt("CoordSynchronize", -1); ++ BASE_WEIGHT = GeoEngine.getInt("BaseWeight", 10); ++ DIAGONAL_WEIGHT = GeoEngine.getInt("DiagonalWeight", 14); ++ OBSTACLE_MULTIPLIER = GeoEngine.getInt("ObstacleMultiplier", 10); ++ HEURISTIC_WEIGHT = GeoEngine.getInt("HeuristicWeight", 20); ++ MAX_ITERATIONS = GeoEngine.getInt("MaxIterations", 3500); + CORRECT_PLAYER_Z = GeoEngine.getBoolean("CorrectPlayerZ", false); + + // Load AllowedPlayerRaces config file (if exists) +Index: java/org/l2jmobius/gameserver/geoengine/GeoEngine.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/GeoEngine.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/GeoEngine.java (working copy) +@@ -16,101 +16,91 @@ + */ + package org.l2jmobius.gameserver.geoengine; + +-import java.io.IOException; ++import java.io.File; + import java.io.RandomAccessFile; + import java.nio.ByteOrder; +-import java.nio.channels.FileChannel.MapMode; +-import java.nio.file.Files; +-import java.nio.file.Path; +-import java.util.concurrent.atomic.AtomicReferenceArray; +-import java.util.logging.Level; ++import java.nio.MappedByteBuffer; ++import java.nio.channels.FileChannel; ++import java.util.List; + 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.geoengine.geodata.Cell; +-import org.l2jmobius.gameserver.geoengine.geodata.IRegion; +-import org.l2jmobius.gameserver.geoengine.geodata.NullRegion; +-import org.l2jmobius.gameserver.geoengine.geodata.Region; ++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.GeoFormat; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoLocation; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; + import org.l2jmobius.gameserver.instancemanager.WarpedSpaceManager; + 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; ++import org.l2jmobius.gameserver.util.MathUtil; + + /** +- * @author -Nemesiss-, HorridoJoho ++ * @author Hasha + */ + public class GeoEngine + { +- private static final Logger LOGGER = Logger.getLogger(GeoEngine.class.getName()); ++ protected static final Logger LOGGER = Logger.getLogger(GeoEngine.class.getName()); + +- private static final int WORLD_MIN_X = -655360; +- private static final int WORLD_MIN_Y = -589824; +- private static final int WORLD_MIN_Z = -16384; ++ private final ABlock[][] _blocks; ++ private final BlockNull _nullBlock; + +- /** 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; +- +- /** The regions array */ +- private final AtomicReferenceArray _regions = new AtomicReferenceArray<>(GEO_REGIONS); +- +- 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; +- +- protected GeoEngine() ++ /** ++ * GeoEngine constructor. Loads all geodata files of chosen geodata format. ++ */ ++ public GeoEngine() + { + LOGGER.info("GeoEngine: Initializing..."); +- for (int i = 0; i < _regions.length(); i++) +- { +- _regions.set(i, NullRegion.INSTANCE); +- } + ++ // 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; +- try ++ long fileSize = 0; ++ for (int rx = World.TILE_X_MIN; rx <= World.TILE_X_MAX; rx++) + { +- for (int regionX = World.TILE_X_MIN; regionX <= World.TILE_X_MAX; regionX++) ++ for (int ry = World.TILE_Y_MIN; ry <= World.TILE_Y_MAX; ry++) + { +- for (int regionY = World.TILE_Y_MIN; regionY <= World.TILE_Y_MAX; regionY++) ++ final File f = new File(Config.GEODATA_PATH + String.format(GeoFormat.L2D.getFilename(), rx, ry)); ++ if (f.exists() && !f.isDirectory()) + { +- 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(rx, ry)) + { +- try (RandomAccessFile raf = new RandomAccessFile(geoFilePath.toFile(), "r")) +- { +- _regions.set((regionX * GEO_REGIONS_Y) + regionY, new Region(raf.getChannel().map(MapMode.READ_ONLY, 0, raf.length()).load().order(ByteOrder.LITTLE_ENDIAN))); +- loaded++; +- } ++ loaded++; ++ fileSize += f.length(); + } + } ++ else ++ { ++ // region file is not load-able, load null blocks ++ loadNullBlocks(rx, ry); ++ } + } + } +- catch (Exception e) ++ ++ LOGGER.info("GeoEngine: Loaded " + loaded + " geodata files."); ++ if (loaded > 0) + { +- LOGGER.log(Level.SEVERE, "GeoEngine: Failed to load geodata!", e); +- System.exit(1); ++ LOGGER.info("GeoEngine: Total geodata file size " + (fileSize / 1024 / 1024) + " MB."); + } + +- LOGGER.info("GeoEngine: Loaded " + loaded + " geodata files."); +- +- // Avoid wrong configs when no files are loaded. ++ // avoid wrong configs when no files are loaded + if (loaded == 0) + { + if (Config.PATHFINDING) +@@ -124,627 +114,832 @@ + LOGGER.info("GeoEngine: Forcing CoordSynchronize setting to -1."); + } + } ++ ++ // release multilayer block temporarily buffer ++ BlockMultilayer.release(); + } + + /** +- * @param geoX +- * @param geoY +- * @return the region ++ * 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 IRegion getRegion(int geoX, int geoY) ++ private final boolean loadGeoBlocks(int regionX, int regionY) + { +- final int region = ((geoX / IRegion.REGION_CELLS_X) * GEO_REGIONS_Y) + (geoY / IRegion.REGION_CELLS_Y); +- if ((region < 0) || (region >= _regions.length())) ++ final String filename = String.format(GeoFormat.L2D.getFilename(), regionX, regionY); ++ ++ // standard load ++ try (RandomAccessFile raf = new RandomAccessFile(Config.GEODATA_PATH + filename, "r"); ++ FileChannel fc = raf.getChannel()) + { +- return null; ++ // initialize file buffer ++ final MappedByteBuffer buffer = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()).load(); ++ buffer.order(ByteOrder.LITTLE_ENDIAN); ++ ++ // 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++) ++ { ++ // get block type ++ final byte type = buffer.get(); ++ ++ // load block according to block type ++ switch (type) ++ { ++ case GeoStructure.TYPE_FLAT_L2D: ++ { ++ _blocks[blockX + ix][blockY + iy] = new BlockFlat(buffer, GeoFormat.L2D); ++ break; ++ } ++ case GeoStructure.TYPE_COMPLEX_L2D: ++ { ++ _blocks[blockX + ix][blockY + iy] = new BlockComplex(buffer, GeoFormat.L2D); ++ break; ++ } ++ case GeoStructure.TYPE_MULTILAYER_L2D: ++ { ++ _blocks[blockX + ix][blockY + iy] = new BlockMultilayer(buffer, GeoFormat.L2D); ++ break; ++ } ++ default: ++ { ++ throw new IllegalArgumentException("Unknown block type: " + type); ++ } ++ } ++ } ++ } ++ ++ // check data consistency ++ if (buffer.remaining() > 0) ++ { ++ LOGGER.warning("GeoEngine: Region file " + filename + " can be corrupted, remaining " + buffer.remaining() + " bytes to read."); ++ } ++ ++ // loading was successful ++ return true; + } +- return _regions.get(region); +- } +- +- /** +- * @param filePath +- * @param regionX +- * @param regionY +- * @throws IOException +- */ +- 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")) ++ catch (Exception e) + { +- _regions.set(regionOffset, new Region(raf.getChannel().map(MapMode.READ_ONLY, 0, raf.length()).load().order(ByteOrder.LITTLE_ENDIAN))); ++ // an error occured while loading, load null blocks ++ LOGGER.warning("GeoEngine: Error while loading " + filename + " region file."); ++ LOGGER.warning(e.getMessage()); ++ ++ // replace whole region file with null blocks ++ loadNullBlocks(regionX, regionY); ++ ++ // loading was not successful ++ return false; + } + } + + /** +- * @param regionX +- * @param regionY ++ * 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. + */ +- public void unloadRegion(int regionX, int regionY) ++ private final void loadNullBlocks(int regionX, int regionY) + { +- _regions.set((regionX * GEO_REGIONS_Y) + regionY, NullRegion.INSTANCE); +- } +- +- /** +- * @param geoX +- * @param geoY +- * @return if geodata exist +- */ +- public boolean hasGeoPos(int geoX, int geoY) +- { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) ++ // 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++) + { +- return false; ++ for (int iy = 0; iy < GeoStructure.REGION_BLOCKS_Y; iy++) ++ { ++ _blocks[blockX + ix][blockY + iy] = _nullBlock; ++ } + } +- return region.hasGeo(); + } + ++ // GEODATA - GENERAL ++ + /** +- * Checks the specified position for available geodata. +- * @param x the world x +- * @param y the world y +- * @return {@code true} if there is geodata for the given coordinates, {@code false} otherwise ++ * Converts world X to geodata X. ++ * @param worldX ++ * @return int : Geo X + */ +- public boolean hasGeo(int x, int y) ++ public static int getGeoX(int worldX) + { +- return hasGeoPos(getGeoX(x), getGeoY(y)); ++ return (MathUtil.limit(worldX, World.MAP_MIN_X, World.MAP_MAX_X) - World.MAP_MIN_X) >> 4; + } + + /** +- * @param geoX +- * @param geoY +- * @param worldZ +- * @param nswe +- * @return the nearest nswe check ++ * Converts world Y to geodata Y. ++ * @param worldY ++ * @return int : Geo Y + */ +- public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) ++ public static int getGeoY(int worldY) + { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) +- { +- return true; +- } +- return region.checkNearestNswe(geoX, geoY, worldZ, nswe); ++ return (MathUtil.limit(worldY, World.MAP_MIN_Y, World.MAP_MAX_Y) - World.MAP_MIN_Y) >> 4; + } + + /** ++ * Converts geodata X to world X. + * @param geoX +- * @param geoY +- * @param worldZ +- * @param nswe +- * @return the nearest nswe anti-corner cut ++ * @return int : World X + */ +- public boolean checkNearestNsweAntiCornerCut(int geoX, int geoY, int worldZ, int nswe) ++ public static int getWorldX(int geoX) + { +- boolean can = true; +- if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) +- { +- can = checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_EAST) && checkNearestNswe(geoX + 1, geoY, worldZ, Cell.NSWE_NORTH); +- } +- if (can && ((nswe & Cell.NSWE_NORTH_WEST) == Cell.NSWE_NORTH_WEST)) +- { +- 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 = 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 = checkNearestNswe(geoX, geoY + 1, worldZ, Cell.NSWE_WEST) && checkNearestNswe(geoX - 1, geoY, worldZ, Cell.NSWE_SOUTH); +- } +- return can && checkNearestNswe(geoX, geoY, worldZ, nswe); ++ return (MathUtil.limit(geoX, 0, GeoStructure.GEO_CELLS_X) << 4) + World.MAP_MIN_X + 8; + } + + /** +- * @param geoX ++ * Converts geodata Y to world Y. + * @param geoY +- * @param worldZ +- * @return the nearest Z value ++ * @return int : World Y + */ +- public int getNearestZ(int geoX, int geoY, int worldZ) ++ public static int getWorldY(int geoY) + { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) +- { +- return worldZ; +- } +- return region.getNearestZ(geoX, geoY, worldZ); ++ return (MathUtil.limit(geoY, 0, GeoStructure.GEO_CELLS_Y) << 4) + World.MAP_MIN_Y + 8; + } + + /** +- * @param geoX +- * @param geoY +- * @param worldZ +- * @return the next lower Z value ++ * Returns block of geodata on given coordinates. ++ * @param geoX : Geodata X ++ * @param geoY : Geodata Y ++ * @return {@link ABlock} : Block of geodata. + */ +- public int getNextLowerZ(int geoX, int geoY, int worldZ) ++ private final ABlock getBlock(int geoX, int geoY) + { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) ++ final int x = geoX / GeoStructure.BLOCK_CELLS_X; ++ final int y = geoY / GeoStructure.BLOCK_CELLS_Y; ++ ++ // if x or y is out of array return null ++ if ((x > -1) && (y > -1) && (x < GeoStructure.GEO_BLOCKS_X) && (y < GeoStructure.GEO_BLOCKS_Y)) + { +- return worldZ; ++ return _blocks[x][y]; + } +- return region.getNextLowerZ(geoX, geoY, worldZ); ++ return null; + } + + /** +- * @param geoX +- * @param geoY +- * @param worldZ +- * @return the next higher Z value ++ * Check if geo coordinates has geo. ++ * @param geoX : Geodata X ++ * @param geoY : Geodata Y ++ * @return boolean : True, if given geo coordinates have geodata + */ +- public int getNextHigherZ(int geoX, int geoY, int worldZ) ++ public boolean hasGeoPos(int geoX, int geoY) + { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) ++ final ABlock block = getBlock(geoX, geoY); ++ if (block == null) // null block check + { +- return worldZ; ++ // TODO: Find when this can be null. (Bad geodata? Check World getRegion method.) ++ // LOGGER.warning("Could not find geodata block at " + getWorldX(geoX) + ", " + getWorldY(geoY) + "."); ++ return false; + } +- return region.getNextHigherZ(geoX, geoY, worldZ); ++ return block.hasGeoPos(); + } + + /** +- * Gets the Z height. +- * @param x the world x +- * @param y the world y +- * @param z the world z +- * @return the nearest Z height ++ * 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 int getHeight(int x, int y, int z) ++ public short getHeightNearest(int geoX, int geoY, int worldZ) + { +- return getNearestZ(getGeoX(x), getGeoY(y), z); ++ final ABlock block = getBlock(geoX, geoY); ++ return block != null ? block.getHeightNearest(geoX, geoY, worldZ) : (short) worldZ; + } + + /** +- * Gets the next lower Z height. +- * @param x the world x +- * @param y the world y +- * @param z the world z +- * @return the nearest Z height ++ * 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 int getLowerHeight(int x, int y, int z) ++ public byte getNsweNearest(int geoX, int geoY, int worldZ) + { +- return getNextLowerZ(getGeoX(x), getGeoY(y), z); ++ final ABlock block = getBlock(geoX, geoY); ++ return block != null ? block.getNsweNearest(geoX, geoY, worldZ) : (byte) 0xFF; + } + + /** +- * Gets the next higher Z height. +- * @param x the world x +- * @param y the world y +- * @param z the world z +- * @return the nearest Z height ++ * Check if world coordinates has geo. ++ * @param worldX : World X ++ * @param worldY : World Y ++ * @return boolean : True, if given world coordinates have geodata + */ +- public int getHigherHeight(int x, int y, int z) ++ public boolean hasGeo(int worldX, int worldY) + { +- return getNextHigherZ(getGeoX(x), getGeoY(y), z); ++ return hasGeoPos(getGeoX(worldX), getGeoY(worldY)); + } + + /** +- * @param worldX +- * @return the geo X ++ * 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 int getGeoX(int worldX) ++ public short getHeight(int worldX, int worldY, int worldZ) + { +- return (worldX - WORLD_MIN_X) / 16; ++ return getHeightNearest(getGeoX(worldX), getGeoY(worldY), worldZ); + } + +- /** +- * @param worldY +- * @return the geo Y +- */ +- public int getGeoY(int worldY) +- { +- return (worldY - WORLD_MIN_Y) / 16; +- } ++ // PATHFINDING + + /** +- * @param worldZ +- * @return the geo Z ++ * Check line of sight from {@link WorldObject} to {@link WorldObject}. ++ * @param origin : The origin object. ++ * @param target : The target object. ++ * @return {@code boolean} : True if origin can see target + */ +- public int getGeoZ(int worldZ) ++ public boolean canSeeTarget(WorldObject origin, WorldObject target) + { +- return (worldZ - WORLD_MIN_Z) / 16; +- } +- +- /** +- * @param geoX +- * @return the world X +- */ +- public int getWorldX(int geoX) +- { +- return (geoX * 16) + WORLD_MIN_X + 8; +- } +- +- /** +- * @param geoY +- * @return the world Y +- */ +- public int getWorldY(int geoY) +- { +- return (geoY * 16) + WORLD_MIN_Y + 8; +- } +- +- /** +- * @param geoZ +- * @return the world Z +- */ +- public int getWorldZ(int geoZ) +- { +- return (geoZ * 16) + WORLD_MIN_Z + 8; +- } +- +- /** +- * 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) +- { +- if (target.isDoor()) ++ if (target.isDoor() || target.isArtefact() || (target.isCreature() && ((Creature) target).isFlying())) + { +- // Can always see doors. + return true; + } +- return 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(), cha.getInstanceWorld(), worldPosition.getX(), worldPosition.getY(), worldPosition.getZ()); +- } +- +- /** +- * Can see target. Checks doors between. +- * @param x the x coordinate +- * @param y the y coordinate +- * @param z the z coordinate +- * @param world +- * @param tx the target's x coordinate +- * @param ty the target's y coordinate +- * @param tz the target's z coordinate +- * @param tworld the target's instanceId +- * @return +- */ +- public boolean canSeeTarget(int x, int y, int z, Instance world, int tx, int ty, int tz, Instance tworld) +- { +- return (world != tworld) ? false : canSeeTarget(x, y, z, world, tx, ty, tz); +- } +- +- /** +- * 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 +- * @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, Instance instance, int tx, int ty, int tz) +- { +- if (DoorData.getInstance().checkIfDoorsBetween(x, y, z, tx, ty, tz, instance, true)) ++ ++ // get origin and target world coordinates ++ final int ox = origin.getX(); ++ final int oy = origin.getY(); ++ final int oz = origin.getZ(); ++ final int tx = target.getX(); ++ final int ty = target.getY(); ++ final int tz = target.getZ(); ++ ++ if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld(), true)) + { + return false; + } +- return canSeeTarget(x, y, z, tx, ty, tz); +- } +- +- /** +- * @param prevX +- * @param prevY +- * @param prevGeoZ +- * @param curX +- * @param curY +- * @param nswe +- * @return the LOS Z value +- */ +- 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))) ++ if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld())) + { +- throw new RuntimeException("Multiple directions!"); ++ return false; + } ++ if (WarpedSpaceManager.getInstance().checkForWarpedSpace(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld())) ++ { ++ return false; ++ } + +- if (checkNearestNsweAntiCornerCut(prevX, prevY, prevGeoZ, nswe)) ++ // get origin and check existing geo coordinates ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) + { +- return getNearestZ(curX, curY, prevGeoZ); ++ return true; + } + +- return getNextHigherZ(curX, curY, prevGeoZ); ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coordinates ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) ++ { ++ return true; ++ } ++ ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // origin and target coordinates are same ++ if ((gox == gtx) && (goy == gty)) ++ { ++ return goz == gtz; ++ } ++ ++ // get origin and target height, real height = collision height * 2 ++ double oheight = 0; ++ if (origin.isCreature()) ++ { ++ oheight = ((Creature) origin).getCollisionHeight() * 2; ++ } ++ ++ double theight = 0; ++ if (target.isCreature()) ++ { ++ theight = ((Creature) target).getCollisionHeight() * 2; ++ } ++ ++ // perform geodata check ++ return checkSee(gox, goy, goz, oheight, gtx, gty, gtz, theight, origin.getInstanceWorld()); + } + + /** +- * Can see target. Does not check doors between. +- * @param xValue the x coordinate +- * @param yValue the y coordinate +- * @param zValue the z coordinate +- * @param txValue the target's x coordinate +- * @param tyValue the target's y coordinate +- * @param tzValue the target's z coordinate +- * @return {@code true} if there is line of sight between the given coordinate sets, {@code false} otherwise ++ * Check line of sight from {@link WorldObject} to {@link Location}. ++ * @param origin : The origin object. ++ * @param position : The target position. ++ * @return {@code boolean} : True if object can see position + */ +- public boolean canSeeTarget(int xValue, int yValue, int zValue, int txValue, int tyValue, int tzValue) ++ public boolean canSeeTarget(WorldObject origin, Location position) + { +- int x = xValue; +- int y = yValue; +- int tx = txValue; +- int ty = tyValue; ++ // get origin and target world coordinates ++ final int ox = origin.getX(); ++ final int oy = origin.getY(); ++ final int oz = origin.getZ(); ++ final int tx = position.getX(); ++ final int ty = position.getY(); ++ final int tz = position.getZ(); + +- int geoX = getGeoX(x); +- int geoY = getGeoY(y); +- int tGeoX = getGeoX(tx); +- int tGeoY = getGeoY(ty); ++ if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld(), true)) ++ { ++ return false; ++ } ++ if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld())) ++ { ++ return false; ++ } ++ if (WarpedSpaceManager.getInstance().checkForWarpedSpace(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld())) ++ { ++ return false; ++ } + +- int z = getNearestZ(geoX, geoY, zValue); +- int tz = getNearestZ(tGeoX, tGeoY, tzValue); ++ // get origin and check existing geo coordinates ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) ++ { ++ return true; ++ } + +- // Fastpath. +- if ((geoX == tGeoX) && (geoY == tGeoY)) ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coordinates ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) + { +- if (hasGeoPos(tGeoX, tGeoY)) +- { +- return z == tz; +- } + return true; + } + +- if (tz > z) ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // origin and target coordinates are same ++ if ((gox == gtx) && (goy == gty)) + { +- int tmp = tx; +- tx = x; +- x = tmp; +- +- tmp = ty; +- ty = y; +- y = tmp; +- +- tmp = tz; +- tz = z; +- z = tmp; +- +- tmp = tGeoX; +- tGeoX = geoX; +- geoX = tmp; +- +- tmp = tGeoY; +- tGeoY = geoY; +- geoY = tmp; ++ return goz == gtz; + } + +- final LinePointIterator3D pointIter = new LinePointIterator3D(geoX, geoY, z, tGeoX, tGeoY, tz); +- // 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()) ++ // get origin and target height, real height = collision height * 2 ++ double oheight = 0; ++ if (origin.isCreature()) + { +- final int curX = pointIter.x(); +- final int curY = pointIter.y(); ++ oheight = ((Creature) origin).getTemplate().getCollisionHeight(); ++ } ++ ++ // perform geodata check ++ return checkSee(gox, goy, goz, oheight, gtx, gty, gtz, 0, origin.getInstanceWorld()); ++ } ++ ++ /** ++ * Simple check for origin to target visibility. ++ * @param goxValue : origin X geodata coordinate ++ * @param goyValue : origin Y geodata coordinate ++ * @param gozValue : origin Z geodata coordinate ++ * @param oheight : origin height (if instance of {@link Character}) ++ * @param gtxValue : target X geodata coordinate ++ * @param gtyValue : target Y geodata coordinate ++ * @param gtzValue : target Z geodata coordinate ++ * @param theight : target height (if instance of {@link Character}) ++ * @param instance ++ * @return {@code boolean} : True, when target can be seen. ++ */ ++ private final boolean checkSee(int goxValue, int goyValue, int gozValue, double oheight, int gtxValue, int gtyValue, int gtzValue, double theight, Instance instance) ++ { ++ int goz = gozValue; ++ int gtz = gtzValue; ++ int gox = goxValue; ++ int goy = goyValue; ++ int gtx = gtxValue; ++ int gty = gtyValue; ++ ++ // get line of sight Z coordinates ++ double losoz = goz + ((oheight * Config.PART_OF_CHARACTER_HEIGHT) / 100); ++ double lostz = gtz + ((theight * Config.PART_OF_CHARACTER_HEIGHT) / 100); ++ ++ // get X delta and signum ++ final int dx = Math.abs(gtx - gox); ++ final int sx = gox < gtx ? 1 : -1; ++ final byte dirox = sx > 0 ? GeoStructure.CELL_FLAG_E : GeoStructure.CELL_FLAG_W; ++ final byte dirtx = sx > 0 ? GeoStructure.CELL_FLAG_W : GeoStructure.CELL_FLAG_E; ++ ++ // get Y delta and signum ++ final int dy = Math.abs(gty - goy); ++ final int sy = goy < gty ? 1 : -1; ++ final byte diroy = sy > 0 ? GeoStructure.CELL_FLAG_S : GeoStructure.CELL_FLAG_N; ++ final byte dirty = sy > 0 ? GeoStructure.CELL_FLAG_N : GeoStructure.CELL_FLAG_S; ++ ++ // get Z delta ++ final int dm = Math.max(dx, dy); ++ final double dz = (lostz - losoz) / dm; ++ ++ // get direction flag for diagonal movement ++ final byte diroxy = getDirXY(dirox, diroy); ++ final byte dirtxy = getDirXY(dirtx, dirty); ++ ++ // delta, determines axis to move on (+..X axis, -..Y axis) ++ int d = dx - dy; ++ ++ // NSWE direction of movement ++ byte diro; ++ byte dirt; ++ ++ // initialize node values ++ int nox = gox; ++ int noy = goy; ++ int ntx = gtx; ++ int nty = gty; ++ byte nsweo = getNsweNearest(gox, goy, goz); ++ byte nswet = getNsweNearest(gtx, gty, gtz); ++ ++ // loop ++ ABlock block; ++ int index; ++ for (int i = 0; i < ((dm + 1) / 2); i++) ++ { ++ // reset direction flag ++ diro = 0; ++ dirt = 0; + +- if ((curX == prevX) && (curY == prevY)) ++ // calculate next point coordinates ++ final int e2 = 2 * d; ++ if ((e2 > -dy) && (e2 < dx)) + { +- continue; ++ // calculate next point XY coordinates ++ d -= dy; ++ d += dx; ++ nox += sx; ++ ntx -= sx; ++ noy += sy; ++ nty -= sy; ++ diro |= diroxy; ++ dirt |= dirtxy; + } ++ else if (e2 > -dy) ++ { ++ // calculate next point X coordinate ++ d -= dy; ++ nox += sx; ++ ntx -= sx; ++ diro |= dirox; ++ dirt |= dirtx; ++ } ++ else if (e2 < dx) ++ { ++ // calculate next point Y coordinate ++ d += dx; ++ noy += sy; ++ nty -= sy; ++ diro |= diroy; ++ dirt |= dirty; ++ } + +- final int beeCurZ = pointIter.z(); +- int curGeoZ = prevGeoZ; +- +- // Check if the position has geodata. +- if (hasGeoPos(curX, curY)) + { +- final int beeCurGeoZ = getNearestZ(curX, curY, beeCurZ); +- final int nswe = GeoUtils.computeNswe(prevX, prevY, curX, curY); // .computeDirection(prevX, prevY, curX, curY); +- curGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, curX, curY, nswe); +- int maxHeight; +- if (ptIndex < ELEVATED_SEE_OVER_DISTANCE) ++ // get block of the next cell ++ block = getBlock(nox, noy); ++ ++ // get index of particular layer, based on movement conditions ++ if ((nsweo & diro) == 0) + { +- maxHeight = z + MAX_SEE_OVER_HEIGHT; ++ index = block.getIndexAbove(nox, noy, goz - GeoStructure.CELL_IGNORE_HEIGHT); + } + else + { +- maxHeight = beeCurZ + MAX_SEE_OVER_HEIGHT; ++ index = block.getIndexBelow(nox, noy, goz + GeoStructure.CELL_IGNORE_HEIGHT); + } + +- boolean canSeeThrough = false; +- if ((curGeoZ <= maxHeight) && (curGeoZ <= beeCurGeoZ)) ++ // layer does not exist, return ++ if (index == -1) + { +- if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) +- { +- 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 +- { +- canSeeThrough = true; +- } ++ return false; + } + +- if (!canSeeThrough) ++ // get layer and next line of sight Z coordinate ++ goz = block.getHeight(index); ++ losoz += dz; ++ ++ // perform line of sight check, return when fails ++ if ((goz - losoz) > Config.MAX_OBSTACLE_HEIGHT) + { + return false; + } ++ ++ // get layer nswe ++ nsweo = block.getNswe(index); + } ++ { ++ // get block of the next cell ++ block = getBlock(ntx, nty); ++ ++ // get index of particular layer, based on movement conditions ++ if ((nswet & dirt) == 0) ++ { ++ index = block.getIndexAbove(ntx, nty, gtz - GeoStructure.CELL_IGNORE_HEIGHT); ++ } ++ else ++ { ++ index = block.getIndexBelow(ntx, nty, gtz + GeoStructure.CELL_IGNORE_HEIGHT); ++ } ++ ++ // layer does not exist, return ++ if (index == -1) ++ { ++ return false; ++ } ++ ++ // get layer and next line of sight Z coordinate ++ gtz = block.getHeight(index); ++ lostz -= dz; ++ ++ // perform line of sight check, return when fails ++ if ((gtz - lostz) > Config.MAX_OBSTACLE_HEIGHT) ++ { ++ return false; ++ } ++ ++ // get layer nswe ++ nswet = block.getNswe(index); ++ } + +- prevX = curX; +- prevY = curY; +- prevGeoZ = curGeoZ; +- ++ptIndex; ++ // update coords ++ gox = nox; ++ goy = noy; ++ gtx = ntx; ++ gty = nty; + } + +- return true; ++ // when iteration is completed, compare final Z coordinates ++ return Math.abs(goz - gtz) < (GeoStructure.CELL_HEIGHT * 4); + } + + /** +- * Move check. +- * @param x the x coordinate +- * @param y the y coordinate +- * @param zValue the z coordinate +- * @param tx the target's x coordinate +- * @param ty the target's y coordinate +- * @param tzValue the target's z coordinate +- * @param instance the instance +- * @return the last Location (x,y,z) where player can walk - just before wall ++ * Check movement from coordinates to 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 {code boolean} : True if target coordinates are reachable from origin coordinates + */ +- public Location canMoveToTargetLoc(int x, int y, int zValue, int tx, int ty, int tzValue, Instance instance) ++ public boolean canMoveToTarget(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + { +- final int geoX = getGeoX(x); +- final int geoY = getGeoY(y); +- final int z = getNearestZ(geoX, geoY, zValue); +- final int tGeoX = getGeoX(tx); +- final int tGeoY = getGeoY(ty); +- final int tz = getNearestZ(tGeoX, tGeoY, tzValue); ++ // get origin and check existing geo coordinates ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) ++ { ++ return true; ++ } + +- if (DoorData.getInstance().checkIfDoorsBetween(x, y, z, tx, ty, tz, instance, false)) ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coordinates ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) + { +- return new Location(x, y, getHeight(x, y, z)); ++ return true; + } +- if (FenceData.getInstance().checkIfFenceBetween(x, y, z, tx, ty, tz, instance)) ++ ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // target coordinates reached ++ if ((gox == gtx) && (goy == gty) && (goz == gtz)) + { +- return new Location(x, y, getHeight(x, y, z)); ++ return true; + } +- if (WarpedSpaceManager.getInstance().checkForWarpedSpace(x, y, z, tx, ty, tz, instance)) ++ ++ // perform geodata check ++ final GeoLocation loc = checkMove(gox, goy, goz, gtx, gty, gtz, instance); ++ return (loc.getGeoX() == gtx) && (loc.getGeoY() == gty); ++ } ++ ++ /** ++ * Check movement from origin to target. Returns last available point in the checked path. ++ * @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 {@link Location} : Last point where object can walk (just before wall) ++ */ ++ public Location canMoveToTargetLoc(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) ++ { ++ // Mobius: Double check for doors before normal checkMove to avoid exploiting key movement. ++ if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, instance, false)) + { +- return new Location(x, y, getHeight(x, y, z)); ++ return new Location(ox, oy, oz); + } ++ if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, instance)) ++ { ++ return new Location(ox, oy, oz); ++ } + +- 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 = z; ++ // get origin and check existing geo coordinates ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) ++ { ++ return new Location(tx, ty, tz); ++ } + +- while (pointIter.next()) ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coordinates ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) + { +- final int curX = pointIter.x(); +- final int curY = pointIter.y(); +- final int curZ = getNearestZ(curX, curY, prevZ); +- +- if (hasGeoPos(prevX, prevY)) +- { +- final int nswe = GeoUtils.computeNswe(prevX, prevY, curX, curY); +- if (!checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, nswe)) +- { +- // Can't move, return previous location. +- return new Location(getWorldX(prevX), getWorldY(prevY), prevZ); +- } +- } +- +- prevX = curX; +- prevY = curY; +- prevZ = curZ; ++ return new Location(tx, ty, tz); + } + +- if (hasGeoPos(prevX, prevY) && (prevZ != tz)) ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // target coordinates reached ++ if ((gox == gtx) && (goy == gty) && (goz == gtz)) + { +- // Different floors, return start location. +- return new Location(x, y, z); ++ return new Location(tx, ty, tz); + } + +- return new Location(tx, ty, tz); ++ // perform geodata check ++ return checkMove(gox, goy, goz, gtx, gty, gtz, instance); + } + + /** +- * 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 fromZvalue 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 toZvalue 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 ++ * With this method you can check if a position is visible or can be reached by beeline movement.
++ * 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 gox : origin X geodata coordinate ++ * @param goy : origin Y geodata coordinate ++ * @param goz : origin Z geodata coordinate ++ * @param gtx : target X geodata coordinate ++ * @param gty : target Y geodata coordinate ++ * @param gtz : target Z geodata coordinate ++ * @param instance ++ * @return {@link GeoLocation} : The last allowed point of movement. + */ +- public boolean canMoveToTarget(int fromX, int fromY, int fromZvalue, int toX, int toY, int toZvalue, Instance instance) ++ protected final GeoLocation checkMove(int gox, int goy, int goz, int gtx, int gty, int gtz, Instance instance) + { +- final int geoX = getGeoX(fromX); +- final int geoY = getGeoY(fromY); +- final int fromZ = getNearestZ(geoX, geoY, fromZvalue); +- final int tGeoX = getGeoX(toX); +- final int tGeoY = getGeoY(toY); +- final int toZ = getNearestZ(tGeoX, tGeoY, toZvalue); +- +- if (DoorData.getInstance().checkIfDoorsBetween(fromX, fromY, fromZ, toX, toY, toZ, instance, false)) ++ if (DoorData.getInstance().checkIfDoorsBetween(gox, goy, goz, gtx, gty, gtz, instance, false)) + { +- return false; ++ return new GeoLocation(gox, goy, goz); + } +- if (FenceData.getInstance().checkIfFenceBetween(fromX, fromY, fromZ, toX, toY, toZ, instance)) ++ if (FenceData.getInstance().checkIfFenceBetween(gox, goy, goz, gtx, gty, gtz, instance)) + { +- return false; ++ return new GeoLocation(gox, goy, goz); + } +- if (WarpedSpaceManager.getInstance().checkForWarpedSpace(fromX, fromY, fromZ, toX, toY, toZ, instance)) ++ if (WarpedSpaceManager.getInstance().checkForWarpedSpace(gox, goy, goz, gtx, gty, gtz, instance)) + { +- return false; ++ return new GeoLocation(gox, goy, goz); + } + +- 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 = fromZ; ++ // get X delta, signum and direction flag ++ final int dx = Math.abs(gtx - gox); ++ final int sx = gox < gtx ? 1 : -1; ++ final byte dirX = sx > 0 ? GeoStructure.CELL_FLAG_E : GeoStructure.CELL_FLAG_W; + +- while (pointIter.next()) ++ // get Y delta, signum and direction flag ++ final int dy = Math.abs(gty - goy); ++ final int sy = goy < gty ? 1 : -1; ++ final byte dirY = sy > 0 ? GeoStructure.CELL_FLAG_S : GeoStructure.CELL_FLAG_N; ++ ++ // get direction flag for diagonal movement ++ final byte dirXY = getDirXY(dirX, dirY); ++ ++ // delta, determines axis to move on (+..X axis, -..Y axis) ++ int d = dx - dy; ++ ++ // NSWE direction of movement ++ byte direction; ++ ++ // load pointer coordinates ++ int gpx = gox; ++ int gpy = goy; ++ int gpz = goz; ++ ++ // load next pointer ++ int nx = gpx; ++ int ny = gpy; ++ ++ // loop ++ int count = 0; ++ while (count++ < Config.MAX_ITERATIONS) + { +- final int curX = pointIter.x(); +- final int curY = pointIter.y(); +- final int curZ = getNearestZ(curX, curY, prevZ); ++ direction = 0; + +- if (hasGeoPos(prevX, prevY)) ++ // calculate next point coordinates ++ final int e2 = 2 * d; ++ if ((e2 > -dy) && (e2 < dx)) + { +- final int nswe = GeoUtils.computeNswe(prevX, prevY, curX, curY); +- if (!checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, nswe)) ++ d -= dy; ++ d += dx; ++ nx += sx; ++ ny += sy; ++ direction |= dirXY; ++ } ++ else if (e2 > -dy) ++ { ++ d -= dy; ++ nx += sx; ++ direction |= dirX; ++ } ++ else if (e2 < dx) ++ { ++ d += dx; ++ ny += sy; ++ direction |= dirY; ++ } ++ ++ // obstacle found, return ++ if ((getNsweNearest(gpx, gpy, gpz) & direction) == 0) ++ { ++ return new GeoLocation(gpx, gpy, gpz); ++ } ++ ++ // update pointer coordinates ++ gpx = nx; ++ gpy = ny; ++ gpz = getHeightNearest(nx, ny, gpz); ++ ++ // target coordinates reached ++ if ((gpx == gtx) && (gpy == gty)) ++ { ++ if (gpz == gtz) + { +- return false; ++ // path found, Z coordinates are okay, return target point ++ return new GeoLocation(gtx, gty, gtz); + } ++ ++ // path found, Z coordinates are not okay, return last good point ++ return new GeoLocation(gpx, gpy, gpz); + } ++ } ++ ++ return new GeoLocation(gox, goy, goz); ++ } ++ ++ /** ++ * Returns diagonal NSWE flag format of combined two NSWE flags. ++ * @param dirX : X direction NSWE flag ++ * @param dirY : Y direction NSWE flag ++ * @return byte : NSWE flag of combined direction ++ */ ++ private static byte getDirXY(byte dirX, byte dirY) ++ { ++ // check axis directions ++ if (dirY == GeoStructure.CELL_FLAG_N) ++ { ++ if (dirX == GeoStructure.CELL_FLAG_W) ++ { ++ return GeoStructure.CELL_FLAG_NW; ++ } + +- prevX = curX; +- prevY = curY; +- prevZ = curZ; ++ return GeoStructure.CELL_FLAG_NE; + } + +- if (hasGeoPos(prevX, prevY) && (prevZ != toZ)) ++ if (dirX == GeoStructure.CELL_FLAG_W) + { +- // Different floors. +- return false; ++ return GeoStructure.CELL_FLAG_SW; + } + +- return true; ++ return GeoStructure.CELL_FLAG_SE; + } + ++ /** ++ * 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 ++ */ ++ public List findPath(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) ++ { ++ return null; ++ } ++ ++ /** ++ * Returns the instance of the {@link GeoEngine}. ++ * @return {@link GeoEngine} : The instance. ++ */ + public static GeoEngine getInstance() + { + return SingletonHolder.INSTANCE; +@@ -752,6 +947,6 @@ + + private static class SingletonHolder + { +- protected static final GeoEngine INSTANCE = new GeoEngine(); ++ protected static final GeoEngine INSTANCE = Config.PATHFINDING ? new GeoEnginePathfinding() : new GeoEngine(); + } +-} ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/GeoEnginePathfinding.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/GeoEnginePathfinding.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/GeoEnginePathfinding.java (working copy) +@@ -20,88 +20,84 @@ + import java.util.LinkedList; + import java.util.List; + import java.util.ListIterator; +-import java.util.logging.Level; +-import java.util.logging.Logger; + + import org.l2jmobius.Config; +-import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; +-import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +-import org.l2jmobius.gameserver.geoengine.pathfinding.CellNode; +-import org.l2jmobius.gameserver.geoengine.pathfinding.CellNodeBuffer; +-import org.l2jmobius.gameserver.geoengine.pathfinding.NodeLoc; +-import org.l2jmobius.gameserver.model.World; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoLocation; ++import org.l2jmobius.gameserver.geoengine.pathfinding.Node; ++import org.l2jmobius.gameserver.geoengine.pathfinding.NodeBuffer; ++import org.l2jmobius.gameserver.model.Location; + import org.l2jmobius.gameserver.model.instancezone.Instance; + + /** +- * @author -Nemesiss- ++ * @author Hasha + */ +-public class GeoEnginePathfinding ++final class GeoEnginePathfinding extends GeoEngine + { +- private static final Logger LOGGER = Logger.getLogger(GeoEnginePathfinding.class.getName()); ++ // pre-allocated buffers ++ private final BufferHolder[] _buffers; + +- private BufferInfo[] _buffers; +- + protected GeoEnginePathfinding() + { +- try ++ super(); ++ ++ final String[] array = Config.PATHFIND_BUFFERS.split(";"); ++ _buffers = new BufferHolder[array.length]; ++ int count = 0; ++ for (int i = 0; i < array.length; i++) + { +- final String[] array = Config.PATHFIND_BUFFERS.split(";"); ++ final String buf = array[i]; ++ final String[] args = buf.split("x"); + +- _buffers = new BufferInfo[array.length]; +- +- String buf; +- String[] args; +- for (int i = 0; i < array.length; i++) ++ try + { +- buf = array[i]; +- args = buf.split("x"); +- if (args.length != 2) +- { +- throw new Exception("Invalid buffer definition: " + buf); +- } +- +- _buffers[i] = new BufferInfo(Integer.parseInt(args[0]), Integer.parseInt(args[1])); ++ final int size = Integer.parseInt(args[1]); ++ count += size; ++ _buffers[i] = new BufferHolder(Integer.parseInt(args[0]), size); + } ++ catch (Exception e) ++ { ++ LOGGER.warning("GeoEnginePathfinding: Can not load buffer setting: " + buf); ++ } + } +- catch (Exception e) +- { +- LOGGER.log(Level.WARNING, "CellPathFinding: Problem during buffer init: " + e.getMessage(), e); +- throw new Error("CellPathFinding: load aborted"); +- } ++ ++ LOGGER.info("GeoEnginePathfinding: Loaded " + count + " node buffers."); + } + +- public boolean pathNodesExist(short regionoffset) ++ @Override ++ public List findPath(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + { +- return false; +- } +- +- public List findPath(int x, int y, int z, int tx, int ty, int tz, Instance instance) +- { +- final int gx = GeoEngine.getInstance().getGeoX(x); +- final int gy = GeoEngine.getInstance().getGeoY(y); +- if (!GeoEngine.getInstance().hasGeo(x, y)) ++ // get origin and check existing geo coords ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) + { + 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)) ++ ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coords ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) + { + 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)))); ++ ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // Prepare buffer for pathfinding calculations ++ final NodeBuffer buffer = getBuffer(64 + (2 * Math.max(Math.abs(gox - gtx), Math.abs(goy - gty)))); + if (buffer == null) + { + return null; + } + +- List path = null; ++ // find path ++ List path = null; + try + { +- final CellNode result = buffer.findPath(gx, gy, gz, gtx, gty, gtz); +- ++ final Node result = buffer.findPath(gox, goy, goz, gtx, gty, gtz); + if (result == null) + { + return null; +@@ -125,33 +121,45 @@ + return path; + } + +- int currentX, currentY, currentZ; +- ListIterator middlePoint; ++ // get path list iterator ++ final ListIterator point = path.listIterator(); + +- middlePoint = path.listIterator(); +- currentX = x; +- currentY = y; +- currentZ = z; ++ // get node A (origin) ++ int nodeAx = gox; ++ int nodeAy = goy; ++ short nodeAz = goz; + +- while (middlePoint.hasNext()) ++ // get node B ++ GeoLocation nodeB = (GeoLocation) point.next(); ++ ++ // iterate thought the path to optimize it ++ int count = 0; ++ while (point.hasNext() && (count++ < Config.MAX_ITERATIONS)) + { +- final AbstractNodeLoc locMiddle = middlePoint.next(); +- if (!middlePoint.hasNext()) +- { +- break; +- } ++ // get node C ++ final GeoLocation nodeC = (GeoLocation) path.get(point.nextIndex()); + +- final AbstractNodeLoc locEnd = path.get(middlePoint.nextIndex()); +- if (GeoEngine.getInstance().canMoveToTarget(currentX, currentY, currentZ, locEnd.getX(), locEnd.getY(), locEnd.getZ(), instance)) ++ // check movement from node A to node C ++ final GeoLocation loc = checkMove(nodeAx, nodeAy, nodeAz, nodeC.getGeoX(), nodeC.getGeoY(), nodeC.getZ(), instance); ++ if ((loc.getGeoX() == nodeC.getGeoX()) && (loc.getGeoY() == nodeC.getGeoY())) + { +- middlePoint.remove(); ++ // can move from node A to node C ++ ++ // remove node B ++ point.remove(); + } + else + { +- currentX = locMiddle.getX(); +- currentY = locMiddle.getY(); +- currentZ = locMiddle.getZ(); ++ // can not move from node A to node C ++ ++ // set node A (node B is part of path, update A coordinates) ++ nodeAx = nodeB.getGeoX(); ++ nodeAy = nodeB.getGeoY(); ++ nodeAz = (short) nodeB.getZ(); + } ++ ++ // set node B ++ nodeB = (GeoLocation) point.next(); + } + + return path; +@@ -158,144 +166,102 @@ + } + + /** +- * Convert geodata position to pathnode position +- * @param geo_pos +- * @return pathnode position ++ * Create list of node locations as result of calculated buffer node tree. ++ * @param node : the entry point ++ * @return List : list of node location + */ +- public short getNodePos(int geo_pos) ++ private static List constructPath(Node node) + { +- return (short) (geo_pos >> 3); // OK? +- } +- +- /** +- * Convert node position to pathnode block position +- * @param node_pos +- * @return pathnode block position (0...255) +- */ +- public short getNodeBlock(int node_pos) +- { +- return (short) (node_pos % 256); +- } +- +- public byte getRegionX(int node_pos) +- { +- return (byte) ((node_pos >> 8) + World.TILE_X_MIN); +- } +- +- public byte getRegionY(int node_pos) +- { +- return (byte) ((node_pos >> 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 node_x rx +- * @return +- */ +- public int calculateWorldX(short node_x) +- { +- return World.MAP_MIN_X + (node_x * 128) + 48; +- } +- +- /** +- * Convert pathnode y to World y position +- * @param node_y +- * @return +- */ +- public int calculateWorldY(short node_y) +- { +- return World.MAP_MIN_Y + (node_y * 128) + 48; +- } +- +- private List constructPath(AbstractNode nodeValue) +- { +- final LinkedList path = new LinkedList<>(); +- int previousDirectionX = Integer.MIN_VALUE; +- int previousDirectionY = Integer.MIN_VALUE; +- int directionX, directionY; ++ // create empty list ++ final LinkedList list = new LinkedList<>(); + +- AbstractNode node = nodeValue; +- while (node.getParent() != null) ++ // set direction X/Y ++ int dx = 0; ++ int dy = 0; ++ ++ // get target parent ++ Node target = node; ++ Node parent = target.getParent(); ++ ++ // while parent exists ++ int count = 0; ++ while ((parent != null) && (count++ < Config.MAX_ITERATIONS)) + { +- directionX = node.getLoc().getNodeX() - node.getParent().getLoc().getNodeX(); +- directionY = node.getLoc().getNodeY() - node.getParent().getLoc().getNodeY(); ++ // get parent <> target direction X/Y ++ final int nx = parent.getLoc().getGeoX() - target.getLoc().getGeoX(); ++ final int ny = parent.getLoc().getGeoY() - target.getLoc().getGeoY(); + +- // only add a new route point if moving direction changes +- if ((directionX != previousDirectionX) || (directionY != previousDirectionY)) ++ // direction has changed? ++ if ((dx != nx) || (dy != ny)) + { +- previousDirectionX = directionX; +- previousDirectionY = directionY; ++ // add node to the beginning of the list ++ list.addFirst(target.getLoc()); + +- path.addFirst(node.getLoc()); +- node.setLoc(null); ++ // update direction X/Y ++ dx = nx; ++ dy = ny; + } + +- node = node.getParent(); ++ // move to next node, set target and get its parent ++ target = parent; ++ parent = target.getParent(); + } + +- return path; ++ // return list ++ return list; + } + +- private CellNodeBuffer alloc(int size) ++ /** ++ * Provides optimize selection of the buffer. When all pre-initialized buffer are locked, creates new buffer. ++ * @param size : pre-calculated minimal required size ++ * @return NodeBuffer : buffer ++ */ ++ private final NodeBuffer getBuffer(int size) + { +- CellNodeBuffer current = null; +- for (BufferInfo i : _buffers) ++ NodeBuffer current = null; ++ for (BufferHolder holder : _buffers) + { +- if (i.mapSize >= size) ++ // Find proper size of buffer ++ if (holder._size < size) + { +- for (CellNodeBuffer buf : i.buffer) ++ continue; ++ } ++ ++ // Find unlocked NodeBuffer ++ for (NodeBuffer buffer : holder._buffer) ++ { ++ if (!buffer.isLocked()) + { +- if (buf.lock()) +- { +- current = buf; +- break; +- } ++ continue; + } +- if (current != null) +- { +- break; +- } + +- // not found, allocate temporary buffer +- current = new CellNodeBuffer(i.mapSize); +- current.lock(); +- if (i.buffer.size() < i.count) +- { +- i.buffer.add(current); +- break; +- } ++ return buffer; + } ++ ++ // NodeBuffer not found, allocate temporary buffer ++ current = new NodeBuffer(holder._size); ++ current.isLocked(); + } + + return current; + } + +- private static final class BufferInfo ++ /** ++ * NodeBuffer container with specified size and count of separate buffers. ++ */ ++ private static final class BufferHolder + { +- final int mapSize; +- final int count; +- ArrayList buffer; ++ final int _size; ++ List _buffer; + +- public BufferInfo(int size, int cnt) ++ public BufferHolder(int size, int count) + { +- mapSize = size; +- count = cnt; +- buffer = new ArrayList<>(count); ++ _size = size; ++ _buffer = new ArrayList<>(count); ++ for (int i = 0; i < count; i++) ++ { ++ _buffer.add(new NodeBuffer(size)); ++ } + } + } +- +- public static GeoEnginePathfinding getInstance() +- { +- return SingletonHolder.INSTANCE; +- } +- +- private static class SingletonHolder +- { +- protected static final GeoEnginePathfinding INSTANCE = new GeoEnginePathfinding(); +- } +-} ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java (working copy) +@@ -0,0 +1,197 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++import java.io.IOException; ++ ++/** ++ * @author Hasha ++ */ ++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 height of cell, which is closest to given coordinates.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @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 getHeightNearestOriginal(int geoX, int geoY, int worldZ); ++ ++ /** ++ * Returns the height of cell, which is first above 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, above given coordinates. ++ */ ++ public abstract short getHeightAbove(int geoX, int geoY, int worldZ); ++ ++ /** ++ * Returns the height of cell, which is first below 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, below given coordinates. ++ */ ++ public abstract short getHeightBelow(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 the NSWE flag byte of cell, which is closest to given coordinates.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @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 getNsweNearestOriginal(int geoX, int geoY, int worldZ); ++ ++ /** ++ * Returns the NSWE flag byte of cell, which is first above 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 getNsweAbove(int geoX, int geoY, int worldZ); ++ ++ /** ++ * Returns the NSWE flag byte of cell, which is first below 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 getNsweBelow(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 above given coordinates.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @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 getIndexAboveOriginal(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 index to data of the cell, which is first layer below given coordinates.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @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 getIndexBelowOriginal(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 height of cell given by cell index.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @param index : Index of the cell. ++ * @return short : Cell geodata Z coordinate, below given coordinates. ++ */ ++ public abstract short getHeightOriginal(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); ++ ++ /** ++ * Returns the NSWE flag byte of cell given by cell index.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @param index : Index of the cell. ++ * @return short : Cell geodata Z coordinate, below given coordinates. ++ */ ++ public abstract byte getNsweOriginal(int index); ++ ++ /** ++ * Sets the NSWE flag byte of cell given by cell index. ++ * @param index : Index of the cell. ++ * @param nswe : New NSWE flag byte. ++ */ ++ public abstract void setNswe(int index, byte nswe); ++ ++ /** ++ * Saves the block in L2D format to {@link BufferedOutputStream}. Used only for L2D geodata conversion. ++ * @param stream : The stream. ++ * @throws IOException : Can't save the block to steam. ++ */ ++ public abstract void saveBlock(BufferedOutputStream stream) throws IOException; ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java (working copy) +@@ -0,0 +1,252 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++import java.io.IOException; ++import java.nio.ByteBuffer; ++ ++/** ++ * @author Hasha ++ */ ++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. ++ * @param format : GeoFormat specifying format of loaded data. ++ */ ++ public BlockComplex(ByteBuffer bb, GeoFormat format) ++ { ++ // initialize buffer ++ _buffer = new byte[GeoStructure.BLOCK_CELLS * 3]; ++ ++ // load data ++ for (int i = 0; i < GeoStructure.BLOCK_CELLS; i++) ++ { ++ if (format != GeoFormat.L2D) ++ { ++ // 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); ++ } ++ else ++ { ++ // get nswe ++ final byte nswe = bb.get(); ++ _buffer[i * 3] = nswe; ++ ++ // get height ++ final short height = bb.getShort(); ++ _buffer[(i * 3) + 1] = (byte) (height & 0x00FF); ++ _buffer[(i * 3) + 2] = (byte) (height >> 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 short getHeightNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getHeightNearest(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeightAbove(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 short height = (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ ++ // check and return height ++ return height > worldZ ? height : Short.MIN_VALUE; ++ } ++ ++ @Override ++ public short getHeightBelow(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 short height = (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ ++ // check and return height ++ return height < worldZ ? height : Short.MAX_VALUE; ++ } ++ ++ @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 byte getNsweNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getNsweNearest(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public byte getNsweAbove(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 ? _buffer[index] : 0; ++ } ++ ++ @Override ++ public byte getNsweBelow(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 ? _buffer[index] : 0; ++ } ++ ++ @Override ++ public 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 getIndexAboveOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexAbove(geoX, geoY, worldZ); ++ } ++ ++ @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 int getIndexBelowOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexBelow(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeight(int index) ++ { ++ return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ } ++ ++ @Override ++ public short getHeightOriginal(int index) ++ { ++ return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ } ++ ++ @Override ++ public byte getNswe(int index) ++ { ++ return _buffer[index]; ++ } ++ ++ @Override ++ public byte getNsweOriginal(int index) ++ { ++ return _buffer[index]; ++ } ++ ++ @Override ++ public void setNswe(int index, byte nswe) ++ { ++ _buffer[index] = nswe; ++ } ++ ++ @Override ++ public void saveBlock(BufferedOutputStream stream) throws IOException ++ { ++ // write block type ++ stream.write(GeoStructure.TYPE_COMPLEX_L2D); ++ ++ // write block data ++ stream.write(_buffer, 0, GeoStructure.BLOCK_CELLS * 3); ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java (working copy) +@@ -0,0 +1,176 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++import java.io.IOException; ++import java.nio.ByteBuffer; ++ ++/** ++ * @author Hasha ++ */ ++public class BlockFlat extends ABlock ++{ ++ protected final short _height; ++ protected byte _nswe; ++ ++ /** ++ * Creates FlatBlock. ++ * @param bb : Input byte buffer. ++ * @param format : GeoFormat specifying format of loaded data. ++ */ ++ public BlockFlat(ByteBuffer bb, GeoFormat format) ++ { ++ _height = bb.getShort(); ++ _nswe = format != GeoFormat.L2D ? 0x0F : (byte) (0xFF); ++ if (format == GeoFormat.L2OFF) ++ { ++ bb.getShort(); ++ } ++ } ++ ++ @Override ++ public boolean hasGeoPos() ++ { ++ return true; ++ } ++ ++ @Override ++ public short getHeightNearest(int geoX, int geoY, int worldZ) ++ { ++ return _height; ++ } ++ ++ @Override ++ public short getHeightNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return _height; ++ } ++ ++ @Override ++ public short getHeightAbove(int geoX, int geoY, int worldZ) ++ { ++ // check and return height ++ return _height > worldZ ? _height : Short.MIN_VALUE; ++ } ++ ++ @Override ++ public short getHeightBelow(int geoX, int geoY, int worldZ) ++ { ++ // check and return height ++ return _height < worldZ ? _height : Short.MAX_VALUE; ++ } ++ ++ @Override ++ public byte getNsweNearest(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweAbove(int geoX, int geoY, int worldZ) ++ { ++ // check height and return nswe ++ return _height > worldZ ? _nswe : 0; ++ } ++ ++ @Override ++ public byte getNsweBelow(int geoX, int geoY, int worldZ) ++ { ++ // check height and return nswe ++ return _height < worldZ ? _nswe : 0; ++ } ++ ++ @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 getIndexAboveOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexAbove(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public int getIndexBelow(int geoX, int geoY, int worldZ) ++ { ++ // check height and return index ++ return _height < worldZ ? 0 : -1; ++ } ++ ++ @Override ++ public int getIndexBelowOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexBelow(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeight(int index) ++ { ++ return _height; ++ } ++ ++ @Override ++ public short getHeightOriginal(int index) ++ { ++ return _height; ++ } ++ ++ @Override ++ public byte getNswe(int index) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweOriginal(int index) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public void setNswe(int index, byte nswe) ++ { ++ _nswe = nswe; ++ } ++ ++ @Override ++ public void saveBlock(BufferedOutputStream stream) throws IOException ++ { ++ // write block type ++ stream.write(GeoStructure.TYPE_FLAT_L2D); ++ ++ // write height ++ stream.write((byte) (_height & 0x00FF)); ++ stream.write((byte) (_height >> 8)); ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java (working copy) +@@ -0,0 +1,465 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++import java.io.IOException; ++import java.nio.ByteBuffer; ++import java.nio.ByteOrder; ++import java.util.Arrays; ++ ++/** ++ * @author Hasha ++ */ ++public class BlockMultilayer extends ABlock ++{ ++ private static final int MAX_LAYERS = Byte.MAX_VALUE; ++ ++ private static ByteBuffer _temp; ++ ++ /** ++ * Initializes the temporarily buffer. ++ */ ++ public static void initialize() ++ { ++ // initialize temporarily buffer and sorting mechanism ++ _temp = ByteBuffer.allocate(GeoStructure.BLOCK_CELLS * MAX_LAYERS * 3); ++ _temp.order(ByteOrder.LITTLE_ENDIAN); ++ } ++ ++ /** ++ * Releases temporarily buffer. ++ */ ++ public static void release() ++ { ++ _temp = null; ++ } ++ ++ protected byte[] _buffer; ++ ++ /** ++ * Implicit constructor for children class. ++ */ ++ protected BlockMultilayer() ++ { ++ _buffer = null; ++ } ++ ++ /** ++ * Creates MultilayerBlock. ++ * @param bb : Input byte buffer. ++ * @param format : GeoFormat specifying format of loaded data. ++ */ ++ public BlockMultilayer(ByteBuffer bb, GeoFormat format) ++ { ++ // 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 = format != GeoFormat.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++) ++ { ++ if (format != GeoFormat.L2D) ++ { ++ // get data ++ final short data = bb.getShort(); ++ ++ // add nswe and height ++ _temp.put((byte) (data & 0x000F)); ++ _temp.putShort((short) ((short) (data & 0xFFF0) >> 1)); ++ } ++ else ++ { ++ // add nswe ++ _temp.put(bb.get()); ++ ++ // add height ++ _temp.putShort(bb.getShort()); ++ } ++ } ++ } ++ ++ // 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 short getHeightNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getHeightNearest(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeightAbove(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 height ++ if (height > worldZ) ++ { ++ return (short) height; ++ } ++ ++ // move index to next layer ++ index -= 3; ++ } ++ ++ // none layer found, return minimum value ++ return Short.MIN_VALUE; ++ } ++ ++ @Override ++ public short getHeightBelow(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 height ++ if (height < worldZ) ++ { ++ return (short) height; ++ } ++ ++ // move index to next layer ++ index += 3; ++ } ++ ++ // none layer found, return maximum value ++ return Short.MAX_VALUE; ++ } ++ ++ @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 byte getNsweNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getNsweNearest(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public byte getNsweAbove(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 nswe ++ if (height > worldZ) ++ { ++ return _buffer[index]; ++ } ++ ++ // move index to next layer ++ index -= 3; ++ } ++ ++ // none layer found, block movement ++ return 0; ++ } ++ ++ @Override ++ public byte getNsweBelow(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 nswe ++ if (height < worldZ) ++ { ++ return _buffer[index]; ++ } ++ ++ // move index to next layer ++ index += 3; ++ } ++ ++ // none layer found, block movement ++ return 0; ++ } ++ ++ @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 first layer data (first from bottom) ++ byte layers = _buffer[index++]; ++ ++ // loop though all cell layers, find closest layer ++ 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; ++ } ++ ++ // none layer found ++ return -1; ++ } ++ ++ @Override ++ public int getIndexAboveOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexAbove(geoX, geoY, worldZ); ++ } ++ ++ @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; ++ } ++ ++ // none layer found ++ return -1; ++ } ++ ++ @Override ++ public int getIndexBelowOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexBelow(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeight(int index) ++ { ++ // get height ++ return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ } ++ ++ @Override ++ public short getHeightOriginal(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]; ++ } ++ ++ @Override ++ public byte getNsweOriginal(int index) ++ { ++ // get nswe ++ return _buffer[index]; ++ } ++ ++ @Override ++ public void setNswe(int index, byte nswe) ++ { ++ // set nswe ++ _buffer[index] = nswe; ++ } ++ ++ @Override ++ public void saveBlock(BufferedOutputStream stream) throws IOException ++ { ++ // write block type ++ stream.write(GeoStructure.TYPE_MULTILAYER_L2D); ++ ++ // for each cell ++ int index = 0; ++ for (int i = 0; i < GeoStructure.BLOCK_CELLS; i++) ++ { ++ // write layers count ++ final byte layers = _buffer[index++]; ++ stream.write(layers); ++ ++ // write cell data ++ stream.write(_buffer, index, layers * 3); ++ ++ // move index to next cell ++ index += layers * 3; ++ } ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java (working copy) +@@ -0,0 +1,150 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++ ++/** ++ * @author Hasha ++ */ ++public class BlockNull extends ABlock ++{ ++ private final byte _nswe; ++ ++ public BlockNull() ++ { ++ _nswe = (byte) 0xFF; ++ } ++ ++ @Override ++ public boolean hasGeoPos() ++ { ++ return false; ++ } ++ ++ @Override ++ public short getHeightNearest(int geoX, int geoY, int worldZ) ++ { ++ return (short) worldZ; ++ } ++ ++ @Override ++ public short getHeightNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return (short) worldZ; ++ } ++ ++ @Override ++ public short getHeightAbove(int geoX, int geoY, int worldZ) ++ { ++ return (short) worldZ; ++ } ++ ++ @Override ++ public short getHeightBelow(int geoX, int geoY, int worldZ) ++ { ++ return (short) worldZ; ++ } ++ ++ @Override ++ public byte getNsweNearest(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweAbove(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweBelow(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) ++ { ++ return 0; ++ } ++ ++ @Override ++ public int getIndexAboveOriginal(int geoX, int geoY, int worldZ) ++ { ++ return 0; ++ } ++ ++ @Override ++ public int getIndexBelow(int geoX, int geoY, int worldZ) ++ { ++ return 0; ++ } ++ ++ @Override ++ public int getIndexBelowOriginal(int geoX, int geoY, int worldZ) ++ { ++ return 0; ++ } ++ ++ @Override ++ public short getHeight(int index) ++ { ++ return 0; ++ } ++ ++ @Override ++ public short getHeightOriginal(int index) ++ { ++ return 0; ++ } ++ ++ @Override ++ public byte getNswe(int index) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweOriginal(int index) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public void setNswe(int index, byte nswe) ++ { ++ } ++ ++ @Override ++ public void saveBlock(BufferedOutputStream stream) ++ { ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java (nonexistent) +@@ -1,48 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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() +- { +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/ComplexBlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/ComplexBlock.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/ComplexBlock.java (nonexistent) +@@ -1,77 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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; +- +-/** +- * @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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/FlatBlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/FlatBlock.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/FlatBlock.java (nonexistent) +@@ -1,56 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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; +- +-/** +- * @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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/GeoFormat.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/GeoFormat.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/GeoFormat.java (working copy) +@@ -0,0 +1,39 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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 Hasha ++ */ ++public enum GeoFormat ++{ ++ L2J("%d_%d.l2j"), ++ L2OFF("%d_%d_conv.dat"), ++ L2D("%d_%d.l2d"); ++ ++ private final String _filename; ++ ++ private GeoFormat(String filename) ++ { ++ _filename = filename; ++ } ++ ++ public String getFilename() ++ { ++ return _filename; ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/GeoLocation.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/GeoLocation.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/GeoLocation.java (working copy) +@@ -0,0 +1,67 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.geoengine.GeoEngine; ++import org.l2jmobius.gameserver.model.Location; ++ ++/** ++ * @author Hasha ++ */ ++public class GeoLocation extends Location ++{ ++ private byte _nswe; ++ ++ public GeoLocation(int x, int y, int z) ++ { ++ super(x, y, GeoEngine.getInstance().getHeightNearest(x, y, z)); ++ _nswe = GeoEngine.getInstance().getNsweNearest(x, y, z); ++ } ++ ++ public void set(int x, int y, short z) ++ { ++ super.setXYZ(x, y, GeoEngine.getInstance().getHeightNearest(x, y, z)); ++ _nswe = GeoEngine.getInstance().getNsweNearest(x, y, z); ++ } ++ ++ public int getGeoX() ++ { ++ return _x; ++ } ++ ++ public int getGeoY() ++ { ++ return _y; ++ } ++ ++ @Override ++ public int getX() ++ { ++ return GeoEngine.getWorldX(_x); ++ } ++ ++ @Override ++ public int getY() ++ { ++ return GeoEngine.getWorldY(_y); ++ } ++ ++ public byte getNSWE() ++ { ++ return _nswe; ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java (working copy) +@@ -0,0 +1,70 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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; ++ ++/** ++ * @author Hasha ++ */ ++public class GeoStructure ++{ ++ // cells ++ public static final byte CELL_FLAG_E = 1 << 0; ++ public static final byte CELL_FLAG_W = 1 << 1; ++ public static final byte CELL_FLAG_S = 1 << 2; ++ public static final byte CELL_FLAG_N = 1 << 3; ++ public static final byte CELL_FLAG_SE = 1 << 4; ++ public static final byte CELL_FLAG_SW = 1 << 5; ++ public static final byte CELL_FLAG_NE = 1 << 6; ++ public static final byte CELL_FLAG_NW = (byte) (1 << 7); ++ ++ public static final int CELL_HEIGHT = 8; ++ public static final int CELL_IGNORE_HEIGHT = CELL_HEIGHT * 6; ++ ++ // blocks ++ public static final byte TYPE_FLAT_L2J_L2OFF = 0; ++ public static final byte TYPE_FLAT_L2D = (byte) 0xD0; ++ public static final byte TYPE_COMPLEX_L2J = 1; ++ public static final byte TYPE_COMPLEX_L2OFF = 0x40; ++ public static final byte TYPE_COMPLEX_L2D = (byte) 0xD1; ++ 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) ++ public static final byte TYPE_MULTILAYER_L2D = (byte) 0xD2; ++ ++ 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; ++ ++ // regions ++ 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; ++ ++ // global geodata ++ private static final int GEO_REGIONS_X = ((World.TILE_X_MAX - World.TILE_X_MIN) + 1); ++ private 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 +Index: java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java (nonexistent) +@@ -1,42 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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); +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/IGeoObject.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/IGeoObject.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/IGeoObject.java (working copy) +@@ -0,0 +1,53 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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 Hasha ++ */ ++public interface IGeoObject ++{ ++ /** ++ * Returns geodata X coordinate of the {@link IGeoObject}. ++ * @return int : Geodata X coordinate. ++ */ ++ int getGeoX(); ++ ++ /** ++ * Returns geodata Y coordinate of the {@link IGeoObject}. ++ * @return int : Geodata Y coordinate. ++ */ ++ int getGeoY(); ++ ++ /** ++ * Returns geodata Z coordinate of the {@link IGeoObject}. ++ * @return int : Geodata Z coordinate. ++ */ ++ int getGeoZ(); ++ ++ /** ++ * Returns height of the {@link IGeoObject}. ++ * @return int : Height. ++ */ ++ int getHeight(); ++ ++ /** ++ * Returns {@link IGeoObject} data. ++ * @return byte[][] : {@link IGeoObject} data. ++ */ ++ byte[][] getObjectGeoData(); ++} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java (nonexistent) +@@ -1,47 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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(); +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/MultilayerBlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/MultilayerBlock.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/MultilayerBlock.java (nonexistent) +@@ -1,183 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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; +- +-/** +- * @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); +- +- // 1 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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/NullRegion.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/NullRegion.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/NullRegion.java (nonexistent) +@@ -1,55 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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 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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/Region.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/Region.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/Region.java (nonexistent) +@@ -1,92 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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; +- +-/** +- * @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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java (nonexistent) +@@ -1,87 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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 T _loc; +- private AbstractNode _parent; +- +- public AbstractNode(T loc) +- { +- _loc = loc; +- } +- +- public void setParent(AbstractNode p) +- { +- _parent = p; +- } +- +- public AbstractNode getParent() +- { +- return _parent; +- } +- +- public T getLoc() +- { +- return _loc; +- } +- +- public void setLoc(T l) +- { +- _loc = l; +- } +- +- @Override +- public int hashCode() +- { +- final int prime = 31; +- int result = 1; +- result = (prime * result) + ((_loc == null) ? 0 : _loc.hashCode()); +- return result; +- } +- +- @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 +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java (nonexistent) +@@ -1,33 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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 int getNodeX(); +- +- public abstract int getNodeY(); +-} +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNode.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNode.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNode.java (nonexistent) +@@ -1,67 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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 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 +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNodeBuffer.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNodeBuffer.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNodeBuffer.java (nonexistent) +@@ -1,348 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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.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 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) +- { +- _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(); +- } +- +- 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 void getNeighbors() +- { +- if (!_current.getLoc().canGoAll()) +- { +- 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); +- } +- +- // SouthEast +- if ((nodeE != null) && (nodeS != null)) +- { +- if (nodeE.getLoc().canGoSouth() && nodeS.getLoc().canGoEast()) +- { +- addNode(x + 1, y + 1, z, true); +- } +- } +- +- // SouthWest +- if ((nodeS != null) && (nodeW != null)) +- { +- if (nodeW.getLoc().canGoSouth() && nodeS.getLoc().canGoWest()) +- { +- addNode(x - 1, y + 1, z, true); +- } +- } +- +- // NorthEast +- if ((nodeN != null) && (nodeE != null)) +- { +- if (nodeE.getLoc().canGoNorth() && nodeN.getLoc().canGoEast()) +- { +- addNode(x + 1, y - 1, z, true); +- } +- } +- +- // NorthWest +- if ((nodeN != null) && (nodeW != null)) +- { +- if (nodeW.getLoc().canGoNorth() && nodeN.getLoc().canGoWest()) +- { +- addNode(x - 1, y - 1, z, true); +- } +- } +- } +- +- private 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(); +- // reinit node if needed +- if (result.getLoc() != null) +- { +- result.getLoc().set(x, y, z); +- } +- else +- { +- result.setLoc(new NodeLoc(x, y, z)); +- } +- } +- +- return result; +- } +- +- private 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 boolean isHighWeight(int x, int y, int z) +- { +- final CellNode result = getNode(x, y, z); +- if (result == null) +- { +- return true; +- } +- +- if (!result.getLoc().canGoAll()) +- { +- return true; +- } +- if (Math.abs(result.getLoc().getZ() - z) > 16) +- { +- return true; +- } +- +- return false; +- } +- +- private 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 +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java (working copy) +@@ -0,0 +1,87 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.geodata.GeoLocation; ++ ++/** ++ * @author Hasha ++ */ ++public class Node ++{ ++ // node coords and nswe flag ++ private GeoLocation _loc; ++ ++ // node parent (for reverse path construction) ++ private Node _parent; ++ // node child (for moving over nodes during iteration) ++ private Node _child; ++ ++ // node G cost (movement cost = parent movement cost + current movement cost) ++ private double _cost = -1000; ++ ++ public void setLoc(int x, int y, int z) ++ { ++ _loc = new GeoLocation(x, y, z); ++ } ++ ++ public GeoLocation getLoc() ++ { ++ return _loc; ++ } ++ ++ public void setParent(Node parent) ++ { ++ _parent = parent; ++ } ++ ++ public Node getParent() ++ { ++ return _parent; ++ } ++ ++ public void setChild(Node child) ++ { ++ _child = child; ++ } ++ ++ public Node getChild() ++ { ++ return _child; ++ } ++ ++ public void setCost(double cost) ++ { ++ _cost = cost; ++ } ++ ++ public double getCost() ++ { ++ return _cost; ++ } ++ ++ public void free() ++ { ++ // reset node location ++ _loc = null; ++ ++ // reset node parent, child and cost ++ _parent = null; ++ _child = null; ++ _cost = -1000; ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java (working copy) +@@ -0,0 +1,306 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.concurrent.locks.ReentrantLock; ++ ++import org.l2jmobius.Config; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; ++ ++/** ++ * @author DS, Hasha; Credits to Diamond ++ */ ++public class NodeBuffer ++{ ++ private final ReentrantLock _lock = new ReentrantLock(); ++ private final int _size; ++ private final Node[][] _buffer; ++ ++ // center coordinates ++ private int _cx = 0; ++ private int _cy = 0; ++ ++ // target coordinates ++ private int _gtx = 0; ++ private int _gty = 0; ++ private short _gtz = 0; ++ ++ private Node _current = null; ++ ++ /** ++ * Constructor of NodeBuffer. ++ * @param size : one dimension size of buffer ++ */ ++ public NodeBuffer(int size) ++ { ++ // set size ++ _size = size; ++ ++ // initialize buffer ++ _buffer = new Node[size][size]; ++ for (int x = 0; x < size; x++) ++ { ++ for (int y = 0; y < size; y++) ++ { ++ _buffer[x][y] = 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 Node : first node of path ++ */ ++ public Node findPath(int gox, int goy, short goz, int gtx, int gty, short gtz) ++ { ++ // set coordinates (middle of the line (gox,goy) - (gtx,gty), will be in the center of the buffer) ++ _cx = gox + ((gtx - gox - _size) / 2); ++ _cy = goy + ((gty - goy - _size) / 2); ++ ++ _gtx = gtx; ++ _gty = gty; ++ _gtz = gtz; ++ ++ _current = getNode(gox, goy, goz); ++ _current.setCost(getCostH(gox, goy, goz)); ++ ++ int count = 0; ++ do ++ { ++ // reached target? ++ if ((_current.getLoc().getGeoX() == _gtx) && (_current.getLoc().getGeoY() == _gty) && (Math.abs(_current.getLoc().getZ() - _gtz) < 8)) ++ { ++ return _current; ++ } ++ ++ // expand current node ++ expand(); ++ ++ // move pointer ++ _current = _current.getChild(); ++ } ++ while ((_current != null) && (count++ < Config.MAX_ITERATIONS)); ++ ++ return null; ++ } ++ ++ public boolean isLocked() ++ { ++ return _lock.tryLock(); ++ } ++ ++ public void free() ++ { ++ _current = null; ++ ++ for (Node[] nodes : _buffer) ++ { ++ for (Node node : nodes) ++ { ++ if (node.getLoc() != null) ++ { ++ node.free(); ++ } ++ } ++ } ++ ++ _lock.unlock(); ++ } ++ ++ /** ++ * Check _current Node and add its neighbors to the buffer. ++ */ ++ private final void expand() ++ { ++ // can't move anywhere, don't expand ++ final byte nswe = _current.getLoc().getNSWE(); ++ if (nswe == 0) ++ { ++ return; ++ } ++ ++ // get geo coords of the node to be expanded ++ final int x = _current.getLoc().getGeoX(); ++ final int y = _current.getLoc().getGeoY(); ++ final short z = (short) _current.getLoc().getZ(); ++ ++ // can move north, expand ++ if ((nswe & GeoStructure.CELL_FLAG_N) != 0) ++ { ++ addNode(x, y - 1, z, Config.BASE_WEIGHT); ++ } ++ ++ // can move south, expand ++ if ((nswe & GeoStructure.CELL_FLAG_S) != 0) ++ { ++ addNode(x, y + 1, z, Config.BASE_WEIGHT); ++ } ++ ++ // can move west, expand ++ if ((nswe & GeoStructure.CELL_FLAG_W) != 0) ++ { ++ addNode(x - 1, y, z, Config.BASE_WEIGHT); ++ } ++ ++ // can move east, expand ++ if ((nswe & GeoStructure.CELL_FLAG_E) != 0) ++ { ++ addNode(x + 1, y, z, Config.BASE_WEIGHT); ++ } ++ ++ // can move north-west, expand ++ if ((nswe & GeoStructure.CELL_FLAG_NW) != 0) ++ { ++ addNode(x - 1, y - 1, z, Config.DIAGONAL_WEIGHT); ++ } ++ ++ // can move north-east, expand ++ if ((nswe & GeoStructure.CELL_FLAG_NE) != 0) ++ { ++ addNode(x + 1, y - 1, z, Config.DIAGONAL_WEIGHT); ++ } ++ ++ // can move south-west, expand ++ if ((nswe & GeoStructure.CELL_FLAG_SW) != 0) ++ { ++ addNode(x - 1, y + 1, z, Config.DIAGONAL_WEIGHT); ++ } ++ ++ // can move south-east, expand ++ if ((nswe & GeoStructure.CELL_FLAG_SE) != 0) ++ { ++ addNode(x + 1, y + 1, z, Config.DIAGONAL_WEIGHT); ++ } ++ } ++ ++ /** ++ * Returns node, if it exists in buffer. ++ * @param x : node X coord ++ * @param y : node Y coord ++ * @param z : node Z coord ++ * @return Node : node, if exits in buffer ++ */ ++ private final Node getNode(int x, int y, short z) ++ { ++ // check node X out of coordinates ++ final int ix = x - _cx; ++ if ((ix < 0) || (ix >= _size)) ++ { ++ return null; ++ } ++ ++ // check node Y out of coordinates ++ final int iy = y - _cy; ++ if ((iy < 0) || (iy >= _size)) ++ { ++ return null; ++ } ++ ++ // get node ++ final Node result = _buffer[ix][iy]; ++ ++ // check and update ++ if (result.getLoc() == null) ++ { ++ result.setLoc(x, y, z); ++ } ++ ++ // return node ++ return result; ++ } ++ ++ /** ++ * Add node given by coordinates to the buffer. ++ * @param x : geo X coord ++ * @param y : geo Y coord ++ * @param z : geo Z coord ++ * @param weight : weight of movement to new node ++ */ ++ private final void addNode(int x, int y, short z, int weight) ++ { ++ // get node to be expanded ++ final Node node = getNode(x, y, z); ++ if (node == null) ++ { ++ return; ++ } ++ ++ // Z distance between nearby cells is higher than cell size ++ if (node.getLoc().getZ() > (z + (2 * GeoStructure.CELL_HEIGHT))) ++ { ++ return; ++ } ++ ++ // node was already expanded, return ++ if (node.getCost() >= 0) ++ { ++ return; ++ } ++ ++ node.setParent(_current); ++ if (node.getLoc().getNSWE() != (byte) 0xFF) ++ { ++ node.setCost(getCostH(x, y, node.getLoc().getZ()) + (weight * Config.OBSTACLE_MULTIPLIER)); ++ } ++ else ++ { ++ node.setCost(getCostH(x, y, node.getLoc().getZ()) + weight); ++ } ++ ++ Node current = _current; ++ int count = 0; ++ while ((current.getChild() != null) && (count < (Config.MAX_ITERATIONS * 4))) ++ { ++ count++; ++ if (current.getChild().getCost() > node.getCost()) ++ { ++ node.setChild(current.getChild()); ++ break; ++ } ++ current = current.getChild(); ++ } ++ ++ if (count >= (Config.MAX_ITERATIONS * 4)) ++ { ++ System.err.println("Pathfinding: too long loop detected, cost:" + node.getCost()); ++ } ++ ++ current.setChild(node); ++ } ++ ++ /** ++ * @param x : node X coord ++ * @param y : node Y coord ++ * @param i : node Z coord ++ * @return double : node cost ++ */ ++ private final double getCostH(int x, int y, int i) ++ { ++ final int dX = x - _gtx; ++ final int dY = y - _gty; ++ final int dZ = (i - _gtz) / GeoStructure.CELL_HEIGHT; ++ ++ // return (Math.abs(dX) + Math.abs(dY) + Math.abs(dZ)) * Config.HEURISTIC_WEIGHT; // Manhattan distance ++ return Math.sqrt((dX * dX) + (dY * dY) + (dZ * dZ)) * Config.HEURISTIC_WEIGHT; // Direct distance ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeLoc.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeLoc.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeLoc.java (nonexistent) +@@ -1,183 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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.Cell; +- +-/** +- * @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 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 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; +- // return super.hashCode(); +- } +- +- @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; +- if (_x != other._x) +- { +- return false; +- } +- if (_y != other._y) +- { +- return false; +- } +- if (_goNorth != other._goNorth) +- { +- return false; +- } +- if (_goEast != other._goEast) +- { +- return false; +- } +- if (_goSouth != other._goSouth) +- { +- return false; +- } +- if (_goWest != other._goWest) +- { +- return false; +- } +- if (_geoHeight != other._geoHeight) +- { +- return false; +- } +- return true; +- } +-} +Index: java/org/l2jmobius/gameserver/model/actor/Creature.java +=================================================================== +--- java/org/l2jmobius/gameserver/model/actor/Creature.java (revision 8399) ++++ java/org/l2jmobius/gameserver/model/actor/Creature.java (working copy) +@@ -66,8 +66,6 @@ + import org.l2jmobius.gameserver.enums.TeleportWhereType; + import org.l2jmobius.gameserver.enums.UserInfoType; + import org.l2jmobius.gameserver.geoengine.GeoEngine; +-import org.l2jmobius.gameserver.geoengine.GeoEnginePathfinding; +-import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; + import org.l2jmobius.gameserver.instancemanager.IdManager; + import org.l2jmobius.gameserver.instancemanager.MapRegionManager; + import org.l2jmobius.gameserver.instancemanager.QuestManager; +@@ -2577,7 +2575,7 @@ + + public boolean disregardingGeodata; + public int onGeodataPathIndex; +- public List geoPath; ++ public List geoPath; + public int geoPathAccurateTx; + public int geoPathAccurateTy; + public int geoPathGtx; +@@ -3466,7 +3464,7 @@ + if (((originalDistance - distance) > 30) && !isControlBlocked() && !isInVehicle) + { + // Path calculation -- overrides previous movement check +- m.geoPath = GeoEnginePathfinding.getInstance().findPath(curX, curY, curZ, originalX, originalY, originalZ, getInstanceWorld()); ++ m.geoPath = GeoEngine.getInstance().findPath(curX, curY, curZ, originalX, originalY, originalZ, getInstanceWorld()); + if ((m.geoPath == null) || (m.geoPath.size() < 2)) // No path found + { + if (isPlayer() && !_isFlying && !isInWater) +Index: java/org/l2jmobius/gameserver/model/actor/instance/PlayerInstance.java +=================================================================== +--- java/org/l2jmobius/gameserver/model/actor/instance/PlayerInstance.java (revision 8397) ++++ java/org/l2jmobius/gameserver/model/actor/instance/PlayerInstance.java (working copy) +@@ -12704,7 +12704,7 @@ + { + if (Config.CORRECT_PLAYER_Z) + { +- final int nearestZ = GeoEngine.getInstance().getHigherHeight(getX(), getY(), getZ()); ++ final int nearestZ = GeoEngine.getInstance().getHeightNearest(getX(), getY(), getZ()); + if (getZ() < nearestZ) + { + teleToLocation(new Location(getX(), getY(), nearestZ)); +Index: java/org/l2jmobius/gameserver/util/GeoUtils.java +=================================================================== +--- java/org/l2jmobius/gameserver/util/GeoUtils.java (revision 8397) ++++ java/org/l2jmobius/gameserver/util/GeoUtils.java (working copy) +@@ -19,7 +19,7 @@ + import java.awt.Color; + + import org.l2jmobius.gameserver.geoengine.GeoEngine; +-import org.l2jmobius.gameserver.geoengine.geodata.Cell; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; + import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance; + import org.l2jmobius.gameserver.network.serverpackets.ExServerPrimitive; + +@@ -26,25 +26,25 @@ + /** + * @author HorridoJoho + */ +-public final class GeoUtils ++public class GeoUtils + { + public static void debug2DLine(PlayerInstance player, int x, int y, int tx, int ty, int z) + { +- final int gx = GeoEngine.getInstance().getGeoX(x); +- final int gy = GeoEngine.getInstance().getGeoY(y); ++ final int gx = GeoEngine.getGeoX(x); ++ final int gy = GeoEngine.getGeoY(y); + +- final int tgx = GeoEngine.getInstance().getGeoX(tx); +- final int tgy = GeoEngine.getInstance().getGeoY(ty); ++ final int tgx = GeoEngine.getGeoX(tx); ++ final int tgy = GeoEngine.getGeoY(ty); + + final ExServerPrimitive prim = new ExServerPrimitive("Debug2DLine", x, y, z); +- prim.addLine(Color.BLUE, GeoEngine.getInstance().getWorldX(gx), GeoEngine.getInstance().getWorldY(gy), z, GeoEngine.getInstance().getWorldX(tgx), GeoEngine.getInstance().getWorldY(tgy), z); ++ prim.addLine(Color.BLUE, GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), z, GeoEngine.getWorldX(tgx), GeoEngine.getWorldY(tgy), z); + + final LinePointIterator iter = new LinePointIterator(gx, gy, tgx, tgy); + + while (iter.next()) + { +- final int wx = GeoEngine.getInstance().getWorldX(iter.x()); +- final int wy = GeoEngine.getInstance().getWorldY(iter.y()); ++ final int wx = GeoEngine.getWorldX(iter.x()); ++ final int wy = GeoEngine.getWorldY(iter.y()); + + prim.addPoint(Color.RED, wx, wy, z); + } +@@ -53,21 +53,21 @@ + + public static void debug3DLine(PlayerInstance player, int x, int y, int z, int tx, int ty, int tz) + { +- final int gx = GeoEngine.getInstance().getGeoX(x); +- final int gy = GeoEngine.getInstance().getGeoY(y); ++ final int gx = GeoEngine.getGeoX(x); ++ final int gy = GeoEngine.getGeoY(y); + +- final int tgx = GeoEngine.getInstance().getGeoX(tx); +- final int tgy = GeoEngine.getInstance().getGeoY(ty); ++ final int tgx = GeoEngine.getGeoX(tx); ++ final int tgy = GeoEngine.getGeoY(ty); + + final ExServerPrimitive prim = new ExServerPrimitive("Debug3DLine", x, y, z); +- prim.addLine(Color.BLUE, GeoEngine.getInstance().getWorldX(gx), GeoEngine.getInstance().getWorldY(gy), z, GeoEngine.getInstance().getWorldX(tgx), GeoEngine.getInstance().getWorldY(tgy), tz); ++ prim.addLine(Color.BLUE, GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), z, GeoEngine.getWorldX(tgx), GeoEngine.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.getInstance().getWorldX(prevX); +- int wy = GeoEngine.getInstance().getWorldY(prevY); ++ int wx = GeoEngine.getWorldX(prevX); ++ int wy = GeoEngine.getWorldY(prevY); + int wz = iter.z(); + prim.addPoint(Color.RED, wx, wy, wz); + +@@ -78,8 +78,8 @@ + + if ((curX != prevX) || (curY != prevY)) + { +- wx = GeoEngine.getInstance().getWorldX(curX); +- wy = GeoEngine.getInstance().getWorldY(curY); ++ wx = GeoEngine.getWorldX(curX); ++ wy = GeoEngine.getWorldY(curY); + wz = iter.z(); + + prim.addPoint(Color.RED, wx, wy, wz); +@@ -93,7 +93,7 @@ + + private static Color getDirectionColor(int x, int y, int z, int nswe) + { +- if (GeoEngine.getInstance().checkNearestNswe(x, y, z, nswe)) ++ if ((GeoEngine.getInstance().getNsweNearest(x, y, z) & nswe) == nswe) + { + return Color.GREEN; + } +@@ -109,9 +109,8 @@ + int iPacket = 0; + + ExServerPrimitive exsp = null; +- final GeoEngine ge = GeoEngine.getInstance(); +- final int playerGx = ge.getGeoX(player.getX()); +- final int playerGy = ge.getGeoY(player.getY()); ++ final int playerGx = GeoEngine.getGeoX(player.getX()); ++ final int playerGy = GeoEngine.getGeoY(player.getY()); + for (int dx = -geoRadius; dx <= geoRadius; ++dx) + { + for (int dy = -geoRadius; dy <= geoRadius; ++dy) +@@ -135,12 +134,12 @@ + final int gx = playerGx + dx; + final int gy = playerGy + dy; + +- final int x = ge.getWorldX(gx); +- final int y = ge.getWorldY(gy); +- final int z = ge.getNearestZ(gx, gy, player.getZ()); ++ final int x = GeoEngine.getWorldX(gx); ++ final int y = GeoEngine.getWorldY(gy); ++ final int z = GeoEngine.getInstance().getHeightNearest(gx, gy, player.getZ()); + + // north arrow +- Color col = getDirectionColor(gx, gy, z, Cell.NSWE_NORTH); ++ Color col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_N); + 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); +@@ -147,7 +146,7 @@ + exsp.addLine(col, x - 4, y - 4, z, x + 4, y - 4, z); + + // east arrow +- col = getDirectionColor(gx, gy, z, Cell.NSWE_EAST); ++ col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_E); + 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); +@@ -154,13 +153,13 @@ + exsp.addLine(col, x + 4, y - 4, z, x + 4, y + 4, z); + + // south arrow +- col = getDirectionColor(gx, gy, z, Cell.NSWE_SOUTH); ++ col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_S); + 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, Cell.NSWE_WEST); ++ col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_W); + 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); +@@ -188,15 +187,15 @@ + { + if (y > lastY) + { +- return Cell.NSWE_SOUTH_EAST; ++ return GeoStructure.CELL_FLAG_SE; // Direction.SOUTH_EAST; + } + else if (y < lastY) + { +- return Cell.NSWE_NORTH_EAST; ++ return GeoStructure.CELL_FLAG_NE; // Direction.NORTH_EAST; + } + else + { +- return Cell.NSWE_EAST; ++ return GeoStructure.CELL_FLAG_E; // Direction.EAST; + } + } + else if (x < lastX) // west +@@ -203,26 +202,27 @@ + { + if (y > lastY) + { +- return Cell.NSWE_SOUTH_WEST; ++ return GeoStructure.CELL_FLAG_SW; // Direction.SOUTH_WEST; + } + else if (y < lastY) + { +- return Cell.NSWE_NORTH_WEST; ++ return GeoStructure.CELL_FLAG_NW; // Direction.NORTH_WEST; + } + else + { +- return Cell.NSWE_WEST; ++ return GeoStructure.CELL_FLAG_W; // Direction.WEST; + } + } +- else // unchanged x ++ else ++ // unchanged x + { + if (y > lastY) + { +- return Cell.NSWE_SOUTH; ++ return GeoStructure.CELL_FLAG_S; // Direction.SOUTH; + } + else if (y < lastY) + { +- return Cell.NSWE_NORTH; ++ return GeoStructure.CELL_FLAG_N; // Direction.NORTH; + } + else + { +Index: java/org/l2jmobius/tools/geodataconverter/GeoDataConverter.java +=================================================================== +--- java/org/l2jmobius/tools/geodataconverter/GeoDataConverter.java (nonexistent) ++++ java/org/l2jmobius/tools/geodataconverter/GeoDataConverter.java (working copy) +@@ -0,0 +1,386 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ */ ++package org.l2jmobius.tools.geodataconverter; ++ ++import java.io.BufferedOutputStream; ++import java.io.File; ++import java.io.FileOutputStream; ++import java.io.RandomAccessFile; ++import java.nio.ByteOrder; ++import java.nio.MappedByteBuffer; ++import java.nio.channels.FileChannel; ++import java.util.Scanner; ++ ++import org.l2jmobius.Config; ++import org.l2jmobius.commons.util.PropertiesParser; ++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.GeoFormat; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; ++import org.l2jmobius.gameserver.model.World; ++ ++/** ++ * @author Hasha ++ */ ++public class GeoDataConverter ++{ ++ private static GeoFormat _format; ++ private static ABlock[][] _blocks; ++ ++ public static void main(String[] args) ++ { ++ // initialize config ++ loadGeoengineConfigs(); ++ ++ // get geodata format ++ String type = ""; ++ try (Scanner scn = new Scanner(System.in)) ++ { ++ while (!(type.equalsIgnoreCase("J") || type.equalsIgnoreCase("O") || type.equalsIgnoreCase("E"))) ++ { ++ System.out.println("GeoDataConverter: Select source geodata type:"); ++ System.out.println(" J: L2J (e.g. 23_22.l2j)"); ++ System.out.println(" O: L2OFF (e.g. 23_22_conv.dat)"); ++ System.out.println(" E: Exit"); ++ System.out.print("Choice: "); ++ type = scn.next(); ++ } ++ } ++ if (type.equalsIgnoreCase("E")) ++ { ++ System.exit(0); ++ } ++ ++ _format = type.equalsIgnoreCase("J") ? GeoFormat.L2J : GeoFormat.L2OFF; ++ ++ // start conversion ++ System.out.println("GeoDataConverter: Converting all " + _format + " files."); ++ ++ // initialize geodata container ++ _blocks = new ABlock[GeoStructure.REGION_BLOCKS_X][GeoStructure.REGION_BLOCKS_Y]; ++ ++ // initialize multilayer temporarily buffer ++ BlockMultilayer.initialize(); ++ ++ // load geo files ++ int converted = 0; ++ for (int rx = World.TILE_X_MIN; rx <= World.TILE_X_MAX; rx++) ++ { ++ for (int ry = World.TILE_Y_MIN; ry <= World.TILE_Y_MAX; ry++) ++ { ++ final String input = String.format(_format.getFilename(), rx, ry); ++ final String filepath = Config.GEODATA_PATH; ++ final File f = new File(filepath + input); ++ if (f.exists() && !f.isDirectory()) ++ { ++ // load geodata ++ if (!loadGeoBlocks(input)) ++ { ++ System.out.println("GeoDataConverter: Unable to load " + input + " region file."); ++ continue; ++ } ++ ++ // recalculate nswe ++ if (!recalculateNswe()) ++ { ++ System.out.println("GeoDataConverter: Unable to convert " + input + " region file."); ++ continue; ++ } ++ ++ // save geodata ++ final String output = String.format(GeoFormat.L2D.getFilename(), rx, ry); ++ if (!saveGeoBlocks(output)) ++ { ++ System.out.println("GeoDataConverter: Unable to save " + output + " region file."); ++ continue; ++ } ++ ++ converted++; ++ System.out.println("GeoDataConverter: Created " + output + " region file."); ++ } ++ } ++ } ++ System.out.println("GeoDataConverter: Converted " + converted + " " + _format + " to L2D region file(s)."); ++ ++ // release multilayer block temporarily buffer ++ BlockMultilayer.release(); ++ } ++ ++ /** ++ * Loads geo blocks from buffer of the region file. ++ * @param filename : The name of the to load. ++ * @return boolean : True when successful. ++ */ ++ private static boolean loadGeoBlocks(String filename) ++ { ++ // region file is load-able, try to load it ++ try (RandomAccessFile raf = new RandomAccessFile(Config.GEODATA_PATH + filename, "r"); ++ FileChannel fc = raf.getChannel()) ++ { ++ final MappedByteBuffer buffer = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()).load(); ++ buffer.order(ByteOrder.LITTLE_ENDIAN); ++ ++ // load 18B header for L2off geodata (1st and 2nd byte...region X and Y) ++ if (_format == GeoFormat.L2OFF) ++ { ++ for (int i = 0; i < 18; i++) ++ { ++ buffer.get(); ++ } ++ } ++ ++ // 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 (_format == GeoFormat.L2J) ++ { ++ // get block type ++ final byte type = buffer.get(); ++ ++ // load block according to block type ++ switch (type) ++ { ++ case GeoStructure.TYPE_FLAT_L2J_L2OFF: ++ { ++ _blocks[ix][iy] = new BlockFlat(buffer, _format); ++ break; ++ } ++ case GeoStructure.TYPE_COMPLEX_L2J: ++ { ++ _blocks[ix][iy] = new BlockComplex(buffer, _format); ++ break; ++ } ++ case GeoStructure.TYPE_MULTILAYER_L2J: ++ { ++ _blocks[ix][iy] = new BlockMultilayer(buffer, _format); ++ break; ++ } ++ default: ++ { ++ throw new IllegalArgumentException("Unknown block type: " + type); ++ } ++ } ++ } ++ else ++ { ++ // get block type ++ final short type = buffer.getShort(); ++ ++ // load block according to block type ++ switch (type) ++ { ++ case GeoStructure.TYPE_FLAT_L2J_L2OFF: ++ { ++ _blocks[ix][iy] = new BlockFlat(buffer, _format); ++ break; ++ } ++ case GeoStructure.TYPE_COMPLEX_L2OFF: ++ { ++ _blocks[ix][iy] = new BlockComplex(buffer, _format); ++ break; ++ } ++ default: ++ { ++ _blocks[ix][iy] = new BlockMultilayer(buffer, _format); ++ break; ++ } ++ } ++ } ++ } ++ } ++ ++ if (buffer.remaining() > 0) ++ { ++ System.out.println("GeoDataConverter: Region file " + filename + " can be corrupted, remaining " + buffer.remaining() + " bytes to read."); ++ return false; ++ } ++ ++ return true; ++ } ++ catch (Exception e) ++ { ++ System.out.println("GeoDataConverter: Error while loading " + filename + " region file."); ++ return false; ++ } ++ } ++ ++ /** ++ * Recalculate diagonal flags for the region file. ++ * @return boolean : True when successful. ++ */ ++ private static boolean recalculateNswe() ++ { ++ try ++ { ++ for (int x = 0; x < GeoStructure.REGION_CELLS_X; x++) ++ { ++ for (int y = 0; y < GeoStructure.REGION_CELLS_Y; y++) ++ { ++ // get block ++ final ABlock block = _blocks[x / GeoStructure.BLOCK_CELLS_X][y / GeoStructure.BLOCK_CELLS_Y]; ++ ++ // skip flat blocks ++ if (block instanceof BlockFlat) ++ { ++ continue; ++ } ++ ++ // for complex and multilayer blocks go though all layers ++ short height = Short.MAX_VALUE; ++ int index; ++ while ((index = block.getIndexBelow(x, y, height)) != -1) ++ { ++ // get height and nswe ++ height = block.getHeight(index); ++ byte nswe = block.getNswe(index); ++ ++ // update nswe with diagonal flags ++ nswe = updateNsweBelow(x, y, height, nswe); ++ ++ // set nswe of the cell ++ block.setNswe(index, nswe); ++ } ++ } ++ } ++ ++ return true; ++ } ++ catch (Exception e) ++ { ++ return false; ++ } ++ } ++ ++ /** ++ * Updates the NSWE flag with diagonal flags. ++ * @param x : Geodata X coordinate. ++ * @param y : Geodata Y coordinate. ++ * @param z : Geodata Z coordinate. ++ * @param nsweValue : NSWE flag to be updated. ++ * @return byte : Updated NSWE flag. ++ */ ++ private static byte updateNsweBelow(int x, int y, short z, byte nsweValue) ++ { ++ byte nswe = nsweValue; ++ ++ // calculate virtual layer height ++ final short height = (short) (z + GeoStructure.CELL_IGNORE_HEIGHT); ++ ++ // get NSWE of neighbor cells below virtual layer (NPC/PC can fall down of clif, but can not climb it -> NSWE of cell below) ++ final byte nsweN = getNsweBelow(x, y - 1, height); ++ final byte nsweS = getNsweBelow(x, y + 1, height); ++ final byte nsweW = getNsweBelow(x - 1, y, height); ++ final byte nsweE = getNsweBelow(x + 1, y, height); ++ ++ // north-west ++ if ((((nswe & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) || (((nswe & GeoStructure.CELL_FLAG_W) != 0) && ((nsweW & GeoStructure.CELL_FLAG_N) != 0))) ++ { ++ nswe |= GeoStructure.CELL_FLAG_NW; ++ } ++ ++ // north-east ++ if ((((nswe & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) || (((nswe & GeoStructure.CELL_FLAG_E) != 0) && ((nsweE & GeoStructure.CELL_FLAG_N) != 0))) ++ { ++ nswe |= GeoStructure.CELL_FLAG_NE; ++ } ++ ++ // south-west ++ if ((((nswe & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) || (((nswe & GeoStructure.CELL_FLAG_W) != 0) && ((nsweW & GeoStructure.CELL_FLAG_S) != 0))) ++ { ++ nswe |= GeoStructure.CELL_FLAG_SW; ++ } ++ ++ // south-east ++ if ((((nswe & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) || (((nswe & GeoStructure.CELL_FLAG_E) != 0) && ((nsweE & GeoStructure.CELL_FLAG_S) != 0))) ++ { ++ nswe |= GeoStructure.CELL_FLAG_SE; ++ } ++ ++ return nswe; ++ } ++ ++ private static byte getNsweBelow(int geoX, int geoY, short worldZ) ++ { ++ // out of geo coordinates ++ if ((geoX < 0) || (geoX >= GeoStructure.REGION_CELLS_X)) ++ { ++ return 0; ++ } ++ ++ // out of geo coordinates ++ if ((geoY < 0) || (geoY >= GeoStructure.REGION_CELLS_Y)) ++ { ++ return 0; ++ } ++ ++ // get block ++ final ABlock block = _blocks[geoX / GeoStructure.BLOCK_CELLS_X][geoY / GeoStructure.BLOCK_CELLS_Y]; ++ ++ // get index, when valid, return nswe ++ final int index = block.getIndexBelow(geoX, geoY, worldZ); ++ return index == -1 ? 0 : block.getNswe(index); ++ } ++ ++ /** ++ * Save region file to file. ++ * @param filename : The name of file to save. ++ * @return boolean : True when successful. ++ */ ++ private static boolean saveGeoBlocks(String filename) ++ { ++ try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(Config.GEODATA_PATH + filename), GeoStructure.REGION_BLOCKS * GeoStructure.BLOCK_CELLS * 3)) ++ { ++ // loop over region blocks and save each block ++ for (int ix = 0; ix < GeoStructure.REGION_BLOCKS_X; ix++) ++ { ++ for (int iy = 0; iy < GeoStructure.REGION_BLOCKS_Y; iy++) ++ { ++ _blocks[ix][iy].saveBlock(bos); ++ } ++ } ++ ++ // flush data to file ++ bos.flush(); ++ ++ return true; ++ } ++ catch (Exception e) ++ { ++ return false; ++ } ++ } ++ ++ private static void loadGeoengineConfigs() ++ { ++ final PropertiesParser geoData = new PropertiesParser(Config.GEOENGINE_CONFIG_FILE); ++ Config.GEODATA_PATH = geoData.getString("GeoDataPath", "./data/geodata/"); ++ Config.COORD_SYNCHRONIZE = geoData.getInt("CoordSynchronize", -1); ++ Config.PART_OF_CHARACTER_HEIGHT = geoData.getInt("PartOfCharacterHeight", 75); ++ Config.MAX_OBSTACLE_HEIGHT = geoData.getInt("MaxObstacleHeight", 32); ++ Config.PATHFINDING = geoData.getBoolean("PathFinding", true); ++ Config.PATHFIND_BUFFERS = geoData.getString("PathFindBuffers", "100x6;128x6;192x6;256x4;320x4;384x4;500x2"); ++ Config.BASE_WEIGHT = geoData.getInt("BaseWeight", 10); ++ Config.DIAGONAL_WEIGHT = geoData.getInt("DiagonalWeight", 14); ++ Config.OBSTACLE_MULTIPLIER = geoData.getInt("ObstacleMultiplier", 10); ++ Config.HEURISTIC_WEIGHT = geoData.getInt("HeuristicWeight", 20); ++ Config.MAX_ITERATIONS = geoData.getInt("MaxIterations", 3500); ++ } ++} +\ No newline at end of file diff --git a/L2J_Mobius_5.5_EtinasFate/dist/game/data/geodata/L2D_GeoEngine.patch b/L2J_Mobius_5.5_EtinasFate/dist/game/data/geodata/L2D_GeoEngine.patch new file mode 100644 index 0000000000..63e2faf0c9 --- /dev/null +++ b/L2J_Mobius_5.5_EtinasFate/dist/game/data/geodata/L2D_GeoEngine.patch @@ -0,0 +1,6052 @@ +Index: dist/game/GeoDataConverter.bat +=================================================================== +--- dist/game/GeoDataConverter.bat (nonexistent) ++++ dist/game/GeoDataConverter.bat (working copy) +@@ -0,0 +1,6 @@ ++@echo off ++title L2D geodata converter ++ ++java -Xmx512m -cp ./../libs/* org.l2jmobius.tools.geodataconverter.GeoDataConverter ++ ++pause +Index: dist/game/GeoDataConverter.sh +=================================================================== +--- dist/game/GeoDataConverter.sh (nonexistent) ++++ dist/game/GeoDataConverter.sh (working copy) +@@ -0,0 +1,4 @@ ++#! /bin/sh ++ ++java -Xmx512m -cp ../libs/*: org.l2jmobius.tools.geodataconverter.GeoDataConverter > log/stdout.log 2>&1 ++ +Index: dist/game/config/GeoEngine.ini +=================================================================== +--- dist/game/config/GeoEngine.ini (revision 8397) ++++ dist/game/config/GeoEngine.ini (working copy) +@@ -1,6 +1,10 @@ + # ================================================================= + # Geodata + # ================================================================= ++# Because of real-time performance we are using geodata files only in ++# diagonal L2D format now (using filename e.g. 22_16.l2d). ++# L2D geodata can be obtained by conversion of existing L2J or L2OFF geodata. ++# Launch "GeoDataConverter.bat/sh" and follow instructions to start the conversion. + + # 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/ +@@ -13,6 +17,16 @@ + CoordSynchronize = 2 + + # ================================================================= ++# Path checking ++# ================================================================= ++ ++# 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 finding + # ================================================================= + +@@ -23,19 +37,23 @@ + # Pathfinding array buffers configuration, default: 100x6;128x6;192x6;256x4;320x4;384x4;500x2 + PathFindBuffers = 100x6;128x6;192x6;256x4;320x4;384x4;500x2 + +-# Weight for nodes without obstacles far from walls +-LowWeight = 0.5 ++# Base path weight, when moving from one node to another on axis direction, default: 10 ++BaseWeight = 10 + +-# Weight for nodes near walls +-MediumWeight = 2 ++# Path weight, when moving from one node to another on diagonal direction, default: BaseWeight * sqrt(2) = 14 ++DiagonalWeight = 14 + +-# Weight for nodes with obstacles +-HighWeight = 3 ++# When movement flags of target node is blocked to any direction, multiply movement weight by this multiplier. ++# This causes pathfinding algorithm to avoid path construction exactly near an obstacle, default: 10 ++ObstacleMultiplier = 10 + +-# Weight for diagonal movement. +-# Default: LowWeight * sqrt(2) +-DiagonalWeight = 0.707 ++# Weight of the heuristic algorithm, which is giving estimated cost from node to target, default: 20 ++# For proper function must be higher than BaseWeight and/or DiagonalWeight. ++HeuristicWeight = 20 + ++# Maximum number of generated nodes per one path-finding process, default 3500 ++MaxIterations = 3500 ++ + # ================================================================= + # Other + # ================================================================= +Index: dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java +=================================================================== +--- dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java (revision 8397) ++++ dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java (working copy) +@@ -55,9 +55,8 @@ + final int worldX = activeChar.getX(); + final int worldY = activeChar.getY(); + final int worldZ = activeChar.getZ(); +- final int geoX = GeoEngine.getInstance().getGeoX(worldX); +- final int geoY = GeoEngine.getInstance().getGeoY(worldY); +- ++ final int geoX = GeoEngine.getGeoX(worldX); ++ final int geoY = GeoEngine.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)); +@@ -73,9 +72,8 @@ + final int worldX = activeChar.getX(); + final int worldY = activeChar.getY(); + final int worldZ = activeChar.getZ(); +- final int geoX = GeoEngine.getInstance().getGeoX(worldX); +- final int geoY = GeoEngine.getInstance().getGeoY(worldY); +- ++ final int geoX = GeoEngine.getGeoX(worldX); ++ final int geoY = GeoEngine.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)); +Index: dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java +=================================================================== +--- dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java (revision 8397) ++++ dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java (working copy) +@@ -18,11 +18,11 @@ + + import java.util.List; + +-import org.l2jmobius.Config; +-import org.l2jmobius.gameserver.geoengine.GeoEnginePathfinding; +-import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; ++import org.l2jmobius.gameserver.geoengine.GeoEngine; + import org.l2jmobius.gameserver.handler.IAdminCommandHandler; ++import org.l2jmobius.gameserver.model.Location; + import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance; ++import org.l2jmobius.gameserver.network.SystemMessageId; + import org.l2jmobius.gameserver.util.BuilderUtil; + + public class AdminPathNode implements IAdminCommandHandler +@@ -37,29 +37,30 @@ + { + if (command.equals("admin_path_find")) + { +- if (!Config.PATHFINDING) +- { +- BuilderUtil.sendSysMessage(activeChar, "PathFinding is disabled."); +- return true; +- } + if (activeChar.getTarget() != null) + { +- final List path = GeoEnginePathfinding.getInstance().findPath(activeChar.getX(), activeChar.getY(), (short) activeChar.getZ(), activeChar.getTarget().getX(), activeChar.getTarget().getY(), (short) activeChar.getTarget().getZ(), activeChar.getInstanceWorld()); ++ 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()); + if (path == null) + { +- BuilderUtil.sendSysMessage(activeChar, "No Route!"); +- return true; ++ BuilderUtil.sendSysMessage(activeChar, "No route found or pathfinding disabled."); + } +- for (AbstractNodeLoc a : path) ++ else + { +- BuilderUtil.sendSysMessage(activeChar, "x:" + a.getX() + " y:" + a.getY() + " z:" + a.getZ()); ++ for (Location point : path) ++ { ++ BuilderUtil.sendSysMessage(activeChar, "x:" + point.getX() + " y:" + point.getY() + " z:" + point.getZ()); ++ } + } + } + else + { +- BuilderUtil.sendSysMessage(activeChar, "No Target!"); ++ activeChar.sendPacket(SystemMessageId.INVALID_TARGET); + } + } ++ else ++ { ++ return false; ++ } + return true; + } + +Index: java/org/l2jmobius/Config.java +=================================================================== +--- java/org/l2jmobius/Config.java (revision 8397) ++++ java/org/l2jmobius/Config.java (working copy) +@@ -32,7 +32,6 @@ + import java.net.UnknownHostException; + import java.nio.charset.StandardCharsets; + import java.nio.file.Files; +-import java.nio.file.Path; + import java.nio.file.Paths; + import java.time.Duration; + import java.util.ArrayList; +@@ -927,14 +926,17 @@ + // -------------------------------------------------- + // GeoEngine + // -------------------------------------------------- +- public static Path GEODATA_PATH; ++ public static String GEODATA_PATH; ++ public static int COORD_SYNCHRONIZE; ++ public static int PART_OF_CHARACTER_HEIGHT; ++ public static int MAX_OBSTACLE_HEIGHT; + public static boolean PATHFINDING; + public static String PATHFIND_BUFFERS; +- public static float LOW_WEIGHT; +- public static float MEDIUM_WEIGHT; +- public static float HIGH_WEIGHT; +- public static float DIAGONAL_WEIGHT; +- public static int COORD_SYNCHRONIZE; ++ public static int BASE_WEIGHT; ++ public static int DIAGONAL_WEIGHT; ++ public static int HEURISTIC_WEIGHT; ++ public static int OBSTACLE_MULTIPLIER; ++ public static int MAX_ITERATIONS; + public static boolean CORRECT_PLAYER_Z; + + /** Attribute System */ +@@ -2468,14 +2470,17 @@ + + // Load GeoEngine config file (if exists) + final PropertiesParser GeoEngine = new PropertiesParser(GEOENGINE_CONFIG_FILE); +- GEODATA_PATH = Paths.get(GeoEngine.getString("GeoDataPath", "./data/geodata")); ++ GEODATA_PATH = GeoEngine.getString("GeoDataPath", "./data/geodata/"); ++ COORD_SYNCHRONIZE = GeoEngine.getInt("CoordSynchronize", -1); ++ PART_OF_CHARACTER_HEIGHT = GeoEngine.getInt("PartOfCharacterHeight", 75); ++ MAX_OBSTACLE_HEIGHT = GeoEngine.getInt("MaxObstacleHeight", 32); + PATHFINDING = GeoEngine.getBoolean("PathFinding", true); + PATHFIND_BUFFERS = GeoEngine.getString("PathFindBuffers", "100x6;128x6;192x6;256x4;320x4;384x4;500x2"); +- LOW_WEIGHT = GeoEngine.getFloat("LowWeight", 0.5f); +- MEDIUM_WEIGHT = GeoEngine.getFloat("MediumWeight", 2); +- HIGH_WEIGHT = GeoEngine.getFloat("HighWeight", 3); +- DIAGONAL_WEIGHT = GeoEngine.getFloat("DiagonalWeight", 0.707f); +- COORD_SYNCHRONIZE = GeoEngine.getInt("CoordSynchronize", -1); ++ BASE_WEIGHT = GeoEngine.getInt("BaseWeight", 10); ++ DIAGONAL_WEIGHT = GeoEngine.getInt("DiagonalWeight", 14); ++ OBSTACLE_MULTIPLIER = GeoEngine.getInt("ObstacleMultiplier", 10); ++ HEURISTIC_WEIGHT = GeoEngine.getInt("HeuristicWeight", 20); ++ MAX_ITERATIONS = GeoEngine.getInt("MaxIterations", 3500); + CORRECT_PLAYER_Z = GeoEngine.getBoolean("CorrectPlayerZ", false); + + // Load AllowedPlayerRaces config file (if exists) +Index: java/org/l2jmobius/gameserver/geoengine/GeoEngine.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/GeoEngine.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/GeoEngine.java (working copy) +@@ -16,101 +16,91 @@ + */ + package org.l2jmobius.gameserver.geoengine; + +-import java.io.IOException; ++import java.io.File; + import java.io.RandomAccessFile; + import java.nio.ByteOrder; +-import java.nio.channels.FileChannel.MapMode; +-import java.nio.file.Files; +-import java.nio.file.Path; +-import java.util.concurrent.atomic.AtomicReferenceArray; +-import java.util.logging.Level; ++import java.nio.MappedByteBuffer; ++import java.nio.channels.FileChannel; ++import java.util.List; + 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.geoengine.geodata.Cell; +-import org.l2jmobius.gameserver.geoengine.geodata.IRegion; +-import org.l2jmobius.gameserver.geoengine.geodata.NullRegion; +-import org.l2jmobius.gameserver.geoengine.geodata.Region; ++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.GeoFormat; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoLocation; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; + import org.l2jmobius.gameserver.instancemanager.WarpedSpaceManager; + 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; ++import org.l2jmobius.gameserver.util.MathUtil; + + /** +- * @author -Nemesiss-, HorridoJoho ++ * @author Hasha + */ + public class GeoEngine + { +- private static final Logger LOGGER = Logger.getLogger(GeoEngine.class.getName()); ++ protected static final Logger LOGGER = Logger.getLogger(GeoEngine.class.getName()); + +- private static final int WORLD_MIN_X = -655360; +- private static final int WORLD_MIN_Y = -589824; +- private static final int WORLD_MIN_Z = -16384; ++ private final ABlock[][] _blocks; ++ private final BlockNull _nullBlock; + +- /** 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; +- +- /** The regions array */ +- private final AtomicReferenceArray _regions = new AtomicReferenceArray<>(GEO_REGIONS); +- +- 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; +- +- protected GeoEngine() ++ /** ++ * GeoEngine constructor. Loads all geodata files of chosen geodata format. ++ */ ++ public GeoEngine() + { + LOGGER.info("GeoEngine: Initializing..."); +- for (int i = 0; i < _regions.length(); i++) +- { +- _regions.set(i, NullRegion.INSTANCE); +- } + ++ // 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; +- try ++ long fileSize = 0; ++ for (int rx = World.TILE_X_MIN; rx <= World.TILE_X_MAX; rx++) + { +- for (int regionX = World.TILE_X_MIN; regionX <= World.TILE_X_MAX; regionX++) ++ for (int ry = World.TILE_Y_MIN; ry <= World.TILE_Y_MAX; ry++) + { +- for (int regionY = World.TILE_Y_MIN; regionY <= World.TILE_Y_MAX; regionY++) ++ final File f = new File(Config.GEODATA_PATH + String.format(GeoFormat.L2D.getFilename(), rx, ry)); ++ if (f.exists() && !f.isDirectory()) + { +- 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(rx, ry)) + { +- try (RandomAccessFile raf = new RandomAccessFile(geoFilePath.toFile(), "r")) +- { +- _regions.set((regionX * GEO_REGIONS_Y) + regionY, new Region(raf.getChannel().map(MapMode.READ_ONLY, 0, raf.length()).load().order(ByteOrder.LITTLE_ENDIAN))); +- loaded++; +- } ++ loaded++; ++ fileSize += f.length(); + } + } ++ else ++ { ++ // region file is not load-able, load null blocks ++ loadNullBlocks(rx, ry); ++ } + } + } +- catch (Exception e) ++ ++ LOGGER.info("GeoEngine: Loaded " + loaded + " geodata files."); ++ if (loaded > 0) + { +- LOGGER.log(Level.SEVERE, "GeoEngine: Failed to load geodata!", e); +- System.exit(1); ++ LOGGER.info("GeoEngine: Total geodata file size " + (fileSize / 1024 / 1024) + " MB."); + } + +- LOGGER.info("GeoEngine: Loaded " + loaded + " geodata files."); +- +- // Avoid wrong configs when no files are loaded. ++ // avoid wrong configs when no files are loaded + if (loaded == 0) + { + if (Config.PATHFINDING) +@@ -124,627 +114,832 @@ + LOGGER.info("GeoEngine: Forcing CoordSynchronize setting to -1."); + } + } ++ ++ // release multilayer block temporarily buffer ++ BlockMultilayer.release(); + } + + /** +- * @param geoX +- * @param geoY +- * @return the region ++ * 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 IRegion getRegion(int geoX, int geoY) ++ private final boolean loadGeoBlocks(int regionX, int regionY) + { +- final int region = ((geoX / IRegion.REGION_CELLS_X) * GEO_REGIONS_Y) + (geoY / IRegion.REGION_CELLS_Y); +- if ((region < 0) || (region >= _regions.length())) ++ final String filename = String.format(GeoFormat.L2D.getFilename(), regionX, regionY); ++ ++ // standard load ++ try (RandomAccessFile raf = new RandomAccessFile(Config.GEODATA_PATH + filename, "r"); ++ FileChannel fc = raf.getChannel()) + { +- return null; ++ // initialize file buffer ++ final MappedByteBuffer buffer = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()).load(); ++ buffer.order(ByteOrder.LITTLE_ENDIAN); ++ ++ // 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++) ++ { ++ // get block type ++ final byte type = buffer.get(); ++ ++ // load block according to block type ++ switch (type) ++ { ++ case GeoStructure.TYPE_FLAT_L2D: ++ { ++ _blocks[blockX + ix][blockY + iy] = new BlockFlat(buffer, GeoFormat.L2D); ++ break; ++ } ++ case GeoStructure.TYPE_COMPLEX_L2D: ++ { ++ _blocks[blockX + ix][blockY + iy] = new BlockComplex(buffer, GeoFormat.L2D); ++ break; ++ } ++ case GeoStructure.TYPE_MULTILAYER_L2D: ++ { ++ _blocks[blockX + ix][blockY + iy] = new BlockMultilayer(buffer, GeoFormat.L2D); ++ break; ++ } ++ default: ++ { ++ throw new IllegalArgumentException("Unknown block type: " + type); ++ } ++ } ++ } ++ } ++ ++ // check data consistency ++ if (buffer.remaining() > 0) ++ { ++ LOGGER.warning("GeoEngine: Region file " + filename + " can be corrupted, remaining " + buffer.remaining() + " bytes to read."); ++ } ++ ++ // loading was successful ++ return true; + } +- return _regions.get(region); +- } +- +- /** +- * @param filePath +- * @param regionX +- * @param regionY +- * @throws IOException +- */ +- 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")) ++ catch (Exception e) + { +- _regions.set(regionOffset, new Region(raf.getChannel().map(MapMode.READ_ONLY, 0, raf.length()).load().order(ByteOrder.LITTLE_ENDIAN))); ++ // an error occured while loading, load null blocks ++ LOGGER.warning("GeoEngine: Error while loading " + filename + " region file."); ++ LOGGER.warning(e.getMessage()); ++ ++ // replace whole region file with null blocks ++ loadNullBlocks(regionX, regionY); ++ ++ // loading was not successful ++ return false; + } + } + + /** +- * @param regionX +- * @param regionY ++ * 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. + */ +- public void unloadRegion(int regionX, int regionY) ++ private final void loadNullBlocks(int regionX, int regionY) + { +- _regions.set((regionX * GEO_REGIONS_Y) + regionY, NullRegion.INSTANCE); +- } +- +- /** +- * @param geoX +- * @param geoY +- * @return if geodata exist +- */ +- public boolean hasGeoPos(int geoX, int geoY) +- { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) ++ // 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++) + { +- return false; ++ for (int iy = 0; iy < GeoStructure.REGION_BLOCKS_Y; iy++) ++ { ++ _blocks[blockX + ix][blockY + iy] = _nullBlock; ++ } + } +- return region.hasGeo(); + } + ++ // GEODATA - GENERAL ++ + /** +- * Checks the specified position for available geodata. +- * @param x the world x +- * @param y the world y +- * @return {@code true} if there is geodata for the given coordinates, {@code false} otherwise ++ * Converts world X to geodata X. ++ * @param worldX ++ * @return int : Geo X + */ +- public boolean hasGeo(int x, int y) ++ public static int getGeoX(int worldX) + { +- return hasGeoPos(getGeoX(x), getGeoY(y)); ++ return (MathUtil.limit(worldX, World.MAP_MIN_X, World.MAP_MAX_X) - World.MAP_MIN_X) >> 4; + } + + /** +- * @param geoX +- * @param geoY +- * @param worldZ +- * @param nswe +- * @return the nearest nswe check ++ * Converts world Y to geodata Y. ++ * @param worldY ++ * @return int : Geo Y + */ +- public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) ++ public static int getGeoY(int worldY) + { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) +- { +- return true; +- } +- return region.checkNearestNswe(geoX, geoY, worldZ, nswe); ++ return (MathUtil.limit(worldY, World.MAP_MIN_Y, World.MAP_MAX_Y) - World.MAP_MIN_Y) >> 4; + } + + /** ++ * Converts geodata X to world X. + * @param geoX +- * @param geoY +- * @param worldZ +- * @param nswe +- * @return the nearest nswe anti-corner cut ++ * @return int : World X + */ +- public boolean checkNearestNsweAntiCornerCut(int geoX, int geoY, int worldZ, int nswe) ++ public static int getWorldX(int geoX) + { +- boolean can = true; +- if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) +- { +- can = checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_EAST) && checkNearestNswe(geoX + 1, geoY, worldZ, Cell.NSWE_NORTH); +- } +- if (can && ((nswe & Cell.NSWE_NORTH_WEST) == Cell.NSWE_NORTH_WEST)) +- { +- 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 = 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 = checkNearestNswe(geoX, geoY + 1, worldZ, Cell.NSWE_WEST) && checkNearestNswe(geoX - 1, geoY, worldZ, Cell.NSWE_SOUTH); +- } +- return can && checkNearestNswe(geoX, geoY, worldZ, nswe); ++ return (MathUtil.limit(geoX, 0, GeoStructure.GEO_CELLS_X) << 4) + World.MAP_MIN_X + 8; + } + + /** +- * @param geoX ++ * Converts geodata Y to world Y. + * @param geoY +- * @param worldZ +- * @return the nearest Z value ++ * @return int : World Y + */ +- public int getNearestZ(int geoX, int geoY, int worldZ) ++ public static int getWorldY(int geoY) + { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) +- { +- return worldZ; +- } +- return region.getNearestZ(geoX, geoY, worldZ); ++ return (MathUtil.limit(geoY, 0, GeoStructure.GEO_CELLS_Y) << 4) + World.MAP_MIN_Y + 8; + } + + /** +- * @param geoX +- * @param geoY +- * @param worldZ +- * @return the next lower Z value ++ * Returns block of geodata on given coordinates. ++ * @param geoX : Geodata X ++ * @param geoY : Geodata Y ++ * @return {@link ABlock} : Block of geodata. + */ +- public int getNextLowerZ(int geoX, int geoY, int worldZ) ++ private final ABlock getBlock(int geoX, int geoY) + { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) ++ final int x = geoX / GeoStructure.BLOCK_CELLS_X; ++ final int y = geoY / GeoStructure.BLOCK_CELLS_Y; ++ ++ // if x or y is out of array return null ++ if ((x > -1) && (y > -1) && (x < GeoStructure.GEO_BLOCKS_X) && (y < GeoStructure.GEO_BLOCKS_Y)) + { +- return worldZ; ++ return _blocks[x][y]; + } +- return region.getNextLowerZ(geoX, geoY, worldZ); ++ return null; + } + + /** +- * @param geoX +- * @param geoY +- * @param worldZ +- * @return the next higher Z value ++ * Check if geo coordinates has geo. ++ * @param geoX : Geodata X ++ * @param geoY : Geodata Y ++ * @return boolean : True, if given geo coordinates have geodata + */ +- public int getNextHigherZ(int geoX, int geoY, int worldZ) ++ public boolean hasGeoPos(int geoX, int geoY) + { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) ++ final ABlock block = getBlock(geoX, geoY); ++ if (block == null) // null block check + { +- return worldZ; ++ // TODO: Find when this can be null. (Bad geodata? Check World getRegion method.) ++ // LOGGER.warning("Could not find geodata block at " + getWorldX(geoX) + ", " + getWorldY(geoY) + "."); ++ return false; + } +- return region.getNextHigherZ(geoX, geoY, worldZ); ++ return block.hasGeoPos(); + } + + /** +- * Gets the Z height. +- * @param x the world x +- * @param y the world y +- * @param z the world z +- * @return the nearest Z height ++ * 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 int getHeight(int x, int y, int z) ++ public short getHeightNearest(int geoX, int geoY, int worldZ) + { +- return getNearestZ(getGeoX(x), getGeoY(y), z); ++ final ABlock block = getBlock(geoX, geoY); ++ return block != null ? block.getHeightNearest(geoX, geoY, worldZ) : (short) worldZ; + } + + /** +- * Gets the next lower Z height. +- * @param x the world x +- * @param y the world y +- * @param z the world z +- * @return the nearest Z height ++ * 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 int getLowerHeight(int x, int y, int z) ++ public byte getNsweNearest(int geoX, int geoY, int worldZ) + { +- return getNextLowerZ(getGeoX(x), getGeoY(y), z); ++ final ABlock block = getBlock(geoX, geoY); ++ return block != null ? block.getNsweNearest(geoX, geoY, worldZ) : (byte) 0xFF; + } + + /** +- * Gets the next higher Z height. +- * @param x the world x +- * @param y the world y +- * @param z the world z +- * @return the nearest Z height ++ * Check if world coordinates has geo. ++ * @param worldX : World X ++ * @param worldY : World Y ++ * @return boolean : True, if given world coordinates have geodata + */ +- public int getHigherHeight(int x, int y, int z) ++ public boolean hasGeo(int worldX, int worldY) + { +- return getNextHigherZ(getGeoX(x), getGeoY(y), z); ++ return hasGeoPos(getGeoX(worldX), getGeoY(worldY)); + } + + /** +- * @param worldX +- * @return the geo X ++ * 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 int getGeoX(int worldX) ++ public short getHeight(int worldX, int worldY, int worldZ) + { +- return (worldX - WORLD_MIN_X) / 16; ++ return getHeightNearest(getGeoX(worldX), getGeoY(worldY), worldZ); + } + +- /** +- * @param worldY +- * @return the geo Y +- */ +- public int getGeoY(int worldY) +- { +- return (worldY - WORLD_MIN_Y) / 16; +- } ++ // PATHFINDING + + /** +- * @param worldZ +- * @return the geo Z ++ * Check line of sight from {@link WorldObject} to {@link WorldObject}. ++ * @param origin : The origin object. ++ * @param target : The target object. ++ * @return {@code boolean} : True if origin can see target + */ +- public int getGeoZ(int worldZ) ++ public boolean canSeeTarget(WorldObject origin, WorldObject target) + { +- return (worldZ - WORLD_MIN_Z) / 16; +- } +- +- /** +- * @param geoX +- * @return the world X +- */ +- public int getWorldX(int geoX) +- { +- return (geoX * 16) + WORLD_MIN_X + 8; +- } +- +- /** +- * @param geoY +- * @return the world Y +- */ +- public int getWorldY(int geoY) +- { +- return (geoY * 16) + WORLD_MIN_Y + 8; +- } +- +- /** +- * @param geoZ +- * @return the world Z +- */ +- public int getWorldZ(int geoZ) +- { +- return (geoZ * 16) + WORLD_MIN_Z + 8; +- } +- +- /** +- * 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) +- { +- if (target.isDoor()) ++ if (target.isDoor() || target.isArtefact() || (target.isCreature() && ((Creature) target).isFlying())) + { +- // Can always see doors. + return true; + } +- return 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(), cha.getInstanceWorld(), worldPosition.getX(), worldPosition.getY(), worldPosition.getZ()); +- } +- +- /** +- * Can see target. Checks doors between. +- * @param x the x coordinate +- * @param y the y coordinate +- * @param z the z coordinate +- * @param world +- * @param tx the target's x coordinate +- * @param ty the target's y coordinate +- * @param tz the target's z coordinate +- * @param tworld the target's instanceId +- * @return +- */ +- public boolean canSeeTarget(int x, int y, int z, Instance world, int tx, int ty, int tz, Instance tworld) +- { +- return (world != tworld) ? false : canSeeTarget(x, y, z, world, tx, ty, tz); +- } +- +- /** +- * 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 +- * @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, Instance instance, int tx, int ty, int tz) +- { +- if (DoorData.getInstance().checkIfDoorsBetween(x, y, z, tx, ty, tz, instance, true)) ++ ++ // get origin and target world coordinates ++ final int ox = origin.getX(); ++ final int oy = origin.getY(); ++ final int oz = origin.getZ(); ++ final int tx = target.getX(); ++ final int ty = target.getY(); ++ final int tz = target.getZ(); ++ ++ if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld(), true)) + { + return false; + } +- return canSeeTarget(x, y, z, tx, ty, tz); +- } +- +- /** +- * @param prevX +- * @param prevY +- * @param prevGeoZ +- * @param curX +- * @param curY +- * @param nswe +- * @return the LOS Z value +- */ +- 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))) ++ if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld())) + { +- throw new RuntimeException("Multiple directions!"); ++ return false; + } ++ if (WarpedSpaceManager.getInstance().checkForWarpedSpace(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld())) ++ { ++ return false; ++ } + +- if (checkNearestNsweAntiCornerCut(prevX, prevY, prevGeoZ, nswe)) ++ // get origin and check existing geo coordinates ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) + { +- return getNearestZ(curX, curY, prevGeoZ); ++ return true; + } + +- return getNextHigherZ(curX, curY, prevGeoZ); ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coordinates ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) ++ { ++ return true; ++ } ++ ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // origin and target coordinates are same ++ if ((gox == gtx) && (goy == gty)) ++ { ++ return goz == gtz; ++ } ++ ++ // get origin and target height, real height = collision height * 2 ++ double oheight = 0; ++ if (origin.isCreature()) ++ { ++ oheight = ((Creature) origin).getCollisionHeight() * 2; ++ } ++ ++ double theight = 0; ++ if (target.isCreature()) ++ { ++ theight = ((Creature) target).getCollisionHeight() * 2; ++ } ++ ++ // perform geodata check ++ return checkSee(gox, goy, goz, oheight, gtx, gty, gtz, theight, origin.getInstanceWorld()); + } + + /** +- * Can see target. Does not check doors between. +- * @param xValue the x coordinate +- * @param yValue the y coordinate +- * @param zValue the z coordinate +- * @param txValue the target's x coordinate +- * @param tyValue the target's y coordinate +- * @param tzValue the target's z coordinate +- * @return {@code true} if there is line of sight between the given coordinate sets, {@code false} otherwise ++ * Check line of sight from {@link WorldObject} to {@link Location}. ++ * @param origin : The origin object. ++ * @param position : The target position. ++ * @return {@code boolean} : True if object can see position + */ +- public boolean canSeeTarget(int xValue, int yValue, int zValue, int txValue, int tyValue, int tzValue) ++ public boolean canSeeTarget(WorldObject origin, Location position) + { +- int x = xValue; +- int y = yValue; +- int tx = txValue; +- int ty = tyValue; ++ // get origin and target world coordinates ++ final int ox = origin.getX(); ++ final int oy = origin.getY(); ++ final int oz = origin.getZ(); ++ final int tx = position.getX(); ++ final int ty = position.getY(); ++ final int tz = position.getZ(); + +- int geoX = getGeoX(x); +- int geoY = getGeoY(y); +- int tGeoX = getGeoX(tx); +- int tGeoY = getGeoY(ty); ++ if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld(), true)) ++ { ++ return false; ++ } ++ if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld())) ++ { ++ return false; ++ } ++ if (WarpedSpaceManager.getInstance().checkForWarpedSpace(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld())) ++ { ++ return false; ++ } + +- int z = getNearestZ(geoX, geoY, zValue); +- int tz = getNearestZ(tGeoX, tGeoY, tzValue); ++ // get origin and check existing geo coordinates ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) ++ { ++ return true; ++ } + +- // Fastpath. +- if ((geoX == tGeoX) && (geoY == tGeoY)) ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coordinates ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) + { +- if (hasGeoPos(tGeoX, tGeoY)) +- { +- return z == tz; +- } + return true; + } + +- if (tz > z) ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // origin and target coordinates are same ++ if ((gox == gtx) && (goy == gty)) + { +- int tmp = tx; +- tx = x; +- x = tmp; +- +- tmp = ty; +- ty = y; +- y = tmp; +- +- tmp = tz; +- tz = z; +- z = tmp; +- +- tmp = tGeoX; +- tGeoX = geoX; +- geoX = tmp; +- +- tmp = tGeoY; +- tGeoY = geoY; +- geoY = tmp; ++ return goz == gtz; + } + +- final LinePointIterator3D pointIter = new LinePointIterator3D(geoX, geoY, z, tGeoX, tGeoY, tz); +- // 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()) ++ // get origin and target height, real height = collision height * 2 ++ double oheight = 0; ++ if (origin.isCreature()) + { +- final int curX = pointIter.x(); +- final int curY = pointIter.y(); ++ oheight = ((Creature) origin).getTemplate().getCollisionHeight(); ++ } ++ ++ // perform geodata check ++ return checkSee(gox, goy, goz, oheight, gtx, gty, gtz, 0, origin.getInstanceWorld()); ++ } ++ ++ /** ++ * Simple check for origin to target visibility. ++ * @param goxValue : origin X geodata coordinate ++ * @param goyValue : origin Y geodata coordinate ++ * @param gozValue : origin Z geodata coordinate ++ * @param oheight : origin height (if instance of {@link Character}) ++ * @param gtxValue : target X geodata coordinate ++ * @param gtyValue : target Y geodata coordinate ++ * @param gtzValue : target Z geodata coordinate ++ * @param theight : target height (if instance of {@link Character}) ++ * @param instance ++ * @return {@code boolean} : True, when target can be seen. ++ */ ++ private final boolean checkSee(int goxValue, int goyValue, int gozValue, double oheight, int gtxValue, int gtyValue, int gtzValue, double theight, Instance instance) ++ { ++ int goz = gozValue; ++ int gtz = gtzValue; ++ int gox = goxValue; ++ int goy = goyValue; ++ int gtx = gtxValue; ++ int gty = gtyValue; ++ ++ // get line of sight Z coordinates ++ double losoz = goz + ((oheight * Config.PART_OF_CHARACTER_HEIGHT) / 100); ++ double lostz = gtz + ((theight * Config.PART_OF_CHARACTER_HEIGHT) / 100); ++ ++ // get X delta and signum ++ final int dx = Math.abs(gtx - gox); ++ final int sx = gox < gtx ? 1 : -1; ++ final byte dirox = sx > 0 ? GeoStructure.CELL_FLAG_E : GeoStructure.CELL_FLAG_W; ++ final byte dirtx = sx > 0 ? GeoStructure.CELL_FLAG_W : GeoStructure.CELL_FLAG_E; ++ ++ // get Y delta and signum ++ final int dy = Math.abs(gty - goy); ++ final int sy = goy < gty ? 1 : -1; ++ final byte diroy = sy > 0 ? GeoStructure.CELL_FLAG_S : GeoStructure.CELL_FLAG_N; ++ final byte dirty = sy > 0 ? GeoStructure.CELL_FLAG_N : GeoStructure.CELL_FLAG_S; ++ ++ // get Z delta ++ final int dm = Math.max(dx, dy); ++ final double dz = (lostz - losoz) / dm; ++ ++ // get direction flag for diagonal movement ++ final byte diroxy = getDirXY(dirox, diroy); ++ final byte dirtxy = getDirXY(dirtx, dirty); ++ ++ // delta, determines axis to move on (+..X axis, -..Y axis) ++ int d = dx - dy; ++ ++ // NSWE direction of movement ++ byte diro; ++ byte dirt; ++ ++ // initialize node values ++ int nox = gox; ++ int noy = goy; ++ int ntx = gtx; ++ int nty = gty; ++ byte nsweo = getNsweNearest(gox, goy, goz); ++ byte nswet = getNsweNearest(gtx, gty, gtz); ++ ++ // loop ++ ABlock block; ++ int index; ++ for (int i = 0; i < ((dm + 1) / 2); i++) ++ { ++ // reset direction flag ++ diro = 0; ++ dirt = 0; + +- if ((curX == prevX) && (curY == prevY)) ++ // calculate next point coordinates ++ final int e2 = 2 * d; ++ if ((e2 > -dy) && (e2 < dx)) + { +- continue; ++ // calculate next point XY coordinates ++ d -= dy; ++ d += dx; ++ nox += sx; ++ ntx -= sx; ++ noy += sy; ++ nty -= sy; ++ diro |= diroxy; ++ dirt |= dirtxy; + } ++ else if (e2 > -dy) ++ { ++ // calculate next point X coordinate ++ d -= dy; ++ nox += sx; ++ ntx -= sx; ++ diro |= dirox; ++ dirt |= dirtx; ++ } ++ else if (e2 < dx) ++ { ++ // calculate next point Y coordinate ++ d += dx; ++ noy += sy; ++ nty -= sy; ++ diro |= diroy; ++ dirt |= dirty; ++ } + +- final int beeCurZ = pointIter.z(); +- int curGeoZ = prevGeoZ; +- +- // Check if the position has geodata. +- if (hasGeoPos(curX, curY)) + { +- final int beeCurGeoZ = getNearestZ(curX, curY, beeCurZ); +- final int nswe = GeoUtils.computeNswe(prevX, prevY, curX, curY); // .computeDirection(prevX, prevY, curX, curY); +- curGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, curX, curY, nswe); +- int maxHeight; +- if (ptIndex < ELEVATED_SEE_OVER_DISTANCE) ++ // get block of the next cell ++ block = getBlock(nox, noy); ++ ++ // get index of particular layer, based on movement conditions ++ if ((nsweo & diro) == 0) + { +- maxHeight = z + MAX_SEE_OVER_HEIGHT; ++ index = block.getIndexAbove(nox, noy, goz - GeoStructure.CELL_IGNORE_HEIGHT); + } + else + { +- maxHeight = beeCurZ + MAX_SEE_OVER_HEIGHT; ++ index = block.getIndexBelow(nox, noy, goz + GeoStructure.CELL_IGNORE_HEIGHT); + } + +- boolean canSeeThrough = false; +- if ((curGeoZ <= maxHeight) && (curGeoZ <= beeCurGeoZ)) ++ // layer does not exist, return ++ if (index == -1) + { +- if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) +- { +- 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 +- { +- canSeeThrough = true; +- } ++ return false; + } + +- if (!canSeeThrough) ++ // get layer and next line of sight Z coordinate ++ goz = block.getHeight(index); ++ losoz += dz; ++ ++ // perform line of sight check, return when fails ++ if ((goz - losoz) > Config.MAX_OBSTACLE_HEIGHT) + { + return false; + } ++ ++ // get layer nswe ++ nsweo = block.getNswe(index); + } ++ { ++ // get block of the next cell ++ block = getBlock(ntx, nty); ++ ++ // get index of particular layer, based on movement conditions ++ if ((nswet & dirt) == 0) ++ { ++ index = block.getIndexAbove(ntx, nty, gtz - GeoStructure.CELL_IGNORE_HEIGHT); ++ } ++ else ++ { ++ index = block.getIndexBelow(ntx, nty, gtz + GeoStructure.CELL_IGNORE_HEIGHT); ++ } ++ ++ // layer does not exist, return ++ if (index == -1) ++ { ++ return false; ++ } ++ ++ // get layer and next line of sight Z coordinate ++ gtz = block.getHeight(index); ++ lostz -= dz; ++ ++ // perform line of sight check, return when fails ++ if ((gtz - lostz) > Config.MAX_OBSTACLE_HEIGHT) ++ { ++ return false; ++ } ++ ++ // get layer nswe ++ nswet = block.getNswe(index); ++ } + +- prevX = curX; +- prevY = curY; +- prevGeoZ = curGeoZ; +- ++ptIndex; ++ // update coords ++ gox = nox; ++ goy = noy; ++ gtx = ntx; ++ gty = nty; + } + +- return true; ++ // when iteration is completed, compare final Z coordinates ++ return Math.abs(goz - gtz) < (GeoStructure.CELL_HEIGHT * 4); + } + + /** +- * Move check. +- * @param x the x coordinate +- * @param y the y coordinate +- * @param zValue the z coordinate +- * @param tx the target's x coordinate +- * @param ty the target's y coordinate +- * @param tzValue the target's z coordinate +- * @param instance the instance +- * @return the last Location (x,y,z) where player can walk - just before wall ++ * Check movement from coordinates to 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 {code boolean} : True if target coordinates are reachable from origin coordinates + */ +- public Location canMoveToTargetLoc(int x, int y, int zValue, int tx, int ty, int tzValue, Instance instance) ++ public boolean canMoveToTarget(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + { +- final int geoX = getGeoX(x); +- final int geoY = getGeoY(y); +- final int z = getNearestZ(geoX, geoY, zValue); +- final int tGeoX = getGeoX(tx); +- final int tGeoY = getGeoY(ty); +- final int tz = getNearestZ(tGeoX, tGeoY, tzValue); ++ // get origin and check existing geo coordinates ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) ++ { ++ return true; ++ } + +- if (DoorData.getInstance().checkIfDoorsBetween(x, y, z, tx, ty, tz, instance, false)) ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coordinates ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) + { +- return new Location(x, y, getHeight(x, y, z)); ++ return true; + } +- if (FenceData.getInstance().checkIfFenceBetween(x, y, z, tx, ty, tz, instance)) ++ ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // target coordinates reached ++ if ((gox == gtx) && (goy == gty) && (goz == gtz)) + { +- return new Location(x, y, getHeight(x, y, z)); ++ return true; + } +- if (WarpedSpaceManager.getInstance().checkForWarpedSpace(x, y, z, tx, ty, tz, instance)) ++ ++ // perform geodata check ++ final GeoLocation loc = checkMove(gox, goy, goz, gtx, gty, gtz, instance); ++ return (loc.getGeoX() == gtx) && (loc.getGeoY() == gty); ++ } ++ ++ /** ++ * Check movement from origin to target. Returns last available point in the checked path. ++ * @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 {@link Location} : Last point where object can walk (just before wall) ++ */ ++ public Location canMoveToTargetLoc(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) ++ { ++ // Mobius: Double check for doors before normal checkMove to avoid exploiting key movement. ++ if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, instance, false)) + { +- return new Location(x, y, getHeight(x, y, z)); ++ return new Location(ox, oy, oz); + } ++ if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, instance)) ++ { ++ return new Location(ox, oy, oz); ++ } + +- 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 = z; ++ // get origin and check existing geo coordinates ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) ++ { ++ return new Location(tx, ty, tz); ++ } + +- while (pointIter.next()) ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coordinates ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) + { +- final int curX = pointIter.x(); +- final int curY = pointIter.y(); +- final int curZ = getNearestZ(curX, curY, prevZ); +- +- if (hasGeoPos(prevX, prevY)) +- { +- final int nswe = GeoUtils.computeNswe(prevX, prevY, curX, curY); +- if (!checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, nswe)) +- { +- // Can't move, return previous location. +- return new Location(getWorldX(prevX), getWorldY(prevY), prevZ); +- } +- } +- +- prevX = curX; +- prevY = curY; +- prevZ = curZ; ++ return new Location(tx, ty, tz); + } + +- if (hasGeoPos(prevX, prevY) && (prevZ != tz)) ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // target coordinates reached ++ if ((gox == gtx) && (goy == gty) && (goz == gtz)) + { +- // Different floors, return start location. +- return new Location(x, y, z); ++ return new Location(tx, ty, tz); + } + +- return new Location(tx, ty, tz); ++ // perform geodata check ++ return checkMove(gox, goy, goz, gtx, gty, gtz, instance); + } + + /** +- * 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 fromZvalue 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 toZvalue 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 ++ * With this method you can check if a position is visible or can be reached by beeline movement.
++ * 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 gox : origin X geodata coordinate ++ * @param goy : origin Y geodata coordinate ++ * @param goz : origin Z geodata coordinate ++ * @param gtx : target X geodata coordinate ++ * @param gty : target Y geodata coordinate ++ * @param gtz : target Z geodata coordinate ++ * @param instance ++ * @return {@link GeoLocation} : The last allowed point of movement. + */ +- public boolean canMoveToTarget(int fromX, int fromY, int fromZvalue, int toX, int toY, int toZvalue, Instance instance) ++ protected final GeoLocation checkMove(int gox, int goy, int goz, int gtx, int gty, int gtz, Instance instance) + { +- final int geoX = getGeoX(fromX); +- final int geoY = getGeoY(fromY); +- final int fromZ = getNearestZ(geoX, geoY, fromZvalue); +- final int tGeoX = getGeoX(toX); +- final int tGeoY = getGeoY(toY); +- final int toZ = getNearestZ(tGeoX, tGeoY, toZvalue); +- +- if (DoorData.getInstance().checkIfDoorsBetween(fromX, fromY, fromZ, toX, toY, toZ, instance, false)) ++ if (DoorData.getInstance().checkIfDoorsBetween(gox, goy, goz, gtx, gty, gtz, instance, false)) + { +- return false; ++ return new GeoLocation(gox, goy, goz); + } +- if (FenceData.getInstance().checkIfFenceBetween(fromX, fromY, fromZ, toX, toY, toZ, instance)) ++ if (FenceData.getInstance().checkIfFenceBetween(gox, goy, goz, gtx, gty, gtz, instance)) + { +- return false; ++ return new GeoLocation(gox, goy, goz); + } +- if (WarpedSpaceManager.getInstance().checkForWarpedSpace(fromX, fromY, fromZ, toX, toY, toZ, instance)) ++ if (WarpedSpaceManager.getInstance().checkForWarpedSpace(gox, goy, goz, gtx, gty, gtz, instance)) + { +- return false; ++ return new GeoLocation(gox, goy, goz); + } + +- 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 = fromZ; ++ // get X delta, signum and direction flag ++ final int dx = Math.abs(gtx - gox); ++ final int sx = gox < gtx ? 1 : -1; ++ final byte dirX = sx > 0 ? GeoStructure.CELL_FLAG_E : GeoStructure.CELL_FLAG_W; + +- while (pointIter.next()) ++ // get Y delta, signum and direction flag ++ final int dy = Math.abs(gty - goy); ++ final int sy = goy < gty ? 1 : -1; ++ final byte dirY = sy > 0 ? GeoStructure.CELL_FLAG_S : GeoStructure.CELL_FLAG_N; ++ ++ // get direction flag for diagonal movement ++ final byte dirXY = getDirXY(dirX, dirY); ++ ++ // delta, determines axis to move on (+..X axis, -..Y axis) ++ int d = dx - dy; ++ ++ // NSWE direction of movement ++ byte direction; ++ ++ // load pointer coordinates ++ int gpx = gox; ++ int gpy = goy; ++ int gpz = goz; ++ ++ // load next pointer ++ int nx = gpx; ++ int ny = gpy; ++ ++ // loop ++ int count = 0; ++ while (count++ < Config.MAX_ITERATIONS) + { +- final int curX = pointIter.x(); +- final int curY = pointIter.y(); +- final int curZ = getNearestZ(curX, curY, prevZ); ++ direction = 0; + +- if (hasGeoPos(prevX, prevY)) ++ // calculate next point coordinates ++ final int e2 = 2 * d; ++ if ((e2 > -dy) && (e2 < dx)) + { +- final int nswe = GeoUtils.computeNswe(prevX, prevY, curX, curY); +- if (!checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, nswe)) ++ d -= dy; ++ d += dx; ++ nx += sx; ++ ny += sy; ++ direction |= dirXY; ++ } ++ else if (e2 > -dy) ++ { ++ d -= dy; ++ nx += sx; ++ direction |= dirX; ++ } ++ else if (e2 < dx) ++ { ++ d += dx; ++ ny += sy; ++ direction |= dirY; ++ } ++ ++ // obstacle found, return ++ if ((getNsweNearest(gpx, gpy, gpz) & direction) == 0) ++ { ++ return new GeoLocation(gpx, gpy, gpz); ++ } ++ ++ // update pointer coordinates ++ gpx = nx; ++ gpy = ny; ++ gpz = getHeightNearest(nx, ny, gpz); ++ ++ // target coordinates reached ++ if ((gpx == gtx) && (gpy == gty)) ++ { ++ if (gpz == gtz) + { +- return false; ++ // path found, Z coordinates are okay, return target point ++ return new GeoLocation(gtx, gty, gtz); + } ++ ++ // path found, Z coordinates are not okay, return last good point ++ return new GeoLocation(gpx, gpy, gpz); + } ++ } ++ ++ return new GeoLocation(gox, goy, goz); ++ } ++ ++ /** ++ * Returns diagonal NSWE flag format of combined two NSWE flags. ++ * @param dirX : X direction NSWE flag ++ * @param dirY : Y direction NSWE flag ++ * @return byte : NSWE flag of combined direction ++ */ ++ private static byte getDirXY(byte dirX, byte dirY) ++ { ++ // check axis directions ++ if (dirY == GeoStructure.CELL_FLAG_N) ++ { ++ if (dirX == GeoStructure.CELL_FLAG_W) ++ { ++ return GeoStructure.CELL_FLAG_NW; ++ } + +- prevX = curX; +- prevY = curY; +- prevZ = curZ; ++ return GeoStructure.CELL_FLAG_NE; + } + +- if (hasGeoPos(prevX, prevY) && (prevZ != toZ)) ++ if (dirX == GeoStructure.CELL_FLAG_W) + { +- // Different floors. +- return false; ++ return GeoStructure.CELL_FLAG_SW; + } + +- return true; ++ return GeoStructure.CELL_FLAG_SE; + } + ++ /** ++ * 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 ++ */ ++ public List findPath(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) ++ { ++ return null; ++ } ++ ++ /** ++ * Returns the instance of the {@link GeoEngine}. ++ * @return {@link GeoEngine} : The instance. ++ */ + public static GeoEngine getInstance() + { + return SingletonHolder.INSTANCE; +@@ -752,6 +947,6 @@ + + private static class SingletonHolder + { +- protected static final GeoEngine INSTANCE = new GeoEngine(); ++ protected static final GeoEngine INSTANCE = Config.PATHFINDING ? new GeoEnginePathfinding() : new GeoEngine(); + } +-} ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/GeoEnginePathfinding.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/GeoEnginePathfinding.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/GeoEnginePathfinding.java (working copy) +@@ -20,88 +20,84 @@ + import java.util.LinkedList; + import java.util.List; + import java.util.ListIterator; +-import java.util.logging.Level; +-import java.util.logging.Logger; + + import org.l2jmobius.Config; +-import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; +-import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +-import org.l2jmobius.gameserver.geoengine.pathfinding.CellNode; +-import org.l2jmobius.gameserver.geoengine.pathfinding.CellNodeBuffer; +-import org.l2jmobius.gameserver.geoengine.pathfinding.NodeLoc; +-import org.l2jmobius.gameserver.model.World; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoLocation; ++import org.l2jmobius.gameserver.geoengine.pathfinding.Node; ++import org.l2jmobius.gameserver.geoengine.pathfinding.NodeBuffer; ++import org.l2jmobius.gameserver.model.Location; + import org.l2jmobius.gameserver.model.instancezone.Instance; + + /** +- * @author -Nemesiss- ++ * @author Hasha + */ +-public class GeoEnginePathfinding ++final class GeoEnginePathfinding extends GeoEngine + { +- private static final Logger LOGGER = Logger.getLogger(GeoEnginePathfinding.class.getName()); ++ // pre-allocated buffers ++ private final BufferHolder[] _buffers; + +- private BufferInfo[] _buffers; +- + protected GeoEnginePathfinding() + { +- try ++ super(); ++ ++ final String[] array = Config.PATHFIND_BUFFERS.split(";"); ++ _buffers = new BufferHolder[array.length]; ++ int count = 0; ++ for (int i = 0; i < array.length; i++) + { +- final String[] array = Config.PATHFIND_BUFFERS.split(";"); ++ final String buf = array[i]; ++ final String[] args = buf.split("x"); + +- _buffers = new BufferInfo[array.length]; +- +- String buf; +- String[] args; +- for (int i = 0; i < array.length; i++) ++ try + { +- buf = array[i]; +- args = buf.split("x"); +- if (args.length != 2) +- { +- throw new Exception("Invalid buffer definition: " + buf); +- } +- +- _buffers[i] = new BufferInfo(Integer.parseInt(args[0]), Integer.parseInt(args[1])); ++ final int size = Integer.parseInt(args[1]); ++ count += size; ++ _buffers[i] = new BufferHolder(Integer.parseInt(args[0]), size); + } ++ catch (Exception e) ++ { ++ LOGGER.warning("GeoEnginePathfinding: Can not load buffer setting: " + buf); ++ } + } +- catch (Exception e) +- { +- LOGGER.log(Level.WARNING, "CellPathFinding: Problem during buffer init: " + e.getMessage(), e); +- throw new Error("CellPathFinding: load aborted"); +- } ++ ++ LOGGER.info("GeoEnginePathfinding: Loaded " + count + " node buffers."); + } + +- public boolean pathNodesExist(short regionoffset) ++ @Override ++ public List findPath(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + { +- return false; +- } +- +- public List findPath(int x, int y, int z, int tx, int ty, int tz, Instance instance) +- { +- final int gx = GeoEngine.getInstance().getGeoX(x); +- final int gy = GeoEngine.getInstance().getGeoY(y); +- if (!GeoEngine.getInstance().hasGeo(x, y)) ++ // get origin and check existing geo coords ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) + { + 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)) ++ ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coords ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) + { + 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)))); ++ ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // Prepare buffer for pathfinding calculations ++ final NodeBuffer buffer = getBuffer(64 + (2 * Math.max(Math.abs(gox - gtx), Math.abs(goy - gty)))); + if (buffer == null) + { + return null; + } + +- List path = null; ++ // find path ++ List path = null; + try + { +- final CellNode result = buffer.findPath(gx, gy, gz, gtx, gty, gtz); +- ++ final Node result = buffer.findPath(gox, goy, goz, gtx, gty, gtz); + if (result == null) + { + return null; +@@ -125,33 +121,45 @@ + return path; + } + +- int currentX, currentY, currentZ; +- ListIterator middlePoint; ++ // get path list iterator ++ final ListIterator point = path.listIterator(); + +- middlePoint = path.listIterator(); +- currentX = x; +- currentY = y; +- currentZ = z; ++ // get node A (origin) ++ int nodeAx = gox; ++ int nodeAy = goy; ++ short nodeAz = goz; + +- while (middlePoint.hasNext()) ++ // get node B ++ GeoLocation nodeB = (GeoLocation) point.next(); ++ ++ // iterate thought the path to optimize it ++ int count = 0; ++ while (point.hasNext() && (count++ < Config.MAX_ITERATIONS)) + { +- final AbstractNodeLoc locMiddle = middlePoint.next(); +- if (!middlePoint.hasNext()) +- { +- break; +- } ++ // get node C ++ final GeoLocation nodeC = (GeoLocation) path.get(point.nextIndex()); + +- final AbstractNodeLoc locEnd = path.get(middlePoint.nextIndex()); +- if (GeoEngine.getInstance().canMoveToTarget(currentX, currentY, currentZ, locEnd.getX(), locEnd.getY(), locEnd.getZ(), instance)) ++ // check movement from node A to node C ++ final GeoLocation loc = checkMove(nodeAx, nodeAy, nodeAz, nodeC.getGeoX(), nodeC.getGeoY(), nodeC.getZ(), instance); ++ if ((loc.getGeoX() == nodeC.getGeoX()) && (loc.getGeoY() == nodeC.getGeoY())) + { +- middlePoint.remove(); ++ // can move from node A to node C ++ ++ // remove node B ++ point.remove(); + } + else + { +- currentX = locMiddle.getX(); +- currentY = locMiddle.getY(); +- currentZ = locMiddle.getZ(); ++ // can not move from node A to node C ++ ++ // set node A (node B is part of path, update A coordinates) ++ nodeAx = nodeB.getGeoX(); ++ nodeAy = nodeB.getGeoY(); ++ nodeAz = (short) nodeB.getZ(); + } ++ ++ // set node B ++ nodeB = (GeoLocation) point.next(); + } + + return path; +@@ -158,144 +166,102 @@ + } + + /** +- * Convert geodata position to pathnode position +- * @param geo_pos +- * @return pathnode position ++ * Create list of node locations as result of calculated buffer node tree. ++ * @param node : the entry point ++ * @return List : list of node location + */ +- public short getNodePos(int geo_pos) ++ private static List constructPath(Node node) + { +- return (short) (geo_pos >> 3); // OK? +- } +- +- /** +- * Convert node position to pathnode block position +- * @param node_pos +- * @return pathnode block position (0...255) +- */ +- public short getNodeBlock(int node_pos) +- { +- return (short) (node_pos % 256); +- } +- +- public byte getRegionX(int node_pos) +- { +- return (byte) ((node_pos >> 8) + World.TILE_X_MIN); +- } +- +- public byte getRegionY(int node_pos) +- { +- return (byte) ((node_pos >> 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 node_x rx +- * @return +- */ +- public int calculateWorldX(short node_x) +- { +- return World.MAP_MIN_X + (node_x * 128) + 48; +- } +- +- /** +- * Convert pathnode y to World y position +- * @param node_y +- * @return +- */ +- public int calculateWorldY(short node_y) +- { +- return World.MAP_MIN_Y + (node_y * 128) + 48; +- } +- +- private List constructPath(AbstractNode nodeValue) +- { +- final LinkedList path = new LinkedList<>(); +- int previousDirectionX = Integer.MIN_VALUE; +- int previousDirectionY = Integer.MIN_VALUE; +- int directionX, directionY; ++ // create empty list ++ final LinkedList list = new LinkedList<>(); + +- AbstractNode node = nodeValue; +- while (node.getParent() != null) ++ // set direction X/Y ++ int dx = 0; ++ int dy = 0; ++ ++ // get target parent ++ Node target = node; ++ Node parent = target.getParent(); ++ ++ // while parent exists ++ int count = 0; ++ while ((parent != null) && (count++ < Config.MAX_ITERATIONS)) + { +- directionX = node.getLoc().getNodeX() - node.getParent().getLoc().getNodeX(); +- directionY = node.getLoc().getNodeY() - node.getParent().getLoc().getNodeY(); ++ // get parent <> target direction X/Y ++ final int nx = parent.getLoc().getGeoX() - target.getLoc().getGeoX(); ++ final int ny = parent.getLoc().getGeoY() - target.getLoc().getGeoY(); + +- // only add a new route point if moving direction changes +- if ((directionX != previousDirectionX) || (directionY != previousDirectionY)) ++ // direction has changed? ++ if ((dx != nx) || (dy != ny)) + { +- previousDirectionX = directionX; +- previousDirectionY = directionY; ++ // add node to the beginning of the list ++ list.addFirst(target.getLoc()); + +- path.addFirst(node.getLoc()); +- node.setLoc(null); ++ // update direction X/Y ++ dx = nx; ++ dy = ny; + } + +- node = node.getParent(); ++ // move to next node, set target and get its parent ++ target = parent; ++ parent = target.getParent(); + } + +- return path; ++ // return list ++ return list; + } + +- private CellNodeBuffer alloc(int size) ++ /** ++ * Provides optimize selection of the buffer. When all pre-initialized buffer are locked, creates new buffer. ++ * @param size : pre-calculated minimal required size ++ * @return NodeBuffer : buffer ++ */ ++ private final NodeBuffer getBuffer(int size) + { +- CellNodeBuffer current = null; +- for (BufferInfo i : _buffers) ++ NodeBuffer current = null; ++ for (BufferHolder holder : _buffers) + { +- if (i.mapSize >= size) ++ // Find proper size of buffer ++ if (holder._size < size) + { +- for (CellNodeBuffer buf : i.buffer) ++ continue; ++ } ++ ++ // Find unlocked NodeBuffer ++ for (NodeBuffer buffer : holder._buffer) ++ { ++ if (!buffer.isLocked()) + { +- if (buf.lock()) +- { +- current = buf; +- break; +- } ++ continue; + } +- if (current != null) +- { +- break; +- } + +- // not found, allocate temporary buffer +- current = new CellNodeBuffer(i.mapSize); +- current.lock(); +- if (i.buffer.size() < i.count) +- { +- i.buffer.add(current); +- break; +- } ++ return buffer; + } ++ ++ // NodeBuffer not found, allocate temporary buffer ++ current = new NodeBuffer(holder._size); ++ current.isLocked(); + } + + return current; + } + +- private static final class BufferInfo ++ /** ++ * NodeBuffer container with specified size and count of separate buffers. ++ */ ++ private static final class BufferHolder + { +- final int mapSize; +- final int count; +- ArrayList buffer; ++ final int _size; ++ List _buffer; + +- public BufferInfo(int size, int cnt) ++ public BufferHolder(int size, int count) + { +- mapSize = size; +- count = cnt; +- buffer = new ArrayList<>(count); ++ _size = size; ++ _buffer = new ArrayList<>(count); ++ for (int i = 0; i < count; i++) ++ { ++ _buffer.add(new NodeBuffer(size)); ++ } + } + } +- +- public static GeoEnginePathfinding getInstance() +- { +- return SingletonHolder.INSTANCE; +- } +- +- private static class SingletonHolder +- { +- protected static final GeoEnginePathfinding INSTANCE = new GeoEnginePathfinding(); +- } +-} ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java (working copy) +@@ -0,0 +1,197 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++import java.io.IOException; ++ ++/** ++ * @author Hasha ++ */ ++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 height of cell, which is closest to given coordinates.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @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 getHeightNearestOriginal(int geoX, int geoY, int worldZ); ++ ++ /** ++ * Returns the height of cell, which is first above 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, above given coordinates. ++ */ ++ public abstract short getHeightAbove(int geoX, int geoY, int worldZ); ++ ++ /** ++ * Returns the height of cell, which is first below 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, below given coordinates. ++ */ ++ public abstract short getHeightBelow(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 the NSWE flag byte of cell, which is closest to given coordinates.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @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 getNsweNearestOriginal(int geoX, int geoY, int worldZ); ++ ++ /** ++ * Returns the NSWE flag byte of cell, which is first above 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 getNsweAbove(int geoX, int geoY, int worldZ); ++ ++ /** ++ * Returns the NSWE flag byte of cell, which is first below 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 getNsweBelow(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 above given coordinates.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @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 getIndexAboveOriginal(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 index to data of the cell, which is first layer below given coordinates.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @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 getIndexBelowOriginal(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 height of cell given by cell index.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @param index : Index of the cell. ++ * @return short : Cell geodata Z coordinate, below given coordinates. ++ */ ++ public abstract short getHeightOriginal(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); ++ ++ /** ++ * Returns the NSWE flag byte of cell given by cell index.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @param index : Index of the cell. ++ * @return short : Cell geodata Z coordinate, below given coordinates. ++ */ ++ public abstract byte getNsweOriginal(int index); ++ ++ /** ++ * Sets the NSWE flag byte of cell given by cell index. ++ * @param index : Index of the cell. ++ * @param nswe : New NSWE flag byte. ++ */ ++ public abstract void setNswe(int index, byte nswe); ++ ++ /** ++ * Saves the block in L2D format to {@link BufferedOutputStream}. Used only for L2D geodata conversion. ++ * @param stream : The stream. ++ * @throws IOException : Can't save the block to steam. ++ */ ++ public abstract void saveBlock(BufferedOutputStream stream) throws IOException; ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java (working copy) +@@ -0,0 +1,252 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++import java.io.IOException; ++import java.nio.ByteBuffer; ++ ++/** ++ * @author Hasha ++ */ ++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. ++ * @param format : GeoFormat specifying format of loaded data. ++ */ ++ public BlockComplex(ByteBuffer bb, GeoFormat format) ++ { ++ // initialize buffer ++ _buffer = new byte[GeoStructure.BLOCK_CELLS * 3]; ++ ++ // load data ++ for (int i = 0; i < GeoStructure.BLOCK_CELLS; i++) ++ { ++ if (format != GeoFormat.L2D) ++ { ++ // 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); ++ } ++ else ++ { ++ // get nswe ++ final byte nswe = bb.get(); ++ _buffer[i * 3] = nswe; ++ ++ // get height ++ final short height = bb.getShort(); ++ _buffer[(i * 3) + 1] = (byte) (height & 0x00FF); ++ _buffer[(i * 3) + 2] = (byte) (height >> 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 short getHeightNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getHeightNearest(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeightAbove(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 short height = (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ ++ // check and return height ++ return height > worldZ ? height : Short.MIN_VALUE; ++ } ++ ++ @Override ++ public short getHeightBelow(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 short height = (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ ++ // check and return height ++ return height < worldZ ? height : Short.MAX_VALUE; ++ } ++ ++ @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 byte getNsweNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getNsweNearest(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public byte getNsweAbove(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 ? _buffer[index] : 0; ++ } ++ ++ @Override ++ public byte getNsweBelow(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 ? _buffer[index] : 0; ++ } ++ ++ @Override ++ public 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 getIndexAboveOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexAbove(geoX, geoY, worldZ); ++ } ++ ++ @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 int getIndexBelowOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexBelow(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeight(int index) ++ { ++ return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ } ++ ++ @Override ++ public short getHeightOriginal(int index) ++ { ++ return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ } ++ ++ @Override ++ public byte getNswe(int index) ++ { ++ return _buffer[index]; ++ } ++ ++ @Override ++ public byte getNsweOriginal(int index) ++ { ++ return _buffer[index]; ++ } ++ ++ @Override ++ public void setNswe(int index, byte nswe) ++ { ++ _buffer[index] = nswe; ++ } ++ ++ @Override ++ public void saveBlock(BufferedOutputStream stream) throws IOException ++ { ++ // write block type ++ stream.write(GeoStructure.TYPE_COMPLEX_L2D); ++ ++ // write block data ++ stream.write(_buffer, 0, GeoStructure.BLOCK_CELLS * 3); ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java (working copy) +@@ -0,0 +1,176 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++import java.io.IOException; ++import java.nio.ByteBuffer; ++ ++/** ++ * @author Hasha ++ */ ++public class BlockFlat extends ABlock ++{ ++ protected final short _height; ++ protected byte _nswe; ++ ++ /** ++ * Creates FlatBlock. ++ * @param bb : Input byte buffer. ++ * @param format : GeoFormat specifying format of loaded data. ++ */ ++ public BlockFlat(ByteBuffer bb, GeoFormat format) ++ { ++ _height = bb.getShort(); ++ _nswe = format != GeoFormat.L2D ? 0x0F : (byte) (0xFF); ++ if (format == GeoFormat.L2OFF) ++ { ++ bb.getShort(); ++ } ++ } ++ ++ @Override ++ public boolean hasGeoPos() ++ { ++ return true; ++ } ++ ++ @Override ++ public short getHeightNearest(int geoX, int geoY, int worldZ) ++ { ++ return _height; ++ } ++ ++ @Override ++ public short getHeightNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return _height; ++ } ++ ++ @Override ++ public short getHeightAbove(int geoX, int geoY, int worldZ) ++ { ++ // check and return height ++ return _height > worldZ ? _height : Short.MIN_VALUE; ++ } ++ ++ @Override ++ public short getHeightBelow(int geoX, int geoY, int worldZ) ++ { ++ // check and return height ++ return _height < worldZ ? _height : Short.MAX_VALUE; ++ } ++ ++ @Override ++ public byte getNsweNearest(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweAbove(int geoX, int geoY, int worldZ) ++ { ++ // check height and return nswe ++ return _height > worldZ ? _nswe : 0; ++ } ++ ++ @Override ++ public byte getNsweBelow(int geoX, int geoY, int worldZ) ++ { ++ // check height and return nswe ++ return _height < worldZ ? _nswe : 0; ++ } ++ ++ @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 getIndexAboveOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexAbove(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public int getIndexBelow(int geoX, int geoY, int worldZ) ++ { ++ // check height and return index ++ return _height < worldZ ? 0 : -1; ++ } ++ ++ @Override ++ public int getIndexBelowOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexBelow(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeight(int index) ++ { ++ return _height; ++ } ++ ++ @Override ++ public short getHeightOriginal(int index) ++ { ++ return _height; ++ } ++ ++ @Override ++ public byte getNswe(int index) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweOriginal(int index) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public void setNswe(int index, byte nswe) ++ { ++ _nswe = nswe; ++ } ++ ++ @Override ++ public void saveBlock(BufferedOutputStream stream) throws IOException ++ { ++ // write block type ++ stream.write(GeoStructure.TYPE_FLAT_L2D); ++ ++ // write height ++ stream.write((byte) (_height & 0x00FF)); ++ stream.write((byte) (_height >> 8)); ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java (working copy) +@@ -0,0 +1,465 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++import java.io.IOException; ++import java.nio.ByteBuffer; ++import java.nio.ByteOrder; ++import java.util.Arrays; ++ ++/** ++ * @author Hasha ++ */ ++public class BlockMultilayer extends ABlock ++{ ++ private static final int MAX_LAYERS = Byte.MAX_VALUE; ++ ++ private static ByteBuffer _temp; ++ ++ /** ++ * Initializes the temporarily buffer. ++ */ ++ public static void initialize() ++ { ++ // initialize temporarily buffer and sorting mechanism ++ _temp = ByteBuffer.allocate(GeoStructure.BLOCK_CELLS * MAX_LAYERS * 3); ++ _temp.order(ByteOrder.LITTLE_ENDIAN); ++ } ++ ++ /** ++ * Releases temporarily buffer. ++ */ ++ public static void release() ++ { ++ _temp = null; ++ } ++ ++ protected byte[] _buffer; ++ ++ /** ++ * Implicit constructor for children class. ++ */ ++ protected BlockMultilayer() ++ { ++ _buffer = null; ++ } ++ ++ /** ++ * Creates MultilayerBlock. ++ * @param bb : Input byte buffer. ++ * @param format : GeoFormat specifying format of loaded data. ++ */ ++ public BlockMultilayer(ByteBuffer bb, GeoFormat format) ++ { ++ // 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 = format != GeoFormat.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++) ++ { ++ if (format != GeoFormat.L2D) ++ { ++ // get data ++ final short data = bb.getShort(); ++ ++ // add nswe and height ++ _temp.put((byte) (data & 0x000F)); ++ _temp.putShort((short) ((short) (data & 0xFFF0) >> 1)); ++ } ++ else ++ { ++ // add nswe ++ _temp.put(bb.get()); ++ ++ // add height ++ _temp.putShort(bb.getShort()); ++ } ++ } ++ } ++ ++ // 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 short getHeightNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getHeightNearest(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeightAbove(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 height ++ if (height > worldZ) ++ { ++ return (short) height; ++ } ++ ++ // move index to next layer ++ index -= 3; ++ } ++ ++ // none layer found, return minimum value ++ return Short.MIN_VALUE; ++ } ++ ++ @Override ++ public short getHeightBelow(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 height ++ if (height < worldZ) ++ { ++ return (short) height; ++ } ++ ++ // move index to next layer ++ index += 3; ++ } ++ ++ // none layer found, return maximum value ++ return Short.MAX_VALUE; ++ } ++ ++ @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 byte getNsweNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getNsweNearest(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public byte getNsweAbove(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 nswe ++ if (height > worldZ) ++ { ++ return _buffer[index]; ++ } ++ ++ // move index to next layer ++ index -= 3; ++ } ++ ++ // none layer found, block movement ++ return 0; ++ } ++ ++ @Override ++ public byte getNsweBelow(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 nswe ++ if (height < worldZ) ++ { ++ return _buffer[index]; ++ } ++ ++ // move index to next layer ++ index += 3; ++ } ++ ++ // none layer found, block movement ++ return 0; ++ } ++ ++ @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 first layer data (first from bottom) ++ byte layers = _buffer[index++]; ++ ++ // loop though all cell layers, find closest layer ++ 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; ++ } ++ ++ // none layer found ++ return -1; ++ } ++ ++ @Override ++ public int getIndexAboveOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexAbove(geoX, geoY, worldZ); ++ } ++ ++ @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; ++ } ++ ++ // none layer found ++ return -1; ++ } ++ ++ @Override ++ public int getIndexBelowOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexBelow(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeight(int index) ++ { ++ // get height ++ return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ } ++ ++ @Override ++ public short getHeightOriginal(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]; ++ } ++ ++ @Override ++ public byte getNsweOriginal(int index) ++ { ++ // get nswe ++ return _buffer[index]; ++ } ++ ++ @Override ++ public void setNswe(int index, byte nswe) ++ { ++ // set nswe ++ _buffer[index] = nswe; ++ } ++ ++ @Override ++ public void saveBlock(BufferedOutputStream stream) throws IOException ++ { ++ // write block type ++ stream.write(GeoStructure.TYPE_MULTILAYER_L2D); ++ ++ // for each cell ++ int index = 0; ++ for (int i = 0; i < GeoStructure.BLOCK_CELLS; i++) ++ { ++ // write layers count ++ final byte layers = _buffer[index++]; ++ stream.write(layers); ++ ++ // write cell data ++ stream.write(_buffer, index, layers * 3); ++ ++ // move index to next cell ++ index += layers * 3; ++ } ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java (working copy) +@@ -0,0 +1,150 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++ ++/** ++ * @author Hasha ++ */ ++public class BlockNull extends ABlock ++{ ++ private final byte _nswe; ++ ++ public BlockNull() ++ { ++ _nswe = (byte) 0xFF; ++ } ++ ++ @Override ++ public boolean hasGeoPos() ++ { ++ return false; ++ } ++ ++ @Override ++ public short getHeightNearest(int geoX, int geoY, int worldZ) ++ { ++ return (short) worldZ; ++ } ++ ++ @Override ++ public short getHeightNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return (short) worldZ; ++ } ++ ++ @Override ++ public short getHeightAbove(int geoX, int geoY, int worldZ) ++ { ++ return (short) worldZ; ++ } ++ ++ @Override ++ public short getHeightBelow(int geoX, int geoY, int worldZ) ++ { ++ return (short) worldZ; ++ } ++ ++ @Override ++ public byte getNsweNearest(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweAbove(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweBelow(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) ++ { ++ return 0; ++ } ++ ++ @Override ++ public int getIndexAboveOriginal(int geoX, int geoY, int worldZ) ++ { ++ return 0; ++ } ++ ++ @Override ++ public int getIndexBelow(int geoX, int geoY, int worldZ) ++ { ++ return 0; ++ } ++ ++ @Override ++ public int getIndexBelowOriginal(int geoX, int geoY, int worldZ) ++ { ++ return 0; ++ } ++ ++ @Override ++ public short getHeight(int index) ++ { ++ return 0; ++ } ++ ++ @Override ++ public short getHeightOriginal(int index) ++ { ++ return 0; ++ } ++ ++ @Override ++ public byte getNswe(int index) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweOriginal(int index) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public void setNswe(int index, byte nswe) ++ { ++ } ++ ++ @Override ++ public void saveBlock(BufferedOutputStream stream) ++ { ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java (nonexistent) +@@ -1,48 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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() +- { +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/ComplexBlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/ComplexBlock.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/ComplexBlock.java (nonexistent) +@@ -1,77 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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; +- +-/** +- * @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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/FlatBlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/FlatBlock.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/FlatBlock.java (nonexistent) +@@ -1,56 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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; +- +-/** +- * @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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/GeoFormat.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/GeoFormat.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/GeoFormat.java (working copy) +@@ -0,0 +1,39 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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 Hasha ++ */ ++public enum GeoFormat ++{ ++ L2J("%d_%d.l2j"), ++ L2OFF("%d_%d_conv.dat"), ++ L2D("%d_%d.l2d"); ++ ++ private final String _filename; ++ ++ private GeoFormat(String filename) ++ { ++ _filename = filename; ++ } ++ ++ public String getFilename() ++ { ++ return _filename; ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/GeoLocation.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/GeoLocation.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/GeoLocation.java (working copy) +@@ -0,0 +1,67 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.geoengine.GeoEngine; ++import org.l2jmobius.gameserver.model.Location; ++ ++/** ++ * @author Hasha ++ */ ++public class GeoLocation extends Location ++{ ++ private byte _nswe; ++ ++ public GeoLocation(int x, int y, int z) ++ { ++ super(x, y, GeoEngine.getInstance().getHeightNearest(x, y, z)); ++ _nswe = GeoEngine.getInstance().getNsweNearest(x, y, z); ++ } ++ ++ public void set(int x, int y, short z) ++ { ++ super.setXYZ(x, y, GeoEngine.getInstance().getHeightNearest(x, y, z)); ++ _nswe = GeoEngine.getInstance().getNsweNearest(x, y, z); ++ } ++ ++ public int getGeoX() ++ { ++ return _x; ++ } ++ ++ public int getGeoY() ++ { ++ return _y; ++ } ++ ++ @Override ++ public int getX() ++ { ++ return GeoEngine.getWorldX(_x); ++ } ++ ++ @Override ++ public int getY() ++ { ++ return GeoEngine.getWorldY(_y); ++ } ++ ++ public byte getNSWE() ++ { ++ return _nswe; ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java (working copy) +@@ -0,0 +1,70 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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; ++ ++/** ++ * @author Hasha ++ */ ++public class GeoStructure ++{ ++ // cells ++ public static final byte CELL_FLAG_E = 1 << 0; ++ public static final byte CELL_FLAG_W = 1 << 1; ++ public static final byte CELL_FLAG_S = 1 << 2; ++ public static final byte CELL_FLAG_N = 1 << 3; ++ public static final byte CELL_FLAG_SE = 1 << 4; ++ public static final byte CELL_FLAG_SW = 1 << 5; ++ public static final byte CELL_FLAG_NE = 1 << 6; ++ public static final byte CELL_FLAG_NW = (byte) (1 << 7); ++ ++ public static final int CELL_HEIGHT = 8; ++ public static final int CELL_IGNORE_HEIGHT = CELL_HEIGHT * 6; ++ ++ // blocks ++ public static final byte TYPE_FLAT_L2J_L2OFF = 0; ++ public static final byte TYPE_FLAT_L2D = (byte) 0xD0; ++ public static final byte TYPE_COMPLEX_L2J = 1; ++ public static final byte TYPE_COMPLEX_L2OFF = 0x40; ++ public static final byte TYPE_COMPLEX_L2D = (byte) 0xD1; ++ 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) ++ public static final byte TYPE_MULTILAYER_L2D = (byte) 0xD2; ++ ++ 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; ++ ++ // regions ++ 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; ++ ++ // global geodata ++ private static final int GEO_REGIONS_X = ((World.TILE_X_MAX - World.TILE_X_MIN) + 1); ++ private 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 +Index: java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java (nonexistent) +@@ -1,42 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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); +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/IGeoObject.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/IGeoObject.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/IGeoObject.java (working copy) +@@ -0,0 +1,53 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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 Hasha ++ */ ++public interface IGeoObject ++{ ++ /** ++ * Returns geodata X coordinate of the {@link IGeoObject}. ++ * @return int : Geodata X coordinate. ++ */ ++ int getGeoX(); ++ ++ /** ++ * Returns geodata Y coordinate of the {@link IGeoObject}. ++ * @return int : Geodata Y coordinate. ++ */ ++ int getGeoY(); ++ ++ /** ++ * Returns geodata Z coordinate of the {@link IGeoObject}. ++ * @return int : Geodata Z coordinate. ++ */ ++ int getGeoZ(); ++ ++ /** ++ * Returns height of the {@link IGeoObject}. ++ * @return int : Height. ++ */ ++ int getHeight(); ++ ++ /** ++ * Returns {@link IGeoObject} data. ++ * @return byte[][] : {@link IGeoObject} data. ++ */ ++ byte[][] getObjectGeoData(); ++} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java (nonexistent) +@@ -1,47 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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(); +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/MultilayerBlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/MultilayerBlock.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/MultilayerBlock.java (nonexistent) +@@ -1,183 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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; +- +-/** +- * @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); +- +- // 1 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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/NullRegion.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/NullRegion.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/NullRegion.java (nonexistent) +@@ -1,55 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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 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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/Region.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/Region.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/Region.java (nonexistent) +@@ -1,92 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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; +- +-/** +- * @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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java (nonexistent) +@@ -1,87 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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 T _loc; +- private AbstractNode _parent; +- +- public AbstractNode(T loc) +- { +- _loc = loc; +- } +- +- public void setParent(AbstractNode p) +- { +- _parent = p; +- } +- +- public AbstractNode getParent() +- { +- return _parent; +- } +- +- public T getLoc() +- { +- return _loc; +- } +- +- public void setLoc(T l) +- { +- _loc = l; +- } +- +- @Override +- public int hashCode() +- { +- final int prime = 31; +- int result = 1; +- result = (prime * result) + ((_loc == null) ? 0 : _loc.hashCode()); +- return result; +- } +- +- @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 +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java (nonexistent) +@@ -1,33 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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 int getNodeX(); +- +- public abstract int getNodeY(); +-} +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNode.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNode.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNode.java (nonexistent) +@@ -1,67 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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 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 +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNodeBuffer.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNodeBuffer.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNodeBuffer.java (nonexistent) +@@ -1,348 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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.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 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) +- { +- _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(); +- } +- +- 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 void getNeighbors() +- { +- if (!_current.getLoc().canGoAll()) +- { +- 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); +- } +- +- // SouthEast +- if ((nodeE != null) && (nodeS != null)) +- { +- if (nodeE.getLoc().canGoSouth() && nodeS.getLoc().canGoEast()) +- { +- addNode(x + 1, y + 1, z, true); +- } +- } +- +- // SouthWest +- if ((nodeS != null) && (nodeW != null)) +- { +- if (nodeW.getLoc().canGoSouth() && nodeS.getLoc().canGoWest()) +- { +- addNode(x - 1, y + 1, z, true); +- } +- } +- +- // NorthEast +- if ((nodeN != null) && (nodeE != null)) +- { +- if (nodeE.getLoc().canGoNorth() && nodeN.getLoc().canGoEast()) +- { +- addNode(x + 1, y - 1, z, true); +- } +- } +- +- // NorthWest +- if ((nodeN != null) && (nodeW != null)) +- { +- if (nodeW.getLoc().canGoNorth() && nodeN.getLoc().canGoWest()) +- { +- addNode(x - 1, y - 1, z, true); +- } +- } +- } +- +- private 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(); +- // reinit node if needed +- if (result.getLoc() != null) +- { +- result.getLoc().set(x, y, z); +- } +- else +- { +- result.setLoc(new NodeLoc(x, y, z)); +- } +- } +- +- return result; +- } +- +- private 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 boolean isHighWeight(int x, int y, int z) +- { +- final CellNode result = getNode(x, y, z); +- if (result == null) +- { +- return true; +- } +- +- if (!result.getLoc().canGoAll()) +- { +- return true; +- } +- if (Math.abs(result.getLoc().getZ() - z) > 16) +- { +- return true; +- } +- +- return false; +- } +- +- private 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 +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java (working copy) +@@ -0,0 +1,87 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.geodata.GeoLocation; ++ ++/** ++ * @author Hasha ++ */ ++public class Node ++{ ++ // node coords and nswe flag ++ private GeoLocation _loc; ++ ++ // node parent (for reverse path construction) ++ private Node _parent; ++ // node child (for moving over nodes during iteration) ++ private Node _child; ++ ++ // node G cost (movement cost = parent movement cost + current movement cost) ++ private double _cost = -1000; ++ ++ public void setLoc(int x, int y, int z) ++ { ++ _loc = new GeoLocation(x, y, z); ++ } ++ ++ public GeoLocation getLoc() ++ { ++ return _loc; ++ } ++ ++ public void setParent(Node parent) ++ { ++ _parent = parent; ++ } ++ ++ public Node getParent() ++ { ++ return _parent; ++ } ++ ++ public void setChild(Node child) ++ { ++ _child = child; ++ } ++ ++ public Node getChild() ++ { ++ return _child; ++ } ++ ++ public void setCost(double cost) ++ { ++ _cost = cost; ++ } ++ ++ public double getCost() ++ { ++ return _cost; ++ } ++ ++ public void free() ++ { ++ // reset node location ++ _loc = null; ++ ++ // reset node parent, child and cost ++ _parent = null; ++ _child = null; ++ _cost = -1000; ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java (working copy) +@@ -0,0 +1,306 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.concurrent.locks.ReentrantLock; ++ ++import org.l2jmobius.Config; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; ++ ++/** ++ * @author DS, Hasha; Credits to Diamond ++ */ ++public class NodeBuffer ++{ ++ private final ReentrantLock _lock = new ReentrantLock(); ++ private final int _size; ++ private final Node[][] _buffer; ++ ++ // center coordinates ++ private int _cx = 0; ++ private int _cy = 0; ++ ++ // target coordinates ++ private int _gtx = 0; ++ private int _gty = 0; ++ private short _gtz = 0; ++ ++ private Node _current = null; ++ ++ /** ++ * Constructor of NodeBuffer. ++ * @param size : one dimension size of buffer ++ */ ++ public NodeBuffer(int size) ++ { ++ // set size ++ _size = size; ++ ++ // initialize buffer ++ _buffer = new Node[size][size]; ++ for (int x = 0; x < size; x++) ++ { ++ for (int y = 0; y < size; y++) ++ { ++ _buffer[x][y] = 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 Node : first node of path ++ */ ++ public Node findPath(int gox, int goy, short goz, int gtx, int gty, short gtz) ++ { ++ // set coordinates (middle of the line (gox,goy) - (gtx,gty), will be in the center of the buffer) ++ _cx = gox + ((gtx - gox - _size) / 2); ++ _cy = goy + ((gty - goy - _size) / 2); ++ ++ _gtx = gtx; ++ _gty = gty; ++ _gtz = gtz; ++ ++ _current = getNode(gox, goy, goz); ++ _current.setCost(getCostH(gox, goy, goz)); ++ ++ int count = 0; ++ do ++ { ++ // reached target? ++ if ((_current.getLoc().getGeoX() == _gtx) && (_current.getLoc().getGeoY() == _gty) && (Math.abs(_current.getLoc().getZ() - _gtz) < 8)) ++ { ++ return _current; ++ } ++ ++ // expand current node ++ expand(); ++ ++ // move pointer ++ _current = _current.getChild(); ++ } ++ while ((_current != null) && (count++ < Config.MAX_ITERATIONS)); ++ ++ return null; ++ } ++ ++ public boolean isLocked() ++ { ++ return _lock.tryLock(); ++ } ++ ++ public void free() ++ { ++ _current = null; ++ ++ for (Node[] nodes : _buffer) ++ { ++ for (Node node : nodes) ++ { ++ if (node.getLoc() != null) ++ { ++ node.free(); ++ } ++ } ++ } ++ ++ _lock.unlock(); ++ } ++ ++ /** ++ * Check _current Node and add its neighbors to the buffer. ++ */ ++ private final void expand() ++ { ++ // can't move anywhere, don't expand ++ final byte nswe = _current.getLoc().getNSWE(); ++ if (nswe == 0) ++ { ++ return; ++ } ++ ++ // get geo coords of the node to be expanded ++ final int x = _current.getLoc().getGeoX(); ++ final int y = _current.getLoc().getGeoY(); ++ final short z = (short) _current.getLoc().getZ(); ++ ++ // can move north, expand ++ if ((nswe & GeoStructure.CELL_FLAG_N) != 0) ++ { ++ addNode(x, y - 1, z, Config.BASE_WEIGHT); ++ } ++ ++ // can move south, expand ++ if ((nswe & GeoStructure.CELL_FLAG_S) != 0) ++ { ++ addNode(x, y + 1, z, Config.BASE_WEIGHT); ++ } ++ ++ // can move west, expand ++ if ((nswe & GeoStructure.CELL_FLAG_W) != 0) ++ { ++ addNode(x - 1, y, z, Config.BASE_WEIGHT); ++ } ++ ++ // can move east, expand ++ if ((nswe & GeoStructure.CELL_FLAG_E) != 0) ++ { ++ addNode(x + 1, y, z, Config.BASE_WEIGHT); ++ } ++ ++ // can move north-west, expand ++ if ((nswe & GeoStructure.CELL_FLAG_NW) != 0) ++ { ++ addNode(x - 1, y - 1, z, Config.DIAGONAL_WEIGHT); ++ } ++ ++ // can move north-east, expand ++ if ((nswe & GeoStructure.CELL_FLAG_NE) != 0) ++ { ++ addNode(x + 1, y - 1, z, Config.DIAGONAL_WEIGHT); ++ } ++ ++ // can move south-west, expand ++ if ((nswe & GeoStructure.CELL_FLAG_SW) != 0) ++ { ++ addNode(x - 1, y + 1, z, Config.DIAGONAL_WEIGHT); ++ } ++ ++ // can move south-east, expand ++ if ((nswe & GeoStructure.CELL_FLAG_SE) != 0) ++ { ++ addNode(x + 1, y + 1, z, Config.DIAGONAL_WEIGHT); ++ } ++ } ++ ++ /** ++ * Returns node, if it exists in buffer. ++ * @param x : node X coord ++ * @param y : node Y coord ++ * @param z : node Z coord ++ * @return Node : node, if exits in buffer ++ */ ++ private final Node getNode(int x, int y, short z) ++ { ++ // check node X out of coordinates ++ final int ix = x - _cx; ++ if ((ix < 0) || (ix >= _size)) ++ { ++ return null; ++ } ++ ++ // check node Y out of coordinates ++ final int iy = y - _cy; ++ if ((iy < 0) || (iy >= _size)) ++ { ++ return null; ++ } ++ ++ // get node ++ final Node result = _buffer[ix][iy]; ++ ++ // check and update ++ if (result.getLoc() == null) ++ { ++ result.setLoc(x, y, z); ++ } ++ ++ // return node ++ return result; ++ } ++ ++ /** ++ * Add node given by coordinates to the buffer. ++ * @param x : geo X coord ++ * @param y : geo Y coord ++ * @param z : geo Z coord ++ * @param weight : weight of movement to new node ++ */ ++ private final void addNode(int x, int y, short z, int weight) ++ { ++ // get node to be expanded ++ final Node node = getNode(x, y, z); ++ if (node == null) ++ { ++ return; ++ } ++ ++ // Z distance between nearby cells is higher than cell size ++ if (node.getLoc().getZ() > (z + (2 * GeoStructure.CELL_HEIGHT))) ++ { ++ return; ++ } ++ ++ // node was already expanded, return ++ if (node.getCost() >= 0) ++ { ++ return; ++ } ++ ++ node.setParent(_current); ++ if (node.getLoc().getNSWE() != (byte) 0xFF) ++ { ++ node.setCost(getCostH(x, y, node.getLoc().getZ()) + (weight * Config.OBSTACLE_MULTIPLIER)); ++ } ++ else ++ { ++ node.setCost(getCostH(x, y, node.getLoc().getZ()) + weight); ++ } ++ ++ Node current = _current; ++ int count = 0; ++ while ((current.getChild() != null) && (count < (Config.MAX_ITERATIONS * 4))) ++ { ++ count++; ++ if (current.getChild().getCost() > node.getCost()) ++ { ++ node.setChild(current.getChild()); ++ break; ++ } ++ current = current.getChild(); ++ } ++ ++ if (count >= (Config.MAX_ITERATIONS * 4)) ++ { ++ System.err.println("Pathfinding: too long loop detected, cost:" + node.getCost()); ++ } ++ ++ current.setChild(node); ++ } ++ ++ /** ++ * @param x : node X coord ++ * @param y : node Y coord ++ * @param i : node Z coord ++ * @return double : node cost ++ */ ++ private final double getCostH(int x, int y, int i) ++ { ++ final int dX = x - _gtx; ++ final int dY = y - _gty; ++ final int dZ = (i - _gtz) / GeoStructure.CELL_HEIGHT; ++ ++ // return (Math.abs(dX) + Math.abs(dY) + Math.abs(dZ)) * Config.HEURISTIC_WEIGHT; // Manhattan distance ++ return Math.sqrt((dX * dX) + (dY * dY) + (dZ * dZ)) * Config.HEURISTIC_WEIGHT; // Direct distance ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeLoc.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeLoc.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeLoc.java (nonexistent) +@@ -1,183 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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.Cell; +- +-/** +- * @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 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 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; +- // return super.hashCode(); +- } +- +- @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; +- if (_x != other._x) +- { +- return false; +- } +- if (_y != other._y) +- { +- return false; +- } +- if (_goNorth != other._goNorth) +- { +- return false; +- } +- if (_goEast != other._goEast) +- { +- return false; +- } +- if (_goSouth != other._goSouth) +- { +- return false; +- } +- if (_goWest != other._goWest) +- { +- return false; +- } +- if (_geoHeight != other._geoHeight) +- { +- return false; +- } +- return true; +- } +-} +Index: java/org/l2jmobius/gameserver/model/actor/Creature.java +=================================================================== +--- java/org/l2jmobius/gameserver/model/actor/Creature.java (revision 8399) ++++ java/org/l2jmobius/gameserver/model/actor/Creature.java (working copy) +@@ -66,8 +66,6 @@ + import org.l2jmobius.gameserver.enums.TeleportWhereType; + import org.l2jmobius.gameserver.enums.UserInfoType; + import org.l2jmobius.gameserver.geoengine.GeoEngine; +-import org.l2jmobius.gameserver.geoengine.GeoEnginePathfinding; +-import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; + import org.l2jmobius.gameserver.instancemanager.IdManager; + import org.l2jmobius.gameserver.instancemanager.MapRegionManager; + import org.l2jmobius.gameserver.instancemanager.QuestManager; +@@ -2577,7 +2575,7 @@ + + public boolean disregardingGeodata; + public int onGeodataPathIndex; +- public List geoPath; ++ public List geoPath; + public int geoPathAccurateTx; + public int geoPathAccurateTy; + public int geoPathGtx; +@@ -3466,7 +3464,7 @@ + if (((originalDistance - distance) > 30) && !isControlBlocked() && !isInVehicle) + { + // Path calculation -- overrides previous movement check +- m.geoPath = GeoEnginePathfinding.getInstance().findPath(curX, curY, curZ, originalX, originalY, originalZ, getInstanceWorld()); ++ m.geoPath = GeoEngine.getInstance().findPath(curX, curY, curZ, originalX, originalY, originalZ, getInstanceWorld()); + if ((m.geoPath == null) || (m.geoPath.size() < 2)) // No path found + { + if (isPlayer() && !_isFlying && !isInWater) +Index: java/org/l2jmobius/gameserver/model/actor/instance/PlayerInstance.java +=================================================================== +--- java/org/l2jmobius/gameserver/model/actor/instance/PlayerInstance.java (revision 8397) ++++ java/org/l2jmobius/gameserver/model/actor/instance/PlayerInstance.java (working copy) +@@ -12704,7 +12704,7 @@ + { + if (Config.CORRECT_PLAYER_Z) + { +- final int nearestZ = GeoEngine.getInstance().getHigherHeight(getX(), getY(), getZ()); ++ final int nearestZ = GeoEngine.getInstance().getHeightNearest(getX(), getY(), getZ()); + if (getZ() < nearestZ) + { + teleToLocation(new Location(getX(), getY(), nearestZ)); +Index: java/org/l2jmobius/gameserver/util/GeoUtils.java +=================================================================== +--- java/org/l2jmobius/gameserver/util/GeoUtils.java (revision 8397) ++++ java/org/l2jmobius/gameserver/util/GeoUtils.java (working copy) +@@ -19,7 +19,7 @@ + import java.awt.Color; + + import org.l2jmobius.gameserver.geoengine.GeoEngine; +-import org.l2jmobius.gameserver.geoengine.geodata.Cell; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; + import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance; + import org.l2jmobius.gameserver.network.serverpackets.ExServerPrimitive; + +@@ -26,25 +26,25 @@ + /** + * @author HorridoJoho + */ +-public final class GeoUtils ++public class GeoUtils + { + public static void debug2DLine(PlayerInstance player, int x, int y, int tx, int ty, int z) + { +- final int gx = GeoEngine.getInstance().getGeoX(x); +- final int gy = GeoEngine.getInstance().getGeoY(y); ++ final int gx = GeoEngine.getGeoX(x); ++ final int gy = GeoEngine.getGeoY(y); + +- final int tgx = GeoEngine.getInstance().getGeoX(tx); +- final int tgy = GeoEngine.getInstance().getGeoY(ty); ++ final int tgx = GeoEngine.getGeoX(tx); ++ final int tgy = GeoEngine.getGeoY(ty); + + final ExServerPrimitive prim = new ExServerPrimitive("Debug2DLine", x, y, z); +- prim.addLine(Color.BLUE, GeoEngine.getInstance().getWorldX(gx), GeoEngine.getInstance().getWorldY(gy), z, GeoEngine.getInstance().getWorldX(tgx), GeoEngine.getInstance().getWorldY(tgy), z); ++ prim.addLine(Color.BLUE, GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), z, GeoEngine.getWorldX(tgx), GeoEngine.getWorldY(tgy), z); + + final LinePointIterator iter = new LinePointIterator(gx, gy, tgx, tgy); + + while (iter.next()) + { +- final int wx = GeoEngine.getInstance().getWorldX(iter.x()); +- final int wy = GeoEngine.getInstance().getWorldY(iter.y()); ++ final int wx = GeoEngine.getWorldX(iter.x()); ++ final int wy = GeoEngine.getWorldY(iter.y()); + + prim.addPoint(Color.RED, wx, wy, z); + } +@@ -53,21 +53,21 @@ + + public static void debug3DLine(PlayerInstance player, int x, int y, int z, int tx, int ty, int tz) + { +- final int gx = GeoEngine.getInstance().getGeoX(x); +- final int gy = GeoEngine.getInstance().getGeoY(y); ++ final int gx = GeoEngine.getGeoX(x); ++ final int gy = GeoEngine.getGeoY(y); + +- final int tgx = GeoEngine.getInstance().getGeoX(tx); +- final int tgy = GeoEngine.getInstance().getGeoY(ty); ++ final int tgx = GeoEngine.getGeoX(tx); ++ final int tgy = GeoEngine.getGeoY(ty); + + final ExServerPrimitive prim = new ExServerPrimitive("Debug3DLine", x, y, z); +- prim.addLine(Color.BLUE, GeoEngine.getInstance().getWorldX(gx), GeoEngine.getInstance().getWorldY(gy), z, GeoEngine.getInstance().getWorldX(tgx), GeoEngine.getInstance().getWorldY(tgy), tz); ++ prim.addLine(Color.BLUE, GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), z, GeoEngine.getWorldX(tgx), GeoEngine.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.getInstance().getWorldX(prevX); +- int wy = GeoEngine.getInstance().getWorldY(prevY); ++ int wx = GeoEngine.getWorldX(prevX); ++ int wy = GeoEngine.getWorldY(prevY); + int wz = iter.z(); + prim.addPoint(Color.RED, wx, wy, wz); + +@@ -78,8 +78,8 @@ + + if ((curX != prevX) || (curY != prevY)) + { +- wx = GeoEngine.getInstance().getWorldX(curX); +- wy = GeoEngine.getInstance().getWorldY(curY); ++ wx = GeoEngine.getWorldX(curX); ++ wy = GeoEngine.getWorldY(curY); + wz = iter.z(); + + prim.addPoint(Color.RED, wx, wy, wz); +@@ -93,7 +93,7 @@ + + private static Color getDirectionColor(int x, int y, int z, int nswe) + { +- if (GeoEngine.getInstance().checkNearestNswe(x, y, z, nswe)) ++ if ((GeoEngine.getInstance().getNsweNearest(x, y, z) & nswe) == nswe) + { + return Color.GREEN; + } +@@ -109,9 +109,8 @@ + int iPacket = 0; + + ExServerPrimitive exsp = null; +- final GeoEngine ge = GeoEngine.getInstance(); +- final int playerGx = ge.getGeoX(player.getX()); +- final int playerGy = ge.getGeoY(player.getY()); ++ final int playerGx = GeoEngine.getGeoX(player.getX()); ++ final int playerGy = GeoEngine.getGeoY(player.getY()); + for (int dx = -geoRadius; dx <= geoRadius; ++dx) + { + for (int dy = -geoRadius; dy <= geoRadius; ++dy) +@@ -135,12 +134,12 @@ + final int gx = playerGx + dx; + final int gy = playerGy + dy; + +- final int x = ge.getWorldX(gx); +- final int y = ge.getWorldY(gy); +- final int z = ge.getNearestZ(gx, gy, player.getZ()); ++ final int x = GeoEngine.getWorldX(gx); ++ final int y = GeoEngine.getWorldY(gy); ++ final int z = GeoEngine.getInstance().getHeightNearest(gx, gy, player.getZ()); + + // north arrow +- Color col = getDirectionColor(gx, gy, z, Cell.NSWE_NORTH); ++ Color col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_N); + 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); +@@ -147,7 +146,7 @@ + exsp.addLine(col, x - 4, y - 4, z, x + 4, y - 4, z); + + // east arrow +- col = getDirectionColor(gx, gy, z, Cell.NSWE_EAST); ++ col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_E); + 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); +@@ -154,13 +153,13 @@ + exsp.addLine(col, x + 4, y - 4, z, x + 4, y + 4, z); + + // south arrow +- col = getDirectionColor(gx, gy, z, Cell.NSWE_SOUTH); ++ col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_S); + 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, Cell.NSWE_WEST); ++ col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_W); + 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); +@@ -188,15 +187,15 @@ + { + if (y > lastY) + { +- return Cell.NSWE_SOUTH_EAST; ++ return GeoStructure.CELL_FLAG_SE; // Direction.SOUTH_EAST; + } + else if (y < lastY) + { +- return Cell.NSWE_NORTH_EAST; ++ return GeoStructure.CELL_FLAG_NE; // Direction.NORTH_EAST; + } + else + { +- return Cell.NSWE_EAST; ++ return GeoStructure.CELL_FLAG_E; // Direction.EAST; + } + } + else if (x < lastX) // west +@@ -203,26 +202,27 @@ + { + if (y > lastY) + { +- return Cell.NSWE_SOUTH_WEST; ++ return GeoStructure.CELL_FLAG_SW; // Direction.SOUTH_WEST; + } + else if (y < lastY) + { +- return Cell.NSWE_NORTH_WEST; ++ return GeoStructure.CELL_FLAG_NW; // Direction.NORTH_WEST; + } + else + { +- return Cell.NSWE_WEST; ++ return GeoStructure.CELL_FLAG_W; // Direction.WEST; + } + } +- else // unchanged x ++ else ++ // unchanged x + { + if (y > lastY) + { +- return Cell.NSWE_SOUTH; ++ return GeoStructure.CELL_FLAG_S; // Direction.SOUTH; + } + else if (y < lastY) + { +- return Cell.NSWE_NORTH; ++ return GeoStructure.CELL_FLAG_N; // Direction.NORTH; + } + else + { +Index: java/org/l2jmobius/tools/geodataconverter/GeoDataConverter.java +=================================================================== +--- java/org/l2jmobius/tools/geodataconverter/GeoDataConverter.java (nonexistent) ++++ java/org/l2jmobius/tools/geodataconverter/GeoDataConverter.java (working copy) +@@ -0,0 +1,386 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ */ ++package org.l2jmobius.tools.geodataconverter; ++ ++import java.io.BufferedOutputStream; ++import java.io.File; ++import java.io.FileOutputStream; ++import java.io.RandomAccessFile; ++import java.nio.ByteOrder; ++import java.nio.MappedByteBuffer; ++import java.nio.channels.FileChannel; ++import java.util.Scanner; ++ ++import org.l2jmobius.Config; ++import org.l2jmobius.commons.util.PropertiesParser; ++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.GeoFormat; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; ++import org.l2jmobius.gameserver.model.World; ++ ++/** ++ * @author Hasha ++ */ ++public class GeoDataConverter ++{ ++ private static GeoFormat _format; ++ private static ABlock[][] _blocks; ++ ++ public static void main(String[] args) ++ { ++ // initialize config ++ loadGeoengineConfigs(); ++ ++ // get geodata format ++ String type = ""; ++ try (Scanner scn = new Scanner(System.in)) ++ { ++ while (!(type.equalsIgnoreCase("J") || type.equalsIgnoreCase("O") || type.equalsIgnoreCase("E"))) ++ { ++ System.out.println("GeoDataConverter: Select source geodata type:"); ++ System.out.println(" J: L2J (e.g. 23_22.l2j)"); ++ System.out.println(" O: L2OFF (e.g. 23_22_conv.dat)"); ++ System.out.println(" E: Exit"); ++ System.out.print("Choice: "); ++ type = scn.next(); ++ } ++ } ++ if (type.equalsIgnoreCase("E")) ++ { ++ System.exit(0); ++ } ++ ++ _format = type.equalsIgnoreCase("J") ? GeoFormat.L2J : GeoFormat.L2OFF; ++ ++ // start conversion ++ System.out.println("GeoDataConverter: Converting all " + _format + " files."); ++ ++ // initialize geodata container ++ _blocks = new ABlock[GeoStructure.REGION_BLOCKS_X][GeoStructure.REGION_BLOCKS_Y]; ++ ++ // initialize multilayer temporarily buffer ++ BlockMultilayer.initialize(); ++ ++ // load geo files ++ int converted = 0; ++ for (int rx = World.TILE_X_MIN; rx <= World.TILE_X_MAX; rx++) ++ { ++ for (int ry = World.TILE_Y_MIN; ry <= World.TILE_Y_MAX; ry++) ++ { ++ final String input = String.format(_format.getFilename(), rx, ry); ++ final String filepath = Config.GEODATA_PATH; ++ final File f = new File(filepath + input); ++ if (f.exists() && !f.isDirectory()) ++ { ++ // load geodata ++ if (!loadGeoBlocks(input)) ++ { ++ System.out.println("GeoDataConverter: Unable to load " + input + " region file."); ++ continue; ++ } ++ ++ // recalculate nswe ++ if (!recalculateNswe()) ++ { ++ System.out.println("GeoDataConverter: Unable to convert " + input + " region file."); ++ continue; ++ } ++ ++ // save geodata ++ final String output = String.format(GeoFormat.L2D.getFilename(), rx, ry); ++ if (!saveGeoBlocks(output)) ++ { ++ System.out.println("GeoDataConverter: Unable to save " + output + " region file."); ++ continue; ++ } ++ ++ converted++; ++ System.out.println("GeoDataConverter: Created " + output + " region file."); ++ } ++ } ++ } ++ System.out.println("GeoDataConverter: Converted " + converted + " " + _format + " to L2D region file(s)."); ++ ++ // release multilayer block temporarily buffer ++ BlockMultilayer.release(); ++ } ++ ++ /** ++ * Loads geo blocks from buffer of the region file. ++ * @param filename : The name of the to load. ++ * @return boolean : True when successful. ++ */ ++ private static boolean loadGeoBlocks(String filename) ++ { ++ // region file is load-able, try to load it ++ try (RandomAccessFile raf = new RandomAccessFile(Config.GEODATA_PATH + filename, "r"); ++ FileChannel fc = raf.getChannel()) ++ { ++ final MappedByteBuffer buffer = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()).load(); ++ buffer.order(ByteOrder.LITTLE_ENDIAN); ++ ++ // load 18B header for L2off geodata (1st and 2nd byte...region X and Y) ++ if (_format == GeoFormat.L2OFF) ++ { ++ for (int i = 0; i < 18; i++) ++ { ++ buffer.get(); ++ } ++ } ++ ++ // 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 (_format == GeoFormat.L2J) ++ { ++ // get block type ++ final byte type = buffer.get(); ++ ++ // load block according to block type ++ switch (type) ++ { ++ case GeoStructure.TYPE_FLAT_L2J_L2OFF: ++ { ++ _blocks[ix][iy] = new BlockFlat(buffer, _format); ++ break; ++ } ++ case GeoStructure.TYPE_COMPLEX_L2J: ++ { ++ _blocks[ix][iy] = new BlockComplex(buffer, _format); ++ break; ++ } ++ case GeoStructure.TYPE_MULTILAYER_L2J: ++ { ++ _blocks[ix][iy] = new BlockMultilayer(buffer, _format); ++ break; ++ } ++ default: ++ { ++ throw new IllegalArgumentException("Unknown block type: " + type); ++ } ++ } ++ } ++ else ++ { ++ // get block type ++ final short type = buffer.getShort(); ++ ++ // load block according to block type ++ switch (type) ++ { ++ case GeoStructure.TYPE_FLAT_L2J_L2OFF: ++ { ++ _blocks[ix][iy] = new BlockFlat(buffer, _format); ++ break; ++ } ++ case GeoStructure.TYPE_COMPLEX_L2OFF: ++ { ++ _blocks[ix][iy] = new BlockComplex(buffer, _format); ++ break; ++ } ++ default: ++ { ++ _blocks[ix][iy] = new BlockMultilayer(buffer, _format); ++ break; ++ } ++ } ++ } ++ } ++ } ++ ++ if (buffer.remaining() > 0) ++ { ++ System.out.println("GeoDataConverter: Region file " + filename + " can be corrupted, remaining " + buffer.remaining() + " bytes to read."); ++ return false; ++ } ++ ++ return true; ++ } ++ catch (Exception e) ++ { ++ System.out.println("GeoDataConverter: Error while loading " + filename + " region file."); ++ return false; ++ } ++ } ++ ++ /** ++ * Recalculate diagonal flags for the region file. ++ * @return boolean : True when successful. ++ */ ++ private static boolean recalculateNswe() ++ { ++ try ++ { ++ for (int x = 0; x < GeoStructure.REGION_CELLS_X; x++) ++ { ++ for (int y = 0; y < GeoStructure.REGION_CELLS_Y; y++) ++ { ++ // get block ++ final ABlock block = _blocks[x / GeoStructure.BLOCK_CELLS_X][y / GeoStructure.BLOCK_CELLS_Y]; ++ ++ // skip flat blocks ++ if (block instanceof BlockFlat) ++ { ++ continue; ++ } ++ ++ // for complex and multilayer blocks go though all layers ++ short height = Short.MAX_VALUE; ++ int index; ++ while ((index = block.getIndexBelow(x, y, height)) != -1) ++ { ++ // get height and nswe ++ height = block.getHeight(index); ++ byte nswe = block.getNswe(index); ++ ++ // update nswe with diagonal flags ++ nswe = updateNsweBelow(x, y, height, nswe); ++ ++ // set nswe of the cell ++ block.setNswe(index, nswe); ++ } ++ } ++ } ++ ++ return true; ++ } ++ catch (Exception e) ++ { ++ return false; ++ } ++ } ++ ++ /** ++ * Updates the NSWE flag with diagonal flags. ++ * @param x : Geodata X coordinate. ++ * @param y : Geodata Y coordinate. ++ * @param z : Geodata Z coordinate. ++ * @param nsweValue : NSWE flag to be updated. ++ * @return byte : Updated NSWE flag. ++ */ ++ private static byte updateNsweBelow(int x, int y, short z, byte nsweValue) ++ { ++ byte nswe = nsweValue; ++ ++ // calculate virtual layer height ++ final short height = (short) (z + GeoStructure.CELL_IGNORE_HEIGHT); ++ ++ // get NSWE of neighbor cells below virtual layer (NPC/PC can fall down of clif, but can not climb it -> NSWE of cell below) ++ final byte nsweN = getNsweBelow(x, y - 1, height); ++ final byte nsweS = getNsweBelow(x, y + 1, height); ++ final byte nsweW = getNsweBelow(x - 1, y, height); ++ final byte nsweE = getNsweBelow(x + 1, y, height); ++ ++ // north-west ++ if ((((nswe & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) || (((nswe & GeoStructure.CELL_FLAG_W) != 0) && ((nsweW & GeoStructure.CELL_FLAG_N) != 0))) ++ { ++ nswe |= GeoStructure.CELL_FLAG_NW; ++ } ++ ++ // north-east ++ if ((((nswe & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) || (((nswe & GeoStructure.CELL_FLAG_E) != 0) && ((nsweE & GeoStructure.CELL_FLAG_N) != 0))) ++ { ++ nswe |= GeoStructure.CELL_FLAG_NE; ++ } ++ ++ // south-west ++ if ((((nswe & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) || (((nswe & GeoStructure.CELL_FLAG_W) != 0) && ((nsweW & GeoStructure.CELL_FLAG_S) != 0))) ++ { ++ nswe |= GeoStructure.CELL_FLAG_SW; ++ } ++ ++ // south-east ++ if ((((nswe & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) || (((nswe & GeoStructure.CELL_FLAG_E) != 0) && ((nsweE & GeoStructure.CELL_FLAG_S) != 0))) ++ { ++ nswe |= GeoStructure.CELL_FLAG_SE; ++ } ++ ++ return nswe; ++ } ++ ++ private static byte getNsweBelow(int geoX, int geoY, short worldZ) ++ { ++ // out of geo coordinates ++ if ((geoX < 0) || (geoX >= GeoStructure.REGION_CELLS_X)) ++ { ++ return 0; ++ } ++ ++ // out of geo coordinates ++ if ((geoY < 0) || (geoY >= GeoStructure.REGION_CELLS_Y)) ++ { ++ return 0; ++ } ++ ++ // get block ++ final ABlock block = _blocks[geoX / GeoStructure.BLOCK_CELLS_X][geoY / GeoStructure.BLOCK_CELLS_Y]; ++ ++ // get index, when valid, return nswe ++ final int index = block.getIndexBelow(geoX, geoY, worldZ); ++ return index == -1 ? 0 : block.getNswe(index); ++ } ++ ++ /** ++ * Save region file to file. ++ * @param filename : The name of file to save. ++ * @return boolean : True when successful. ++ */ ++ private static boolean saveGeoBlocks(String filename) ++ { ++ try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(Config.GEODATA_PATH + filename), GeoStructure.REGION_BLOCKS * GeoStructure.BLOCK_CELLS * 3)) ++ { ++ // loop over region blocks and save each block ++ for (int ix = 0; ix < GeoStructure.REGION_BLOCKS_X; ix++) ++ { ++ for (int iy = 0; iy < GeoStructure.REGION_BLOCKS_Y; iy++) ++ { ++ _blocks[ix][iy].saveBlock(bos); ++ } ++ } ++ ++ // flush data to file ++ bos.flush(); ++ ++ return true; ++ } ++ catch (Exception e) ++ { ++ return false; ++ } ++ } ++ ++ private static void loadGeoengineConfigs() ++ { ++ final PropertiesParser geoData = new PropertiesParser(Config.GEOENGINE_CONFIG_FILE); ++ Config.GEODATA_PATH = geoData.getString("GeoDataPath", "./data/geodata/"); ++ Config.COORD_SYNCHRONIZE = geoData.getInt("CoordSynchronize", -1); ++ Config.PART_OF_CHARACTER_HEIGHT = geoData.getInt("PartOfCharacterHeight", 75); ++ Config.MAX_OBSTACLE_HEIGHT = geoData.getInt("MaxObstacleHeight", 32); ++ Config.PATHFINDING = geoData.getBoolean("PathFinding", true); ++ Config.PATHFIND_BUFFERS = geoData.getString("PathFindBuffers", "100x6;128x6;192x6;256x4;320x4;384x4;500x2"); ++ Config.BASE_WEIGHT = geoData.getInt("BaseWeight", 10); ++ Config.DIAGONAL_WEIGHT = geoData.getInt("DiagonalWeight", 14); ++ Config.OBSTACLE_MULTIPLIER = geoData.getInt("ObstacleMultiplier", 10); ++ Config.HEURISTIC_WEIGHT = geoData.getInt("HeuristicWeight", 20); ++ Config.MAX_ITERATIONS = geoData.getInt("MaxIterations", 3500); ++ } ++} +\ No newline at end of file diff --git a/L2J_Mobius_6.0_Fafurion/dist/game/data/geodata/L2D_GeoEngine.patch b/L2J_Mobius_6.0_Fafurion/dist/game/data/geodata/L2D_GeoEngine.patch new file mode 100644 index 0000000000..63e2faf0c9 --- /dev/null +++ b/L2J_Mobius_6.0_Fafurion/dist/game/data/geodata/L2D_GeoEngine.patch @@ -0,0 +1,6052 @@ +Index: dist/game/GeoDataConverter.bat +=================================================================== +--- dist/game/GeoDataConverter.bat (nonexistent) ++++ dist/game/GeoDataConverter.bat (working copy) +@@ -0,0 +1,6 @@ ++@echo off ++title L2D geodata converter ++ ++java -Xmx512m -cp ./../libs/* org.l2jmobius.tools.geodataconverter.GeoDataConverter ++ ++pause +Index: dist/game/GeoDataConverter.sh +=================================================================== +--- dist/game/GeoDataConverter.sh (nonexistent) ++++ dist/game/GeoDataConverter.sh (working copy) +@@ -0,0 +1,4 @@ ++#! /bin/sh ++ ++java -Xmx512m -cp ../libs/*: org.l2jmobius.tools.geodataconverter.GeoDataConverter > log/stdout.log 2>&1 ++ +Index: dist/game/config/GeoEngine.ini +=================================================================== +--- dist/game/config/GeoEngine.ini (revision 8397) ++++ dist/game/config/GeoEngine.ini (working copy) +@@ -1,6 +1,10 @@ + # ================================================================= + # Geodata + # ================================================================= ++# Because of real-time performance we are using geodata files only in ++# diagonal L2D format now (using filename e.g. 22_16.l2d). ++# L2D geodata can be obtained by conversion of existing L2J or L2OFF geodata. ++# Launch "GeoDataConverter.bat/sh" and follow instructions to start the conversion. + + # 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/ +@@ -13,6 +17,16 @@ + CoordSynchronize = 2 + + # ================================================================= ++# Path checking ++# ================================================================= ++ ++# 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 finding + # ================================================================= + +@@ -23,19 +37,23 @@ + # Pathfinding array buffers configuration, default: 100x6;128x6;192x6;256x4;320x4;384x4;500x2 + PathFindBuffers = 100x6;128x6;192x6;256x4;320x4;384x4;500x2 + +-# Weight for nodes without obstacles far from walls +-LowWeight = 0.5 ++# Base path weight, when moving from one node to another on axis direction, default: 10 ++BaseWeight = 10 + +-# Weight for nodes near walls +-MediumWeight = 2 ++# Path weight, when moving from one node to another on diagonal direction, default: BaseWeight * sqrt(2) = 14 ++DiagonalWeight = 14 + +-# Weight for nodes with obstacles +-HighWeight = 3 ++# When movement flags of target node is blocked to any direction, multiply movement weight by this multiplier. ++# This causes pathfinding algorithm to avoid path construction exactly near an obstacle, default: 10 ++ObstacleMultiplier = 10 + +-# Weight for diagonal movement. +-# Default: LowWeight * sqrt(2) +-DiagonalWeight = 0.707 ++# Weight of the heuristic algorithm, which is giving estimated cost from node to target, default: 20 ++# For proper function must be higher than BaseWeight and/or DiagonalWeight. ++HeuristicWeight = 20 + ++# Maximum number of generated nodes per one path-finding process, default 3500 ++MaxIterations = 3500 ++ + # ================================================================= + # Other + # ================================================================= +Index: dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java +=================================================================== +--- dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java (revision 8397) ++++ dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java (working copy) +@@ -55,9 +55,8 @@ + final int worldX = activeChar.getX(); + final int worldY = activeChar.getY(); + final int worldZ = activeChar.getZ(); +- final int geoX = GeoEngine.getInstance().getGeoX(worldX); +- final int geoY = GeoEngine.getInstance().getGeoY(worldY); +- ++ final int geoX = GeoEngine.getGeoX(worldX); ++ final int geoY = GeoEngine.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)); +@@ -73,9 +72,8 @@ + final int worldX = activeChar.getX(); + final int worldY = activeChar.getY(); + final int worldZ = activeChar.getZ(); +- final int geoX = GeoEngine.getInstance().getGeoX(worldX); +- final int geoY = GeoEngine.getInstance().getGeoY(worldY); +- ++ final int geoX = GeoEngine.getGeoX(worldX); ++ final int geoY = GeoEngine.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)); +Index: dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java +=================================================================== +--- dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java (revision 8397) ++++ dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java (working copy) +@@ -18,11 +18,11 @@ + + import java.util.List; + +-import org.l2jmobius.Config; +-import org.l2jmobius.gameserver.geoengine.GeoEnginePathfinding; +-import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; ++import org.l2jmobius.gameserver.geoengine.GeoEngine; + import org.l2jmobius.gameserver.handler.IAdminCommandHandler; ++import org.l2jmobius.gameserver.model.Location; + import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance; ++import org.l2jmobius.gameserver.network.SystemMessageId; + import org.l2jmobius.gameserver.util.BuilderUtil; + + public class AdminPathNode implements IAdminCommandHandler +@@ -37,29 +37,30 @@ + { + if (command.equals("admin_path_find")) + { +- if (!Config.PATHFINDING) +- { +- BuilderUtil.sendSysMessage(activeChar, "PathFinding is disabled."); +- return true; +- } + if (activeChar.getTarget() != null) + { +- final List path = GeoEnginePathfinding.getInstance().findPath(activeChar.getX(), activeChar.getY(), (short) activeChar.getZ(), activeChar.getTarget().getX(), activeChar.getTarget().getY(), (short) activeChar.getTarget().getZ(), activeChar.getInstanceWorld()); ++ 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()); + if (path == null) + { +- BuilderUtil.sendSysMessage(activeChar, "No Route!"); +- return true; ++ BuilderUtil.sendSysMessage(activeChar, "No route found or pathfinding disabled."); + } +- for (AbstractNodeLoc a : path) ++ else + { +- BuilderUtil.sendSysMessage(activeChar, "x:" + a.getX() + " y:" + a.getY() + " z:" + a.getZ()); ++ for (Location point : path) ++ { ++ BuilderUtil.sendSysMessage(activeChar, "x:" + point.getX() + " y:" + point.getY() + " z:" + point.getZ()); ++ } + } + } + else + { +- BuilderUtil.sendSysMessage(activeChar, "No Target!"); ++ activeChar.sendPacket(SystemMessageId.INVALID_TARGET); + } + } ++ else ++ { ++ return false; ++ } + return true; + } + +Index: java/org/l2jmobius/Config.java +=================================================================== +--- java/org/l2jmobius/Config.java (revision 8397) ++++ java/org/l2jmobius/Config.java (working copy) +@@ -32,7 +32,6 @@ + import java.net.UnknownHostException; + import java.nio.charset.StandardCharsets; + import java.nio.file.Files; +-import java.nio.file.Path; + import java.nio.file.Paths; + import java.time.Duration; + import java.util.ArrayList; +@@ -927,14 +926,17 @@ + // -------------------------------------------------- + // GeoEngine + // -------------------------------------------------- +- public static Path GEODATA_PATH; ++ public static String GEODATA_PATH; ++ public static int COORD_SYNCHRONIZE; ++ public static int PART_OF_CHARACTER_HEIGHT; ++ public static int MAX_OBSTACLE_HEIGHT; + public static boolean PATHFINDING; + public static String PATHFIND_BUFFERS; +- public static float LOW_WEIGHT; +- public static float MEDIUM_WEIGHT; +- public static float HIGH_WEIGHT; +- public static float DIAGONAL_WEIGHT; +- public static int COORD_SYNCHRONIZE; ++ public static int BASE_WEIGHT; ++ public static int DIAGONAL_WEIGHT; ++ public static int HEURISTIC_WEIGHT; ++ public static int OBSTACLE_MULTIPLIER; ++ public static int MAX_ITERATIONS; + public static boolean CORRECT_PLAYER_Z; + + /** Attribute System */ +@@ -2468,14 +2470,17 @@ + + // Load GeoEngine config file (if exists) + final PropertiesParser GeoEngine = new PropertiesParser(GEOENGINE_CONFIG_FILE); +- GEODATA_PATH = Paths.get(GeoEngine.getString("GeoDataPath", "./data/geodata")); ++ GEODATA_PATH = GeoEngine.getString("GeoDataPath", "./data/geodata/"); ++ COORD_SYNCHRONIZE = GeoEngine.getInt("CoordSynchronize", -1); ++ PART_OF_CHARACTER_HEIGHT = GeoEngine.getInt("PartOfCharacterHeight", 75); ++ MAX_OBSTACLE_HEIGHT = GeoEngine.getInt("MaxObstacleHeight", 32); + PATHFINDING = GeoEngine.getBoolean("PathFinding", true); + PATHFIND_BUFFERS = GeoEngine.getString("PathFindBuffers", "100x6;128x6;192x6;256x4;320x4;384x4;500x2"); +- LOW_WEIGHT = GeoEngine.getFloat("LowWeight", 0.5f); +- MEDIUM_WEIGHT = GeoEngine.getFloat("MediumWeight", 2); +- HIGH_WEIGHT = GeoEngine.getFloat("HighWeight", 3); +- DIAGONAL_WEIGHT = GeoEngine.getFloat("DiagonalWeight", 0.707f); +- COORD_SYNCHRONIZE = GeoEngine.getInt("CoordSynchronize", -1); ++ BASE_WEIGHT = GeoEngine.getInt("BaseWeight", 10); ++ DIAGONAL_WEIGHT = GeoEngine.getInt("DiagonalWeight", 14); ++ OBSTACLE_MULTIPLIER = GeoEngine.getInt("ObstacleMultiplier", 10); ++ HEURISTIC_WEIGHT = GeoEngine.getInt("HeuristicWeight", 20); ++ MAX_ITERATIONS = GeoEngine.getInt("MaxIterations", 3500); + CORRECT_PLAYER_Z = GeoEngine.getBoolean("CorrectPlayerZ", false); + + // Load AllowedPlayerRaces config file (if exists) +Index: java/org/l2jmobius/gameserver/geoengine/GeoEngine.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/GeoEngine.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/GeoEngine.java (working copy) +@@ -16,101 +16,91 @@ + */ + package org.l2jmobius.gameserver.geoengine; + +-import java.io.IOException; ++import java.io.File; + import java.io.RandomAccessFile; + import java.nio.ByteOrder; +-import java.nio.channels.FileChannel.MapMode; +-import java.nio.file.Files; +-import java.nio.file.Path; +-import java.util.concurrent.atomic.AtomicReferenceArray; +-import java.util.logging.Level; ++import java.nio.MappedByteBuffer; ++import java.nio.channels.FileChannel; ++import java.util.List; + 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.geoengine.geodata.Cell; +-import org.l2jmobius.gameserver.geoengine.geodata.IRegion; +-import org.l2jmobius.gameserver.geoengine.geodata.NullRegion; +-import org.l2jmobius.gameserver.geoengine.geodata.Region; ++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.GeoFormat; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoLocation; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; + import org.l2jmobius.gameserver.instancemanager.WarpedSpaceManager; + 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; ++import org.l2jmobius.gameserver.util.MathUtil; + + /** +- * @author -Nemesiss-, HorridoJoho ++ * @author Hasha + */ + public class GeoEngine + { +- private static final Logger LOGGER = Logger.getLogger(GeoEngine.class.getName()); ++ protected static final Logger LOGGER = Logger.getLogger(GeoEngine.class.getName()); + +- private static final int WORLD_MIN_X = -655360; +- private static final int WORLD_MIN_Y = -589824; +- private static final int WORLD_MIN_Z = -16384; ++ private final ABlock[][] _blocks; ++ private final BlockNull _nullBlock; + +- /** 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; +- +- /** The regions array */ +- private final AtomicReferenceArray _regions = new AtomicReferenceArray<>(GEO_REGIONS); +- +- 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; +- +- protected GeoEngine() ++ /** ++ * GeoEngine constructor. Loads all geodata files of chosen geodata format. ++ */ ++ public GeoEngine() + { + LOGGER.info("GeoEngine: Initializing..."); +- for (int i = 0; i < _regions.length(); i++) +- { +- _regions.set(i, NullRegion.INSTANCE); +- } + ++ // 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; +- try ++ long fileSize = 0; ++ for (int rx = World.TILE_X_MIN; rx <= World.TILE_X_MAX; rx++) + { +- for (int regionX = World.TILE_X_MIN; regionX <= World.TILE_X_MAX; regionX++) ++ for (int ry = World.TILE_Y_MIN; ry <= World.TILE_Y_MAX; ry++) + { +- for (int regionY = World.TILE_Y_MIN; regionY <= World.TILE_Y_MAX; regionY++) ++ final File f = new File(Config.GEODATA_PATH + String.format(GeoFormat.L2D.getFilename(), rx, ry)); ++ if (f.exists() && !f.isDirectory()) + { +- 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(rx, ry)) + { +- try (RandomAccessFile raf = new RandomAccessFile(geoFilePath.toFile(), "r")) +- { +- _regions.set((regionX * GEO_REGIONS_Y) + regionY, new Region(raf.getChannel().map(MapMode.READ_ONLY, 0, raf.length()).load().order(ByteOrder.LITTLE_ENDIAN))); +- loaded++; +- } ++ loaded++; ++ fileSize += f.length(); + } + } ++ else ++ { ++ // region file is not load-able, load null blocks ++ loadNullBlocks(rx, ry); ++ } + } + } +- catch (Exception e) ++ ++ LOGGER.info("GeoEngine: Loaded " + loaded + " geodata files."); ++ if (loaded > 0) + { +- LOGGER.log(Level.SEVERE, "GeoEngine: Failed to load geodata!", e); +- System.exit(1); ++ LOGGER.info("GeoEngine: Total geodata file size " + (fileSize / 1024 / 1024) + " MB."); + } + +- LOGGER.info("GeoEngine: Loaded " + loaded + " geodata files."); +- +- // Avoid wrong configs when no files are loaded. ++ // avoid wrong configs when no files are loaded + if (loaded == 0) + { + if (Config.PATHFINDING) +@@ -124,627 +114,832 @@ + LOGGER.info("GeoEngine: Forcing CoordSynchronize setting to -1."); + } + } ++ ++ // release multilayer block temporarily buffer ++ BlockMultilayer.release(); + } + + /** +- * @param geoX +- * @param geoY +- * @return the region ++ * 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 IRegion getRegion(int geoX, int geoY) ++ private final boolean loadGeoBlocks(int regionX, int regionY) + { +- final int region = ((geoX / IRegion.REGION_CELLS_X) * GEO_REGIONS_Y) + (geoY / IRegion.REGION_CELLS_Y); +- if ((region < 0) || (region >= _regions.length())) ++ final String filename = String.format(GeoFormat.L2D.getFilename(), regionX, regionY); ++ ++ // standard load ++ try (RandomAccessFile raf = new RandomAccessFile(Config.GEODATA_PATH + filename, "r"); ++ FileChannel fc = raf.getChannel()) + { +- return null; ++ // initialize file buffer ++ final MappedByteBuffer buffer = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()).load(); ++ buffer.order(ByteOrder.LITTLE_ENDIAN); ++ ++ // 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++) ++ { ++ // get block type ++ final byte type = buffer.get(); ++ ++ // load block according to block type ++ switch (type) ++ { ++ case GeoStructure.TYPE_FLAT_L2D: ++ { ++ _blocks[blockX + ix][blockY + iy] = new BlockFlat(buffer, GeoFormat.L2D); ++ break; ++ } ++ case GeoStructure.TYPE_COMPLEX_L2D: ++ { ++ _blocks[blockX + ix][blockY + iy] = new BlockComplex(buffer, GeoFormat.L2D); ++ break; ++ } ++ case GeoStructure.TYPE_MULTILAYER_L2D: ++ { ++ _blocks[blockX + ix][blockY + iy] = new BlockMultilayer(buffer, GeoFormat.L2D); ++ break; ++ } ++ default: ++ { ++ throw new IllegalArgumentException("Unknown block type: " + type); ++ } ++ } ++ } ++ } ++ ++ // check data consistency ++ if (buffer.remaining() > 0) ++ { ++ LOGGER.warning("GeoEngine: Region file " + filename + " can be corrupted, remaining " + buffer.remaining() + " bytes to read."); ++ } ++ ++ // loading was successful ++ return true; + } +- return _regions.get(region); +- } +- +- /** +- * @param filePath +- * @param regionX +- * @param regionY +- * @throws IOException +- */ +- 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")) ++ catch (Exception e) + { +- _regions.set(regionOffset, new Region(raf.getChannel().map(MapMode.READ_ONLY, 0, raf.length()).load().order(ByteOrder.LITTLE_ENDIAN))); ++ // an error occured while loading, load null blocks ++ LOGGER.warning("GeoEngine: Error while loading " + filename + " region file."); ++ LOGGER.warning(e.getMessage()); ++ ++ // replace whole region file with null blocks ++ loadNullBlocks(regionX, regionY); ++ ++ // loading was not successful ++ return false; + } + } + + /** +- * @param regionX +- * @param regionY ++ * 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. + */ +- public void unloadRegion(int regionX, int regionY) ++ private final void loadNullBlocks(int regionX, int regionY) + { +- _regions.set((regionX * GEO_REGIONS_Y) + regionY, NullRegion.INSTANCE); +- } +- +- /** +- * @param geoX +- * @param geoY +- * @return if geodata exist +- */ +- public boolean hasGeoPos(int geoX, int geoY) +- { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) ++ // 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++) + { +- return false; ++ for (int iy = 0; iy < GeoStructure.REGION_BLOCKS_Y; iy++) ++ { ++ _blocks[blockX + ix][blockY + iy] = _nullBlock; ++ } + } +- return region.hasGeo(); + } + ++ // GEODATA - GENERAL ++ + /** +- * Checks the specified position for available geodata. +- * @param x the world x +- * @param y the world y +- * @return {@code true} if there is geodata for the given coordinates, {@code false} otherwise ++ * Converts world X to geodata X. ++ * @param worldX ++ * @return int : Geo X + */ +- public boolean hasGeo(int x, int y) ++ public static int getGeoX(int worldX) + { +- return hasGeoPos(getGeoX(x), getGeoY(y)); ++ return (MathUtil.limit(worldX, World.MAP_MIN_X, World.MAP_MAX_X) - World.MAP_MIN_X) >> 4; + } + + /** +- * @param geoX +- * @param geoY +- * @param worldZ +- * @param nswe +- * @return the nearest nswe check ++ * Converts world Y to geodata Y. ++ * @param worldY ++ * @return int : Geo Y + */ +- public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) ++ public static int getGeoY(int worldY) + { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) +- { +- return true; +- } +- return region.checkNearestNswe(geoX, geoY, worldZ, nswe); ++ return (MathUtil.limit(worldY, World.MAP_MIN_Y, World.MAP_MAX_Y) - World.MAP_MIN_Y) >> 4; + } + + /** ++ * Converts geodata X to world X. + * @param geoX +- * @param geoY +- * @param worldZ +- * @param nswe +- * @return the nearest nswe anti-corner cut ++ * @return int : World X + */ +- public boolean checkNearestNsweAntiCornerCut(int geoX, int geoY, int worldZ, int nswe) ++ public static int getWorldX(int geoX) + { +- boolean can = true; +- if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) +- { +- can = checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_EAST) && checkNearestNswe(geoX + 1, geoY, worldZ, Cell.NSWE_NORTH); +- } +- if (can && ((nswe & Cell.NSWE_NORTH_WEST) == Cell.NSWE_NORTH_WEST)) +- { +- 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 = 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 = checkNearestNswe(geoX, geoY + 1, worldZ, Cell.NSWE_WEST) && checkNearestNswe(geoX - 1, geoY, worldZ, Cell.NSWE_SOUTH); +- } +- return can && checkNearestNswe(geoX, geoY, worldZ, nswe); ++ return (MathUtil.limit(geoX, 0, GeoStructure.GEO_CELLS_X) << 4) + World.MAP_MIN_X + 8; + } + + /** +- * @param geoX ++ * Converts geodata Y to world Y. + * @param geoY +- * @param worldZ +- * @return the nearest Z value ++ * @return int : World Y + */ +- public int getNearestZ(int geoX, int geoY, int worldZ) ++ public static int getWorldY(int geoY) + { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) +- { +- return worldZ; +- } +- return region.getNearestZ(geoX, geoY, worldZ); ++ return (MathUtil.limit(geoY, 0, GeoStructure.GEO_CELLS_Y) << 4) + World.MAP_MIN_Y + 8; + } + + /** +- * @param geoX +- * @param geoY +- * @param worldZ +- * @return the next lower Z value ++ * Returns block of geodata on given coordinates. ++ * @param geoX : Geodata X ++ * @param geoY : Geodata Y ++ * @return {@link ABlock} : Block of geodata. + */ +- public int getNextLowerZ(int geoX, int geoY, int worldZ) ++ private final ABlock getBlock(int geoX, int geoY) + { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) ++ final int x = geoX / GeoStructure.BLOCK_CELLS_X; ++ final int y = geoY / GeoStructure.BLOCK_CELLS_Y; ++ ++ // if x or y is out of array return null ++ if ((x > -1) && (y > -1) && (x < GeoStructure.GEO_BLOCKS_X) && (y < GeoStructure.GEO_BLOCKS_Y)) + { +- return worldZ; ++ return _blocks[x][y]; + } +- return region.getNextLowerZ(geoX, geoY, worldZ); ++ return null; + } + + /** +- * @param geoX +- * @param geoY +- * @param worldZ +- * @return the next higher Z value ++ * Check if geo coordinates has geo. ++ * @param geoX : Geodata X ++ * @param geoY : Geodata Y ++ * @return boolean : True, if given geo coordinates have geodata + */ +- public int getNextHigherZ(int geoX, int geoY, int worldZ) ++ public boolean hasGeoPos(int geoX, int geoY) + { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) ++ final ABlock block = getBlock(geoX, geoY); ++ if (block == null) // null block check + { +- return worldZ; ++ // TODO: Find when this can be null. (Bad geodata? Check World getRegion method.) ++ // LOGGER.warning("Could not find geodata block at " + getWorldX(geoX) + ", " + getWorldY(geoY) + "."); ++ return false; + } +- return region.getNextHigherZ(geoX, geoY, worldZ); ++ return block.hasGeoPos(); + } + + /** +- * Gets the Z height. +- * @param x the world x +- * @param y the world y +- * @param z the world z +- * @return the nearest Z height ++ * 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 int getHeight(int x, int y, int z) ++ public short getHeightNearest(int geoX, int geoY, int worldZ) + { +- return getNearestZ(getGeoX(x), getGeoY(y), z); ++ final ABlock block = getBlock(geoX, geoY); ++ return block != null ? block.getHeightNearest(geoX, geoY, worldZ) : (short) worldZ; + } + + /** +- * Gets the next lower Z height. +- * @param x the world x +- * @param y the world y +- * @param z the world z +- * @return the nearest Z height ++ * 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 int getLowerHeight(int x, int y, int z) ++ public byte getNsweNearest(int geoX, int geoY, int worldZ) + { +- return getNextLowerZ(getGeoX(x), getGeoY(y), z); ++ final ABlock block = getBlock(geoX, geoY); ++ return block != null ? block.getNsweNearest(geoX, geoY, worldZ) : (byte) 0xFF; + } + + /** +- * Gets the next higher Z height. +- * @param x the world x +- * @param y the world y +- * @param z the world z +- * @return the nearest Z height ++ * Check if world coordinates has geo. ++ * @param worldX : World X ++ * @param worldY : World Y ++ * @return boolean : True, if given world coordinates have geodata + */ +- public int getHigherHeight(int x, int y, int z) ++ public boolean hasGeo(int worldX, int worldY) + { +- return getNextHigherZ(getGeoX(x), getGeoY(y), z); ++ return hasGeoPos(getGeoX(worldX), getGeoY(worldY)); + } + + /** +- * @param worldX +- * @return the geo X ++ * 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 int getGeoX(int worldX) ++ public short getHeight(int worldX, int worldY, int worldZ) + { +- return (worldX - WORLD_MIN_X) / 16; ++ return getHeightNearest(getGeoX(worldX), getGeoY(worldY), worldZ); + } + +- /** +- * @param worldY +- * @return the geo Y +- */ +- public int getGeoY(int worldY) +- { +- return (worldY - WORLD_MIN_Y) / 16; +- } ++ // PATHFINDING + + /** +- * @param worldZ +- * @return the geo Z ++ * Check line of sight from {@link WorldObject} to {@link WorldObject}. ++ * @param origin : The origin object. ++ * @param target : The target object. ++ * @return {@code boolean} : True if origin can see target + */ +- public int getGeoZ(int worldZ) ++ public boolean canSeeTarget(WorldObject origin, WorldObject target) + { +- return (worldZ - WORLD_MIN_Z) / 16; +- } +- +- /** +- * @param geoX +- * @return the world X +- */ +- public int getWorldX(int geoX) +- { +- return (geoX * 16) + WORLD_MIN_X + 8; +- } +- +- /** +- * @param geoY +- * @return the world Y +- */ +- public int getWorldY(int geoY) +- { +- return (geoY * 16) + WORLD_MIN_Y + 8; +- } +- +- /** +- * @param geoZ +- * @return the world Z +- */ +- public int getWorldZ(int geoZ) +- { +- return (geoZ * 16) + WORLD_MIN_Z + 8; +- } +- +- /** +- * 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) +- { +- if (target.isDoor()) ++ if (target.isDoor() || target.isArtefact() || (target.isCreature() && ((Creature) target).isFlying())) + { +- // Can always see doors. + return true; + } +- return 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(), cha.getInstanceWorld(), worldPosition.getX(), worldPosition.getY(), worldPosition.getZ()); +- } +- +- /** +- * Can see target. Checks doors between. +- * @param x the x coordinate +- * @param y the y coordinate +- * @param z the z coordinate +- * @param world +- * @param tx the target's x coordinate +- * @param ty the target's y coordinate +- * @param tz the target's z coordinate +- * @param tworld the target's instanceId +- * @return +- */ +- public boolean canSeeTarget(int x, int y, int z, Instance world, int tx, int ty, int tz, Instance tworld) +- { +- return (world != tworld) ? false : canSeeTarget(x, y, z, world, tx, ty, tz); +- } +- +- /** +- * 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 +- * @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, Instance instance, int tx, int ty, int tz) +- { +- if (DoorData.getInstance().checkIfDoorsBetween(x, y, z, tx, ty, tz, instance, true)) ++ ++ // get origin and target world coordinates ++ final int ox = origin.getX(); ++ final int oy = origin.getY(); ++ final int oz = origin.getZ(); ++ final int tx = target.getX(); ++ final int ty = target.getY(); ++ final int tz = target.getZ(); ++ ++ if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld(), true)) + { + return false; + } +- return canSeeTarget(x, y, z, tx, ty, tz); +- } +- +- /** +- * @param prevX +- * @param prevY +- * @param prevGeoZ +- * @param curX +- * @param curY +- * @param nswe +- * @return the LOS Z value +- */ +- 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))) ++ if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld())) + { +- throw new RuntimeException("Multiple directions!"); ++ return false; + } ++ if (WarpedSpaceManager.getInstance().checkForWarpedSpace(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld())) ++ { ++ return false; ++ } + +- if (checkNearestNsweAntiCornerCut(prevX, prevY, prevGeoZ, nswe)) ++ // get origin and check existing geo coordinates ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) + { +- return getNearestZ(curX, curY, prevGeoZ); ++ return true; + } + +- return getNextHigherZ(curX, curY, prevGeoZ); ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coordinates ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) ++ { ++ return true; ++ } ++ ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // origin and target coordinates are same ++ if ((gox == gtx) && (goy == gty)) ++ { ++ return goz == gtz; ++ } ++ ++ // get origin and target height, real height = collision height * 2 ++ double oheight = 0; ++ if (origin.isCreature()) ++ { ++ oheight = ((Creature) origin).getCollisionHeight() * 2; ++ } ++ ++ double theight = 0; ++ if (target.isCreature()) ++ { ++ theight = ((Creature) target).getCollisionHeight() * 2; ++ } ++ ++ // perform geodata check ++ return checkSee(gox, goy, goz, oheight, gtx, gty, gtz, theight, origin.getInstanceWorld()); + } + + /** +- * Can see target. Does not check doors between. +- * @param xValue the x coordinate +- * @param yValue the y coordinate +- * @param zValue the z coordinate +- * @param txValue the target's x coordinate +- * @param tyValue the target's y coordinate +- * @param tzValue the target's z coordinate +- * @return {@code true} if there is line of sight between the given coordinate sets, {@code false} otherwise ++ * Check line of sight from {@link WorldObject} to {@link Location}. ++ * @param origin : The origin object. ++ * @param position : The target position. ++ * @return {@code boolean} : True if object can see position + */ +- public boolean canSeeTarget(int xValue, int yValue, int zValue, int txValue, int tyValue, int tzValue) ++ public boolean canSeeTarget(WorldObject origin, Location position) + { +- int x = xValue; +- int y = yValue; +- int tx = txValue; +- int ty = tyValue; ++ // get origin and target world coordinates ++ final int ox = origin.getX(); ++ final int oy = origin.getY(); ++ final int oz = origin.getZ(); ++ final int tx = position.getX(); ++ final int ty = position.getY(); ++ final int tz = position.getZ(); + +- int geoX = getGeoX(x); +- int geoY = getGeoY(y); +- int tGeoX = getGeoX(tx); +- int tGeoY = getGeoY(ty); ++ if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld(), true)) ++ { ++ return false; ++ } ++ if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld())) ++ { ++ return false; ++ } ++ if (WarpedSpaceManager.getInstance().checkForWarpedSpace(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld())) ++ { ++ return false; ++ } + +- int z = getNearestZ(geoX, geoY, zValue); +- int tz = getNearestZ(tGeoX, tGeoY, tzValue); ++ // get origin and check existing geo coordinates ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) ++ { ++ return true; ++ } + +- // Fastpath. +- if ((geoX == tGeoX) && (geoY == tGeoY)) ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coordinates ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) + { +- if (hasGeoPos(tGeoX, tGeoY)) +- { +- return z == tz; +- } + return true; + } + +- if (tz > z) ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // origin and target coordinates are same ++ if ((gox == gtx) && (goy == gty)) + { +- int tmp = tx; +- tx = x; +- x = tmp; +- +- tmp = ty; +- ty = y; +- y = tmp; +- +- tmp = tz; +- tz = z; +- z = tmp; +- +- tmp = tGeoX; +- tGeoX = geoX; +- geoX = tmp; +- +- tmp = tGeoY; +- tGeoY = geoY; +- geoY = tmp; ++ return goz == gtz; + } + +- final LinePointIterator3D pointIter = new LinePointIterator3D(geoX, geoY, z, tGeoX, tGeoY, tz); +- // 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()) ++ // get origin and target height, real height = collision height * 2 ++ double oheight = 0; ++ if (origin.isCreature()) + { +- final int curX = pointIter.x(); +- final int curY = pointIter.y(); ++ oheight = ((Creature) origin).getTemplate().getCollisionHeight(); ++ } ++ ++ // perform geodata check ++ return checkSee(gox, goy, goz, oheight, gtx, gty, gtz, 0, origin.getInstanceWorld()); ++ } ++ ++ /** ++ * Simple check for origin to target visibility. ++ * @param goxValue : origin X geodata coordinate ++ * @param goyValue : origin Y geodata coordinate ++ * @param gozValue : origin Z geodata coordinate ++ * @param oheight : origin height (if instance of {@link Character}) ++ * @param gtxValue : target X geodata coordinate ++ * @param gtyValue : target Y geodata coordinate ++ * @param gtzValue : target Z geodata coordinate ++ * @param theight : target height (if instance of {@link Character}) ++ * @param instance ++ * @return {@code boolean} : True, when target can be seen. ++ */ ++ private final boolean checkSee(int goxValue, int goyValue, int gozValue, double oheight, int gtxValue, int gtyValue, int gtzValue, double theight, Instance instance) ++ { ++ int goz = gozValue; ++ int gtz = gtzValue; ++ int gox = goxValue; ++ int goy = goyValue; ++ int gtx = gtxValue; ++ int gty = gtyValue; ++ ++ // get line of sight Z coordinates ++ double losoz = goz + ((oheight * Config.PART_OF_CHARACTER_HEIGHT) / 100); ++ double lostz = gtz + ((theight * Config.PART_OF_CHARACTER_HEIGHT) / 100); ++ ++ // get X delta and signum ++ final int dx = Math.abs(gtx - gox); ++ final int sx = gox < gtx ? 1 : -1; ++ final byte dirox = sx > 0 ? GeoStructure.CELL_FLAG_E : GeoStructure.CELL_FLAG_W; ++ final byte dirtx = sx > 0 ? GeoStructure.CELL_FLAG_W : GeoStructure.CELL_FLAG_E; ++ ++ // get Y delta and signum ++ final int dy = Math.abs(gty - goy); ++ final int sy = goy < gty ? 1 : -1; ++ final byte diroy = sy > 0 ? GeoStructure.CELL_FLAG_S : GeoStructure.CELL_FLAG_N; ++ final byte dirty = sy > 0 ? GeoStructure.CELL_FLAG_N : GeoStructure.CELL_FLAG_S; ++ ++ // get Z delta ++ final int dm = Math.max(dx, dy); ++ final double dz = (lostz - losoz) / dm; ++ ++ // get direction flag for diagonal movement ++ final byte diroxy = getDirXY(dirox, diroy); ++ final byte dirtxy = getDirXY(dirtx, dirty); ++ ++ // delta, determines axis to move on (+..X axis, -..Y axis) ++ int d = dx - dy; ++ ++ // NSWE direction of movement ++ byte diro; ++ byte dirt; ++ ++ // initialize node values ++ int nox = gox; ++ int noy = goy; ++ int ntx = gtx; ++ int nty = gty; ++ byte nsweo = getNsweNearest(gox, goy, goz); ++ byte nswet = getNsweNearest(gtx, gty, gtz); ++ ++ // loop ++ ABlock block; ++ int index; ++ for (int i = 0; i < ((dm + 1) / 2); i++) ++ { ++ // reset direction flag ++ diro = 0; ++ dirt = 0; + +- if ((curX == prevX) && (curY == prevY)) ++ // calculate next point coordinates ++ final int e2 = 2 * d; ++ if ((e2 > -dy) && (e2 < dx)) + { +- continue; ++ // calculate next point XY coordinates ++ d -= dy; ++ d += dx; ++ nox += sx; ++ ntx -= sx; ++ noy += sy; ++ nty -= sy; ++ diro |= diroxy; ++ dirt |= dirtxy; + } ++ else if (e2 > -dy) ++ { ++ // calculate next point X coordinate ++ d -= dy; ++ nox += sx; ++ ntx -= sx; ++ diro |= dirox; ++ dirt |= dirtx; ++ } ++ else if (e2 < dx) ++ { ++ // calculate next point Y coordinate ++ d += dx; ++ noy += sy; ++ nty -= sy; ++ diro |= diroy; ++ dirt |= dirty; ++ } + +- final int beeCurZ = pointIter.z(); +- int curGeoZ = prevGeoZ; +- +- // Check if the position has geodata. +- if (hasGeoPos(curX, curY)) + { +- final int beeCurGeoZ = getNearestZ(curX, curY, beeCurZ); +- final int nswe = GeoUtils.computeNswe(prevX, prevY, curX, curY); // .computeDirection(prevX, prevY, curX, curY); +- curGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, curX, curY, nswe); +- int maxHeight; +- if (ptIndex < ELEVATED_SEE_OVER_DISTANCE) ++ // get block of the next cell ++ block = getBlock(nox, noy); ++ ++ // get index of particular layer, based on movement conditions ++ if ((nsweo & diro) == 0) + { +- maxHeight = z + MAX_SEE_OVER_HEIGHT; ++ index = block.getIndexAbove(nox, noy, goz - GeoStructure.CELL_IGNORE_HEIGHT); + } + else + { +- maxHeight = beeCurZ + MAX_SEE_OVER_HEIGHT; ++ index = block.getIndexBelow(nox, noy, goz + GeoStructure.CELL_IGNORE_HEIGHT); + } + +- boolean canSeeThrough = false; +- if ((curGeoZ <= maxHeight) && (curGeoZ <= beeCurGeoZ)) ++ // layer does not exist, return ++ if (index == -1) + { +- if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) +- { +- 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 +- { +- canSeeThrough = true; +- } ++ return false; + } + +- if (!canSeeThrough) ++ // get layer and next line of sight Z coordinate ++ goz = block.getHeight(index); ++ losoz += dz; ++ ++ // perform line of sight check, return when fails ++ if ((goz - losoz) > Config.MAX_OBSTACLE_HEIGHT) + { + return false; + } ++ ++ // get layer nswe ++ nsweo = block.getNswe(index); + } ++ { ++ // get block of the next cell ++ block = getBlock(ntx, nty); ++ ++ // get index of particular layer, based on movement conditions ++ if ((nswet & dirt) == 0) ++ { ++ index = block.getIndexAbove(ntx, nty, gtz - GeoStructure.CELL_IGNORE_HEIGHT); ++ } ++ else ++ { ++ index = block.getIndexBelow(ntx, nty, gtz + GeoStructure.CELL_IGNORE_HEIGHT); ++ } ++ ++ // layer does not exist, return ++ if (index == -1) ++ { ++ return false; ++ } ++ ++ // get layer and next line of sight Z coordinate ++ gtz = block.getHeight(index); ++ lostz -= dz; ++ ++ // perform line of sight check, return when fails ++ if ((gtz - lostz) > Config.MAX_OBSTACLE_HEIGHT) ++ { ++ return false; ++ } ++ ++ // get layer nswe ++ nswet = block.getNswe(index); ++ } + +- prevX = curX; +- prevY = curY; +- prevGeoZ = curGeoZ; +- ++ptIndex; ++ // update coords ++ gox = nox; ++ goy = noy; ++ gtx = ntx; ++ gty = nty; + } + +- return true; ++ // when iteration is completed, compare final Z coordinates ++ return Math.abs(goz - gtz) < (GeoStructure.CELL_HEIGHT * 4); + } + + /** +- * Move check. +- * @param x the x coordinate +- * @param y the y coordinate +- * @param zValue the z coordinate +- * @param tx the target's x coordinate +- * @param ty the target's y coordinate +- * @param tzValue the target's z coordinate +- * @param instance the instance +- * @return the last Location (x,y,z) where player can walk - just before wall ++ * Check movement from coordinates to 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 {code boolean} : True if target coordinates are reachable from origin coordinates + */ +- public Location canMoveToTargetLoc(int x, int y, int zValue, int tx, int ty, int tzValue, Instance instance) ++ public boolean canMoveToTarget(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + { +- final int geoX = getGeoX(x); +- final int geoY = getGeoY(y); +- final int z = getNearestZ(geoX, geoY, zValue); +- final int tGeoX = getGeoX(tx); +- final int tGeoY = getGeoY(ty); +- final int tz = getNearestZ(tGeoX, tGeoY, tzValue); ++ // get origin and check existing geo coordinates ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) ++ { ++ return true; ++ } + +- if (DoorData.getInstance().checkIfDoorsBetween(x, y, z, tx, ty, tz, instance, false)) ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coordinates ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) + { +- return new Location(x, y, getHeight(x, y, z)); ++ return true; + } +- if (FenceData.getInstance().checkIfFenceBetween(x, y, z, tx, ty, tz, instance)) ++ ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // target coordinates reached ++ if ((gox == gtx) && (goy == gty) && (goz == gtz)) + { +- return new Location(x, y, getHeight(x, y, z)); ++ return true; + } +- if (WarpedSpaceManager.getInstance().checkForWarpedSpace(x, y, z, tx, ty, tz, instance)) ++ ++ // perform geodata check ++ final GeoLocation loc = checkMove(gox, goy, goz, gtx, gty, gtz, instance); ++ return (loc.getGeoX() == gtx) && (loc.getGeoY() == gty); ++ } ++ ++ /** ++ * Check movement from origin to target. Returns last available point in the checked path. ++ * @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 {@link Location} : Last point where object can walk (just before wall) ++ */ ++ public Location canMoveToTargetLoc(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) ++ { ++ // Mobius: Double check for doors before normal checkMove to avoid exploiting key movement. ++ if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, instance, false)) + { +- return new Location(x, y, getHeight(x, y, z)); ++ return new Location(ox, oy, oz); + } ++ if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, instance)) ++ { ++ return new Location(ox, oy, oz); ++ } + +- 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 = z; ++ // get origin and check existing geo coordinates ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) ++ { ++ return new Location(tx, ty, tz); ++ } + +- while (pointIter.next()) ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coordinates ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) + { +- final int curX = pointIter.x(); +- final int curY = pointIter.y(); +- final int curZ = getNearestZ(curX, curY, prevZ); +- +- if (hasGeoPos(prevX, prevY)) +- { +- final int nswe = GeoUtils.computeNswe(prevX, prevY, curX, curY); +- if (!checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, nswe)) +- { +- // Can't move, return previous location. +- return new Location(getWorldX(prevX), getWorldY(prevY), prevZ); +- } +- } +- +- prevX = curX; +- prevY = curY; +- prevZ = curZ; ++ return new Location(tx, ty, tz); + } + +- if (hasGeoPos(prevX, prevY) && (prevZ != tz)) ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // target coordinates reached ++ if ((gox == gtx) && (goy == gty) && (goz == gtz)) + { +- // Different floors, return start location. +- return new Location(x, y, z); ++ return new Location(tx, ty, tz); + } + +- return new Location(tx, ty, tz); ++ // perform geodata check ++ return checkMove(gox, goy, goz, gtx, gty, gtz, instance); + } + + /** +- * 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 fromZvalue 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 toZvalue 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 ++ * With this method you can check if a position is visible or can be reached by beeline movement.
++ * 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 gox : origin X geodata coordinate ++ * @param goy : origin Y geodata coordinate ++ * @param goz : origin Z geodata coordinate ++ * @param gtx : target X geodata coordinate ++ * @param gty : target Y geodata coordinate ++ * @param gtz : target Z geodata coordinate ++ * @param instance ++ * @return {@link GeoLocation} : The last allowed point of movement. + */ +- public boolean canMoveToTarget(int fromX, int fromY, int fromZvalue, int toX, int toY, int toZvalue, Instance instance) ++ protected final GeoLocation checkMove(int gox, int goy, int goz, int gtx, int gty, int gtz, Instance instance) + { +- final int geoX = getGeoX(fromX); +- final int geoY = getGeoY(fromY); +- final int fromZ = getNearestZ(geoX, geoY, fromZvalue); +- final int tGeoX = getGeoX(toX); +- final int tGeoY = getGeoY(toY); +- final int toZ = getNearestZ(tGeoX, tGeoY, toZvalue); +- +- if (DoorData.getInstance().checkIfDoorsBetween(fromX, fromY, fromZ, toX, toY, toZ, instance, false)) ++ if (DoorData.getInstance().checkIfDoorsBetween(gox, goy, goz, gtx, gty, gtz, instance, false)) + { +- return false; ++ return new GeoLocation(gox, goy, goz); + } +- if (FenceData.getInstance().checkIfFenceBetween(fromX, fromY, fromZ, toX, toY, toZ, instance)) ++ if (FenceData.getInstance().checkIfFenceBetween(gox, goy, goz, gtx, gty, gtz, instance)) + { +- return false; ++ return new GeoLocation(gox, goy, goz); + } +- if (WarpedSpaceManager.getInstance().checkForWarpedSpace(fromX, fromY, fromZ, toX, toY, toZ, instance)) ++ if (WarpedSpaceManager.getInstance().checkForWarpedSpace(gox, goy, goz, gtx, gty, gtz, instance)) + { +- return false; ++ return new GeoLocation(gox, goy, goz); + } + +- 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 = fromZ; ++ // get X delta, signum and direction flag ++ final int dx = Math.abs(gtx - gox); ++ final int sx = gox < gtx ? 1 : -1; ++ final byte dirX = sx > 0 ? GeoStructure.CELL_FLAG_E : GeoStructure.CELL_FLAG_W; + +- while (pointIter.next()) ++ // get Y delta, signum and direction flag ++ final int dy = Math.abs(gty - goy); ++ final int sy = goy < gty ? 1 : -1; ++ final byte dirY = sy > 0 ? GeoStructure.CELL_FLAG_S : GeoStructure.CELL_FLAG_N; ++ ++ // get direction flag for diagonal movement ++ final byte dirXY = getDirXY(dirX, dirY); ++ ++ // delta, determines axis to move on (+..X axis, -..Y axis) ++ int d = dx - dy; ++ ++ // NSWE direction of movement ++ byte direction; ++ ++ // load pointer coordinates ++ int gpx = gox; ++ int gpy = goy; ++ int gpz = goz; ++ ++ // load next pointer ++ int nx = gpx; ++ int ny = gpy; ++ ++ // loop ++ int count = 0; ++ while (count++ < Config.MAX_ITERATIONS) + { +- final int curX = pointIter.x(); +- final int curY = pointIter.y(); +- final int curZ = getNearestZ(curX, curY, prevZ); ++ direction = 0; + +- if (hasGeoPos(prevX, prevY)) ++ // calculate next point coordinates ++ final int e2 = 2 * d; ++ if ((e2 > -dy) && (e2 < dx)) + { +- final int nswe = GeoUtils.computeNswe(prevX, prevY, curX, curY); +- if (!checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, nswe)) ++ d -= dy; ++ d += dx; ++ nx += sx; ++ ny += sy; ++ direction |= dirXY; ++ } ++ else if (e2 > -dy) ++ { ++ d -= dy; ++ nx += sx; ++ direction |= dirX; ++ } ++ else if (e2 < dx) ++ { ++ d += dx; ++ ny += sy; ++ direction |= dirY; ++ } ++ ++ // obstacle found, return ++ if ((getNsweNearest(gpx, gpy, gpz) & direction) == 0) ++ { ++ return new GeoLocation(gpx, gpy, gpz); ++ } ++ ++ // update pointer coordinates ++ gpx = nx; ++ gpy = ny; ++ gpz = getHeightNearest(nx, ny, gpz); ++ ++ // target coordinates reached ++ if ((gpx == gtx) && (gpy == gty)) ++ { ++ if (gpz == gtz) + { +- return false; ++ // path found, Z coordinates are okay, return target point ++ return new GeoLocation(gtx, gty, gtz); + } ++ ++ // path found, Z coordinates are not okay, return last good point ++ return new GeoLocation(gpx, gpy, gpz); + } ++ } ++ ++ return new GeoLocation(gox, goy, goz); ++ } ++ ++ /** ++ * Returns diagonal NSWE flag format of combined two NSWE flags. ++ * @param dirX : X direction NSWE flag ++ * @param dirY : Y direction NSWE flag ++ * @return byte : NSWE flag of combined direction ++ */ ++ private static byte getDirXY(byte dirX, byte dirY) ++ { ++ // check axis directions ++ if (dirY == GeoStructure.CELL_FLAG_N) ++ { ++ if (dirX == GeoStructure.CELL_FLAG_W) ++ { ++ return GeoStructure.CELL_FLAG_NW; ++ } + +- prevX = curX; +- prevY = curY; +- prevZ = curZ; ++ return GeoStructure.CELL_FLAG_NE; + } + +- if (hasGeoPos(prevX, prevY) && (prevZ != toZ)) ++ if (dirX == GeoStructure.CELL_FLAG_W) + { +- // Different floors. +- return false; ++ return GeoStructure.CELL_FLAG_SW; + } + +- return true; ++ return GeoStructure.CELL_FLAG_SE; + } + ++ /** ++ * 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 ++ */ ++ public List findPath(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) ++ { ++ return null; ++ } ++ ++ /** ++ * Returns the instance of the {@link GeoEngine}. ++ * @return {@link GeoEngine} : The instance. ++ */ + public static GeoEngine getInstance() + { + return SingletonHolder.INSTANCE; +@@ -752,6 +947,6 @@ + + private static class SingletonHolder + { +- protected static final GeoEngine INSTANCE = new GeoEngine(); ++ protected static final GeoEngine INSTANCE = Config.PATHFINDING ? new GeoEnginePathfinding() : new GeoEngine(); + } +-} ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/GeoEnginePathfinding.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/GeoEnginePathfinding.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/GeoEnginePathfinding.java (working copy) +@@ -20,88 +20,84 @@ + import java.util.LinkedList; + import java.util.List; + import java.util.ListIterator; +-import java.util.logging.Level; +-import java.util.logging.Logger; + + import org.l2jmobius.Config; +-import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; +-import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +-import org.l2jmobius.gameserver.geoengine.pathfinding.CellNode; +-import org.l2jmobius.gameserver.geoengine.pathfinding.CellNodeBuffer; +-import org.l2jmobius.gameserver.geoengine.pathfinding.NodeLoc; +-import org.l2jmobius.gameserver.model.World; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoLocation; ++import org.l2jmobius.gameserver.geoengine.pathfinding.Node; ++import org.l2jmobius.gameserver.geoengine.pathfinding.NodeBuffer; ++import org.l2jmobius.gameserver.model.Location; + import org.l2jmobius.gameserver.model.instancezone.Instance; + + /** +- * @author -Nemesiss- ++ * @author Hasha + */ +-public class GeoEnginePathfinding ++final class GeoEnginePathfinding extends GeoEngine + { +- private static final Logger LOGGER = Logger.getLogger(GeoEnginePathfinding.class.getName()); ++ // pre-allocated buffers ++ private final BufferHolder[] _buffers; + +- private BufferInfo[] _buffers; +- + protected GeoEnginePathfinding() + { +- try ++ super(); ++ ++ final String[] array = Config.PATHFIND_BUFFERS.split(";"); ++ _buffers = new BufferHolder[array.length]; ++ int count = 0; ++ for (int i = 0; i < array.length; i++) + { +- final String[] array = Config.PATHFIND_BUFFERS.split(";"); ++ final String buf = array[i]; ++ final String[] args = buf.split("x"); + +- _buffers = new BufferInfo[array.length]; +- +- String buf; +- String[] args; +- for (int i = 0; i < array.length; i++) ++ try + { +- buf = array[i]; +- args = buf.split("x"); +- if (args.length != 2) +- { +- throw new Exception("Invalid buffer definition: " + buf); +- } +- +- _buffers[i] = new BufferInfo(Integer.parseInt(args[0]), Integer.parseInt(args[1])); ++ final int size = Integer.parseInt(args[1]); ++ count += size; ++ _buffers[i] = new BufferHolder(Integer.parseInt(args[0]), size); + } ++ catch (Exception e) ++ { ++ LOGGER.warning("GeoEnginePathfinding: Can not load buffer setting: " + buf); ++ } + } +- catch (Exception e) +- { +- LOGGER.log(Level.WARNING, "CellPathFinding: Problem during buffer init: " + e.getMessage(), e); +- throw new Error("CellPathFinding: load aborted"); +- } ++ ++ LOGGER.info("GeoEnginePathfinding: Loaded " + count + " node buffers."); + } + +- public boolean pathNodesExist(short regionoffset) ++ @Override ++ public List findPath(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + { +- return false; +- } +- +- public List findPath(int x, int y, int z, int tx, int ty, int tz, Instance instance) +- { +- final int gx = GeoEngine.getInstance().getGeoX(x); +- final int gy = GeoEngine.getInstance().getGeoY(y); +- if (!GeoEngine.getInstance().hasGeo(x, y)) ++ // get origin and check existing geo coords ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) + { + 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)) ++ ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coords ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) + { + 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)))); ++ ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // Prepare buffer for pathfinding calculations ++ final NodeBuffer buffer = getBuffer(64 + (2 * Math.max(Math.abs(gox - gtx), Math.abs(goy - gty)))); + if (buffer == null) + { + return null; + } + +- List path = null; ++ // find path ++ List path = null; + try + { +- final CellNode result = buffer.findPath(gx, gy, gz, gtx, gty, gtz); +- ++ final Node result = buffer.findPath(gox, goy, goz, gtx, gty, gtz); + if (result == null) + { + return null; +@@ -125,33 +121,45 @@ + return path; + } + +- int currentX, currentY, currentZ; +- ListIterator middlePoint; ++ // get path list iterator ++ final ListIterator point = path.listIterator(); + +- middlePoint = path.listIterator(); +- currentX = x; +- currentY = y; +- currentZ = z; ++ // get node A (origin) ++ int nodeAx = gox; ++ int nodeAy = goy; ++ short nodeAz = goz; + +- while (middlePoint.hasNext()) ++ // get node B ++ GeoLocation nodeB = (GeoLocation) point.next(); ++ ++ // iterate thought the path to optimize it ++ int count = 0; ++ while (point.hasNext() && (count++ < Config.MAX_ITERATIONS)) + { +- final AbstractNodeLoc locMiddle = middlePoint.next(); +- if (!middlePoint.hasNext()) +- { +- break; +- } ++ // get node C ++ final GeoLocation nodeC = (GeoLocation) path.get(point.nextIndex()); + +- final AbstractNodeLoc locEnd = path.get(middlePoint.nextIndex()); +- if (GeoEngine.getInstance().canMoveToTarget(currentX, currentY, currentZ, locEnd.getX(), locEnd.getY(), locEnd.getZ(), instance)) ++ // check movement from node A to node C ++ final GeoLocation loc = checkMove(nodeAx, nodeAy, nodeAz, nodeC.getGeoX(), nodeC.getGeoY(), nodeC.getZ(), instance); ++ if ((loc.getGeoX() == nodeC.getGeoX()) && (loc.getGeoY() == nodeC.getGeoY())) + { +- middlePoint.remove(); ++ // can move from node A to node C ++ ++ // remove node B ++ point.remove(); + } + else + { +- currentX = locMiddle.getX(); +- currentY = locMiddle.getY(); +- currentZ = locMiddle.getZ(); ++ // can not move from node A to node C ++ ++ // set node A (node B is part of path, update A coordinates) ++ nodeAx = nodeB.getGeoX(); ++ nodeAy = nodeB.getGeoY(); ++ nodeAz = (short) nodeB.getZ(); + } ++ ++ // set node B ++ nodeB = (GeoLocation) point.next(); + } + + return path; +@@ -158,144 +166,102 @@ + } + + /** +- * Convert geodata position to pathnode position +- * @param geo_pos +- * @return pathnode position ++ * Create list of node locations as result of calculated buffer node tree. ++ * @param node : the entry point ++ * @return List : list of node location + */ +- public short getNodePos(int geo_pos) ++ private static List constructPath(Node node) + { +- return (short) (geo_pos >> 3); // OK? +- } +- +- /** +- * Convert node position to pathnode block position +- * @param node_pos +- * @return pathnode block position (0...255) +- */ +- public short getNodeBlock(int node_pos) +- { +- return (short) (node_pos % 256); +- } +- +- public byte getRegionX(int node_pos) +- { +- return (byte) ((node_pos >> 8) + World.TILE_X_MIN); +- } +- +- public byte getRegionY(int node_pos) +- { +- return (byte) ((node_pos >> 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 node_x rx +- * @return +- */ +- public int calculateWorldX(short node_x) +- { +- return World.MAP_MIN_X + (node_x * 128) + 48; +- } +- +- /** +- * Convert pathnode y to World y position +- * @param node_y +- * @return +- */ +- public int calculateWorldY(short node_y) +- { +- return World.MAP_MIN_Y + (node_y * 128) + 48; +- } +- +- private List constructPath(AbstractNode nodeValue) +- { +- final LinkedList path = new LinkedList<>(); +- int previousDirectionX = Integer.MIN_VALUE; +- int previousDirectionY = Integer.MIN_VALUE; +- int directionX, directionY; ++ // create empty list ++ final LinkedList list = new LinkedList<>(); + +- AbstractNode node = nodeValue; +- while (node.getParent() != null) ++ // set direction X/Y ++ int dx = 0; ++ int dy = 0; ++ ++ // get target parent ++ Node target = node; ++ Node parent = target.getParent(); ++ ++ // while parent exists ++ int count = 0; ++ while ((parent != null) && (count++ < Config.MAX_ITERATIONS)) + { +- directionX = node.getLoc().getNodeX() - node.getParent().getLoc().getNodeX(); +- directionY = node.getLoc().getNodeY() - node.getParent().getLoc().getNodeY(); ++ // get parent <> target direction X/Y ++ final int nx = parent.getLoc().getGeoX() - target.getLoc().getGeoX(); ++ final int ny = parent.getLoc().getGeoY() - target.getLoc().getGeoY(); + +- // only add a new route point if moving direction changes +- if ((directionX != previousDirectionX) || (directionY != previousDirectionY)) ++ // direction has changed? ++ if ((dx != nx) || (dy != ny)) + { +- previousDirectionX = directionX; +- previousDirectionY = directionY; ++ // add node to the beginning of the list ++ list.addFirst(target.getLoc()); + +- path.addFirst(node.getLoc()); +- node.setLoc(null); ++ // update direction X/Y ++ dx = nx; ++ dy = ny; + } + +- node = node.getParent(); ++ // move to next node, set target and get its parent ++ target = parent; ++ parent = target.getParent(); + } + +- return path; ++ // return list ++ return list; + } + +- private CellNodeBuffer alloc(int size) ++ /** ++ * Provides optimize selection of the buffer. When all pre-initialized buffer are locked, creates new buffer. ++ * @param size : pre-calculated minimal required size ++ * @return NodeBuffer : buffer ++ */ ++ private final NodeBuffer getBuffer(int size) + { +- CellNodeBuffer current = null; +- for (BufferInfo i : _buffers) ++ NodeBuffer current = null; ++ for (BufferHolder holder : _buffers) + { +- if (i.mapSize >= size) ++ // Find proper size of buffer ++ if (holder._size < size) + { +- for (CellNodeBuffer buf : i.buffer) ++ continue; ++ } ++ ++ // Find unlocked NodeBuffer ++ for (NodeBuffer buffer : holder._buffer) ++ { ++ if (!buffer.isLocked()) + { +- if (buf.lock()) +- { +- current = buf; +- break; +- } ++ continue; + } +- if (current != null) +- { +- break; +- } + +- // not found, allocate temporary buffer +- current = new CellNodeBuffer(i.mapSize); +- current.lock(); +- if (i.buffer.size() < i.count) +- { +- i.buffer.add(current); +- break; +- } ++ return buffer; + } ++ ++ // NodeBuffer not found, allocate temporary buffer ++ current = new NodeBuffer(holder._size); ++ current.isLocked(); + } + + return current; + } + +- private static final class BufferInfo ++ /** ++ * NodeBuffer container with specified size and count of separate buffers. ++ */ ++ private static final class BufferHolder + { +- final int mapSize; +- final int count; +- ArrayList buffer; ++ final int _size; ++ List _buffer; + +- public BufferInfo(int size, int cnt) ++ public BufferHolder(int size, int count) + { +- mapSize = size; +- count = cnt; +- buffer = new ArrayList<>(count); ++ _size = size; ++ _buffer = new ArrayList<>(count); ++ for (int i = 0; i < count; i++) ++ { ++ _buffer.add(new NodeBuffer(size)); ++ } + } + } +- +- public static GeoEnginePathfinding getInstance() +- { +- return SingletonHolder.INSTANCE; +- } +- +- private static class SingletonHolder +- { +- protected static final GeoEnginePathfinding INSTANCE = new GeoEnginePathfinding(); +- } +-} ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java (working copy) +@@ -0,0 +1,197 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++import java.io.IOException; ++ ++/** ++ * @author Hasha ++ */ ++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 height of cell, which is closest to given coordinates.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @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 getHeightNearestOriginal(int geoX, int geoY, int worldZ); ++ ++ /** ++ * Returns the height of cell, which is first above 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, above given coordinates. ++ */ ++ public abstract short getHeightAbove(int geoX, int geoY, int worldZ); ++ ++ /** ++ * Returns the height of cell, which is first below 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, below given coordinates. ++ */ ++ public abstract short getHeightBelow(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 the NSWE flag byte of cell, which is closest to given coordinates.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @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 getNsweNearestOriginal(int geoX, int geoY, int worldZ); ++ ++ /** ++ * Returns the NSWE flag byte of cell, which is first above 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 getNsweAbove(int geoX, int geoY, int worldZ); ++ ++ /** ++ * Returns the NSWE flag byte of cell, which is first below 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 getNsweBelow(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 above given coordinates.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @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 getIndexAboveOriginal(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 index to data of the cell, which is first layer below given coordinates.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @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 getIndexBelowOriginal(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 height of cell given by cell index.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @param index : Index of the cell. ++ * @return short : Cell geodata Z coordinate, below given coordinates. ++ */ ++ public abstract short getHeightOriginal(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); ++ ++ /** ++ * Returns the NSWE flag byte of cell given by cell index.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @param index : Index of the cell. ++ * @return short : Cell geodata Z coordinate, below given coordinates. ++ */ ++ public abstract byte getNsweOriginal(int index); ++ ++ /** ++ * Sets the NSWE flag byte of cell given by cell index. ++ * @param index : Index of the cell. ++ * @param nswe : New NSWE flag byte. ++ */ ++ public abstract void setNswe(int index, byte nswe); ++ ++ /** ++ * Saves the block in L2D format to {@link BufferedOutputStream}. Used only for L2D geodata conversion. ++ * @param stream : The stream. ++ * @throws IOException : Can't save the block to steam. ++ */ ++ public abstract void saveBlock(BufferedOutputStream stream) throws IOException; ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java (working copy) +@@ -0,0 +1,252 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++import java.io.IOException; ++import java.nio.ByteBuffer; ++ ++/** ++ * @author Hasha ++ */ ++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. ++ * @param format : GeoFormat specifying format of loaded data. ++ */ ++ public BlockComplex(ByteBuffer bb, GeoFormat format) ++ { ++ // initialize buffer ++ _buffer = new byte[GeoStructure.BLOCK_CELLS * 3]; ++ ++ // load data ++ for (int i = 0; i < GeoStructure.BLOCK_CELLS; i++) ++ { ++ if (format != GeoFormat.L2D) ++ { ++ // 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); ++ } ++ else ++ { ++ // get nswe ++ final byte nswe = bb.get(); ++ _buffer[i * 3] = nswe; ++ ++ // get height ++ final short height = bb.getShort(); ++ _buffer[(i * 3) + 1] = (byte) (height & 0x00FF); ++ _buffer[(i * 3) + 2] = (byte) (height >> 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 short getHeightNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getHeightNearest(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeightAbove(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 short height = (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ ++ // check and return height ++ return height > worldZ ? height : Short.MIN_VALUE; ++ } ++ ++ @Override ++ public short getHeightBelow(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 short height = (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ ++ // check and return height ++ return height < worldZ ? height : Short.MAX_VALUE; ++ } ++ ++ @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 byte getNsweNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getNsweNearest(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public byte getNsweAbove(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 ? _buffer[index] : 0; ++ } ++ ++ @Override ++ public byte getNsweBelow(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 ? _buffer[index] : 0; ++ } ++ ++ @Override ++ public 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 getIndexAboveOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexAbove(geoX, geoY, worldZ); ++ } ++ ++ @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 int getIndexBelowOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexBelow(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeight(int index) ++ { ++ return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ } ++ ++ @Override ++ public short getHeightOriginal(int index) ++ { ++ return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ } ++ ++ @Override ++ public byte getNswe(int index) ++ { ++ return _buffer[index]; ++ } ++ ++ @Override ++ public byte getNsweOriginal(int index) ++ { ++ return _buffer[index]; ++ } ++ ++ @Override ++ public void setNswe(int index, byte nswe) ++ { ++ _buffer[index] = nswe; ++ } ++ ++ @Override ++ public void saveBlock(BufferedOutputStream stream) throws IOException ++ { ++ // write block type ++ stream.write(GeoStructure.TYPE_COMPLEX_L2D); ++ ++ // write block data ++ stream.write(_buffer, 0, GeoStructure.BLOCK_CELLS * 3); ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java (working copy) +@@ -0,0 +1,176 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++import java.io.IOException; ++import java.nio.ByteBuffer; ++ ++/** ++ * @author Hasha ++ */ ++public class BlockFlat extends ABlock ++{ ++ protected final short _height; ++ protected byte _nswe; ++ ++ /** ++ * Creates FlatBlock. ++ * @param bb : Input byte buffer. ++ * @param format : GeoFormat specifying format of loaded data. ++ */ ++ public BlockFlat(ByteBuffer bb, GeoFormat format) ++ { ++ _height = bb.getShort(); ++ _nswe = format != GeoFormat.L2D ? 0x0F : (byte) (0xFF); ++ if (format == GeoFormat.L2OFF) ++ { ++ bb.getShort(); ++ } ++ } ++ ++ @Override ++ public boolean hasGeoPos() ++ { ++ return true; ++ } ++ ++ @Override ++ public short getHeightNearest(int geoX, int geoY, int worldZ) ++ { ++ return _height; ++ } ++ ++ @Override ++ public short getHeightNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return _height; ++ } ++ ++ @Override ++ public short getHeightAbove(int geoX, int geoY, int worldZ) ++ { ++ // check and return height ++ return _height > worldZ ? _height : Short.MIN_VALUE; ++ } ++ ++ @Override ++ public short getHeightBelow(int geoX, int geoY, int worldZ) ++ { ++ // check and return height ++ return _height < worldZ ? _height : Short.MAX_VALUE; ++ } ++ ++ @Override ++ public byte getNsweNearest(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweAbove(int geoX, int geoY, int worldZ) ++ { ++ // check height and return nswe ++ return _height > worldZ ? _nswe : 0; ++ } ++ ++ @Override ++ public byte getNsweBelow(int geoX, int geoY, int worldZ) ++ { ++ // check height and return nswe ++ return _height < worldZ ? _nswe : 0; ++ } ++ ++ @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 getIndexAboveOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexAbove(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public int getIndexBelow(int geoX, int geoY, int worldZ) ++ { ++ // check height and return index ++ return _height < worldZ ? 0 : -1; ++ } ++ ++ @Override ++ public int getIndexBelowOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexBelow(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeight(int index) ++ { ++ return _height; ++ } ++ ++ @Override ++ public short getHeightOriginal(int index) ++ { ++ return _height; ++ } ++ ++ @Override ++ public byte getNswe(int index) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweOriginal(int index) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public void setNswe(int index, byte nswe) ++ { ++ _nswe = nswe; ++ } ++ ++ @Override ++ public void saveBlock(BufferedOutputStream stream) throws IOException ++ { ++ // write block type ++ stream.write(GeoStructure.TYPE_FLAT_L2D); ++ ++ // write height ++ stream.write((byte) (_height & 0x00FF)); ++ stream.write((byte) (_height >> 8)); ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java (working copy) +@@ -0,0 +1,465 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++import java.io.IOException; ++import java.nio.ByteBuffer; ++import java.nio.ByteOrder; ++import java.util.Arrays; ++ ++/** ++ * @author Hasha ++ */ ++public class BlockMultilayer extends ABlock ++{ ++ private static final int MAX_LAYERS = Byte.MAX_VALUE; ++ ++ private static ByteBuffer _temp; ++ ++ /** ++ * Initializes the temporarily buffer. ++ */ ++ public static void initialize() ++ { ++ // initialize temporarily buffer and sorting mechanism ++ _temp = ByteBuffer.allocate(GeoStructure.BLOCK_CELLS * MAX_LAYERS * 3); ++ _temp.order(ByteOrder.LITTLE_ENDIAN); ++ } ++ ++ /** ++ * Releases temporarily buffer. ++ */ ++ public static void release() ++ { ++ _temp = null; ++ } ++ ++ protected byte[] _buffer; ++ ++ /** ++ * Implicit constructor for children class. ++ */ ++ protected BlockMultilayer() ++ { ++ _buffer = null; ++ } ++ ++ /** ++ * Creates MultilayerBlock. ++ * @param bb : Input byte buffer. ++ * @param format : GeoFormat specifying format of loaded data. ++ */ ++ public BlockMultilayer(ByteBuffer bb, GeoFormat format) ++ { ++ // 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 = format != GeoFormat.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++) ++ { ++ if (format != GeoFormat.L2D) ++ { ++ // get data ++ final short data = bb.getShort(); ++ ++ // add nswe and height ++ _temp.put((byte) (data & 0x000F)); ++ _temp.putShort((short) ((short) (data & 0xFFF0) >> 1)); ++ } ++ else ++ { ++ // add nswe ++ _temp.put(bb.get()); ++ ++ // add height ++ _temp.putShort(bb.getShort()); ++ } ++ } ++ } ++ ++ // 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 short getHeightNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getHeightNearest(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeightAbove(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 height ++ if (height > worldZ) ++ { ++ return (short) height; ++ } ++ ++ // move index to next layer ++ index -= 3; ++ } ++ ++ // none layer found, return minimum value ++ return Short.MIN_VALUE; ++ } ++ ++ @Override ++ public short getHeightBelow(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 height ++ if (height < worldZ) ++ { ++ return (short) height; ++ } ++ ++ // move index to next layer ++ index += 3; ++ } ++ ++ // none layer found, return maximum value ++ return Short.MAX_VALUE; ++ } ++ ++ @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 byte getNsweNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getNsweNearest(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public byte getNsweAbove(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 nswe ++ if (height > worldZ) ++ { ++ return _buffer[index]; ++ } ++ ++ // move index to next layer ++ index -= 3; ++ } ++ ++ // none layer found, block movement ++ return 0; ++ } ++ ++ @Override ++ public byte getNsweBelow(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 nswe ++ if (height < worldZ) ++ { ++ return _buffer[index]; ++ } ++ ++ // move index to next layer ++ index += 3; ++ } ++ ++ // none layer found, block movement ++ return 0; ++ } ++ ++ @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 first layer data (first from bottom) ++ byte layers = _buffer[index++]; ++ ++ // loop though all cell layers, find closest layer ++ 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; ++ } ++ ++ // none layer found ++ return -1; ++ } ++ ++ @Override ++ public int getIndexAboveOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexAbove(geoX, geoY, worldZ); ++ } ++ ++ @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; ++ } ++ ++ // none layer found ++ return -1; ++ } ++ ++ @Override ++ public int getIndexBelowOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexBelow(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeight(int index) ++ { ++ // get height ++ return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ } ++ ++ @Override ++ public short getHeightOriginal(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]; ++ } ++ ++ @Override ++ public byte getNsweOriginal(int index) ++ { ++ // get nswe ++ return _buffer[index]; ++ } ++ ++ @Override ++ public void setNswe(int index, byte nswe) ++ { ++ // set nswe ++ _buffer[index] = nswe; ++ } ++ ++ @Override ++ public void saveBlock(BufferedOutputStream stream) throws IOException ++ { ++ // write block type ++ stream.write(GeoStructure.TYPE_MULTILAYER_L2D); ++ ++ // for each cell ++ int index = 0; ++ for (int i = 0; i < GeoStructure.BLOCK_CELLS; i++) ++ { ++ // write layers count ++ final byte layers = _buffer[index++]; ++ stream.write(layers); ++ ++ // write cell data ++ stream.write(_buffer, index, layers * 3); ++ ++ // move index to next cell ++ index += layers * 3; ++ } ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java (working copy) +@@ -0,0 +1,150 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++ ++/** ++ * @author Hasha ++ */ ++public class BlockNull extends ABlock ++{ ++ private final byte _nswe; ++ ++ public BlockNull() ++ { ++ _nswe = (byte) 0xFF; ++ } ++ ++ @Override ++ public boolean hasGeoPos() ++ { ++ return false; ++ } ++ ++ @Override ++ public short getHeightNearest(int geoX, int geoY, int worldZ) ++ { ++ return (short) worldZ; ++ } ++ ++ @Override ++ public short getHeightNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return (short) worldZ; ++ } ++ ++ @Override ++ public short getHeightAbove(int geoX, int geoY, int worldZ) ++ { ++ return (short) worldZ; ++ } ++ ++ @Override ++ public short getHeightBelow(int geoX, int geoY, int worldZ) ++ { ++ return (short) worldZ; ++ } ++ ++ @Override ++ public byte getNsweNearest(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweAbove(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweBelow(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) ++ { ++ return 0; ++ } ++ ++ @Override ++ public int getIndexAboveOriginal(int geoX, int geoY, int worldZ) ++ { ++ return 0; ++ } ++ ++ @Override ++ public int getIndexBelow(int geoX, int geoY, int worldZ) ++ { ++ return 0; ++ } ++ ++ @Override ++ public int getIndexBelowOriginal(int geoX, int geoY, int worldZ) ++ { ++ return 0; ++ } ++ ++ @Override ++ public short getHeight(int index) ++ { ++ return 0; ++ } ++ ++ @Override ++ public short getHeightOriginal(int index) ++ { ++ return 0; ++ } ++ ++ @Override ++ public byte getNswe(int index) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweOriginal(int index) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public void setNswe(int index, byte nswe) ++ { ++ } ++ ++ @Override ++ public void saveBlock(BufferedOutputStream stream) ++ { ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java (nonexistent) +@@ -1,48 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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() +- { +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/ComplexBlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/ComplexBlock.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/ComplexBlock.java (nonexistent) +@@ -1,77 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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; +- +-/** +- * @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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/FlatBlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/FlatBlock.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/FlatBlock.java (nonexistent) +@@ -1,56 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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; +- +-/** +- * @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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/GeoFormat.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/GeoFormat.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/GeoFormat.java (working copy) +@@ -0,0 +1,39 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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 Hasha ++ */ ++public enum GeoFormat ++{ ++ L2J("%d_%d.l2j"), ++ L2OFF("%d_%d_conv.dat"), ++ L2D("%d_%d.l2d"); ++ ++ private final String _filename; ++ ++ private GeoFormat(String filename) ++ { ++ _filename = filename; ++ } ++ ++ public String getFilename() ++ { ++ return _filename; ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/GeoLocation.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/GeoLocation.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/GeoLocation.java (working copy) +@@ -0,0 +1,67 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.geoengine.GeoEngine; ++import org.l2jmobius.gameserver.model.Location; ++ ++/** ++ * @author Hasha ++ */ ++public class GeoLocation extends Location ++{ ++ private byte _nswe; ++ ++ public GeoLocation(int x, int y, int z) ++ { ++ super(x, y, GeoEngine.getInstance().getHeightNearest(x, y, z)); ++ _nswe = GeoEngine.getInstance().getNsweNearest(x, y, z); ++ } ++ ++ public void set(int x, int y, short z) ++ { ++ super.setXYZ(x, y, GeoEngine.getInstance().getHeightNearest(x, y, z)); ++ _nswe = GeoEngine.getInstance().getNsweNearest(x, y, z); ++ } ++ ++ public int getGeoX() ++ { ++ return _x; ++ } ++ ++ public int getGeoY() ++ { ++ return _y; ++ } ++ ++ @Override ++ public int getX() ++ { ++ return GeoEngine.getWorldX(_x); ++ } ++ ++ @Override ++ public int getY() ++ { ++ return GeoEngine.getWorldY(_y); ++ } ++ ++ public byte getNSWE() ++ { ++ return _nswe; ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java (working copy) +@@ -0,0 +1,70 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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; ++ ++/** ++ * @author Hasha ++ */ ++public class GeoStructure ++{ ++ // cells ++ public static final byte CELL_FLAG_E = 1 << 0; ++ public static final byte CELL_FLAG_W = 1 << 1; ++ public static final byte CELL_FLAG_S = 1 << 2; ++ public static final byte CELL_FLAG_N = 1 << 3; ++ public static final byte CELL_FLAG_SE = 1 << 4; ++ public static final byte CELL_FLAG_SW = 1 << 5; ++ public static final byte CELL_FLAG_NE = 1 << 6; ++ public static final byte CELL_FLAG_NW = (byte) (1 << 7); ++ ++ public static final int CELL_HEIGHT = 8; ++ public static final int CELL_IGNORE_HEIGHT = CELL_HEIGHT * 6; ++ ++ // blocks ++ public static final byte TYPE_FLAT_L2J_L2OFF = 0; ++ public static final byte TYPE_FLAT_L2D = (byte) 0xD0; ++ public static final byte TYPE_COMPLEX_L2J = 1; ++ public static final byte TYPE_COMPLEX_L2OFF = 0x40; ++ public static final byte TYPE_COMPLEX_L2D = (byte) 0xD1; ++ 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) ++ public static final byte TYPE_MULTILAYER_L2D = (byte) 0xD2; ++ ++ 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; ++ ++ // regions ++ 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; ++ ++ // global geodata ++ private static final int GEO_REGIONS_X = ((World.TILE_X_MAX - World.TILE_X_MIN) + 1); ++ private 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 +Index: java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java (nonexistent) +@@ -1,42 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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); +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/IGeoObject.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/IGeoObject.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/IGeoObject.java (working copy) +@@ -0,0 +1,53 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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 Hasha ++ */ ++public interface IGeoObject ++{ ++ /** ++ * Returns geodata X coordinate of the {@link IGeoObject}. ++ * @return int : Geodata X coordinate. ++ */ ++ int getGeoX(); ++ ++ /** ++ * Returns geodata Y coordinate of the {@link IGeoObject}. ++ * @return int : Geodata Y coordinate. ++ */ ++ int getGeoY(); ++ ++ /** ++ * Returns geodata Z coordinate of the {@link IGeoObject}. ++ * @return int : Geodata Z coordinate. ++ */ ++ int getGeoZ(); ++ ++ /** ++ * Returns height of the {@link IGeoObject}. ++ * @return int : Height. ++ */ ++ int getHeight(); ++ ++ /** ++ * Returns {@link IGeoObject} data. ++ * @return byte[][] : {@link IGeoObject} data. ++ */ ++ byte[][] getObjectGeoData(); ++} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java (nonexistent) +@@ -1,47 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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(); +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/MultilayerBlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/MultilayerBlock.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/MultilayerBlock.java (nonexistent) +@@ -1,183 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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; +- +-/** +- * @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); +- +- // 1 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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/NullRegion.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/NullRegion.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/NullRegion.java (nonexistent) +@@ -1,55 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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 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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/Region.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/Region.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/Region.java (nonexistent) +@@ -1,92 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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; +- +-/** +- * @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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java (nonexistent) +@@ -1,87 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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 T _loc; +- private AbstractNode _parent; +- +- public AbstractNode(T loc) +- { +- _loc = loc; +- } +- +- public void setParent(AbstractNode p) +- { +- _parent = p; +- } +- +- public AbstractNode getParent() +- { +- return _parent; +- } +- +- public T getLoc() +- { +- return _loc; +- } +- +- public void setLoc(T l) +- { +- _loc = l; +- } +- +- @Override +- public int hashCode() +- { +- final int prime = 31; +- int result = 1; +- result = (prime * result) + ((_loc == null) ? 0 : _loc.hashCode()); +- return result; +- } +- +- @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 +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java (nonexistent) +@@ -1,33 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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 int getNodeX(); +- +- public abstract int getNodeY(); +-} +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNode.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNode.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNode.java (nonexistent) +@@ -1,67 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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 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 +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNodeBuffer.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNodeBuffer.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNodeBuffer.java (nonexistent) +@@ -1,348 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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.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 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) +- { +- _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(); +- } +- +- 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 void getNeighbors() +- { +- if (!_current.getLoc().canGoAll()) +- { +- 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); +- } +- +- // SouthEast +- if ((nodeE != null) && (nodeS != null)) +- { +- if (nodeE.getLoc().canGoSouth() && nodeS.getLoc().canGoEast()) +- { +- addNode(x + 1, y + 1, z, true); +- } +- } +- +- // SouthWest +- if ((nodeS != null) && (nodeW != null)) +- { +- if (nodeW.getLoc().canGoSouth() && nodeS.getLoc().canGoWest()) +- { +- addNode(x - 1, y + 1, z, true); +- } +- } +- +- // NorthEast +- if ((nodeN != null) && (nodeE != null)) +- { +- if (nodeE.getLoc().canGoNorth() && nodeN.getLoc().canGoEast()) +- { +- addNode(x + 1, y - 1, z, true); +- } +- } +- +- // NorthWest +- if ((nodeN != null) && (nodeW != null)) +- { +- if (nodeW.getLoc().canGoNorth() && nodeN.getLoc().canGoWest()) +- { +- addNode(x - 1, y - 1, z, true); +- } +- } +- } +- +- private 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(); +- // reinit node if needed +- if (result.getLoc() != null) +- { +- result.getLoc().set(x, y, z); +- } +- else +- { +- result.setLoc(new NodeLoc(x, y, z)); +- } +- } +- +- return result; +- } +- +- private 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 boolean isHighWeight(int x, int y, int z) +- { +- final CellNode result = getNode(x, y, z); +- if (result == null) +- { +- return true; +- } +- +- if (!result.getLoc().canGoAll()) +- { +- return true; +- } +- if (Math.abs(result.getLoc().getZ() - z) > 16) +- { +- return true; +- } +- +- return false; +- } +- +- private 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 +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java (working copy) +@@ -0,0 +1,87 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.geodata.GeoLocation; ++ ++/** ++ * @author Hasha ++ */ ++public class Node ++{ ++ // node coords and nswe flag ++ private GeoLocation _loc; ++ ++ // node parent (for reverse path construction) ++ private Node _parent; ++ // node child (for moving over nodes during iteration) ++ private Node _child; ++ ++ // node G cost (movement cost = parent movement cost + current movement cost) ++ private double _cost = -1000; ++ ++ public void setLoc(int x, int y, int z) ++ { ++ _loc = new GeoLocation(x, y, z); ++ } ++ ++ public GeoLocation getLoc() ++ { ++ return _loc; ++ } ++ ++ public void setParent(Node parent) ++ { ++ _parent = parent; ++ } ++ ++ public Node getParent() ++ { ++ return _parent; ++ } ++ ++ public void setChild(Node child) ++ { ++ _child = child; ++ } ++ ++ public Node getChild() ++ { ++ return _child; ++ } ++ ++ public void setCost(double cost) ++ { ++ _cost = cost; ++ } ++ ++ public double getCost() ++ { ++ return _cost; ++ } ++ ++ public void free() ++ { ++ // reset node location ++ _loc = null; ++ ++ // reset node parent, child and cost ++ _parent = null; ++ _child = null; ++ _cost = -1000; ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java (working copy) +@@ -0,0 +1,306 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.concurrent.locks.ReentrantLock; ++ ++import org.l2jmobius.Config; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; ++ ++/** ++ * @author DS, Hasha; Credits to Diamond ++ */ ++public class NodeBuffer ++{ ++ private final ReentrantLock _lock = new ReentrantLock(); ++ private final int _size; ++ private final Node[][] _buffer; ++ ++ // center coordinates ++ private int _cx = 0; ++ private int _cy = 0; ++ ++ // target coordinates ++ private int _gtx = 0; ++ private int _gty = 0; ++ private short _gtz = 0; ++ ++ private Node _current = null; ++ ++ /** ++ * Constructor of NodeBuffer. ++ * @param size : one dimension size of buffer ++ */ ++ public NodeBuffer(int size) ++ { ++ // set size ++ _size = size; ++ ++ // initialize buffer ++ _buffer = new Node[size][size]; ++ for (int x = 0; x < size; x++) ++ { ++ for (int y = 0; y < size; y++) ++ { ++ _buffer[x][y] = 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 Node : first node of path ++ */ ++ public Node findPath(int gox, int goy, short goz, int gtx, int gty, short gtz) ++ { ++ // set coordinates (middle of the line (gox,goy) - (gtx,gty), will be in the center of the buffer) ++ _cx = gox + ((gtx - gox - _size) / 2); ++ _cy = goy + ((gty - goy - _size) / 2); ++ ++ _gtx = gtx; ++ _gty = gty; ++ _gtz = gtz; ++ ++ _current = getNode(gox, goy, goz); ++ _current.setCost(getCostH(gox, goy, goz)); ++ ++ int count = 0; ++ do ++ { ++ // reached target? ++ if ((_current.getLoc().getGeoX() == _gtx) && (_current.getLoc().getGeoY() == _gty) && (Math.abs(_current.getLoc().getZ() - _gtz) < 8)) ++ { ++ return _current; ++ } ++ ++ // expand current node ++ expand(); ++ ++ // move pointer ++ _current = _current.getChild(); ++ } ++ while ((_current != null) && (count++ < Config.MAX_ITERATIONS)); ++ ++ return null; ++ } ++ ++ public boolean isLocked() ++ { ++ return _lock.tryLock(); ++ } ++ ++ public void free() ++ { ++ _current = null; ++ ++ for (Node[] nodes : _buffer) ++ { ++ for (Node node : nodes) ++ { ++ if (node.getLoc() != null) ++ { ++ node.free(); ++ } ++ } ++ } ++ ++ _lock.unlock(); ++ } ++ ++ /** ++ * Check _current Node and add its neighbors to the buffer. ++ */ ++ private final void expand() ++ { ++ // can't move anywhere, don't expand ++ final byte nswe = _current.getLoc().getNSWE(); ++ if (nswe == 0) ++ { ++ return; ++ } ++ ++ // get geo coords of the node to be expanded ++ final int x = _current.getLoc().getGeoX(); ++ final int y = _current.getLoc().getGeoY(); ++ final short z = (short) _current.getLoc().getZ(); ++ ++ // can move north, expand ++ if ((nswe & GeoStructure.CELL_FLAG_N) != 0) ++ { ++ addNode(x, y - 1, z, Config.BASE_WEIGHT); ++ } ++ ++ // can move south, expand ++ if ((nswe & GeoStructure.CELL_FLAG_S) != 0) ++ { ++ addNode(x, y + 1, z, Config.BASE_WEIGHT); ++ } ++ ++ // can move west, expand ++ if ((nswe & GeoStructure.CELL_FLAG_W) != 0) ++ { ++ addNode(x - 1, y, z, Config.BASE_WEIGHT); ++ } ++ ++ // can move east, expand ++ if ((nswe & GeoStructure.CELL_FLAG_E) != 0) ++ { ++ addNode(x + 1, y, z, Config.BASE_WEIGHT); ++ } ++ ++ // can move north-west, expand ++ if ((nswe & GeoStructure.CELL_FLAG_NW) != 0) ++ { ++ addNode(x - 1, y - 1, z, Config.DIAGONAL_WEIGHT); ++ } ++ ++ // can move north-east, expand ++ if ((nswe & GeoStructure.CELL_FLAG_NE) != 0) ++ { ++ addNode(x + 1, y - 1, z, Config.DIAGONAL_WEIGHT); ++ } ++ ++ // can move south-west, expand ++ if ((nswe & GeoStructure.CELL_FLAG_SW) != 0) ++ { ++ addNode(x - 1, y + 1, z, Config.DIAGONAL_WEIGHT); ++ } ++ ++ // can move south-east, expand ++ if ((nswe & GeoStructure.CELL_FLAG_SE) != 0) ++ { ++ addNode(x + 1, y + 1, z, Config.DIAGONAL_WEIGHT); ++ } ++ } ++ ++ /** ++ * Returns node, if it exists in buffer. ++ * @param x : node X coord ++ * @param y : node Y coord ++ * @param z : node Z coord ++ * @return Node : node, if exits in buffer ++ */ ++ private final Node getNode(int x, int y, short z) ++ { ++ // check node X out of coordinates ++ final int ix = x - _cx; ++ if ((ix < 0) || (ix >= _size)) ++ { ++ return null; ++ } ++ ++ // check node Y out of coordinates ++ final int iy = y - _cy; ++ if ((iy < 0) || (iy >= _size)) ++ { ++ return null; ++ } ++ ++ // get node ++ final Node result = _buffer[ix][iy]; ++ ++ // check and update ++ if (result.getLoc() == null) ++ { ++ result.setLoc(x, y, z); ++ } ++ ++ // return node ++ return result; ++ } ++ ++ /** ++ * Add node given by coordinates to the buffer. ++ * @param x : geo X coord ++ * @param y : geo Y coord ++ * @param z : geo Z coord ++ * @param weight : weight of movement to new node ++ */ ++ private final void addNode(int x, int y, short z, int weight) ++ { ++ // get node to be expanded ++ final Node node = getNode(x, y, z); ++ if (node == null) ++ { ++ return; ++ } ++ ++ // Z distance between nearby cells is higher than cell size ++ if (node.getLoc().getZ() > (z + (2 * GeoStructure.CELL_HEIGHT))) ++ { ++ return; ++ } ++ ++ // node was already expanded, return ++ if (node.getCost() >= 0) ++ { ++ return; ++ } ++ ++ node.setParent(_current); ++ if (node.getLoc().getNSWE() != (byte) 0xFF) ++ { ++ node.setCost(getCostH(x, y, node.getLoc().getZ()) + (weight * Config.OBSTACLE_MULTIPLIER)); ++ } ++ else ++ { ++ node.setCost(getCostH(x, y, node.getLoc().getZ()) + weight); ++ } ++ ++ Node current = _current; ++ int count = 0; ++ while ((current.getChild() != null) && (count < (Config.MAX_ITERATIONS * 4))) ++ { ++ count++; ++ if (current.getChild().getCost() > node.getCost()) ++ { ++ node.setChild(current.getChild()); ++ break; ++ } ++ current = current.getChild(); ++ } ++ ++ if (count >= (Config.MAX_ITERATIONS * 4)) ++ { ++ System.err.println("Pathfinding: too long loop detected, cost:" + node.getCost()); ++ } ++ ++ current.setChild(node); ++ } ++ ++ /** ++ * @param x : node X coord ++ * @param y : node Y coord ++ * @param i : node Z coord ++ * @return double : node cost ++ */ ++ private final double getCostH(int x, int y, int i) ++ { ++ final int dX = x - _gtx; ++ final int dY = y - _gty; ++ final int dZ = (i - _gtz) / GeoStructure.CELL_HEIGHT; ++ ++ // return (Math.abs(dX) + Math.abs(dY) + Math.abs(dZ)) * Config.HEURISTIC_WEIGHT; // Manhattan distance ++ return Math.sqrt((dX * dX) + (dY * dY) + (dZ * dZ)) * Config.HEURISTIC_WEIGHT; // Direct distance ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeLoc.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeLoc.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeLoc.java (nonexistent) +@@ -1,183 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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.Cell; +- +-/** +- * @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 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 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; +- // return super.hashCode(); +- } +- +- @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; +- if (_x != other._x) +- { +- return false; +- } +- if (_y != other._y) +- { +- return false; +- } +- if (_goNorth != other._goNorth) +- { +- return false; +- } +- if (_goEast != other._goEast) +- { +- return false; +- } +- if (_goSouth != other._goSouth) +- { +- return false; +- } +- if (_goWest != other._goWest) +- { +- return false; +- } +- if (_geoHeight != other._geoHeight) +- { +- return false; +- } +- return true; +- } +-} +Index: java/org/l2jmobius/gameserver/model/actor/Creature.java +=================================================================== +--- java/org/l2jmobius/gameserver/model/actor/Creature.java (revision 8399) ++++ java/org/l2jmobius/gameserver/model/actor/Creature.java (working copy) +@@ -66,8 +66,6 @@ + import org.l2jmobius.gameserver.enums.TeleportWhereType; + import org.l2jmobius.gameserver.enums.UserInfoType; + import org.l2jmobius.gameserver.geoengine.GeoEngine; +-import org.l2jmobius.gameserver.geoengine.GeoEnginePathfinding; +-import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; + import org.l2jmobius.gameserver.instancemanager.IdManager; + import org.l2jmobius.gameserver.instancemanager.MapRegionManager; + import org.l2jmobius.gameserver.instancemanager.QuestManager; +@@ -2577,7 +2575,7 @@ + + public boolean disregardingGeodata; + public int onGeodataPathIndex; +- public List geoPath; ++ public List geoPath; + public int geoPathAccurateTx; + public int geoPathAccurateTy; + public int geoPathGtx; +@@ -3466,7 +3464,7 @@ + if (((originalDistance - distance) > 30) && !isControlBlocked() && !isInVehicle) + { + // Path calculation -- overrides previous movement check +- m.geoPath = GeoEnginePathfinding.getInstance().findPath(curX, curY, curZ, originalX, originalY, originalZ, getInstanceWorld()); ++ m.geoPath = GeoEngine.getInstance().findPath(curX, curY, curZ, originalX, originalY, originalZ, getInstanceWorld()); + if ((m.geoPath == null) || (m.geoPath.size() < 2)) // No path found + { + if (isPlayer() && !_isFlying && !isInWater) +Index: java/org/l2jmobius/gameserver/model/actor/instance/PlayerInstance.java +=================================================================== +--- java/org/l2jmobius/gameserver/model/actor/instance/PlayerInstance.java (revision 8397) ++++ java/org/l2jmobius/gameserver/model/actor/instance/PlayerInstance.java (working copy) +@@ -12704,7 +12704,7 @@ + { + if (Config.CORRECT_PLAYER_Z) + { +- final int nearestZ = GeoEngine.getInstance().getHigherHeight(getX(), getY(), getZ()); ++ final int nearestZ = GeoEngine.getInstance().getHeightNearest(getX(), getY(), getZ()); + if (getZ() < nearestZ) + { + teleToLocation(new Location(getX(), getY(), nearestZ)); +Index: java/org/l2jmobius/gameserver/util/GeoUtils.java +=================================================================== +--- java/org/l2jmobius/gameserver/util/GeoUtils.java (revision 8397) ++++ java/org/l2jmobius/gameserver/util/GeoUtils.java (working copy) +@@ -19,7 +19,7 @@ + import java.awt.Color; + + import org.l2jmobius.gameserver.geoengine.GeoEngine; +-import org.l2jmobius.gameserver.geoengine.geodata.Cell; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; + import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance; + import org.l2jmobius.gameserver.network.serverpackets.ExServerPrimitive; + +@@ -26,25 +26,25 @@ + /** + * @author HorridoJoho + */ +-public final class GeoUtils ++public class GeoUtils + { + public static void debug2DLine(PlayerInstance player, int x, int y, int tx, int ty, int z) + { +- final int gx = GeoEngine.getInstance().getGeoX(x); +- final int gy = GeoEngine.getInstance().getGeoY(y); ++ final int gx = GeoEngine.getGeoX(x); ++ final int gy = GeoEngine.getGeoY(y); + +- final int tgx = GeoEngine.getInstance().getGeoX(tx); +- final int tgy = GeoEngine.getInstance().getGeoY(ty); ++ final int tgx = GeoEngine.getGeoX(tx); ++ final int tgy = GeoEngine.getGeoY(ty); + + final ExServerPrimitive prim = new ExServerPrimitive("Debug2DLine", x, y, z); +- prim.addLine(Color.BLUE, GeoEngine.getInstance().getWorldX(gx), GeoEngine.getInstance().getWorldY(gy), z, GeoEngine.getInstance().getWorldX(tgx), GeoEngine.getInstance().getWorldY(tgy), z); ++ prim.addLine(Color.BLUE, GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), z, GeoEngine.getWorldX(tgx), GeoEngine.getWorldY(tgy), z); + + final LinePointIterator iter = new LinePointIterator(gx, gy, tgx, tgy); + + while (iter.next()) + { +- final int wx = GeoEngine.getInstance().getWorldX(iter.x()); +- final int wy = GeoEngine.getInstance().getWorldY(iter.y()); ++ final int wx = GeoEngine.getWorldX(iter.x()); ++ final int wy = GeoEngine.getWorldY(iter.y()); + + prim.addPoint(Color.RED, wx, wy, z); + } +@@ -53,21 +53,21 @@ + + public static void debug3DLine(PlayerInstance player, int x, int y, int z, int tx, int ty, int tz) + { +- final int gx = GeoEngine.getInstance().getGeoX(x); +- final int gy = GeoEngine.getInstance().getGeoY(y); ++ final int gx = GeoEngine.getGeoX(x); ++ final int gy = GeoEngine.getGeoY(y); + +- final int tgx = GeoEngine.getInstance().getGeoX(tx); +- final int tgy = GeoEngine.getInstance().getGeoY(ty); ++ final int tgx = GeoEngine.getGeoX(tx); ++ final int tgy = GeoEngine.getGeoY(ty); + + final ExServerPrimitive prim = new ExServerPrimitive("Debug3DLine", x, y, z); +- prim.addLine(Color.BLUE, GeoEngine.getInstance().getWorldX(gx), GeoEngine.getInstance().getWorldY(gy), z, GeoEngine.getInstance().getWorldX(tgx), GeoEngine.getInstance().getWorldY(tgy), tz); ++ prim.addLine(Color.BLUE, GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), z, GeoEngine.getWorldX(tgx), GeoEngine.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.getInstance().getWorldX(prevX); +- int wy = GeoEngine.getInstance().getWorldY(prevY); ++ int wx = GeoEngine.getWorldX(prevX); ++ int wy = GeoEngine.getWorldY(prevY); + int wz = iter.z(); + prim.addPoint(Color.RED, wx, wy, wz); + +@@ -78,8 +78,8 @@ + + if ((curX != prevX) || (curY != prevY)) + { +- wx = GeoEngine.getInstance().getWorldX(curX); +- wy = GeoEngine.getInstance().getWorldY(curY); ++ wx = GeoEngine.getWorldX(curX); ++ wy = GeoEngine.getWorldY(curY); + wz = iter.z(); + + prim.addPoint(Color.RED, wx, wy, wz); +@@ -93,7 +93,7 @@ + + private static Color getDirectionColor(int x, int y, int z, int nswe) + { +- if (GeoEngine.getInstance().checkNearestNswe(x, y, z, nswe)) ++ if ((GeoEngine.getInstance().getNsweNearest(x, y, z) & nswe) == nswe) + { + return Color.GREEN; + } +@@ -109,9 +109,8 @@ + int iPacket = 0; + + ExServerPrimitive exsp = null; +- final GeoEngine ge = GeoEngine.getInstance(); +- final int playerGx = ge.getGeoX(player.getX()); +- final int playerGy = ge.getGeoY(player.getY()); ++ final int playerGx = GeoEngine.getGeoX(player.getX()); ++ final int playerGy = GeoEngine.getGeoY(player.getY()); + for (int dx = -geoRadius; dx <= geoRadius; ++dx) + { + for (int dy = -geoRadius; dy <= geoRadius; ++dy) +@@ -135,12 +134,12 @@ + final int gx = playerGx + dx; + final int gy = playerGy + dy; + +- final int x = ge.getWorldX(gx); +- final int y = ge.getWorldY(gy); +- final int z = ge.getNearestZ(gx, gy, player.getZ()); ++ final int x = GeoEngine.getWorldX(gx); ++ final int y = GeoEngine.getWorldY(gy); ++ final int z = GeoEngine.getInstance().getHeightNearest(gx, gy, player.getZ()); + + // north arrow +- Color col = getDirectionColor(gx, gy, z, Cell.NSWE_NORTH); ++ Color col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_N); + 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); +@@ -147,7 +146,7 @@ + exsp.addLine(col, x - 4, y - 4, z, x + 4, y - 4, z); + + // east arrow +- col = getDirectionColor(gx, gy, z, Cell.NSWE_EAST); ++ col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_E); + 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); +@@ -154,13 +153,13 @@ + exsp.addLine(col, x + 4, y - 4, z, x + 4, y + 4, z); + + // south arrow +- col = getDirectionColor(gx, gy, z, Cell.NSWE_SOUTH); ++ col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_S); + 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, Cell.NSWE_WEST); ++ col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_W); + 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); +@@ -188,15 +187,15 @@ + { + if (y > lastY) + { +- return Cell.NSWE_SOUTH_EAST; ++ return GeoStructure.CELL_FLAG_SE; // Direction.SOUTH_EAST; + } + else if (y < lastY) + { +- return Cell.NSWE_NORTH_EAST; ++ return GeoStructure.CELL_FLAG_NE; // Direction.NORTH_EAST; + } + else + { +- return Cell.NSWE_EAST; ++ return GeoStructure.CELL_FLAG_E; // Direction.EAST; + } + } + else if (x < lastX) // west +@@ -203,26 +202,27 @@ + { + if (y > lastY) + { +- return Cell.NSWE_SOUTH_WEST; ++ return GeoStructure.CELL_FLAG_SW; // Direction.SOUTH_WEST; + } + else if (y < lastY) + { +- return Cell.NSWE_NORTH_WEST; ++ return GeoStructure.CELL_FLAG_NW; // Direction.NORTH_WEST; + } + else + { +- return Cell.NSWE_WEST; ++ return GeoStructure.CELL_FLAG_W; // Direction.WEST; + } + } +- else // unchanged x ++ else ++ // unchanged x + { + if (y > lastY) + { +- return Cell.NSWE_SOUTH; ++ return GeoStructure.CELL_FLAG_S; // Direction.SOUTH; + } + else if (y < lastY) + { +- return Cell.NSWE_NORTH; ++ return GeoStructure.CELL_FLAG_N; // Direction.NORTH; + } + else + { +Index: java/org/l2jmobius/tools/geodataconverter/GeoDataConverter.java +=================================================================== +--- java/org/l2jmobius/tools/geodataconverter/GeoDataConverter.java (nonexistent) ++++ java/org/l2jmobius/tools/geodataconverter/GeoDataConverter.java (working copy) +@@ -0,0 +1,386 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ */ ++package org.l2jmobius.tools.geodataconverter; ++ ++import java.io.BufferedOutputStream; ++import java.io.File; ++import java.io.FileOutputStream; ++import java.io.RandomAccessFile; ++import java.nio.ByteOrder; ++import java.nio.MappedByteBuffer; ++import java.nio.channels.FileChannel; ++import java.util.Scanner; ++ ++import org.l2jmobius.Config; ++import org.l2jmobius.commons.util.PropertiesParser; ++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.GeoFormat; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; ++import org.l2jmobius.gameserver.model.World; ++ ++/** ++ * @author Hasha ++ */ ++public class GeoDataConverter ++{ ++ private static GeoFormat _format; ++ private static ABlock[][] _blocks; ++ ++ public static void main(String[] args) ++ { ++ // initialize config ++ loadGeoengineConfigs(); ++ ++ // get geodata format ++ String type = ""; ++ try (Scanner scn = new Scanner(System.in)) ++ { ++ while (!(type.equalsIgnoreCase("J") || type.equalsIgnoreCase("O") || type.equalsIgnoreCase("E"))) ++ { ++ System.out.println("GeoDataConverter: Select source geodata type:"); ++ System.out.println(" J: L2J (e.g. 23_22.l2j)"); ++ System.out.println(" O: L2OFF (e.g. 23_22_conv.dat)"); ++ System.out.println(" E: Exit"); ++ System.out.print("Choice: "); ++ type = scn.next(); ++ } ++ } ++ if (type.equalsIgnoreCase("E")) ++ { ++ System.exit(0); ++ } ++ ++ _format = type.equalsIgnoreCase("J") ? GeoFormat.L2J : GeoFormat.L2OFF; ++ ++ // start conversion ++ System.out.println("GeoDataConverter: Converting all " + _format + " files."); ++ ++ // initialize geodata container ++ _blocks = new ABlock[GeoStructure.REGION_BLOCKS_X][GeoStructure.REGION_BLOCKS_Y]; ++ ++ // initialize multilayer temporarily buffer ++ BlockMultilayer.initialize(); ++ ++ // load geo files ++ int converted = 0; ++ for (int rx = World.TILE_X_MIN; rx <= World.TILE_X_MAX; rx++) ++ { ++ for (int ry = World.TILE_Y_MIN; ry <= World.TILE_Y_MAX; ry++) ++ { ++ final String input = String.format(_format.getFilename(), rx, ry); ++ final String filepath = Config.GEODATA_PATH; ++ final File f = new File(filepath + input); ++ if (f.exists() && !f.isDirectory()) ++ { ++ // load geodata ++ if (!loadGeoBlocks(input)) ++ { ++ System.out.println("GeoDataConverter: Unable to load " + input + " region file."); ++ continue; ++ } ++ ++ // recalculate nswe ++ if (!recalculateNswe()) ++ { ++ System.out.println("GeoDataConverter: Unable to convert " + input + " region file."); ++ continue; ++ } ++ ++ // save geodata ++ final String output = String.format(GeoFormat.L2D.getFilename(), rx, ry); ++ if (!saveGeoBlocks(output)) ++ { ++ System.out.println("GeoDataConverter: Unable to save " + output + " region file."); ++ continue; ++ } ++ ++ converted++; ++ System.out.println("GeoDataConverter: Created " + output + " region file."); ++ } ++ } ++ } ++ System.out.println("GeoDataConverter: Converted " + converted + " " + _format + " to L2D region file(s)."); ++ ++ // release multilayer block temporarily buffer ++ BlockMultilayer.release(); ++ } ++ ++ /** ++ * Loads geo blocks from buffer of the region file. ++ * @param filename : The name of the to load. ++ * @return boolean : True when successful. ++ */ ++ private static boolean loadGeoBlocks(String filename) ++ { ++ // region file is load-able, try to load it ++ try (RandomAccessFile raf = new RandomAccessFile(Config.GEODATA_PATH + filename, "r"); ++ FileChannel fc = raf.getChannel()) ++ { ++ final MappedByteBuffer buffer = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()).load(); ++ buffer.order(ByteOrder.LITTLE_ENDIAN); ++ ++ // load 18B header for L2off geodata (1st and 2nd byte...region X and Y) ++ if (_format == GeoFormat.L2OFF) ++ { ++ for (int i = 0; i < 18; i++) ++ { ++ buffer.get(); ++ } ++ } ++ ++ // 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 (_format == GeoFormat.L2J) ++ { ++ // get block type ++ final byte type = buffer.get(); ++ ++ // load block according to block type ++ switch (type) ++ { ++ case GeoStructure.TYPE_FLAT_L2J_L2OFF: ++ { ++ _blocks[ix][iy] = new BlockFlat(buffer, _format); ++ break; ++ } ++ case GeoStructure.TYPE_COMPLEX_L2J: ++ { ++ _blocks[ix][iy] = new BlockComplex(buffer, _format); ++ break; ++ } ++ case GeoStructure.TYPE_MULTILAYER_L2J: ++ { ++ _blocks[ix][iy] = new BlockMultilayer(buffer, _format); ++ break; ++ } ++ default: ++ { ++ throw new IllegalArgumentException("Unknown block type: " + type); ++ } ++ } ++ } ++ else ++ { ++ // get block type ++ final short type = buffer.getShort(); ++ ++ // load block according to block type ++ switch (type) ++ { ++ case GeoStructure.TYPE_FLAT_L2J_L2OFF: ++ { ++ _blocks[ix][iy] = new BlockFlat(buffer, _format); ++ break; ++ } ++ case GeoStructure.TYPE_COMPLEX_L2OFF: ++ { ++ _blocks[ix][iy] = new BlockComplex(buffer, _format); ++ break; ++ } ++ default: ++ { ++ _blocks[ix][iy] = new BlockMultilayer(buffer, _format); ++ break; ++ } ++ } ++ } ++ } ++ } ++ ++ if (buffer.remaining() > 0) ++ { ++ System.out.println("GeoDataConverter: Region file " + filename + " can be corrupted, remaining " + buffer.remaining() + " bytes to read."); ++ return false; ++ } ++ ++ return true; ++ } ++ catch (Exception e) ++ { ++ System.out.println("GeoDataConverter: Error while loading " + filename + " region file."); ++ return false; ++ } ++ } ++ ++ /** ++ * Recalculate diagonal flags for the region file. ++ * @return boolean : True when successful. ++ */ ++ private static boolean recalculateNswe() ++ { ++ try ++ { ++ for (int x = 0; x < GeoStructure.REGION_CELLS_X; x++) ++ { ++ for (int y = 0; y < GeoStructure.REGION_CELLS_Y; y++) ++ { ++ // get block ++ final ABlock block = _blocks[x / GeoStructure.BLOCK_CELLS_X][y / GeoStructure.BLOCK_CELLS_Y]; ++ ++ // skip flat blocks ++ if (block instanceof BlockFlat) ++ { ++ continue; ++ } ++ ++ // for complex and multilayer blocks go though all layers ++ short height = Short.MAX_VALUE; ++ int index; ++ while ((index = block.getIndexBelow(x, y, height)) != -1) ++ { ++ // get height and nswe ++ height = block.getHeight(index); ++ byte nswe = block.getNswe(index); ++ ++ // update nswe with diagonal flags ++ nswe = updateNsweBelow(x, y, height, nswe); ++ ++ // set nswe of the cell ++ block.setNswe(index, nswe); ++ } ++ } ++ } ++ ++ return true; ++ } ++ catch (Exception e) ++ { ++ return false; ++ } ++ } ++ ++ /** ++ * Updates the NSWE flag with diagonal flags. ++ * @param x : Geodata X coordinate. ++ * @param y : Geodata Y coordinate. ++ * @param z : Geodata Z coordinate. ++ * @param nsweValue : NSWE flag to be updated. ++ * @return byte : Updated NSWE flag. ++ */ ++ private static byte updateNsweBelow(int x, int y, short z, byte nsweValue) ++ { ++ byte nswe = nsweValue; ++ ++ // calculate virtual layer height ++ final short height = (short) (z + GeoStructure.CELL_IGNORE_HEIGHT); ++ ++ // get NSWE of neighbor cells below virtual layer (NPC/PC can fall down of clif, but can not climb it -> NSWE of cell below) ++ final byte nsweN = getNsweBelow(x, y - 1, height); ++ final byte nsweS = getNsweBelow(x, y + 1, height); ++ final byte nsweW = getNsweBelow(x - 1, y, height); ++ final byte nsweE = getNsweBelow(x + 1, y, height); ++ ++ // north-west ++ if ((((nswe & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) || (((nswe & GeoStructure.CELL_FLAG_W) != 0) && ((nsweW & GeoStructure.CELL_FLAG_N) != 0))) ++ { ++ nswe |= GeoStructure.CELL_FLAG_NW; ++ } ++ ++ // north-east ++ if ((((nswe & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) || (((nswe & GeoStructure.CELL_FLAG_E) != 0) && ((nsweE & GeoStructure.CELL_FLAG_N) != 0))) ++ { ++ nswe |= GeoStructure.CELL_FLAG_NE; ++ } ++ ++ // south-west ++ if ((((nswe & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) || (((nswe & GeoStructure.CELL_FLAG_W) != 0) && ((nsweW & GeoStructure.CELL_FLAG_S) != 0))) ++ { ++ nswe |= GeoStructure.CELL_FLAG_SW; ++ } ++ ++ // south-east ++ if ((((nswe & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) || (((nswe & GeoStructure.CELL_FLAG_E) != 0) && ((nsweE & GeoStructure.CELL_FLAG_S) != 0))) ++ { ++ nswe |= GeoStructure.CELL_FLAG_SE; ++ } ++ ++ return nswe; ++ } ++ ++ private static byte getNsweBelow(int geoX, int geoY, short worldZ) ++ { ++ // out of geo coordinates ++ if ((geoX < 0) || (geoX >= GeoStructure.REGION_CELLS_X)) ++ { ++ return 0; ++ } ++ ++ // out of geo coordinates ++ if ((geoY < 0) || (geoY >= GeoStructure.REGION_CELLS_Y)) ++ { ++ return 0; ++ } ++ ++ // get block ++ final ABlock block = _blocks[geoX / GeoStructure.BLOCK_CELLS_X][geoY / GeoStructure.BLOCK_CELLS_Y]; ++ ++ // get index, when valid, return nswe ++ final int index = block.getIndexBelow(geoX, geoY, worldZ); ++ return index == -1 ? 0 : block.getNswe(index); ++ } ++ ++ /** ++ * Save region file to file. ++ * @param filename : The name of file to save. ++ * @return boolean : True when successful. ++ */ ++ private static boolean saveGeoBlocks(String filename) ++ { ++ try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(Config.GEODATA_PATH + filename), GeoStructure.REGION_BLOCKS * GeoStructure.BLOCK_CELLS * 3)) ++ { ++ // loop over region blocks and save each block ++ for (int ix = 0; ix < GeoStructure.REGION_BLOCKS_X; ix++) ++ { ++ for (int iy = 0; iy < GeoStructure.REGION_BLOCKS_Y; iy++) ++ { ++ _blocks[ix][iy].saveBlock(bos); ++ } ++ } ++ ++ // flush data to file ++ bos.flush(); ++ ++ return true; ++ } ++ catch (Exception e) ++ { ++ return false; ++ } ++ } ++ ++ private static void loadGeoengineConfigs() ++ { ++ final PropertiesParser geoData = new PropertiesParser(Config.GEOENGINE_CONFIG_FILE); ++ Config.GEODATA_PATH = geoData.getString("GeoDataPath", "./data/geodata/"); ++ Config.COORD_SYNCHRONIZE = geoData.getInt("CoordSynchronize", -1); ++ Config.PART_OF_CHARACTER_HEIGHT = geoData.getInt("PartOfCharacterHeight", 75); ++ Config.MAX_OBSTACLE_HEIGHT = geoData.getInt("MaxObstacleHeight", 32); ++ Config.PATHFINDING = geoData.getBoolean("PathFinding", true); ++ Config.PATHFIND_BUFFERS = geoData.getString("PathFindBuffers", "100x6;128x6;192x6;256x4;320x4;384x4;500x2"); ++ Config.BASE_WEIGHT = geoData.getInt("BaseWeight", 10); ++ Config.DIAGONAL_WEIGHT = geoData.getInt("DiagonalWeight", 14); ++ Config.OBSTACLE_MULTIPLIER = geoData.getInt("ObstacleMultiplier", 10); ++ Config.HEURISTIC_WEIGHT = geoData.getInt("HeuristicWeight", 20); ++ Config.MAX_ITERATIONS = geoData.getInt("MaxIterations", 3500); ++ } ++} +\ No newline at end of file diff --git a/L2J_Mobius_7.0_PreludeOfWar/dist/game/data/geodata/L2D_GeoEngine.patch b/L2J_Mobius_7.0_PreludeOfWar/dist/game/data/geodata/L2D_GeoEngine.patch new file mode 100644 index 0000000000..63e2faf0c9 --- /dev/null +++ b/L2J_Mobius_7.0_PreludeOfWar/dist/game/data/geodata/L2D_GeoEngine.patch @@ -0,0 +1,6052 @@ +Index: dist/game/GeoDataConverter.bat +=================================================================== +--- dist/game/GeoDataConverter.bat (nonexistent) ++++ dist/game/GeoDataConverter.bat (working copy) +@@ -0,0 +1,6 @@ ++@echo off ++title L2D geodata converter ++ ++java -Xmx512m -cp ./../libs/* org.l2jmobius.tools.geodataconverter.GeoDataConverter ++ ++pause +Index: dist/game/GeoDataConverter.sh +=================================================================== +--- dist/game/GeoDataConverter.sh (nonexistent) ++++ dist/game/GeoDataConverter.sh (working copy) +@@ -0,0 +1,4 @@ ++#! /bin/sh ++ ++java -Xmx512m -cp ../libs/*: org.l2jmobius.tools.geodataconverter.GeoDataConverter > log/stdout.log 2>&1 ++ +Index: dist/game/config/GeoEngine.ini +=================================================================== +--- dist/game/config/GeoEngine.ini (revision 8397) ++++ dist/game/config/GeoEngine.ini (working copy) +@@ -1,6 +1,10 @@ + # ================================================================= + # Geodata + # ================================================================= ++# Because of real-time performance we are using geodata files only in ++# diagonal L2D format now (using filename e.g. 22_16.l2d). ++# L2D geodata can be obtained by conversion of existing L2J or L2OFF geodata. ++# Launch "GeoDataConverter.bat/sh" and follow instructions to start the conversion. + + # 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/ +@@ -13,6 +17,16 @@ + CoordSynchronize = 2 + + # ================================================================= ++# Path checking ++# ================================================================= ++ ++# 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 finding + # ================================================================= + +@@ -23,19 +37,23 @@ + # Pathfinding array buffers configuration, default: 100x6;128x6;192x6;256x4;320x4;384x4;500x2 + PathFindBuffers = 100x6;128x6;192x6;256x4;320x4;384x4;500x2 + +-# Weight for nodes without obstacles far from walls +-LowWeight = 0.5 ++# Base path weight, when moving from one node to another on axis direction, default: 10 ++BaseWeight = 10 + +-# Weight for nodes near walls +-MediumWeight = 2 ++# Path weight, when moving from one node to another on diagonal direction, default: BaseWeight * sqrt(2) = 14 ++DiagonalWeight = 14 + +-# Weight for nodes with obstacles +-HighWeight = 3 ++# When movement flags of target node is blocked to any direction, multiply movement weight by this multiplier. ++# This causes pathfinding algorithm to avoid path construction exactly near an obstacle, default: 10 ++ObstacleMultiplier = 10 + +-# Weight for diagonal movement. +-# Default: LowWeight * sqrt(2) +-DiagonalWeight = 0.707 ++# Weight of the heuristic algorithm, which is giving estimated cost from node to target, default: 20 ++# For proper function must be higher than BaseWeight and/or DiagonalWeight. ++HeuristicWeight = 20 + ++# Maximum number of generated nodes per one path-finding process, default 3500 ++MaxIterations = 3500 ++ + # ================================================================= + # Other + # ================================================================= +Index: dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java +=================================================================== +--- dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java (revision 8397) ++++ dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java (working copy) +@@ -55,9 +55,8 @@ + final int worldX = activeChar.getX(); + final int worldY = activeChar.getY(); + final int worldZ = activeChar.getZ(); +- final int geoX = GeoEngine.getInstance().getGeoX(worldX); +- final int geoY = GeoEngine.getInstance().getGeoY(worldY); +- ++ final int geoX = GeoEngine.getGeoX(worldX); ++ final int geoY = GeoEngine.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)); +@@ -73,9 +72,8 @@ + final int worldX = activeChar.getX(); + final int worldY = activeChar.getY(); + final int worldZ = activeChar.getZ(); +- final int geoX = GeoEngine.getInstance().getGeoX(worldX); +- final int geoY = GeoEngine.getInstance().getGeoY(worldY); +- ++ final int geoX = GeoEngine.getGeoX(worldX); ++ final int geoY = GeoEngine.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)); +Index: dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java +=================================================================== +--- dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java (revision 8397) ++++ dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java (working copy) +@@ -18,11 +18,11 @@ + + import java.util.List; + +-import org.l2jmobius.Config; +-import org.l2jmobius.gameserver.geoengine.GeoEnginePathfinding; +-import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; ++import org.l2jmobius.gameserver.geoengine.GeoEngine; + import org.l2jmobius.gameserver.handler.IAdminCommandHandler; ++import org.l2jmobius.gameserver.model.Location; + import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance; ++import org.l2jmobius.gameserver.network.SystemMessageId; + import org.l2jmobius.gameserver.util.BuilderUtil; + + public class AdminPathNode implements IAdminCommandHandler +@@ -37,29 +37,30 @@ + { + if (command.equals("admin_path_find")) + { +- if (!Config.PATHFINDING) +- { +- BuilderUtil.sendSysMessage(activeChar, "PathFinding is disabled."); +- return true; +- } + if (activeChar.getTarget() != null) + { +- final List path = GeoEnginePathfinding.getInstance().findPath(activeChar.getX(), activeChar.getY(), (short) activeChar.getZ(), activeChar.getTarget().getX(), activeChar.getTarget().getY(), (short) activeChar.getTarget().getZ(), activeChar.getInstanceWorld()); ++ 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()); + if (path == null) + { +- BuilderUtil.sendSysMessage(activeChar, "No Route!"); +- return true; ++ BuilderUtil.sendSysMessage(activeChar, "No route found or pathfinding disabled."); + } +- for (AbstractNodeLoc a : path) ++ else + { +- BuilderUtil.sendSysMessage(activeChar, "x:" + a.getX() + " y:" + a.getY() + " z:" + a.getZ()); ++ for (Location point : path) ++ { ++ BuilderUtil.sendSysMessage(activeChar, "x:" + point.getX() + " y:" + point.getY() + " z:" + point.getZ()); ++ } + } + } + else + { +- BuilderUtil.sendSysMessage(activeChar, "No Target!"); ++ activeChar.sendPacket(SystemMessageId.INVALID_TARGET); + } + } ++ else ++ { ++ return false; ++ } + return true; + } + +Index: java/org/l2jmobius/Config.java +=================================================================== +--- java/org/l2jmobius/Config.java (revision 8397) ++++ java/org/l2jmobius/Config.java (working copy) +@@ -32,7 +32,6 @@ + import java.net.UnknownHostException; + import java.nio.charset.StandardCharsets; + import java.nio.file.Files; +-import java.nio.file.Path; + import java.nio.file.Paths; + import java.time.Duration; + import java.util.ArrayList; +@@ -927,14 +926,17 @@ + // -------------------------------------------------- + // GeoEngine + // -------------------------------------------------- +- public static Path GEODATA_PATH; ++ public static String GEODATA_PATH; ++ public static int COORD_SYNCHRONIZE; ++ public static int PART_OF_CHARACTER_HEIGHT; ++ public static int MAX_OBSTACLE_HEIGHT; + public static boolean PATHFINDING; + public static String PATHFIND_BUFFERS; +- public static float LOW_WEIGHT; +- public static float MEDIUM_WEIGHT; +- public static float HIGH_WEIGHT; +- public static float DIAGONAL_WEIGHT; +- public static int COORD_SYNCHRONIZE; ++ public static int BASE_WEIGHT; ++ public static int DIAGONAL_WEIGHT; ++ public static int HEURISTIC_WEIGHT; ++ public static int OBSTACLE_MULTIPLIER; ++ public static int MAX_ITERATIONS; + public static boolean CORRECT_PLAYER_Z; + + /** Attribute System */ +@@ -2468,14 +2470,17 @@ + + // Load GeoEngine config file (if exists) + final PropertiesParser GeoEngine = new PropertiesParser(GEOENGINE_CONFIG_FILE); +- GEODATA_PATH = Paths.get(GeoEngine.getString("GeoDataPath", "./data/geodata")); ++ GEODATA_PATH = GeoEngine.getString("GeoDataPath", "./data/geodata/"); ++ COORD_SYNCHRONIZE = GeoEngine.getInt("CoordSynchronize", -1); ++ PART_OF_CHARACTER_HEIGHT = GeoEngine.getInt("PartOfCharacterHeight", 75); ++ MAX_OBSTACLE_HEIGHT = GeoEngine.getInt("MaxObstacleHeight", 32); + PATHFINDING = GeoEngine.getBoolean("PathFinding", true); + PATHFIND_BUFFERS = GeoEngine.getString("PathFindBuffers", "100x6;128x6;192x6;256x4;320x4;384x4;500x2"); +- LOW_WEIGHT = GeoEngine.getFloat("LowWeight", 0.5f); +- MEDIUM_WEIGHT = GeoEngine.getFloat("MediumWeight", 2); +- HIGH_WEIGHT = GeoEngine.getFloat("HighWeight", 3); +- DIAGONAL_WEIGHT = GeoEngine.getFloat("DiagonalWeight", 0.707f); +- COORD_SYNCHRONIZE = GeoEngine.getInt("CoordSynchronize", -1); ++ BASE_WEIGHT = GeoEngine.getInt("BaseWeight", 10); ++ DIAGONAL_WEIGHT = GeoEngine.getInt("DiagonalWeight", 14); ++ OBSTACLE_MULTIPLIER = GeoEngine.getInt("ObstacleMultiplier", 10); ++ HEURISTIC_WEIGHT = GeoEngine.getInt("HeuristicWeight", 20); ++ MAX_ITERATIONS = GeoEngine.getInt("MaxIterations", 3500); + CORRECT_PLAYER_Z = GeoEngine.getBoolean("CorrectPlayerZ", false); + + // Load AllowedPlayerRaces config file (if exists) +Index: java/org/l2jmobius/gameserver/geoengine/GeoEngine.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/GeoEngine.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/GeoEngine.java (working copy) +@@ -16,101 +16,91 @@ + */ + package org.l2jmobius.gameserver.geoengine; + +-import java.io.IOException; ++import java.io.File; + import java.io.RandomAccessFile; + import java.nio.ByteOrder; +-import java.nio.channels.FileChannel.MapMode; +-import java.nio.file.Files; +-import java.nio.file.Path; +-import java.util.concurrent.atomic.AtomicReferenceArray; +-import java.util.logging.Level; ++import java.nio.MappedByteBuffer; ++import java.nio.channels.FileChannel; ++import java.util.List; + 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.geoengine.geodata.Cell; +-import org.l2jmobius.gameserver.geoengine.geodata.IRegion; +-import org.l2jmobius.gameserver.geoengine.geodata.NullRegion; +-import org.l2jmobius.gameserver.geoengine.geodata.Region; ++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.GeoFormat; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoLocation; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; + import org.l2jmobius.gameserver.instancemanager.WarpedSpaceManager; + 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; ++import org.l2jmobius.gameserver.util.MathUtil; + + /** +- * @author -Nemesiss-, HorridoJoho ++ * @author Hasha + */ + public class GeoEngine + { +- private static final Logger LOGGER = Logger.getLogger(GeoEngine.class.getName()); ++ protected static final Logger LOGGER = Logger.getLogger(GeoEngine.class.getName()); + +- private static final int WORLD_MIN_X = -655360; +- private static final int WORLD_MIN_Y = -589824; +- private static final int WORLD_MIN_Z = -16384; ++ private final ABlock[][] _blocks; ++ private final BlockNull _nullBlock; + +- /** 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; +- +- /** The regions array */ +- private final AtomicReferenceArray _regions = new AtomicReferenceArray<>(GEO_REGIONS); +- +- 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; +- +- protected GeoEngine() ++ /** ++ * GeoEngine constructor. Loads all geodata files of chosen geodata format. ++ */ ++ public GeoEngine() + { + LOGGER.info("GeoEngine: Initializing..."); +- for (int i = 0; i < _regions.length(); i++) +- { +- _regions.set(i, NullRegion.INSTANCE); +- } + ++ // 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; +- try ++ long fileSize = 0; ++ for (int rx = World.TILE_X_MIN; rx <= World.TILE_X_MAX; rx++) + { +- for (int regionX = World.TILE_X_MIN; regionX <= World.TILE_X_MAX; regionX++) ++ for (int ry = World.TILE_Y_MIN; ry <= World.TILE_Y_MAX; ry++) + { +- for (int regionY = World.TILE_Y_MIN; regionY <= World.TILE_Y_MAX; regionY++) ++ final File f = new File(Config.GEODATA_PATH + String.format(GeoFormat.L2D.getFilename(), rx, ry)); ++ if (f.exists() && !f.isDirectory()) + { +- 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(rx, ry)) + { +- try (RandomAccessFile raf = new RandomAccessFile(geoFilePath.toFile(), "r")) +- { +- _regions.set((regionX * GEO_REGIONS_Y) + regionY, new Region(raf.getChannel().map(MapMode.READ_ONLY, 0, raf.length()).load().order(ByteOrder.LITTLE_ENDIAN))); +- loaded++; +- } ++ loaded++; ++ fileSize += f.length(); + } + } ++ else ++ { ++ // region file is not load-able, load null blocks ++ loadNullBlocks(rx, ry); ++ } + } + } +- catch (Exception e) ++ ++ LOGGER.info("GeoEngine: Loaded " + loaded + " geodata files."); ++ if (loaded > 0) + { +- LOGGER.log(Level.SEVERE, "GeoEngine: Failed to load geodata!", e); +- System.exit(1); ++ LOGGER.info("GeoEngine: Total geodata file size " + (fileSize / 1024 / 1024) + " MB."); + } + +- LOGGER.info("GeoEngine: Loaded " + loaded + " geodata files."); +- +- // Avoid wrong configs when no files are loaded. ++ // avoid wrong configs when no files are loaded + if (loaded == 0) + { + if (Config.PATHFINDING) +@@ -124,627 +114,832 @@ + LOGGER.info("GeoEngine: Forcing CoordSynchronize setting to -1."); + } + } ++ ++ // release multilayer block temporarily buffer ++ BlockMultilayer.release(); + } + + /** +- * @param geoX +- * @param geoY +- * @return the region ++ * 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 IRegion getRegion(int geoX, int geoY) ++ private final boolean loadGeoBlocks(int regionX, int regionY) + { +- final int region = ((geoX / IRegion.REGION_CELLS_X) * GEO_REGIONS_Y) + (geoY / IRegion.REGION_CELLS_Y); +- if ((region < 0) || (region >= _regions.length())) ++ final String filename = String.format(GeoFormat.L2D.getFilename(), regionX, regionY); ++ ++ // standard load ++ try (RandomAccessFile raf = new RandomAccessFile(Config.GEODATA_PATH + filename, "r"); ++ FileChannel fc = raf.getChannel()) + { +- return null; ++ // initialize file buffer ++ final MappedByteBuffer buffer = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()).load(); ++ buffer.order(ByteOrder.LITTLE_ENDIAN); ++ ++ // 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++) ++ { ++ // get block type ++ final byte type = buffer.get(); ++ ++ // load block according to block type ++ switch (type) ++ { ++ case GeoStructure.TYPE_FLAT_L2D: ++ { ++ _blocks[blockX + ix][blockY + iy] = new BlockFlat(buffer, GeoFormat.L2D); ++ break; ++ } ++ case GeoStructure.TYPE_COMPLEX_L2D: ++ { ++ _blocks[blockX + ix][blockY + iy] = new BlockComplex(buffer, GeoFormat.L2D); ++ break; ++ } ++ case GeoStructure.TYPE_MULTILAYER_L2D: ++ { ++ _blocks[blockX + ix][blockY + iy] = new BlockMultilayer(buffer, GeoFormat.L2D); ++ break; ++ } ++ default: ++ { ++ throw new IllegalArgumentException("Unknown block type: " + type); ++ } ++ } ++ } ++ } ++ ++ // check data consistency ++ if (buffer.remaining() > 0) ++ { ++ LOGGER.warning("GeoEngine: Region file " + filename + " can be corrupted, remaining " + buffer.remaining() + " bytes to read."); ++ } ++ ++ // loading was successful ++ return true; + } +- return _regions.get(region); +- } +- +- /** +- * @param filePath +- * @param regionX +- * @param regionY +- * @throws IOException +- */ +- 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")) ++ catch (Exception e) + { +- _regions.set(regionOffset, new Region(raf.getChannel().map(MapMode.READ_ONLY, 0, raf.length()).load().order(ByteOrder.LITTLE_ENDIAN))); ++ // an error occured while loading, load null blocks ++ LOGGER.warning("GeoEngine: Error while loading " + filename + " region file."); ++ LOGGER.warning(e.getMessage()); ++ ++ // replace whole region file with null blocks ++ loadNullBlocks(regionX, regionY); ++ ++ // loading was not successful ++ return false; + } + } + + /** +- * @param regionX +- * @param regionY ++ * 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. + */ +- public void unloadRegion(int regionX, int regionY) ++ private final void loadNullBlocks(int regionX, int regionY) + { +- _regions.set((regionX * GEO_REGIONS_Y) + regionY, NullRegion.INSTANCE); +- } +- +- /** +- * @param geoX +- * @param geoY +- * @return if geodata exist +- */ +- public boolean hasGeoPos(int geoX, int geoY) +- { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) ++ // 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++) + { +- return false; ++ for (int iy = 0; iy < GeoStructure.REGION_BLOCKS_Y; iy++) ++ { ++ _blocks[blockX + ix][blockY + iy] = _nullBlock; ++ } + } +- return region.hasGeo(); + } + ++ // GEODATA - GENERAL ++ + /** +- * Checks the specified position for available geodata. +- * @param x the world x +- * @param y the world y +- * @return {@code true} if there is geodata for the given coordinates, {@code false} otherwise ++ * Converts world X to geodata X. ++ * @param worldX ++ * @return int : Geo X + */ +- public boolean hasGeo(int x, int y) ++ public static int getGeoX(int worldX) + { +- return hasGeoPos(getGeoX(x), getGeoY(y)); ++ return (MathUtil.limit(worldX, World.MAP_MIN_X, World.MAP_MAX_X) - World.MAP_MIN_X) >> 4; + } + + /** +- * @param geoX +- * @param geoY +- * @param worldZ +- * @param nswe +- * @return the nearest nswe check ++ * Converts world Y to geodata Y. ++ * @param worldY ++ * @return int : Geo Y + */ +- public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) ++ public static int getGeoY(int worldY) + { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) +- { +- return true; +- } +- return region.checkNearestNswe(geoX, geoY, worldZ, nswe); ++ return (MathUtil.limit(worldY, World.MAP_MIN_Y, World.MAP_MAX_Y) - World.MAP_MIN_Y) >> 4; + } + + /** ++ * Converts geodata X to world X. + * @param geoX +- * @param geoY +- * @param worldZ +- * @param nswe +- * @return the nearest nswe anti-corner cut ++ * @return int : World X + */ +- public boolean checkNearestNsweAntiCornerCut(int geoX, int geoY, int worldZ, int nswe) ++ public static int getWorldX(int geoX) + { +- boolean can = true; +- if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) +- { +- can = checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_EAST) && checkNearestNswe(geoX + 1, geoY, worldZ, Cell.NSWE_NORTH); +- } +- if (can && ((nswe & Cell.NSWE_NORTH_WEST) == Cell.NSWE_NORTH_WEST)) +- { +- 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 = 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 = checkNearestNswe(geoX, geoY + 1, worldZ, Cell.NSWE_WEST) && checkNearestNswe(geoX - 1, geoY, worldZ, Cell.NSWE_SOUTH); +- } +- return can && checkNearestNswe(geoX, geoY, worldZ, nswe); ++ return (MathUtil.limit(geoX, 0, GeoStructure.GEO_CELLS_X) << 4) + World.MAP_MIN_X + 8; + } + + /** +- * @param geoX ++ * Converts geodata Y to world Y. + * @param geoY +- * @param worldZ +- * @return the nearest Z value ++ * @return int : World Y + */ +- public int getNearestZ(int geoX, int geoY, int worldZ) ++ public static int getWorldY(int geoY) + { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) +- { +- return worldZ; +- } +- return region.getNearestZ(geoX, geoY, worldZ); ++ return (MathUtil.limit(geoY, 0, GeoStructure.GEO_CELLS_Y) << 4) + World.MAP_MIN_Y + 8; + } + + /** +- * @param geoX +- * @param geoY +- * @param worldZ +- * @return the next lower Z value ++ * Returns block of geodata on given coordinates. ++ * @param geoX : Geodata X ++ * @param geoY : Geodata Y ++ * @return {@link ABlock} : Block of geodata. + */ +- public int getNextLowerZ(int geoX, int geoY, int worldZ) ++ private final ABlock getBlock(int geoX, int geoY) + { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) ++ final int x = geoX / GeoStructure.BLOCK_CELLS_X; ++ final int y = geoY / GeoStructure.BLOCK_CELLS_Y; ++ ++ // if x or y is out of array return null ++ if ((x > -1) && (y > -1) && (x < GeoStructure.GEO_BLOCKS_X) && (y < GeoStructure.GEO_BLOCKS_Y)) + { +- return worldZ; ++ return _blocks[x][y]; + } +- return region.getNextLowerZ(geoX, geoY, worldZ); ++ return null; + } + + /** +- * @param geoX +- * @param geoY +- * @param worldZ +- * @return the next higher Z value ++ * Check if geo coordinates has geo. ++ * @param geoX : Geodata X ++ * @param geoY : Geodata Y ++ * @return boolean : True, if given geo coordinates have geodata + */ +- public int getNextHigherZ(int geoX, int geoY, int worldZ) ++ public boolean hasGeoPos(int geoX, int geoY) + { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) ++ final ABlock block = getBlock(geoX, geoY); ++ if (block == null) // null block check + { +- return worldZ; ++ // TODO: Find when this can be null. (Bad geodata? Check World getRegion method.) ++ // LOGGER.warning("Could not find geodata block at " + getWorldX(geoX) + ", " + getWorldY(geoY) + "."); ++ return false; + } +- return region.getNextHigherZ(geoX, geoY, worldZ); ++ return block.hasGeoPos(); + } + + /** +- * Gets the Z height. +- * @param x the world x +- * @param y the world y +- * @param z the world z +- * @return the nearest Z height ++ * 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 int getHeight(int x, int y, int z) ++ public short getHeightNearest(int geoX, int geoY, int worldZ) + { +- return getNearestZ(getGeoX(x), getGeoY(y), z); ++ final ABlock block = getBlock(geoX, geoY); ++ return block != null ? block.getHeightNearest(geoX, geoY, worldZ) : (short) worldZ; + } + + /** +- * Gets the next lower Z height. +- * @param x the world x +- * @param y the world y +- * @param z the world z +- * @return the nearest Z height ++ * 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 int getLowerHeight(int x, int y, int z) ++ public byte getNsweNearest(int geoX, int geoY, int worldZ) + { +- return getNextLowerZ(getGeoX(x), getGeoY(y), z); ++ final ABlock block = getBlock(geoX, geoY); ++ return block != null ? block.getNsweNearest(geoX, geoY, worldZ) : (byte) 0xFF; + } + + /** +- * Gets the next higher Z height. +- * @param x the world x +- * @param y the world y +- * @param z the world z +- * @return the nearest Z height ++ * Check if world coordinates has geo. ++ * @param worldX : World X ++ * @param worldY : World Y ++ * @return boolean : True, if given world coordinates have geodata + */ +- public int getHigherHeight(int x, int y, int z) ++ public boolean hasGeo(int worldX, int worldY) + { +- return getNextHigherZ(getGeoX(x), getGeoY(y), z); ++ return hasGeoPos(getGeoX(worldX), getGeoY(worldY)); + } + + /** +- * @param worldX +- * @return the geo X ++ * 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 int getGeoX(int worldX) ++ public short getHeight(int worldX, int worldY, int worldZ) + { +- return (worldX - WORLD_MIN_X) / 16; ++ return getHeightNearest(getGeoX(worldX), getGeoY(worldY), worldZ); + } + +- /** +- * @param worldY +- * @return the geo Y +- */ +- public int getGeoY(int worldY) +- { +- return (worldY - WORLD_MIN_Y) / 16; +- } ++ // PATHFINDING + + /** +- * @param worldZ +- * @return the geo Z ++ * Check line of sight from {@link WorldObject} to {@link WorldObject}. ++ * @param origin : The origin object. ++ * @param target : The target object. ++ * @return {@code boolean} : True if origin can see target + */ +- public int getGeoZ(int worldZ) ++ public boolean canSeeTarget(WorldObject origin, WorldObject target) + { +- return (worldZ - WORLD_MIN_Z) / 16; +- } +- +- /** +- * @param geoX +- * @return the world X +- */ +- public int getWorldX(int geoX) +- { +- return (geoX * 16) + WORLD_MIN_X + 8; +- } +- +- /** +- * @param geoY +- * @return the world Y +- */ +- public int getWorldY(int geoY) +- { +- return (geoY * 16) + WORLD_MIN_Y + 8; +- } +- +- /** +- * @param geoZ +- * @return the world Z +- */ +- public int getWorldZ(int geoZ) +- { +- return (geoZ * 16) + WORLD_MIN_Z + 8; +- } +- +- /** +- * 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) +- { +- if (target.isDoor()) ++ if (target.isDoor() || target.isArtefact() || (target.isCreature() && ((Creature) target).isFlying())) + { +- // Can always see doors. + return true; + } +- return 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(), cha.getInstanceWorld(), worldPosition.getX(), worldPosition.getY(), worldPosition.getZ()); +- } +- +- /** +- * Can see target. Checks doors between. +- * @param x the x coordinate +- * @param y the y coordinate +- * @param z the z coordinate +- * @param world +- * @param tx the target's x coordinate +- * @param ty the target's y coordinate +- * @param tz the target's z coordinate +- * @param tworld the target's instanceId +- * @return +- */ +- public boolean canSeeTarget(int x, int y, int z, Instance world, int tx, int ty, int tz, Instance tworld) +- { +- return (world != tworld) ? false : canSeeTarget(x, y, z, world, tx, ty, tz); +- } +- +- /** +- * 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 +- * @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, Instance instance, int tx, int ty, int tz) +- { +- if (DoorData.getInstance().checkIfDoorsBetween(x, y, z, tx, ty, tz, instance, true)) ++ ++ // get origin and target world coordinates ++ final int ox = origin.getX(); ++ final int oy = origin.getY(); ++ final int oz = origin.getZ(); ++ final int tx = target.getX(); ++ final int ty = target.getY(); ++ final int tz = target.getZ(); ++ ++ if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld(), true)) + { + return false; + } +- return canSeeTarget(x, y, z, tx, ty, tz); +- } +- +- /** +- * @param prevX +- * @param prevY +- * @param prevGeoZ +- * @param curX +- * @param curY +- * @param nswe +- * @return the LOS Z value +- */ +- 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))) ++ if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld())) + { +- throw new RuntimeException("Multiple directions!"); ++ return false; + } ++ if (WarpedSpaceManager.getInstance().checkForWarpedSpace(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld())) ++ { ++ return false; ++ } + +- if (checkNearestNsweAntiCornerCut(prevX, prevY, prevGeoZ, nswe)) ++ // get origin and check existing geo coordinates ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) + { +- return getNearestZ(curX, curY, prevGeoZ); ++ return true; + } + +- return getNextHigherZ(curX, curY, prevGeoZ); ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coordinates ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) ++ { ++ return true; ++ } ++ ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // origin and target coordinates are same ++ if ((gox == gtx) && (goy == gty)) ++ { ++ return goz == gtz; ++ } ++ ++ // get origin and target height, real height = collision height * 2 ++ double oheight = 0; ++ if (origin.isCreature()) ++ { ++ oheight = ((Creature) origin).getCollisionHeight() * 2; ++ } ++ ++ double theight = 0; ++ if (target.isCreature()) ++ { ++ theight = ((Creature) target).getCollisionHeight() * 2; ++ } ++ ++ // perform geodata check ++ return checkSee(gox, goy, goz, oheight, gtx, gty, gtz, theight, origin.getInstanceWorld()); + } + + /** +- * Can see target. Does not check doors between. +- * @param xValue the x coordinate +- * @param yValue the y coordinate +- * @param zValue the z coordinate +- * @param txValue the target's x coordinate +- * @param tyValue the target's y coordinate +- * @param tzValue the target's z coordinate +- * @return {@code true} if there is line of sight between the given coordinate sets, {@code false} otherwise ++ * Check line of sight from {@link WorldObject} to {@link Location}. ++ * @param origin : The origin object. ++ * @param position : The target position. ++ * @return {@code boolean} : True if object can see position + */ +- public boolean canSeeTarget(int xValue, int yValue, int zValue, int txValue, int tyValue, int tzValue) ++ public boolean canSeeTarget(WorldObject origin, Location position) + { +- int x = xValue; +- int y = yValue; +- int tx = txValue; +- int ty = tyValue; ++ // get origin and target world coordinates ++ final int ox = origin.getX(); ++ final int oy = origin.getY(); ++ final int oz = origin.getZ(); ++ final int tx = position.getX(); ++ final int ty = position.getY(); ++ final int tz = position.getZ(); + +- int geoX = getGeoX(x); +- int geoY = getGeoY(y); +- int tGeoX = getGeoX(tx); +- int tGeoY = getGeoY(ty); ++ if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld(), true)) ++ { ++ return false; ++ } ++ if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld())) ++ { ++ return false; ++ } ++ if (WarpedSpaceManager.getInstance().checkForWarpedSpace(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld())) ++ { ++ return false; ++ } + +- int z = getNearestZ(geoX, geoY, zValue); +- int tz = getNearestZ(tGeoX, tGeoY, tzValue); ++ // get origin and check existing geo coordinates ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) ++ { ++ return true; ++ } + +- // Fastpath. +- if ((geoX == tGeoX) && (geoY == tGeoY)) ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coordinates ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) + { +- if (hasGeoPos(tGeoX, tGeoY)) +- { +- return z == tz; +- } + return true; + } + +- if (tz > z) ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // origin and target coordinates are same ++ if ((gox == gtx) && (goy == gty)) + { +- int tmp = tx; +- tx = x; +- x = tmp; +- +- tmp = ty; +- ty = y; +- y = tmp; +- +- tmp = tz; +- tz = z; +- z = tmp; +- +- tmp = tGeoX; +- tGeoX = geoX; +- geoX = tmp; +- +- tmp = tGeoY; +- tGeoY = geoY; +- geoY = tmp; ++ return goz == gtz; + } + +- final LinePointIterator3D pointIter = new LinePointIterator3D(geoX, geoY, z, tGeoX, tGeoY, tz); +- // 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()) ++ // get origin and target height, real height = collision height * 2 ++ double oheight = 0; ++ if (origin.isCreature()) + { +- final int curX = pointIter.x(); +- final int curY = pointIter.y(); ++ oheight = ((Creature) origin).getTemplate().getCollisionHeight(); ++ } ++ ++ // perform geodata check ++ return checkSee(gox, goy, goz, oheight, gtx, gty, gtz, 0, origin.getInstanceWorld()); ++ } ++ ++ /** ++ * Simple check for origin to target visibility. ++ * @param goxValue : origin X geodata coordinate ++ * @param goyValue : origin Y geodata coordinate ++ * @param gozValue : origin Z geodata coordinate ++ * @param oheight : origin height (if instance of {@link Character}) ++ * @param gtxValue : target X geodata coordinate ++ * @param gtyValue : target Y geodata coordinate ++ * @param gtzValue : target Z geodata coordinate ++ * @param theight : target height (if instance of {@link Character}) ++ * @param instance ++ * @return {@code boolean} : True, when target can be seen. ++ */ ++ private final boolean checkSee(int goxValue, int goyValue, int gozValue, double oheight, int gtxValue, int gtyValue, int gtzValue, double theight, Instance instance) ++ { ++ int goz = gozValue; ++ int gtz = gtzValue; ++ int gox = goxValue; ++ int goy = goyValue; ++ int gtx = gtxValue; ++ int gty = gtyValue; ++ ++ // get line of sight Z coordinates ++ double losoz = goz + ((oheight * Config.PART_OF_CHARACTER_HEIGHT) / 100); ++ double lostz = gtz + ((theight * Config.PART_OF_CHARACTER_HEIGHT) / 100); ++ ++ // get X delta and signum ++ final int dx = Math.abs(gtx - gox); ++ final int sx = gox < gtx ? 1 : -1; ++ final byte dirox = sx > 0 ? GeoStructure.CELL_FLAG_E : GeoStructure.CELL_FLAG_W; ++ final byte dirtx = sx > 0 ? GeoStructure.CELL_FLAG_W : GeoStructure.CELL_FLAG_E; ++ ++ // get Y delta and signum ++ final int dy = Math.abs(gty - goy); ++ final int sy = goy < gty ? 1 : -1; ++ final byte diroy = sy > 0 ? GeoStructure.CELL_FLAG_S : GeoStructure.CELL_FLAG_N; ++ final byte dirty = sy > 0 ? GeoStructure.CELL_FLAG_N : GeoStructure.CELL_FLAG_S; ++ ++ // get Z delta ++ final int dm = Math.max(dx, dy); ++ final double dz = (lostz - losoz) / dm; ++ ++ // get direction flag for diagonal movement ++ final byte diroxy = getDirXY(dirox, diroy); ++ final byte dirtxy = getDirXY(dirtx, dirty); ++ ++ // delta, determines axis to move on (+..X axis, -..Y axis) ++ int d = dx - dy; ++ ++ // NSWE direction of movement ++ byte diro; ++ byte dirt; ++ ++ // initialize node values ++ int nox = gox; ++ int noy = goy; ++ int ntx = gtx; ++ int nty = gty; ++ byte nsweo = getNsweNearest(gox, goy, goz); ++ byte nswet = getNsweNearest(gtx, gty, gtz); ++ ++ // loop ++ ABlock block; ++ int index; ++ for (int i = 0; i < ((dm + 1) / 2); i++) ++ { ++ // reset direction flag ++ diro = 0; ++ dirt = 0; + +- if ((curX == prevX) && (curY == prevY)) ++ // calculate next point coordinates ++ final int e2 = 2 * d; ++ if ((e2 > -dy) && (e2 < dx)) + { +- continue; ++ // calculate next point XY coordinates ++ d -= dy; ++ d += dx; ++ nox += sx; ++ ntx -= sx; ++ noy += sy; ++ nty -= sy; ++ diro |= diroxy; ++ dirt |= dirtxy; + } ++ else if (e2 > -dy) ++ { ++ // calculate next point X coordinate ++ d -= dy; ++ nox += sx; ++ ntx -= sx; ++ diro |= dirox; ++ dirt |= dirtx; ++ } ++ else if (e2 < dx) ++ { ++ // calculate next point Y coordinate ++ d += dx; ++ noy += sy; ++ nty -= sy; ++ diro |= diroy; ++ dirt |= dirty; ++ } + +- final int beeCurZ = pointIter.z(); +- int curGeoZ = prevGeoZ; +- +- // Check if the position has geodata. +- if (hasGeoPos(curX, curY)) + { +- final int beeCurGeoZ = getNearestZ(curX, curY, beeCurZ); +- final int nswe = GeoUtils.computeNswe(prevX, prevY, curX, curY); // .computeDirection(prevX, prevY, curX, curY); +- curGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, curX, curY, nswe); +- int maxHeight; +- if (ptIndex < ELEVATED_SEE_OVER_DISTANCE) ++ // get block of the next cell ++ block = getBlock(nox, noy); ++ ++ // get index of particular layer, based on movement conditions ++ if ((nsweo & diro) == 0) + { +- maxHeight = z + MAX_SEE_OVER_HEIGHT; ++ index = block.getIndexAbove(nox, noy, goz - GeoStructure.CELL_IGNORE_HEIGHT); + } + else + { +- maxHeight = beeCurZ + MAX_SEE_OVER_HEIGHT; ++ index = block.getIndexBelow(nox, noy, goz + GeoStructure.CELL_IGNORE_HEIGHT); + } + +- boolean canSeeThrough = false; +- if ((curGeoZ <= maxHeight) && (curGeoZ <= beeCurGeoZ)) ++ // layer does not exist, return ++ if (index == -1) + { +- if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) +- { +- 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 +- { +- canSeeThrough = true; +- } ++ return false; + } + +- if (!canSeeThrough) ++ // get layer and next line of sight Z coordinate ++ goz = block.getHeight(index); ++ losoz += dz; ++ ++ // perform line of sight check, return when fails ++ if ((goz - losoz) > Config.MAX_OBSTACLE_HEIGHT) + { + return false; + } ++ ++ // get layer nswe ++ nsweo = block.getNswe(index); + } ++ { ++ // get block of the next cell ++ block = getBlock(ntx, nty); ++ ++ // get index of particular layer, based on movement conditions ++ if ((nswet & dirt) == 0) ++ { ++ index = block.getIndexAbove(ntx, nty, gtz - GeoStructure.CELL_IGNORE_HEIGHT); ++ } ++ else ++ { ++ index = block.getIndexBelow(ntx, nty, gtz + GeoStructure.CELL_IGNORE_HEIGHT); ++ } ++ ++ // layer does not exist, return ++ if (index == -1) ++ { ++ return false; ++ } ++ ++ // get layer and next line of sight Z coordinate ++ gtz = block.getHeight(index); ++ lostz -= dz; ++ ++ // perform line of sight check, return when fails ++ if ((gtz - lostz) > Config.MAX_OBSTACLE_HEIGHT) ++ { ++ return false; ++ } ++ ++ // get layer nswe ++ nswet = block.getNswe(index); ++ } + +- prevX = curX; +- prevY = curY; +- prevGeoZ = curGeoZ; +- ++ptIndex; ++ // update coords ++ gox = nox; ++ goy = noy; ++ gtx = ntx; ++ gty = nty; + } + +- return true; ++ // when iteration is completed, compare final Z coordinates ++ return Math.abs(goz - gtz) < (GeoStructure.CELL_HEIGHT * 4); + } + + /** +- * Move check. +- * @param x the x coordinate +- * @param y the y coordinate +- * @param zValue the z coordinate +- * @param tx the target's x coordinate +- * @param ty the target's y coordinate +- * @param tzValue the target's z coordinate +- * @param instance the instance +- * @return the last Location (x,y,z) where player can walk - just before wall ++ * Check movement from coordinates to 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 {code boolean} : True if target coordinates are reachable from origin coordinates + */ +- public Location canMoveToTargetLoc(int x, int y, int zValue, int tx, int ty, int tzValue, Instance instance) ++ public boolean canMoveToTarget(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + { +- final int geoX = getGeoX(x); +- final int geoY = getGeoY(y); +- final int z = getNearestZ(geoX, geoY, zValue); +- final int tGeoX = getGeoX(tx); +- final int tGeoY = getGeoY(ty); +- final int tz = getNearestZ(tGeoX, tGeoY, tzValue); ++ // get origin and check existing geo coordinates ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) ++ { ++ return true; ++ } + +- if (DoorData.getInstance().checkIfDoorsBetween(x, y, z, tx, ty, tz, instance, false)) ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coordinates ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) + { +- return new Location(x, y, getHeight(x, y, z)); ++ return true; + } +- if (FenceData.getInstance().checkIfFenceBetween(x, y, z, tx, ty, tz, instance)) ++ ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // target coordinates reached ++ if ((gox == gtx) && (goy == gty) && (goz == gtz)) + { +- return new Location(x, y, getHeight(x, y, z)); ++ return true; + } +- if (WarpedSpaceManager.getInstance().checkForWarpedSpace(x, y, z, tx, ty, tz, instance)) ++ ++ // perform geodata check ++ final GeoLocation loc = checkMove(gox, goy, goz, gtx, gty, gtz, instance); ++ return (loc.getGeoX() == gtx) && (loc.getGeoY() == gty); ++ } ++ ++ /** ++ * Check movement from origin to target. Returns last available point in the checked path. ++ * @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 {@link Location} : Last point where object can walk (just before wall) ++ */ ++ public Location canMoveToTargetLoc(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) ++ { ++ // Mobius: Double check for doors before normal checkMove to avoid exploiting key movement. ++ if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, instance, false)) + { +- return new Location(x, y, getHeight(x, y, z)); ++ return new Location(ox, oy, oz); + } ++ if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, instance)) ++ { ++ return new Location(ox, oy, oz); ++ } + +- 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 = z; ++ // get origin and check existing geo coordinates ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) ++ { ++ return new Location(tx, ty, tz); ++ } + +- while (pointIter.next()) ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coordinates ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) + { +- final int curX = pointIter.x(); +- final int curY = pointIter.y(); +- final int curZ = getNearestZ(curX, curY, prevZ); +- +- if (hasGeoPos(prevX, prevY)) +- { +- final int nswe = GeoUtils.computeNswe(prevX, prevY, curX, curY); +- if (!checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, nswe)) +- { +- // Can't move, return previous location. +- return new Location(getWorldX(prevX), getWorldY(prevY), prevZ); +- } +- } +- +- prevX = curX; +- prevY = curY; +- prevZ = curZ; ++ return new Location(tx, ty, tz); + } + +- if (hasGeoPos(prevX, prevY) && (prevZ != tz)) ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // target coordinates reached ++ if ((gox == gtx) && (goy == gty) && (goz == gtz)) + { +- // Different floors, return start location. +- return new Location(x, y, z); ++ return new Location(tx, ty, tz); + } + +- return new Location(tx, ty, tz); ++ // perform geodata check ++ return checkMove(gox, goy, goz, gtx, gty, gtz, instance); + } + + /** +- * 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 fromZvalue 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 toZvalue 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 ++ * With this method you can check if a position is visible or can be reached by beeline movement.
++ * 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 gox : origin X geodata coordinate ++ * @param goy : origin Y geodata coordinate ++ * @param goz : origin Z geodata coordinate ++ * @param gtx : target X geodata coordinate ++ * @param gty : target Y geodata coordinate ++ * @param gtz : target Z geodata coordinate ++ * @param instance ++ * @return {@link GeoLocation} : The last allowed point of movement. + */ +- public boolean canMoveToTarget(int fromX, int fromY, int fromZvalue, int toX, int toY, int toZvalue, Instance instance) ++ protected final GeoLocation checkMove(int gox, int goy, int goz, int gtx, int gty, int gtz, Instance instance) + { +- final int geoX = getGeoX(fromX); +- final int geoY = getGeoY(fromY); +- final int fromZ = getNearestZ(geoX, geoY, fromZvalue); +- final int tGeoX = getGeoX(toX); +- final int tGeoY = getGeoY(toY); +- final int toZ = getNearestZ(tGeoX, tGeoY, toZvalue); +- +- if (DoorData.getInstance().checkIfDoorsBetween(fromX, fromY, fromZ, toX, toY, toZ, instance, false)) ++ if (DoorData.getInstance().checkIfDoorsBetween(gox, goy, goz, gtx, gty, gtz, instance, false)) + { +- return false; ++ return new GeoLocation(gox, goy, goz); + } +- if (FenceData.getInstance().checkIfFenceBetween(fromX, fromY, fromZ, toX, toY, toZ, instance)) ++ if (FenceData.getInstance().checkIfFenceBetween(gox, goy, goz, gtx, gty, gtz, instance)) + { +- return false; ++ return new GeoLocation(gox, goy, goz); + } +- if (WarpedSpaceManager.getInstance().checkForWarpedSpace(fromX, fromY, fromZ, toX, toY, toZ, instance)) ++ if (WarpedSpaceManager.getInstance().checkForWarpedSpace(gox, goy, goz, gtx, gty, gtz, instance)) + { +- return false; ++ return new GeoLocation(gox, goy, goz); + } + +- 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 = fromZ; ++ // get X delta, signum and direction flag ++ final int dx = Math.abs(gtx - gox); ++ final int sx = gox < gtx ? 1 : -1; ++ final byte dirX = sx > 0 ? GeoStructure.CELL_FLAG_E : GeoStructure.CELL_FLAG_W; + +- while (pointIter.next()) ++ // get Y delta, signum and direction flag ++ final int dy = Math.abs(gty - goy); ++ final int sy = goy < gty ? 1 : -1; ++ final byte dirY = sy > 0 ? GeoStructure.CELL_FLAG_S : GeoStructure.CELL_FLAG_N; ++ ++ // get direction flag for diagonal movement ++ final byte dirXY = getDirXY(dirX, dirY); ++ ++ // delta, determines axis to move on (+..X axis, -..Y axis) ++ int d = dx - dy; ++ ++ // NSWE direction of movement ++ byte direction; ++ ++ // load pointer coordinates ++ int gpx = gox; ++ int gpy = goy; ++ int gpz = goz; ++ ++ // load next pointer ++ int nx = gpx; ++ int ny = gpy; ++ ++ // loop ++ int count = 0; ++ while (count++ < Config.MAX_ITERATIONS) + { +- final int curX = pointIter.x(); +- final int curY = pointIter.y(); +- final int curZ = getNearestZ(curX, curY, prevZ); ++ direction = 0; + +- if (hasGeoPos(prevX, prevY)) ++ // calculate next point coordinates ++ final int e2 = 2 * d; ++ if ((e2 > -dy) && (e2 < dx)) + { +- final int nswe = GeoUtils.computeNswe(prevX, prevY, curX, curY); +- if (!checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, nswe)) ++ d -= dy; ++ d += dx; ++ nx += sx; ++ ny += sy; ++ direction |= dirXY; ++ } ++ else if (e2 > -dy) ++ { ++ d -= dy; ++ nx += sx; ++ direction |= dirX; ++ } ++ else if (e2 < dx) ++ { ++ d += dx; ++ ny += sy; ++ direction |= dirY; ++ } ++ ++ // obstacle found, return ++ if ((getNsweNearest(gpx, gpy, gpz) & direction) == 0) ++ { ++ return new GeoLocation(gpx, gpy, gpz); ++ } ++ ++ // update pointer coordinates ++ gpx = nx; ++ gpy = ny; ++ gpz = getHeightNearest(nx, ny, gpz); ++ ++ // target coordinates reached ++ if ((gpx == gtx) && (gpy == gty)) ++ { ++ if (gpz == gtz) + { +- return false; ++ // path found, Z coordinates are okay, return target point ++ return new GeoLocation(gtx, gty, gtz); + } ++ ++ // path found, Z coordinates are not okay, return last good point ++ return new GeoLocation(gpx, gpy, gpz); + } ++ } ++ ++ return new GeoLocation(gox, goy, goz); ++ } ++ ++ /** ++ * Returns diagonal NSWE flag format of combined two NSWE flags. ++ * @param dirX : X direction NSWE flag ++ * @param dirY : Y direction NSWE flag ++ * @return byte : NSWE flag of combined direction ++ */ ++ private static byte getDirXY(byte dirX, byte dirY) ++ { ++ // check axis directions ++ if (dirY == GeoStructure.CELL_FLAG_N) ++ { ++ if (dirX == GeoStructure.CELL_FLAG_W) ++ { ++ return GeoStructure.CELL_FLAG_NW; ++ } + +- prevX = curX; +- prevY = curY; +- prevZ = curZ; ++ return GeoStructure.CELL_FLAG_NE; + } + +- if (hasGeoPos(prevX, prevY) && (prevZ != toZ)) ++ if (dirX == GeoStructure.CELL_FLAG_W) + { +- // Different floors. +- return false; ++ return GeoStructure.CELL_FLAG_SW; + } + +- return true; ++ return GeoStructure.CELL_FLAG_SE; + } + ++ /** ++ * 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 ++ */ ++ public List findPath(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) ++ { ++ return null; ++ } ++ ++ /** ++ * Returns the instance of the {@link GeoEngine}. ++ * @return {@link GeoEngine} : The instance. ++ */ + public static GeoEngine getInstance() + { + return SingletonHolder.INSTANCE; +@@ -752,6 +947,6 @@ + + private static class SingletonHolder + { +- protected static final GeoEngine INSTANCE = new GeoEngine(); ++ protected static final GeoEngine INSTANCE = Config.PATHFINDING ? new GeoEnginePathfinding() : new GeoEngine(); + } +-} ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/GeoEnginePathfinding.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/GeoEnginePathfinding.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/GeoEnginePathfinding.java (working copy) +@@ -20,88 +20,84 @@ + import java.util.LinkedList; + import java.util.List; + import java.util.ListIterator; +-import java.util.logging.Level; +-import java.util.logging.Logger; + + import org.l2jmobius.Config; +-import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; +-import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +-import org.l2jmobius.gameserver.geoengine.pathfinding.CellNode; +-import org.l2jmobius.gameserver.geoengine.pathfinding.CellNodeBuffer; +-import org.l2jmobius.gameserver.geoengine.pathfinding.NodeLoc; +-import org.l2jmobius.gameserver.model.World; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoLocation; ++import org.l2jmobius.gameserver.geoengine.pathfinding.Node; ++import org.l2jmobius.gameserver.geoengine.pathfinding.NodeBuffer; ++import org.l2jmobius.gameserver.model.Location; + import org.l2jmobius.gameserver.model.instancezone.Instance; + + /** +- * @author -Nemesiss- ++ * @author Hasha + */ +-public class GeoEnginePathfinding ++final class GeoEnginePathfinding extends GeoEngine + { +- private static final Logger LOGGER = Logger.getLogger(GeoEnginePathfinding.class.getName()); ++ // pre-allocated buffers ++ private final BufferHolder[] _buffers; + +- private BufferInfo[] _buffers; +- + protected GeoEnginePathfinding() + { +- try ++ super(); ++ ++ final String[] array = Config.PATHFIND_BUFFERS.split(";"); ++ _buffers = new BufferHolder[array.length]; ++ int count = 0; ++ for (int i = 0; i < array.length; i++) + { +- final String[] array = Config.PATHFIND_BUFFERS.split(";"); ++ final String buf = array[i]; ++ final String[] args = buf.split("x"); + +- _buffers = new BufferInfo[array.length]; +- +- String buf; +- String[] args; +- for (int i = 0; i < array.length; i++) ++ try + { +- buf = array[i]; +- args = buf.split("x"); +- if (args.length != 2) +- { +- throw new Exception("Invalid buffer definition: " + buf); +- } +- +- _buffers[i] = new BufferInfo(Integer.parseInt(args[0]), Integer.parseInt(args[1])); ++ final int size = Integer.parseInt(args[1]); ++ count += size; ++ _buffers[i] = new BufferHolder(Integer.parseInt(args[0]), size); + } ++ catch (Exception e) ++ { ++ LOGGER.warning("GeoEnginePathfinding: Can not load buffer setting: " + buf); ++ } + } +- catch (Exception e) +- { +- LOGGER.log(Level.WARNING, "CellPathFinding: Problem during buffer init: " + e.getMessage(), e); +- throw new Error("CellPathFinding: load aborted"); +- } ++ ++ LOGGER.info("GeoEnginePathfinding: Loaded " + count + " node buffers."); + } + +- public boolean pathNodesExist(short regionoffset) ++ @Override ++ public List findPath(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + { +- return false; +- } +- +- public List findPath(int x, int y, int z, int tx, int ty, int tz, Instance instance) +- { +- final int gx = GeoEngine.getInstance().getGeoX(x); +- final int gy = GeoEngine.getInstance().getGeoY(y); +- if (!GeoEngine.getInstance().hasGeo(x, y)) ++ // get origin and check existing geo coords ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) + { + 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)) ++ ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coords ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) + { + 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)))); ++ ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // Prepare buffer for pathfinding calculations ++ final NodeBuffer buffer = getBuffer(64 + (2 * Math.max(Math.abs(gox - gtx), Math.abs(goy - gty)))); + if (buffer == null) + { + return null; + } + +- List path = null; ++ // find path ++ List path = null; + try + { +- final CellNode result = buffer.findPath(gx, gy, gz, gtx, gty, gtz); +- ++ final Node result = buffer.findPath(gox, goy, goz, gtx, gty, gtz); + if (result == null) + { + return null; +@@ -125,33 +121,45 @@ + return path; + } + +- int currentX, currentY, currentZ; +- ListIterator middlePoint; ++ // get path list iterator ++ final ListIterator point = path.listIterator(); + +- middlePoint = path.listIterator(); +- currentX = x; +- currentY = y; +- currentZ = z; ++ // get node A (origin) ++ int nodeAx = gox; ++ int nodeAy = goy; ++ short nodeAz = goz; + +- while (middlePoint.hasNext()) ++ // get node B ++ GeoLocation nodeB = (GeoLocation) point.next(); ++ ++ // iterate thought the path to optimize it ++ int count = 0; ++ while (point.hasNext() && (count++ < Config.MAX_ITERATIONS)) + { +- final AbstractNodeLoc locMiddle = middlePoint.next(); +- if (!middlePoint.hasNext()) +- { +- break; +- } ++ // get node C ++ final GeoLocation nodeC = (GeoLocation) path.get(point.nextIndex()); + +- final AbstractNodeLoc locEnd = path.get(middlePoint.nextIndex()); +- if (GeoEngine.getInstance().canMoveToTarget(currentX, currentY, currentZ, locEnd.getX(), locEnd.getY(), locEnd.getZ(), instance)) ++ // check movement from node A to node C ++ final GeoLocation loc = checkMove(nodeAx, nodeAy, nodeAz, nodeC.getGeoX(), nodeC.getGeoY(), nodeC.getZ(), instance); ++ if ((loc.getGeoX() == nodeC.getGeoX()) && (loc.getGeoY() == nodeC.getGeoY())) + { +- middlePoint.remove(); ++ // can move from node A to node C ++ ++ // remove node B ++ point.remove(); + } + else + { +- currentX = locMiddle.getX(); +- currentY = locMiddle.getY(); +- currentZ = locMiddle.getZ(); ++ // can not move from node A to node C ++ ++ // set node A (node B is part of path, update A coordinates) ++ nodeAx = nodeB.getGeoX(); ++ nodeAy = nodeB.getGeoY(); ++ nodeAz = (short) nodeB.getZ(); + } ++ ++ // set node B ++ nodeB = (GeoLocation) point.next(); + } + + return path; +@@ -158,144 +166,102 @@ + } + + /** +- * Convert geodata position to pathnode position +- * @param geo_pos +- * @return pathnode position ++ * Create list of node locations as result of calculated buffer node tree. ++ * @param node : the entry point ++ * @return List : list of node location + */ +- public short getNodePos(int geo_pos) ++ private static List constructPath(Node node) + { +- return (short) (geo_pos >> 3); // OK? +- } +- +- /** +- * Convert node position to pathnode block position +- * @param node_pos +- * @return pathnode block position (0...255) +- */ +- public short getNodeBlock(int node_pos) +- { +- return (short) (node_pos % 256); +- } +- +- public byte getRegionX(int node_pos) +- { +- return (byte) ((node_pos >> 8) + World.TILE_X_MIN); +- } +- +- public byte getRegionY(int node_pos) +- { +- return (byte) ((node_pos >> 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 node_x rx +- * @return +- */ +- public int calculateWorldX(short node_x) +- { +- return World.MAP_MIN_X + (node_x * 128) + 48; +- } +- +- /** +- * Convert pathnode y to World y position +- * @param node_y +- * @return +- */ +- public int calculateWorldY(short node_y) +- { +- return World.MAP_MIN_Y + (node_y * 128) + 48; +- } +- +- private List constructPath(AbstractNode nodeValue) +- { +- final LinkedList path = new LinkedList<>(); +- int previousDirectionX = Integer.MIN_VALUE; +- int previousDirectionY = Integer.MIN_VALUE; +- int directionX, directionY; ++ // create empty list ++ final LinkedList list = new LinkedList<>(); + +- AbstractNode node = nodeValue; +- while (node.getParent() != null) ++ // set direction X/Y ++ int dx = 0; ++ int dy = 0; ++ ++ // get target parent ++ Node target = node; ++ Node parent = target.getParent(); ++ ++ // while parent exists ++ int count = 0; ++ while ((parent != null) && (count++ < Config.MAX_ITERATIONS)) + { +- directionX = node.getLoc().getNodeX() - node.getParent().getLoc().getNodeX(); +- directionY = node.getLoc().getNodeY() - node.getParent().getLoc().getNodeY(); ++ // get parent <> target direction X/Y ++ final int nx = parent.getLoc().getGeoX() - target.getLoc().getGeoX(); ++ final int ny = parent.getLoc().getGeoY() - target.getLoc().getGeoY(); + +- // only add a new route point if moving direction changes +- if ((directionX != previousDirectionX) || (directionY != previousDirectionY)) ++ // direction has changed? ++ if ((dx != nx) || (dy != ny)) + { +- previousDirectionX = directionX; +- previousDirectionY = directionY; ++ // add node to the beginning of the list ++ list.addFirst(target.getLoc()); + +- path.addFirst(node.getLoc()); +- node.setLoc(null); ++ // update direction X/Y ++ dx = nx; ++ dy = ny; + } + +- node = node.getParent(); ++ // move to next node, set target and get its parent ++ target = parent; ++ parent = target.getParent(); + } + +- return path; ++ // return list ++ return list; + } + +- private CellNodeBuffer alloc(int size) ++ /** ++ * Provides optimize selection of the buffer. When all pre-initialized buffer are locked, creates new buffer. ++ * @param size : pre-calculated minimal required size ++ * @return NodeBuffer : buffer ++ */ ++ private final NodeBuffer getBuffer(int size) + { +- CellNodeBuffer current = null; +- for (BufferInfo i : _buffers) ++ NodeBuffer current = null; ++ for (BufferHolder holder : _buffers) + { +- if (i.mapSize >= size) ++ // Find proper size of buffer ++ if (holder._size < size) + { +- for (CellNodeBuffer buf : i.buffer) ++ continue; ++ } ++ ++ // Find unlocked NodeBuffer ++ for (NodeBuffer buffer : holder._buffer) ++ { ++ if (!buffer.isLocked()) + { +- if (buf.lock()) +- { +- current = buf; +- break; +- } ++ continue; + } +- if (current != null) +- { +- break; +- } + +- // not found, allocate temporary buffer +- current = new CellNodeBuffer(i.mapSize); +- current.lock(); +- if (i.buffer.size() < i.count) +- { +- i.buffer.add(current); +- break; +- } ++ return buffer; + } ++ ++ // NodeBuffer not found, allocate temporary buffer ++ current = new NodeBuffer(holder._size); ++ current.isLocked(); + } + + return current; + } + +- private static final class BufferInfo ++ /** ++ * NodeBuffer container with specified size and count of separate buffers. ++ */ ++ private static final class BufferHolder + { +- final int mapSize; +- final int count; +- ArrayList buffer; ++ final int _size; ++ List _buffer; + +- public BufferInfo(int size, int cnt) ++ public BufferHolder(int size, int count) + { +- mapSize = size; +- count = cnt; +- buffer = new ArrayList<>(count); ++ _size = size; ++ _buffer = new ArrayList<>(count); ++ for (int i = 0; i < count; i++) ++ { ++ _buffer.add(new NodeBuffer(size)); ++ } + } + } +- +- public static GeoEnginePathfinding getInstance() +- { +- return SingletonHolder.INSTANCE; +- } +- +- private static class SingletonHolder +- { +- protected static final GeoEnginePathfinding INSTANCE = new GeoEnginePathfinding(); +- } +-} ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java (working copy) +@@ -0,0 +1,197 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++import java.io.IOException; ++ ++/** ++ * @author Hasha ++ */ ++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 height of cell, which is closest to given coordinates.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @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 getHeightNearestOriginal(int geoX, int geoY, int worldZ); ++ ++ /** ++ * Returns the height of cell, which is first above 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, above given coordinates. ++ */ ++ public abstract short getHeightAbove(int geoX, int geoY, int worldZ); ++ ++ /** ++ * Returns the height of cell, which is first below 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, below given coordinates. ++ */ ++ public abstract short getHeightBelow(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 the NSWE flag byte of cell, which is closest to given coordinates.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @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 getNsweNearestOriginal(int geoX, int geoY, int worldZ); ++ ++ /** ++ * Returns the NSWE flag byte of cell, which is first above 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 getNsweAbove(int geoX, int geoY, int worldZ); ++ ++ /** ++ * Returns the NSWE flag byte of cell, which is first below 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 getNsweBelow(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 above given coordinates.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @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 getIndexAboveOriginal(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 index to data of the cell, which is first layer below given coordinates.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @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 getIndexBelowOriginal(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 height of cell given by cell index.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @param index : Index of the cell. ++ * @return short : Cell geodata Z coordinate, below given coordinates. ++ */ ++ public abstract short getHeightOriginal(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); ++ ++ /** ++ * Returns the NSWE flag byte of cell given by cell index.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @param index : Index of the cell. ++ * @return short : Cell geodata Z coordinate, below given coordinates. ++ */ ++ public abstract byte getNsweOriginal(int index); ++ ++ /** ++ * Sets the NSWE flag byte of cell given by cell index. ++ * @param index : Index of the cell. ++ * @param nswe : New NSWE flag byte. ++ */ ++ public abstract void setNswe(int index, byte nswe); ++ ++ /** ++ * Saves the block in L2D format to {@link BufferedOutputStream}. Used only for L2D geodata conversion. ++ * @param stream : The stream. ++ * @throws IOException : Can't save the block to steam. ++ */ ++ public abstract void saveBlock(BufferedOutputStream stream) throws IOException; ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java (working copy) +@@ -0,0 +1,252 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++import java.io.IOException; ++import java.nio.ByteBuffer; ++ ++/** ++ * @author Hasha ++ */ ++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. ++ * @param format : GeoFormat specifying format of loaded data. ++ */ ++ public BlockComplex(ByteBuffer bb, GeoFormat format) ++ { ++ // initialize buffer ++ _buffer = new byte[GeoStructure.BLOCK_CELLS * 3]; ++ ++ // load data ++ for (int i = 0; i < GeoStructure.BLOCK_CELLS; i++) ++ { ++ if (format != GeoFormat.L2D) ++ { ++ // 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); ++ } ++ else ++ { ++ // get nswe ++ final byte nswe = bb.get(); ++ _buffer[i * 3] = nswe; ++ ++ // get height ++ final short height = bb.getShort(); ++ _buffer[(i * 3) + 1] = (byte) (height & 0x00FF); ++ _buffer[(i * 3) + 2] = (byte) (height >> 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 short getHeightNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getHeightNearest(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeightAbove(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 short height = (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ ++ // check and return height ++ return height > worldZ ? height : Short.MIN_VALUE; ++ } ++ ++ @Override ++ public short getHeightBelow(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 short height = (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ ++ // check and return height ++ return height < worldZ ? height : Short.MAX_VALUE; ++ } ++ ++ @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 byte getNsweNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getNsweNearest(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public byte getNsweAbove(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 ? _buffer[index] : 0; ++ } ++ ++ @Override ++ public byte getNsweBelow(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 ? _buffer[index] : 0; ++ } ++ ++ @Override ++ public 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 getIndexAboveOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexAbove(geoX, geoY, worldZ); ++ } ++ ++ @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 int getIndexBelowOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexBelow(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeight(int index) ++ { ++ return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ } ++ ++ @Override ++ public short getHeightOriginal(int index) ++ { ++ return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ } ++ ++ @Override ++ public byte getNswe(int index) ++ { ++ return _buffer[index]; ++ } ++ ++ @Override ++ public byte getNsweOriginal(int index) ++ { ++ return _buffer[index]; ++ } ++ ++ @Override ++ public void setNswe(int index, byte nswe) ++ { ++ _buffer[index] = nswe; ++ } ++ ++ @Override ++ public void saveBlock(BufferedOutputStream stream) throws IOException ++ { ++ // write block type ++ stream.write(GeoStructure.TYPE_COMPLEX_L2D); ++ ++ // write block data ++ stream.write(_buffer, 0, GeoStructure.BLOCK_CELLS * 3); ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java (working copy) +@@ -0,0 +1,176 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++import java.io.IOException; ++import java.nio.ByteBuffer; ++ ++/** ++ * @author Hasha ++ */ ++public class BlockFlat extends ABlock ++{ ++ protected final short _height; ++ protected byte _nswe; ++ ++ /** ++ * Creates FlatBlock. ++ * @param bb : Input byte buffer. ++ * @param format : GeoFormat specifying format of loaded data. ++ */ ++ public BlockFlat(ByteBuffer bb, GeoFormat format) ++ { ++ _height = bb.getShort(); ++ _nswe = format != GeoFormat.L2D ? 0x0F : (byte) (0xFF); ++ if (format == GeoFormat.L2OFF) ++ { ++ bb.getShort(); ++ } ++ } ++ ++ @Override ++ public boolean hasGeoPos() ++ { ++ return true; ++ } ++ ++ @Override ++ public short getHeightNearest(int geoX, int geoY, int worldZ) ++ { ++ return _height; ++ } ++ ++ @Override ++ public short getHeightNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return _height; ++ } ++ ++ @Override ++ public short getHeightAbove(int geoX, int geoY, int worldZ) ++ { ++ // check and return height ++ return _height > worldZ ? _height : Short.MIN_VALUE; ++ } ++ ++ @Override ++ public short getHeightBelow(int geoX, int geoY, int worldZ) ++ { ++ // check and return height ++ return _height < worldZ ? _height : Short.MAX_VALUE; ++ } ++ ++ @Override ++ public byte getNsweNearest(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweAbove(int geoX, int geoY, int worldZ) ++ { ++ // check height and return nswe ++ return _height > worldZ ? _nswe : 0; ++ } ++ ++ @Override ++ public byte getNsweBelow(int geoX, int geoY, int worldZ) ++ { ++ // check height and return nswe ++ return _height < worldZ ? _nswe : 0; ++ } ++ ++ @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 getIndexAboveOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexAbove(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public int getIndexBelow(int geoX, int geoY, int worldZ) ++ { ++ // check height and return index ++ return _height < worldZ ? 0 : -1; ++ } ++ ++ @Override ++ public int getIndexBelowOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexBelow(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeight(int index) ++ { ++ return _height; ++ } ++ ++ @Override ++ public short getHeightOriginal(int index) ++ { ++ return _height; ++ } ++ ++ @Override ++ public byte getNswe(int index) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweOriginal(int index) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public void setNswe(int index, byte nswe) ++ { ++ _nswe = nswe; ++ } ++ ++ @Override ++ public void saveBlock(BufferedOutputStream stream) throws IOException ++ { ++ // write block type ++ stream.write(GeoStructure.TYPE_FLAT_L2D); ++ ++ // write height ++ stream.write((byte) (_height & 0x00FF)); ++ stream.write((byte) (_height >> 8)); ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java (working copy) +@@ -0,0 +1,465 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++import java.io.IOException; ++import java.nio.ByteBuffer; ++import java.nio.ByteOrder; ++import java.util.Arrays; ++ ++/** ++ * @author Hasha ++ */ ++public class BlockMultilayer extends ABlock ++{ ++ private static final int MAX_LAYERS = Byte.MAX_VALUE; ++ ++ private static ByteBuffer _temp; ++ ++ /** ++ * Initializes the temporarily buffer. ++ */ ++ public static void initialize() ++ { ++ // initialize temporarily buffer and sorting mechanism ++ _temp = ByteBuffer.allocate(GeoStructure.BLOCK_CELLS * MAX_LAYERS * 3); ++ _temp.order(ByteOrder.LITTLE_ENDIAN); ++ } ++ ++ /** ++ * Releases temporarily buffer. ++ */ ++ public static void release() ++ { ++ _temp = null; ++ } ++ ++ protected byte[] _buffer; ++ ++ /** ++ * Implicit constructor for children class. ++ */ ++ protected BlockMultilayer() ++ { ++ _buffer = null; ++ } ++ ++ /** ++ * Creates MultilayerBlock. ++ * @param bb : Input byte buffer. ++ * @param format : GeoFormat specifying format of loaded data. ++ */ ++ public BlockMultilayer(ByteBuffer bb, GeoFormat format) ++ { ++ // 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 = format != GeoFormat.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++) ++ { ++ if (format != GeoFormat.L2D) ++ { ++ // get data ++ final short data = bb.getShort(); ++ ++ // add nswe and height ++ _temp.put((byte) (data & 0x000F)); ++ _temp.putShort((short) ((short) (data & 0xFFF0) >> 1)); ++ } ++ else ++ { ++ // add nswe ++ _temp.put(bb.get()); ++ ++ // add height ++ _temp.putShort(bb.getShort()); ++ } ++ } ++ } ++ ++ // 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 short getHeightNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getHeightNearest(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeightAbove(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 height ++ if (height > worldZ) ++ { ++ return (short) height; ++ } ++ ++ // move index to next layer ++ index -= 3; ++ } ++ ++ // none layer found, return minimum value ++ return Short.MIN_VALUE; ++ } ++ ++ @Override ++ public short getHeightBelow(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 height ++ if (height < worldZ) ++ { ++ return (short) height; ++ } ++ ++ // move index to next layer ++ index += 3; ++ } ++ ++ // none layer found, return maximum value ++ return Short.MAX_VALUE; ++ } ++ ++ @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 byte getNsweNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getNsweNearest(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public byte getNsweAbove(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 nswe ++ if (height > worldZ) ++ { ++ return _buffer[index]; ++ } ++ ++ // move index to next layer ++ index -= 3; ++ } ++ ++ // none layer found, block movement ++ return 0; ++ } ++ ++ @Override ++ public byte getNsweBelow(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 nswe ++ if (height < worldZ) ++ { ++ return _buffer[index]; ++ } ++ ++ // move index to next layer ++ index += 3; ++ } ++ ++ // none layer found, block movement ++ return 0; ++ } ++ ++ @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 first layer data (first from bottom) ++ byte layers = _buffer[index++]; ++ ++ // loop though all cell layers, find closest layer ++ 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; ++ } ++ ++ // none layer found ++ return -1; ++ } ++ ++ @Override ++ public int getIndexAboveOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexAbove(geoX, geoY, worldZ); ++ } ++ ++ @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; ++ } ++ ++ // none layer found ++ return -1; ++ } ++ ++ @Override ++ public int getIndexBelowOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexBelow(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeight(int index) ++ { ++ // get height ++ return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ } ++ ++ @Override ++ public short getHeightOriginal(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]; ++ } ++ ++ @Override ++ public byte getNsweOriginal(int index) ++ { ++ // get nswe ++ return _buffer[index]; ++ } ++ ++ @Override ++ public void setNswe(int index, byte nswe) ++ { ++ // set nswe ++ _buffer[index] = nswe; ++ } ++ ++ @Override ++ public void saveBlock(BufferedOutputStream stream) throws IOException ++ { ++ // write block type ++ stream.write(GeoStructure.TYPE_MULTILAYER_L2D); ++ ++ // for each cell ++ int index = 0; ++ for (int i = 0; i < GeoStructure.BLOCK_CELLS; i++) ++ { ++ // write layers count ++ final byte layers = _buffer[index++]; ++ stream.write(layers); ++ ++ // write cell data ++ stream.write(_buffer, index, layers * 3); ++ ++ // move index to next cell ++ index += layers * 3; ++ } ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java (working copy) +@@ -0,0 +1,150 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++ ++/** ++ * @author Hasha ++ */ ++public class BlockNull extends ABlock ++{ ++ private final byte _nswe; ++ ++ public BlockNull() ++ { ++ _nswe = (byte) 0xFF; ++ } ++ ++ @Override ++ public boolean hasGeoPos() ++ { ++ return false; ++ } ++ ++ @Override ++ public short getHeightNearest(int geoX, int geoY, int worldZ) ++ { ++ return (short) worldZ; ++ } ++ ++ @Override ++ public short getHeightNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return (short) worldZ; ++ } ++ ++ @Override ++ public short getHeightAbove(int geoX, int geoY, int worldZ) ++ { ++ return (short) worldZ; ++ } ++ ++ @Override ++ public short getHeightBelow(int geoX, int geoY, int worldZ) ++ { ++ return (short) worldZ; ++ } ++ ++ @Override ++ public byte getNsweNearest(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweAbove(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweBelow(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) ++ { ++ return 0; ++ } ++ ++ @Override ++ public int getIndexAboveOriginal(int geoX, int geoY, int worldZ) ++ { ++ return 0; ++ } ++ ++ @Override ++ public int getIndexBelow(int geoX, int geoY, int worldZ) ++ { ++ return 0; ++ } ++ ++ @Override ++ public int getIndexBelowOriginal(int geoX, int geoY, int worldZ) ++ { ++ return 0; ++ } ++ ++ @Override ++ public short getHeight(int index) ++ { ++ return 0; ++ } ++ ++ @Override ++ public short getHeightOriginal(int index) ++ { ++ return 0; ++ } ++ ++ @Override ++ public byte getNswe(int index) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweOriginal(int index) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public void setNswe(int index, byte nswe) ++ { ++ } ++ ++ @Override ++ public void saveBlock(BufferedOutputStream stream) ++ { ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java (nonexistent) +@@ -1,48 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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() +- { +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/ComplexBlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/ComplexBlock.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/ComplexBlock.java (nonexistent) +@@ -1,77 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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; +- +-/** +- * @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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/FlatBlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/FlatBlock.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/FlatBlock.java (nonexistent) +@@ -1,56 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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; +- +-/** +- * @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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/GeoFormat.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/GeoFormat.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/GeoFormat.java (working copy) +@@ -0,0 +1,39 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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 Hasha ++ */ ++public enum GeoFormat ++{ ++ L2J("%d_%d.l2j"), ++ L2OFF("%d_%d_conv.dat"), ++ L2D("%d_%d.l2d"); ++ ++ private final String _filename; ++ ++ private GeoFormat(String filename) ++ { ++ _filename = filename; ++ } ++ ++ public String getFilename() ++ { ++ return _filename; ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/GeoLocation.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/GeoLocation.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/GeoLocation.java (working copy) +@@ -0,0 +1,67 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.geoengine.GeoEngine; ++import org.l2jmobius.gameserver.model.Location; ++ ++/** ++ * @author Hasha ++ */ ++public class GeoLocation extends Location ++{ ++ private byte _nswe; ++ ++ public GeoLocation(int x, int y, int z) ++ { ++ super(x, y, GeoEngine.getInstance().getHeightNearest(x, y, z)); ++ _nswe = GeoEngine.getInstance().getNsweNearest(x, y, z); ++ } ++ ++ public void set(int x, int y, short z) ++ { ++ super.setXYZ(x, y, GeoEngine.getInstance().getHeightNearest(x, y, z)); ++ _nswe = GeoEngine.getInstance().getNsweNearest(x, y, z); ++ } ++ ++ public int getGeoX() ++ { ++ return _x; ++ } ++ ++ public int getGeoY() ++ { ++ return _y; ++ } ++ ++ @Override ++ public int getX() ++ { ++ return GeoEngine.getWorldX(_x); ++ } ++ ++ @Override ++ public int getY() ++ { ++ return GeoEngine.getWorldY(_y); ++ } ++ ++ public byte getNSWE() ++ { ++ return _nswe; ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java (working copy) +@@ -0,0 +1,70 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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; ++ ++/** ++ * @author Hasha ++ */ ++public class GeoStructure ++{ ++ // cells ++ public static final byte CELL_FLAG_E = 1 << 0; ++ public static final byte CELL_FLAG_W = 1 << 1; ++ public static final byte CELL_FLAG_S = 1 << 2; ++ public static final byte CELL_FLAG_N = 1 << 3; ++ public static final byte CELL_FLAG_SE = 1 << 4; ++ public static final byte CELL_FLAG_SW = 1 << 5; ++ public static final byte CELL_FLAG_NE = 1 << 6; ++ public static final byte CELL_FLAG_NW = (byte) (1 << 7); ++ ++ public static final int CELL_HEIGHT = 8; ++ public static final int CELL_IGNORE_HEIGHT = CELL_HEIGHT * 6; ++ ++ // blocks ++ public static final byte TYPE_FLAT_L2J_L2OFF = 0; ++ public static final byte TYPE_FLAT_L2D = (byte) 0xD0; ++ public static final byte TYPE_COMPLEX_L2J = 1; ++ public static final byte TYPE_COMPLEX_L2OFF = 0x40; ++ public static final byte TYPE_COMPLEX_L2D = (byte) 0xD1; ++ 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) ++ public static final byte TYPE_MULTILAYER_L2D = (byte) 0xD2; ++ ++ 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; ++ ++ // regions ++ 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; ++ ++ // global geodata ++ private static final int GEO_REGIONS_X = ((World.TILE_X_MAX - World.TILE_X_MIN) + 1); ++ private 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 +Index: java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java (nonexistent) +@@ -1,42 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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); +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/IGeoObject.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/IGeoObject.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/IGeoObject.java (working copy) +@@ -0,0 +1,53 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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 Hasha ++ */ ++public interface IGeoObject ++{ ++ /** ++ * Returns geodata X coordinate of the {@link IGeoObject}. ++ * @return int : Geodata X coordinate. ++ */ ++ int getGeoX(); ++ ++ /** ++ * Returns geodata Y coordinate of the {@link IGeoObject}. ++ * @return int : Geodata Y coordinate. ++ */ ++ int getGeoY(); ++ ++ /** ++ * Returns geodata Z coordinate of the {@link IGeoObject}. ++ * @return int : Geodata Z coordinate. ++ */ ++ int getGeoZ(); ++ ++ /** ++ * Returns height of the {@link IGeoObject}. ++ * @return int : Height. ++ */ ++ int getHeight(); ++ ++ /** ++ * Returns {@link IGeoObject} data. ++ * @return byte[][] : {@link IGeoObject} data. ++ */ ++ byte[][] getObjectGeoData(); ++} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java (nonexistent) +@@ -1,47 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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(); +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/MultilayerBlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/MultilayerBlock.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/MultilayerBlock.java (nonexistent) +@@ -1,183 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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; +- +-/** +- * @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); +- +- // 1 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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/NullRegion.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/NullRegion.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/NullRegion.java (nonexistent) +@@ -1,55 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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 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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/Region.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/Region.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/Region.java (nonexistent) +@@ -1,92 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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; +- +-/** +- * @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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java (nonexistent) +@@ -1,87 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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 T _loc; +- private AbstractNode _parent; +- +- public AbstractNode(T loc) +- { +- _loc = loc; +- } +- +- public void setParent(AbstractNode p) +- { +- _parent = p; +- } +- +- public AbstractNode getParent() +- { +- return _parent; +- } +- +- public T getLoc() +- { +- return _loc; +- } +- +- public void setLoc(T l) +- { +- _loc = l; +- } +- +- @Override +- public int hashCode() +- { +- final int prime = 31; +- int result = 1; +- result = (prime * result) + ((_loc == null) ? 0 : _loc.hashCode()); +- return result; +- } +- +- @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 +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java (nonexistent) +@@ -1,33 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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 int getNodeX(); +- +- public abstract int getNodeY(); +-} +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNode.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNode.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNode.java (nonexistent) +@@ -1,67 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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 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 +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNodeBuffer.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNodeBuffer.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNodeBuffer.java (nonexistent) +@@ -1,348 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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.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 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) +- { +- _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(); +- } +- +- 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 void getNeighbors() +- { +- if (!_current.getLoc().canGoAll()) +- { +- 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); +- } +- +- // SouthEast +- if ((nodeE != null) && (nodeS != null)) +- { +- if (nodeE.getLoc().canGoSouth() && nodeS.getLoc().canGoEast()) +- { +- addNode(x + 1, y + 1, z, true); +- } +- } +- +- // SouthWest +- if ((nodeS != null) && (nodeW != null)) +- { +- if (nodeW.getLoc().canGoSouth() && nodeS.getLoc().canGoWest()) +- { +- addNode(x - 1, y + 1, z, true); +- } +- } +- +- // NorthEast +- if ((nodeN != null) && (nodeE != null)) +- { +- if (nodeE.getLoc().canGoNorth() && nodeN.getLoc().canGoEast()) +- { +- addNode(x + 1, y - 1, z, true); +- } +- } +- +- // NorthWest +- if ((nodeN != null) && (nodeW != null)) +- { +- if (nodeW.getLoc().canGoNorth() && nodeN.getLoc().canGoWest()) +- { +- addNode(x - 1, y - 1, z, true); +- } +- } +- } +- +- private 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(); +- // reinit node if needed +- if (result.getLoc() != null) +- { +- result.getLoc().set(x, y, z); +- } +- else +- { +- result.setLoc(new NodeLoc(x, y, z)); +- } +- } +- +- return result; +- } +- +- private 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 boolean isHighWeight(int x, int y, int z) +- { +- final CellNode result = getNode(x, y, z); +- if (result == null) +- { +- return true; +- } +- +- if (!result.getLoc().canGoAll()) +- { +- return true; +- } +- if (Math.abs(result.getLoc().getZ() - z) > 16) +- { +- return true; +- } +- +- return false; +- } +- +- private 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 +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java (working copy) +@@ -0,0 +1,87 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.geodata.GeoLocation; ++ ++/** ++ * @author Hasha ++ */ ++public class Node ++{ ++ // node coords and nswe flag ++ private GeoLocation _loc; ++ ++ // node parent (for reverse path construction) ++ private Node _parent; ++ // node child (for moving over nodes during iteration) ++ private Node _child; ++ ++ // node G cost (movement cost = parent movement cost + current movement cost) ++ private double _cost = -1000; ++ ++ public void setLoc(int x, int y, int z) ++ { ++ _loc = new GeoLocation(x, y, z); ++ } ++ ++ public GeoLocation getLoc() ++ { ++ return _loc; ++ } ++ ++ public void setParent(Node parent) ++ { ++ _parent = parent; ++ } ++ ++ public Node getParent() ++ { ++ return _parent; ++ } ++ ++ public void setChild(Node child) ++ { ++ _child = child; ++ } ++ ++ public Node getChild() ++ { ++ return _child; ++ } ++ ++ public void setCost(double cost) ++ { ++ _cost = cost; ++ } ++ ++ public double getCost() ++ { ++ return _cost; ++ } ++ ++ public void free() ++ { ++ // reset node location ++ _loc = null; ++ ++ // reset node parent, child and cost ++ _parent = null; ++ _child = null; ++ _cost = -1000; ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java (working copy) +@@ -0,0 +1,306 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.concurrent.locks.ReentrantLock; ++ ++import org.l2jmobius.Config; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; ++ ++/** ++ * @author DS, Hasha; Credits to Diamond ++ */ ++public class NodeBuffer ++{ ++ private final ReentrantLock _lock = new ReentrantLock(); ++ private final int _size; ++ private final Node[][] _buffer; ++ ++ // center coordinates ++ private int _cx = 0; ++ private int _cy = 0; ++ ++ // target coordinates ++ private int _gtx = 0; ++ private int _gty = 0; ++ private short _gtz = 0; ++ ++ private Node _current = null; ++ ++ /** ++ * Constructor of NodeBuffer. ++ * @param size : one dimension size of buffer ++ */ ++ public NodeBuffer(int size) ++ { ++ // set size ++ _size = size; ++ ++ // initialize buffer ++ _buffer = new Node[size][size]; ++ for (int x = 0; x < size; x++) ++ { ++ for (int y = 0; y < size; y++) ++ { ++ _buffer[x][y] = 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 Node : first node of path ++ */ ++ public Node findPath(int gox, int goy, short goz, int gtx, int gty, short gtz) ++ { ++ // set coordinates (middle of the line (gox,goy) - (gtx,gty), will be in the center of the buffer) ++ _cx = gox + ((gtx - gox - _size) / 2); ++ _cy = goy + ((gty - goy - _size) / 2); ++ ++ _gtx = gtx; ++ _gty = gty; ++ _gtz = gtz; ++ ++ _current = getNode(gox, goy, goz); ++ _current.setCost(getCostH(gox, goy, goz)); ++ ++ int count = 0; ++ do ++ { ++ // reached target? ++ if ((_current.getLoc().getGeoX() == _gtx) && (_current.getLoc().getGeoY() == _gty) && (Math.abs(_current.getLoc().getZ() - _gtz) < 8)) ++ { ++ return _current; ++ } ++ ++ // expand current node ++ expand(); ++ ++ // move pointer ++ _current = _current.getChild(); ++ } ++ while ((_current != null) && (count++ < Config.MAX_ITERATIONS)); ++ ++ return null; ++ } ++ ++ public boolean isLocked() ++ { ++ return _lock.tryLock(); ++ } ++ ++ public void free() ++ { ++ _current = null; ++ ++ for (Node[] nodes : _buffer) ++ { ++ for (Node node : nodes) ++ { ++ if (node.getLoc() != null) ++ { ++ node.free(); ++ } ++ } ++ } ++ ++ _lock.unlock(); ++ } ++ ++ /** ++ * Check _current Node and add its neighbors to the buffer. ++ */ ++ private final void expand() ++ { ++ // can't move anywhere, don't expand ++ final byte nswe = _current.getLoc().getNSWE(); ++ if (nswe == 0) ++ { ++ return; ++ } ++ ++ // get geo coords of the node to be expanded ++ final int x = _current.getLoc().getGeoX(); ++ final int y = _current.getLoc().getGeoY(); ++ final short z = (short) _current.getLoc().getZ(); ++ ++ // can move north, expand ++ if ((nswe & GeoStructure.CELL_FLAG_N) != 0) ++ { ++ addNode(x, y - 1, z, Config.BASE_WEIGHT); ++ } ++ ++ // can move south, expand ++ if ((nswe & GeoStructure.CELL_FLAG_S) != 0) ++ { ++ addNode(x, y + 1, z, Config.BASE_WEIGHT); ++ } ++ ++ // can move west, expand ++ if ((nswe & GeoStructure.CELL_FLAG_W) != 0) ++ { ++ addNode(x - 1, y, z, Config.BASE_WEIGHT); ++ } ++ ++ // can move east, expand ++ if ((nswe & GeoStructure.CELL_FLAG_E) != 0) ++ { ++ addNode(x + 1, y, z, Config.BASE_WEIGHT); ++ } ++ ++ // can move north-west, expand ++ if ((nswe & GeoStructure.CELL_FLAG_NW) != 0) ++ { ++ addNode(x - 1, y - 1, z, Config.DIAGONAL_WEIGHT); ++ } ++ ++ // can move north-east, expand ++ if ((nswe & GeoStructure.CELL_FLAG_NE) != 0) ++ { ++ addNode(x + 1, y - 1, z, Config.DIAGONAL_WEIGHT); ++ } ++ ++ // can move south-west, expand ++ if ((nswe & GeoStructure.CELL_FLAG_SW) != 0) ++ { ++ addNode(x - 1, y + 1, z, Config.DIAGONAL_WEIGHT); ++ } ++ ++ // can move south-east, expand ++ if ((nswe & GeoStructure.CELL_FLAG_SE) != 0) ++ { ++ addNode(x + 1, y + 1, z, Config.DIAGONAL_WEIGHT); ++ } ++ } ++ ++ /** ++ * Returns node, if it exists in buffer. ++ * @param x : node X coord ++ * @param y : node Y coord ++ * @param z : node Z coord ++ * @return Node : node, if exits in buffer ++ */ ++ private final Node getNode(int x, int y, short z) ++ { ++ // check node X out of coordinates ++ final int ix = x - _cx; ++ if ((ix < 0) || (ix >= _size)) ++ { ++ return null; ++ } ++ ++ // check node Y out of coordinates ++ final int iy = y - _cy; ++ if ((iy < 0) || (iy >= _size)) ++ { ++ return null; ++ } ++ ++ // get node ++ final Node result = _buffer[ix][iy]; ++ ++ // check and update ++ if (result.getLoc() == null) ++ { ++ result.setLoc(x, y, z); ++ } ++ ++ // return node ++ return result; ++ } ++ ++ /** ++ * Add node given by coordinates to the buffer. ++ * @param x : geo X coord ++ * @param y : geo Y coord ++ * @param z : geo Z coord ++ * @param weight : weight of movement to new node ++ */ ++ private final void addNode(int x, int y, short z, int weight) ++ { ++ // get node to be expanded ++ final Node node = getNode(x, y, z); ++ if (node == null) ++ { ++ return; ++ } ++ ++ // Z distance between nearby cells is higher than cell size ++ if (node.getLoc().getZ() > (z + (2 * GeoStructure.CELL_HEIGHT))) ++ { ++ return; ++ } ++ ++ // node was already expanded, return ++ if (node.getCost() >= 0) ++ { ++ return; ++ } ++ ++ node.setParent(_current); ++ if (node.getLoc().getNSWE() != (byte) 0xFF) ++ { ++ node.setCost(getCostH(x, y, node.getLoc().getZ()) + (weight * Config.OBSTACLE_MULTIPLIER)); ++ } ++ else ++ { ++ node.setCost(getCostH(x, y, node.getLoc().getZ()) + weight); ++ } ++ ++ Node current = _current; ++ int count = 0; ++ while ((current.getChild() != null) && (count < (Config.MAX_ITERATIONS * 4))) ++ { ++ count++; ++ if (current.getChild().getCost() > node.getCost()) ++ { ++ node.setChild(current.getChild()); ++ break; ++ } ++ current = current.getChild(); ++ } ++ ++ if (count >= (Config.MAX_ITERATIONS * 4)) ++ { ++ System.err.println("Pathfinding: too long loop detected, cost:" + node.getCost()); ++ } ++ ++ current.setChild(node); ++ } ++ ++ /** ++ * @param x : node X coord ++ * @param y : node Y coord ++ * @param i : node Z coord ++ * @return double : node cost ++ */ ++ private final double getCostH(int x, int y, int i) ++ { ++ final int dX = x - _gtx; ++ final int dY = y - _gty; ++ final int dZ = (i - _gtz) / GeoStructure.CELL_HEIGHT; ++ ++ // return (Math.abs(dX) + Math.abs(dY) + Math.abs(dZ)) * Config.HEURISTIC_WEIGHT; // Manhattan distance ++ return Math.sqrt((dX * dX) + (dY * dY) + (dZ * dZ)) * Config.HEURISTIC_WEIGHT; // Direct distance ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeLoc.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeLoc.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeLoc.java (nonexistent) +@@ -1,183 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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.Cell; +- +-/** +- * @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 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 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; +- // return super.hashCode(); +- } +- +- @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; +- if (_x != other._x) +- { +- return false; +- } +- if (_y != other._y) +- { +- return false; +- } +- if (_goNorth != other._goNorth) +- { +- return false; +- } +- if (_goEast != other._goEast) +- { +- return false; +- } +- if (_goSouth != other._goSouth) +- { +- return false; +- } +- if (_goWest != other._goWest) +- { +- return false; +- } +- if (_geoHeight != other._geoHeight) +- { +- return false; +- } +- return true; +- } +-} +Index: java/org/l2jmobius/gameserver/model/actor/Creature.java +=================================================================== +--- java/org/l2jmobius/gameserver/model/actor/Creature.java (revision 8399) ++++ java/org/l2jmobius/gameserver/model/actor/Creature.java (working copy) +@@ -66,8 +66,6 @@ + import org.l2jmobius.gameserver.enums.TeleportWhereType; + import org.l2jmobius.gameserver.enums.UserInfoType; + import org.l2jmobius.gameserver.geoengine.GeoEngine; +-import org.l2jmobius.gameserver.geoengine.GeoEnginePathfinding; +-import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; + import org.l2jmobius.gameserver.instancemanager.IdManager; + import org.l2jmobius.gameserver.instancemanager.MapRegionManager; + import org.l2jmobius.gameserver.instancemanager.QuestManager; +@@ -2577,7 +2575,7 @@ + + public boolean disregardingGeodata; + public int onGeodataPathIndex; +- public List geoPath; ++ public List geoPath; + public int geoPathAccurateTx; + public int geoPathAccurateTy; + public int geoPathGtx; +@@ -3466,7 +3464,7 @@ + if (((originalDistance - distance) > 30) && !isControlBlocked() && !isInVehicle) + { + // Path calculation -- overrides previous movement check +- m.geoPath = GeoEnginePathfinding.getInstance().findPath(curX, curY, curZ, originalX, originalY, originalZ, getInstanceWorld()); ++ m.geoPath = GeoEngine.getInstance().findPath(curX, curY, curZ, originalX, originalY, originalZ, getInstanceWorld()); + if ((m.geoPath == null) || (m.geoPath.size() < 2)) // No path found + { + if (isPlayer() && !_isFlying && !isInWater) +Index: java/org/l2jmobius/gameserver/model/actor/instance/PlayerInstance.java +=================================================================== +--- java/org/l2jmobius/gameserver/model/actor/instance/PlayerInstance.java (revision 8397) ++++ java/org/l2jmobius/gameserver/model/actor/instance/PlayerInstance.java (working copy) +@@ -12704,7 +12704,7 @@ + { + if (Config.CORRECT_PLAYER_Z) + { +- final int nearestZ = GeoEngine.getInstance().getHigherHeight(getX(), getY(), getZ()); ++ final int nearestZ = GeoEngine.getInstance().getHeightNearest(getX(), getY(), getZ()); + if (getZ() < nearestZ) + { + teleToLocation(new Location(getX(), getY(), nearestZ)); +Index: java/org/l2jmobius/gameserver/util/GeoUtils.java +=================================================================== +--- java/org/l2jmobius/gameserver/util/GeoUtils.java (revision 8397) ++++ java/org/l2jmobius/gameserver/util/GeoUtils.java (working copy) +@@ -19,7 +19,7 @@ + import java.awt.Color; + + import org.l2jmobius.gameserver.geoengine.GeoEngine; +-import org.l2jmobius.gameserver.geoengine.geodata.Cell; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; + import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance; + import org.l2jmobius.gameserver.network.serverpackets.ExServerPrimitive; + +@@ -26,25 +26,25 @@ + /** + * @author HorridoJoho + */ +-public final class GeoUtils ++public class GeoUtils + { + public static void debug2DLine(PlayerInstance player, int x, int y, int tx, int ty, int z) + { +- final int gx = GeoEngine.getInstance().getGeoX(x); +- final int gy = GeoEngine.getInstance().getGeoY(y); ++ final int gx = GeoEngine.getGeoX(x); ++ final int gy = GeoEngine.getGeoY(y); + +- final int tgx = GeoEngine.getInstance().getGeoX(tx); +- final int tgy = GeoEngine.getInstance().getGeoY(ty); ++ final int tgx = GeoEngine.getGeoX(tx); ++ final int tgy = GeoEngine.getGeoY(ty); + + final ExServerPrimitive prim = new ExServerPrimitive("Debug2DLine", x, y, z); +- prim.addLine(Color.BLUE, GeoEngine.getInstance().getWorldX(gx), GeoEngine.getInstance().getWorldY(gy), z, GeoEngine.getInstance().getWorldX(tgx), GeoEngine.getInstance().getWorldY(tgy), z); ++ prim.addLine(Color.BLUE, GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), z, GeoEngine.getWorldX(tgx), GeoEngine.getWorldY(tgy), z); + + final LinePointIterator iter = new LinePointIterator(gx, gy, tgx, tgy); + + while (iter.next()) + { +- final int wx = GeoEngine.getInstance().getWorldX(iter.x()); +- final int wy = GeoEngine.getInstance().getWorldY(iter.y()); ++ final int wx = GeoEngine.getWorldX(iter.x()); ++ final int wy = GeoEngine.getWorldY(iter.y()); + + prim.addPoint(Color.RED, wx, wy, z); + } +@@ -53,21 +53,21 @@ + + public static void debug3DLine(PlayerInstance player, int x, int y, int z, int tx, int ty, int tz) + { +- final int gx = GeoEngine.getInstance().getGeoX(x); +- final int gy = GeoEngine.getInstance().getGeoY(y); ++ final int gx = GeoEngine.getGeoX(x); ++ final int gy = GeoEngine.getGeoY(y); + +- final int tgx = GeoEngine.getInstance().getGeoX(tx); +- final int tgy = GeoEngine.getInstance().getGeoY(ty); ++ final int tgx = GeoEngine.getGeoX(tx); ++ final int tgy = GeoEngine.getGeoY(ty); + + final ExServerPrimitive prim = new ExServerPrimitive("Debug3DLine", x, y, z); +- prim.addLine(Color.BLUE, GeoEngine.getInstance().getWorldX(gx), GeoEngine.getInstance().getWorldY(gy), z, GeoEngine.getInstance().getWorldX(tgx), GeoEngine.getInstance().getWorldY(tgy), tz); ++ prim.addLine(Color.BLUE, GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), z, GeoEngine.getWorldX(tgx), GeoEngine.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.getInstance().getWorldX(prevX); +- int wy = GeoEngine.getInstance().getWorldY(prevY); ++ int wx = GeoEngine.getWorldX(prevX); ++ int wy = GeoEngine.getWorldY(prevY); + int wz = iter.z(); + prim.addPoint(Color.RED, wx, wy, wz); + +@@ -78,8 +78,8 @@ + + if ((curX != prevX) || (curY != prevY)) + { +- wx = GeoEngine.getInstance().getWorldX(curX); +- wy = GeoEngine.getInstance().getWorldY(curY); ++ wx = GeoEngine.getWorldX(curX); ++ wy = GeoEngine.getWorldY(curY); + wz = iter.z(); + + prim.addPoint(Color.RED, wx, wy, wz); +@@ -93,7 +93,7 @@ + + private static Color getDirectionColor(int x, int y, int z, int nswe) + { +- if (GeoEngine.getInstance().checkNearestNswe(x, y, z, nswe)) ++ if ((GeoEngine.getInstance().getNsweNearest(x, y, z) & nswe) == nswe) + { + return Color.GREEN; + } +@@ -109,9 +109,8 @@ + int iPacket = 0; + + ExServerPrimitive exsp = null; +- final GeoEngine ge = GeoEngine.getInstance(); +- final int playerGx = ge.getGeoX(player.getX()); +- final int playerGy = ge.getGeoY(player.getY()); ++ final int playerGx = GeoEngine.getGeoX(player.getX()); ++ final int playerGy = GeoEngine.getGeoY(player.getY()); + for (int dx = -geoRadius; dx <= geoRadius; ++dx) + { + for (int dy = -geoRadius; dy <= geoRadius; ++dy) +@@ -135,12 +134,12 @@ + final int gx = playerGx + dx; + final int gy = playerGy + dy; + +- final int x = ge.getWorldX(gx); +- final int y = ge.getWorldY(gy); +- final int z = ge.getNearestZ(gx, gy, player.getZ()); ++ final int x = GeoEngine.getWorldX(gx); ++ final int y = GeoEngine.getWorldY(gy); ++ final int z = GeoEngine.getInstance().getHeightNearest(gx, gy, player.getZ()); + + // north arrow +- Color col = getDirectionColor(gx, gy, z, Cell.NSWE_NORTH); ++ Color col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_N); + 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); +@@ -147,7 +146,7 @@ + exsp.addLine(col, x - 4, y - 4, z, x + 4, y - 4, z); + + // east arrow +- col = getDirectionColor(gx, gy, z, Cell.NSWE_EAST); ++ col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_E); + 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); +@@ -154,13 +153,13 @@ + exsp.addLine(col, x + 4, y - 4, z, x + 4, y + 4, z); + + // south arrow +- col = getDirectionColor(gx, gy, z, Cell.NSWE_SOUTH); ++ col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_S); + 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, Cell.NSWE_WEST); ++ col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_W); + 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); +@@ -188,15 +187,15 @@ + { + if (y > lastY) + { +- return Cell.NSWE_SOUTH_EAST; ++ return GeoStructure.CELL_FLAG_SE; // Direction.SOUTH_EAST; + } + else if (y < lastY) + { +- return Cell.NSWE_NORTH_EAST; ++ return GeoStructure.CELL_FLAG_NE; // Direction.NORTH_EAST; + } + else + { +- return Cell.NSWE_EAST; ++ return GeoStructure.CELL_FLAG_E; // Direction.EAST; + } + } + else if (x < lastX) // west +@@ -203,26 +202,27 @@ + { + if (y > lastY) + { +- return Cell.NSWE_SOUTH_WEST; ++ return GeoStructure.CELL_FLAG_SW; // Direction.SOUTH_WEST; + } + else if (y < lastY) + { +- return Cell.NSWE_NORTH_WEST; ++ return GeoStructure.CELL_FLAG_NW; // Direction.NORTH_WEST; + } + else + { +- return Cell.NSWE_WEST; ++ return GeoStructure.CELL_FLAG_W; // Direction.WEST; + } + } +- else // unchanged x ++ else ++ // unchanged x + { + if (y > lastY) + { +- return Cell.NSWE_SOUTH; ++ return GeoStructure.CELL_FLAG_S; // Direction.SOUTH; + } + else if (y < lastY) + { +- return Cell.NSWE_NORTH; ++ return GeoStructure.CELL_FLAG_N; // Direction.NORTH; + } + else + { +Index: java/org/l2jmobius/tools/geodataconverter/GeoDataConverter.java +=================================================================== +--- java/org/l2jmobius/tools/geodataconverter/GeoDataConverter.java (nonexistent) ++++ java/org/l2jmobius/tools/geodataconverter/GeoDataConverter.java (working copy) +@@ -0,0 +1,386 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ */ ++package org.l2jmobius.tools.geodataconverter; ++ ++import java.io.BufferedOutputStream; ++import java.io.File; ++import java.io.FileOutputStream; ++import java.io.RandomAccessFile; ++import java.nio.ByteOrder; ++import java.nio.MappedByteBuffer; ++import java.nio.channels.FileChannel; ++import java.util.Scanner; ++ ++import org.l2jmobius.Config; ++import org.l2jmobius.commons.util.PropertiesParser; ++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.GeoFormat; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; ++import org.l2jmobius.gameserver.model.World; ++ ++/** ++ * @author Hasha ++ */ ++public class GeoDataConverter ++{ ++ private static GeoFormat _format; ++ private static ABlock[][] _blocks; ++ ++ public static void main(String[] args) ++ { ++ // initialize config ++ loadGeoengineConfigs(); ++ ++ // get geodata format ++ String type = ""; ++ try (Scanner scn = new Scanner(System.in)) ++ { ++ while (!(type.equalsIgnoreCase("J") || type.equalsIgnoreCase("O") || type.equalsIgnoreCase("E"))) ++ { ++ System.out.println("GeoDataConverter: Select source geodata type:"); ++ System.out.println(" J: L2J (e.g. 23_22.l2j)"); ++ System.out.println(" O: L2OFF (e.g. 23_22_conv.dat)"); ++ System.out.println(" E: Exit"); ++ System.out.print("Choice: "); ++ type = scn.next(); ++ } ++ } ++ if (type.equalsIgnoreCase("E")) ++ { ++ System.exit(0); ++ } ++ ++ _format = type.equalsIgnoreCase("J") ? GeoFormat.L2J : GeoFormat.L2OFF; ++ ++ // start conversion ++ System.out.println("GeoDataConverter: Converting all " + _format + " files."); ++ ++ // initialize geodata container ++ _blocks = new ABlock[GeoStructure.REGION_BLOCKS_X][GeoStructure.REGION_BLOCKS_Y]; ++ ++ // initialize multilayer temporarily buffer ++ BlockMultilayer.initialize(); ++ ++ // load geo files ++ int converted = 0; ++ for (int rx = World.TILE_X_MIN; rx <= World.TILE_X_MAX; rx++) ++ { ++ for (int ry = World.TILE_Y_MIN; ry <= World.TILE_Y_MAX; ry++) ++ { ++ final String input = String.format(_format.getFilename(), rx, ry); ++ final String filepath = Config.GEODATA_PATH; ++ final File f = new File(filepath + input); ++ if (f.exists() && !f.isDirectory()) ++ { ++ // load geodata ++ if (!loadGeoBlocks(input)) ++ { ++ System.out.println("GeoDataConverter: Unable to load " + input + " region file."); ++ continue; ++ } ++ ++ // recalculate nswe ++ if (!recalculateNswe()) ++ { ++ System.out.println("GeoDataConverter: Unable to convert " + input + " region file."); ++ continue; ++ } ++ ++ // save geodata ++ final String output = String.format(GeoFormat.L2D.getFilename(), rx, ry); ++ if (!saveGeoBlocks(output)) ++ { ++ System.out.println("GeoDataConverter: Unable to save " + output + " region file."); ++ continue; ++ } ++ ++ converted++; ++ System.out.println("GeoDataConverter: Created " + output + " region file."); ++ } ++ } ++ } ++ System.out.println("GeoDataConverter: Converted " + converted + " " + _format + " to L2D region file(s)."); ++ ++ // release multilayer block temporarily buffer ++ BlockMultilayer.release(); ++ } ++ ++ /** ++ * Loads geo blocks from buffer of the region file. ++ * @param filename : The name of the to load. ++ * @return boolean : True when successful. ++ */ ++ private static boolean loadGeoBlocks(String filename) ++ { ++ // region file is load-able, try to load it ++ try (RandomAccessFile raf = new RandomAccessFile(Config.GEODATA_PATH + filename, "r"); ++ FileChannel fc = raf.getChannel()) ++ { ++ final MappedByteBuffer buffer = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()).load(); ++ buffer.order(ByteOrder.LITTLE_ENDIAN); ++ ++ // load 18B header for L2off geodata (1st and 2nd byte...region X and Y) ++ if (_format == GeoFormat.L2OFF) ++ { ++ for (int i = 0; i < 18; i++) ++ { ++ buffer.get(); ++ } ++ } ++ ++ // 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 (_format == GeoFormat.L2J) ++ { ++ // get block type ++ final byte type = buffer.get(); ++ ++ // load block according to block type ++ switch (type) ++ { ++ case GeoStructure.TYPE_FLAT_L2J_L2OFF: ++ { ++ _blocks[ix][iy] = new BlockFlat(buffer, _format); ++ break; ++ } ++ case GeoStructure.TYPE_COMPLEX_L2J: ++ { ++ _blocks[ix][iy] = new BlockComplex(buffer, _format); ++ break; ++ } ++ case GeoStructure.TYPE_MULTILAYER_L2J: ++ { ++ _blocks[ix][iy] = new BlockMultilayer(buffer, _format); ++ break; ++ } ++ default: ++ { ++ throw new IllegalArgumentException("Unknown block type: " + type); ++ } ++ } ++ } ++ else ++ { ++ // get block type ++ final short type = buffer.getShort(); ++ ++ // load block according to block type ++ switch (type) ++ { ++ case GeoStructure.TYPE_FLAT_L2J_L2OFF: ++ { ++ _blocks[ix][iy] = new BlockFlat(buffer, _format); ++ break; ++ } ++ case GeoStructure.TYPE_COMPLEX_L2OFF: ++ { ++ _blocks[ix][iy] = new BlockComplex(buffer, _format); ++ break; ++ } ++ default: ++ { ++ _blocks[ix][iy] = new BlockMultilayer(buffer, _format); ++ break; ++ } ++ } ++ } ++ } ++ } ++ ++ if (buffer.remaining() > 0) ++ { ++ System.out.println("GeoDataConverter: Region file " + filename + " can be corrupted, remaining " + buffer.remaining() + " bytes to read."); ++ return false; ++ } ++ ++ return true; ++ } ++ catch (Exception e) ++ { ++ System.out.println("GeoDataConverter: Error while loading " + filename + " region file."); ++ return false; ++ } ++ } ++ ++ /** ++ * Recalculate diagonal flags for the region file. ++ * @return boolean : True when successful. ++ */ ++ private static boolean recalculateNswe() ++ { ++ try ++ { ++ for (int x = 0; x < GeoStructure.REGION_CELLS_X; x++) ++ { ++ for (int y = 0; y < GeoStructure.REGION_CELLS_Y; y++) ++ { ++ // get block ++ final ABlock block = _blocks[x / GeoStructure.BLOCK_CELLS_X][y / GeoStructure.BLOCK_CELLS_Y]; ++ ++ // skip flat blocks ++ if (block instanceof BlockFlat) ++ { ++ continue; ++ } ++ ++ // for complex and multilayer blocks go though all layers ++ short height = Short.MAX_VALUE; ++ int index; ++ while ((index = block.getIndexBelow(x, y, height)) != -1) ++ { ++ // get height and nswe ++ height = block.getHeight(index); ++ byte nswe = block.getNswe(index); ++ ++ // update nswe with diagonal flags ++ nswe = updateNsweBelow(x, y, height, nswe); ++ ++ // set nswe of the cell ++ block.setNswe(index, nswe); ++ } ++ } ++ } ++ ++ return true; ++ } ++ catch (Exception e) ++ { ++ return false; ++ } ++ } ++ ++ /** ++ * Updates the NSWE flag with diagonal flags. ++ * @param x : Geodata X coordinate. ++ * @param y : Geodata Y coordinate. ++ * @param z : Geodata Z coordinate. ++ * @param nsweValue : NSWE flag to be updated. ++ * @return byte : Updated NSWE flag. ++ */ ++ private static byte updateNsweBelow(int x, int y, short z, byte nsweValue) ++ { ++ byte nswe = nsweValue; ++ ++ // calculate virtual layer height ++ final short height = (short) (z + GeoStructure.CELL_IGNORE_HEIGHT); ++ ++ // get NSWE of neighbor cells below virtual layer (NPC/PC can fall down of clif, but can not climb it -> NSWE of cell below) ++ final byte nsweN = getNsweBelow(x, y - 1, height); ++ final byte nsweS = getNsweBelow(x, y + 1, height); ++ final byte nsweW = getNsweBelow(x - 1, y, height); ++ final byte nsweE = getNsweBelow(x + 1, y, height); ++ ++ // north-west ++ if ((((nswe & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) || (((nswe & GeoStructure.CELL_FLAG_W) != 0) && ((nsweW & GeoStructure.CELL_FLAG_N) != 0))) ++ { ++ nswe |= GeoStructure.CELL_FLAG_NW; ++ } ++ ++ // north-east ++ if ((((nswe & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) || (((nswe & GeoStructure.CELL_FLAG_E) != 0) && ((nsweE & GeoStructure.CELL_FLAG_N) != 0))) ++ { ++ nswe |= GeoStructure.CELL_FLAG_NE; ++ } ++ ++ // south-west ++ if ((((nswe & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) || (((nswe & GeoStructure.CELL_FLAG_W) != 0) && ((nsweW & GeoStructure.CELL_FLAG_S) != 0))) ++ { ++ nswe |= GeoStructure.CELL_FLAG_SW; ++ } ++ ++ // south-east ++ if ((((nswe & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) || (((nswe & GeoStructure.CELL_FLAG_E) != 0) && ((nsweE & GeoStructure.CELL_FLAG_S) != 0))) ++ { ++ nswe |= GeoStructure.CELL_FLAG_SE; ++ } ++ ++ return nswe; ++ } ++ ++ private static byte getNsweBelow(int geoX, int geoY, short worldZ) ++ { ++ // out of geo coordinates ++ if ((geoX < 0) || (geoX >= GeoStructure.REGION_CELLS_X)) ++ { ++ return 0; ++ } ++ ++ // out of geo coordinates ++ if ((geoY < 0) || (geoY >= GeoStructure.REGION_CELLS_Y)) ++ { ++ return 0; ++ } ++ ++ // get block ++ final ABlock block = _blocks[geoX / GeoStructure.BLOCK_CELLS_X][geoY / GeoStructure.BLOCK_CELLS_Y]; ++ ++ // get index, when valid, return nswe ++ final int index = block.getIndexBelow(geoX, geoY, worldZ); ++ return index == -1 ? 0 : block.getNswe(index); ++ } ++ ++ /** ++ * Save region file to file. ++ * @param filename : The name of file to save. ++ * @return boolean : True when successful. ++ */ ++ private static boolean saveGeoBlocks(String filename) ++ { ++ try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(Config.GEODATA_PATH + filename), GeoStructure.REGION_BLOCKS * GeoStructure.BLOCK_CELLS * 3)) ++ { ++ // loop over region blocks and save each block ++ for (int ix = 0; ix < GeoStructure.REGION_BLOCKS_X; ix++) ++ { ++ for (int iy = 0; iy < GeoStructure.REGION_BLOCKS_Y; iy++) ++ { ++ _blocks[ix][iy].saveBlock(bos); ++ } ++ } ++ ++ // flush data to file ++ bos.flush(); ++ ++ return true; ++ } ++ catch (Exception e) ++ { ++ return false; ++ } ++ } ++ ++ private static void loadGeoengineConfigs() ++ { ++ final PropertiesParser geoData = new PropertiesParser(Config.GEOENGINE_CONFIG_FILE); ++ Config.GEODATA_PATH = geoData.getString("GeoDataPath", "./data/geodata/"); ++ Config.COORD_SYNCHRONIZE = geoData.getInt("CoordSynchronize", -1); ++ Config.PART_OF_CHARACTER_HEIGHT = geoData.getInt("PartOfCharacterHeight", 75); ++ Config.MAX_OBSTACLE_HEIGHT = geoData.getInt("MaxObstacleHeight", 32); ++ Config.PATHFINDING = geoData.getBoolean("PathFinding", true); ++ Config.PATHFIND_BUFFERS = geoData.getString("PathFindBuffers", "100x6;128x6;192x6;256x4;320x4;384x4;500x2"); ++ Config.BASE_WEIGHT = geoData.getInt("BaseWeight", 10); ++ Config.DIAGONAL_WEIGHT = geoData.getInt("DiagonalWeight", 14); ++ Config.OBSTACLE_MULTIPLIER = geoData.getInt("ObstacleMultiplier", 10); ++ Config.HEURISTIC_WEIGHT = geoData.getInt("HeuristicWeight", 20); ++ Config.MAX_ITERATIONS = geoData.getInt("MaxIterations", 3500); ++ } ++} +\ No newline at end of file diff --git a/L2J_Mobius_8.0_Homunculus/dist/game/data/geodata/L2D_GeoEngine.patch b/L2J_Mobius_8.0_Homunculus/dist/game/data/geodata/L2D_GeoEngine.patch new file mode 100644 index 0000000000..63e2faf0c9 --- /dev/null +++ b/L2J_Mobius_8.0_Homunculus/dist/game/data/geodata/L2D_GeoEngine.patch @@ -0,0 +1,6052 @@ +Index: dist/game/GeoDataConverter.bat +=================================================================== +--- dist/game/GeoDataConverter.bat (nonexistent) ++++ dist/game/GeoDataConverter.bat (working copy) +@@ -0,0 +1,6 @@ ++@echo off ++title L2D geodata converter ++ ++java -Xmx512m -cp ./../libs/* org.l2jmobius.tools.geodataconverter.GeoDataConverter ++ ++pause +Index: dist/game/GeoDataConverter.sh +=================================================================== +--- dist/game/GeoDataConverter.sh (nonexistent) ++++ dist/game/GeoDataConverter.sh (working copy) +@@ -0,0 +1,4 @@ ++#! /bin/sh ++ ++java -Xmx512m -cp ../libs/*: org.l2jmobius.tools.geodataconverter.GeoDataConverter > log/stdout.log 2>&1 ++ +Index: dist/game/config/GeoEngine.ini +=================================================================== +--- dist/game/config/GeoEngine.ini (revision 8397) ++++ dist/game/config/GeoEngine.ini (working copy) +@@ -1,6 +1,10 @@ + # ================================================================= + # Geodata + # ================================================================= ++# Because of real-time performance we are using geodata files only in ++# diagonal L2D format now (using filename e.g. 22_16.l2d). ++# L2D geodata can be obtained by conversion of existing L2J or L2OFF geodata. ++# Launch "GeoDataConverter.bat/sh" and follow instructions to start the conversion. + + # 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/ +@@ -13,6 +17,16 @@ + CoordSynchronize = 2 + + # ================================================================= ++# Path checking ++# ================================================================= ++ ++# 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 finding + # ================================================================= + +@@ -23,19 +37,23 @@ + # Pathfinding array buffers configuration, default: 100x6;128x6;192x6;256x4;320x4;384x4;500x2 + PathFindBuffers = 100x6;128x6;192x6;256x4;320x4;384x4;500x2 + +-# Weight for nodes without obstacles far from walls +-LowWeight = 0.5 ++# Base path weight, when moving from one node to another on axis direction, default: 10 ++BaseWeight = 10 + +-# Weight for nodes near walls +-MediumWeight = 2 ++# Path weight, when moving from one node to another on diagonal direction, default: BaseWeight * sqrt(2) = 14 ++DiagonalWeight = 14 + +-# Weight for nodes with obstacles +-HighWeight = 3 ++# When movement flags of target node is blocked to any direction, multiply movement weight by this multiplier. ++# This causes pathfinding algorithm to avoid path construction exactly near an obstacle, default: 10 ++ObstacleMultiplier = 10 + +-# Weight for diagonal movement. +-# Default: LowWeight * sqrt(2) +-DiagonalWeight = 0.707 ++# Weight of the heuristic algorithm, which is giving estimated cost from node to target, default: 20 ++# For proper function must be higher than BaseWeight and/or DiagonalWeight. ++HeuristicWeight = 20 + ++# Maximum number of generated nodes per one path-finding process, default 3500 ++MaxIterations = 3500 ++ + # ================================================================= + # Other + # ================================================================= +Index: dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java +=================================================================== +--- dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java (revision 8397) ++++ dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java (working copy) +@@ -55,9 +55,8 @@ + final int worldX = activeChar.getX(); + final int worldY = activeChar.getY(); + final int worldZ = activeChar.getZ(); +- final int geoX = GeoEngine.getInstance().getGeoX(worldX); +- final int geoY = GeoEngine.getInstance().getGeoY(worldY); +- ++ final int geoX = GeoEngine.getGeoX(worldX); ++ final int geoY = GeoEngine.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)); +@@ -73,9 +72,8 @@ + final int worldX = activeChar.getX(); + final int worldY = activeChar.getY(); + final int worldZ = activeChar.getZ(); +- final int geoX = GeoEngine.getInstance().getGeoX(worldX); +- final int geoY = GeoEngine.getInstance().getGeoY(worldY); +- ++ final int geoX = GeoEngine.getGeoX(worldX); ++ final int geoY = GeoEngine.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)); +Index: dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java +=================================================================== +--- dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java (revision 8397) ++++ dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java (working copy) +@@ -18,11 +18,11 @@ + + import java.util.List; + +-import org.l2jmobius.Config; +-import org.l2jmobius.gameserver.geoengine.GeoEnginePathfinding; +-import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; ++import org.l2jmobius.gameserver.geoengine.GeoEngine; + import org.l2jmobius.gameserver.handler.IAdminCommandHandler; ++import org.l2jmobius.gameserver.model.Location; + import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance; ++import org.l2jmobius.gameserver.network.SystemMessageId; + import org.l2jmobius.gameserver.util.BuilderUtil; + + public class AdminPathNode implements IAdminCommandHandler +@@ -37,29 +37,30 @@ + { + if (command.equals("admin_path_find")) + { +- if (!Config.PATHFINDING) +- { +- BuilderUtil.sendSysMessage(activeChar, "PathFinding is disabled."); +- return true; +- } + if (activeChar.getTarget() != null) + { +- final List path = GeoEnginePathfinding.getInstance().findPath(activeChar.getX(), activeChar.getY(), (short) activeChar.getZ(), activeChar.getTarget().getX(), activeChar.getTarget().getY(), (short) activeChar.getTarget().getZ(), activeChar.getInstanceWorld()); ++ 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()); + if (path == null) + { +- BuilderUtil.sendSysMessage(activeChar, "No Route!"); +- return true; ++ BuilderUtil.sendSysMessage(activeChar, "No route found or pathfinding disabled."); + } +- for (AbstractNodeLoc a : path) ++ else + { +- BuilderUtil.sendSysMessage(activeChar, "x:" + a.getX() + " y:" + a.getY() + " z:" + a.getZ()); ++ for (Location point : path) ++ { ++ BuilderUtil.sendSysMessage(activeChar, "x:" + point.getX() + " y:" + point.getY() + " z:" + point.getZ()); ++ } + } + } + else + { +- BuilderUtil.sendSysMessage(activeChar, "No Target!"); ++ activeChar.sendPacket(SystemMessageId.INVALID_TARGET); + } + } ++ else ++ { ++ return false; ++ } + return true; + } + +Index: java/org/l2jmobius/Config.java +=================================================================== +--- java/org/l2jmobius/Config.java (revision 8397) ++++ java/org/l2jmobius/Config.java (working copy) +@@ -32,7 +32,6 @@ + import java.net.UnknownHostException; + import java.nio.charset.StandardCharsets; + import java.nio.file.Files; +-import java.nio.file.Path; + import java.nio.file.Paths; + import java.time.Duration; + import java.util.ArrayList; +@@ -927,14 +926,17 @@ + // -------------------------------------------------- + // GeoEngine + // -------------------------------------------------- +- public static Path GEODATA_PATH; ++ public static String GEODATA_PATH; ++ public static int COORD_SYNCHRONIZE; ++ public static int PART_OF_CHARACTER_HEIGHT; ++ public static int MAX_OBSTACLE_HEIGHT; + public static boolean PATHFINDING; + public static String PATHFIND_BUFFERS; +- public static float LOW_WEIGHT; +- public static float MEDIUM_WEIGHT; +- public static float HIGH_WEIGHT; +- public static float DIAGONAL_WEIGHT; +- public static int COORD_SYNCHRONIZE; ++ public static int BASE_WEIGHT; ++ public static int DIAGONAL_WEIGHT; ++ public static int HEURISTIC_WEIGHT; ++ public static int OBSTACLE_MULTIPLIER; ++ public static int MAX_ITERATIONS; + public static boolean CORRECT_PLAYER_Z; + + /** Attribute System */ +@@ -2468,14 +2470,17 @@ + + // Load GeoEngine config file (if exists) + final PropertiesParser GeoEngine = new PropertiesParser(GEOENGINE_CONFIG_FILE); +- GEODATA_PATH = Paths.get(GeoEngine.getString("GeoDataPath", "./data/geodata")); ++ GEODATA_PATH = GeoEngine.getString("GeoDataPath", "./data/geodata/"); ++ COORD_SYNCHRONIZE = GeoEngine.getInt("CoordSynchronize", -1); ++ PART_OF_CHARACTER_HEIGHT = GeoEngine.getInt("PartOfCharacterHeight", 75); ++ MAX_OBSTACLE_HEIGHT = GeoEngine.getInt("MaxObstacleHeight", 32); + PATHFINDING = GeoEngine.getBoolean("PathFinding", true); + PATHFIND_BUFFERS = GeoEngine.getString("PathFindBuffers", "100x6;128x6;192x6;256x4;320x4;384x4;500x2"); +- LOW_WEIGHT = GeoEngine.getFloat("LowWeight", 0.5f); +- MEDIUM_WEIGHT = GeoEngine.getFloat("MediumWeight", 2); +- HIGH_WEIGHT = GeoEngine.getFloat("HighWeight", 3); +- DIAGONAL_WEIGHT = GeoEngine.getFloat("DiagonalWeight", 0.707f); +- COORD_SYNCHRONIZE = GeoEngine.getInt("CoordSynchronize", -1); ++ BASE_WEIGHT = GeoEngine.getInt("BaseWeight", 10); ++ DIAGONAL_WEIGHT = GeoEngine.getInt("DiagonalWeight", 14); ++ OBSTACLE_MULTIPLIER = GeoEngine.getInt("ObstacleMultiplier", 10); ++ HEURISTIC_WEIGHT = GeoEngine.getInt("HeuristicWeight", 20); ++ MAX_ITERATIONS = GeoEngine.getInt("MaxIterations", 3500); + CORRECT_PLAYER_Z = GeoEngine.getBoolean("CorrectPlayerZ", false); + + // Load AllowedPlayerRaces config file (if exists) +Index: java/org/l2jmobius/gameserver/geoengine/GeoEngine.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/GeoEngine.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/GeoEngine.java (working copy) +@@ -16,101 +16,91 @@ + */ + package org.l2jmobius.gameserver.geoengine; + +-import java.io.IOException; ++import java.io.File; + import java.io.RandomAccessFile; + import java.nio.ByteOrder; +-import java.nio.channels.FileChannel.MapMode; +-import java.nio.file.Files; +-import java.nio.file.Path; +-import java.util.concurrent.atomic.AtomicReferenceArray; +-import java.util.logging.Level; ++import java.nio.MappedByteBuffer; ++import java.nio.channels.FileChannel; ++import java.util.List; + 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.geoengine.geodata.Cell; +-import org.l2jmobius.gameserver.geoengine.geodata.IRegion; +-import org.l2jmobius.gameserver.geoengine.geodata.NullRegion; +-import org.l2jmobius.gameserver.geoengine.geodata.Region; ++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.GeoFormat; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoLocation; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; + import org.l2jmobius.gameserver.instancemanager.WarpedSpaceManager; + 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; ++import org.l2jmobius.gameserver.util.MathUtil; + + /** +- * @author -Nemesiss-, HorridoJoho ++ * @author Hasha + */ + public class GeoEngine + { +- private static final Logger LOGGER = Logger.getLogger(GeoEngine.class.getName()); ++ protected static final Logger LOGGER = Logger.getLogger(GeoEngine.class.getName()); + +- private static final int WORLD_MIN_X = -655360; +- private static final int WORLD_MIN_Y = -589824; +- private static final int WORLD_MIN_Z = -16384; ++ private final ABlock[][] _blocks; ++ private final BlockNull _nullBlock; + +- /** 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; +- +- /** The regions array */ +- private final AtomicReferenceArray _regions = new AtomicReferenceArray<>(GEO_REGIONS); +- +- 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; +- +- protected GeoEngine() ++ /** ++ * GeoEngine constructor. Loads all geodata files of chosen geodata format. ++ */ ++ public GeoEngine() + { + LOGGER.info("GeoEngine: Initializing..."); +- for (int i = 0; i < _regions.length(); i++) +- { +- _regions.set(i, NullRegion.INSTANCE); +- } + ++ // 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; +- try ++ long fileSize = 0; ++ for (int rx = World.TILE_X_MIN; rx <= World.TILE_X_MAX; rx++) + { +- for (int regionX = World.TILE_X_MIN; regionX <= World.TILE_X_MAX; regionX++) ++ for (int ry = World.TILE_Y_MIN; ry <= World.TILE_Y_MAX; ry++) + { +- for (int regionY = World.TILE_Y_MIN; regionY <= World.TILE_Y_MAX; regionY++) ++ final File f = new File(Config.GEODATA_PATH + String.format(GeoFormat.L2D.getFilename(), rx, ry)); ++ if (f.exists() && !f.isDirectory()) + { +- 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(rx, ry)) + { +- try (RandomAccessFile raf = new RandomAccessFile(geoFilePath.toFile(), "r")) +- { +- _regions.set((regionX * GEO_REGIONS_Y) + regionY, new Region(raf.getChannel().map(MapMode.READ_ONLY, 0, raf.length()).load().order(ByteOrder.LITTLE_ENDIAN))); +- loaded++; +- } ++ loaded++; ++ fileSize += f.length(); + } + } ++ else ++ { ++ // region file is not load-able, load null blocks ++ loadNullBlocks(rx, ry); ++ } + } + } +- catch (Exception e) ++ ++ LOGGER.info("GeoEngine: Loaded " + loaded + " geodata files."); ++ if (loaded > 0) + { +- LOGGER.log(Level.SEVERE, "GeoEngine: Failed to load geodata!", e); +- System.exit(1); ++ LOGGER.info("GeoEngine: Total geodata file size " + (fileSize / 1024 / 1024) + " MB."); + } + +- LOGGER.info("GeoEngine: Loaded " + loaded + " geodata files."); +- +- // Avoid wrong configs when no files are loaded. ++ // avoid wrong configs when no files are loaded + if (loaded == 0) + { + if (Config.PATHFINDING) +@@ -124,627 +114,832 @@ + LOGGER.info("GeoEngine: Forcing CoordSynchronize setting to -1."); + } + } ++ ++ // release multilayer block temporarily buffer ++ BlockMultilayer.release(); + } + + /** +- * @param geoX +- * @param geoY +- * @return the region ++ * 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 IRegion getRegion(int geoX, int geoY) ++ private final boolean loadGeoBlocks(int regionX, int regionY) + { +- final int region = ((geoX / IRegion.REGION_CELLS_X) * GEO_REGIONS_Y) + (geoY / IRegion.REGION_CELLS_Y); +- if ((region < 0) || (region >= _regions.length())) ++ final String filename = String.format(GeoFormat.L2D.getFilename(), regionX, regionY); ++ ++ // standard load ++ try (RandomAccessFile raf = new RandomAccessFile(Config.GEODATA_PATH + filename, "r"); ++ FileChannel fc = raf.getChannel()) + { +- return null; ++ // initialize file buffer ++ final MappedByteBuffer buffer = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()).load(); ++ buffer.order(ByteOrder.LITTLE_ENDIAN); ++ ++ // 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++) ++ { ++ // get block type ++ final byte type = buffer.get(); ++ ++ // load block according to block type ++ switch (type) ++ { ++ case GeoStructure.TYPE_FLAT_L2D: ++ { ++ _blocks[blockX + ix][blockY + iy] = new BlockFlat(buffer, GeoFormat.L2D); ++ break; ++ } ++ case GeoStructure.TYPE_COMPLEX_L2D: ++ { ++ _blocks[blockX + ix][blockY + iy] = new BlockComplex(buffer, GeoFormat.L2D); ++ break; ++ } ++ case GeoStructure.TYPE_MULTILAYER_L2D: ++ { ++ _blocks[blockX + ix][blockY + iy] = new BlockMultilayer(buffer, GeoFormat.L2D); ++ break; ++ } ++ default: ++ { ++ throw new IllegalArgumentException("Unknown block type: " + type); ++ } ++ } ++ } ++ } ++ ++ // check data consistency ++ if (buffer.remaining() > 0) ++ { ++ LOGGER.warning("GeoEngine: Region file " + filename + " can be corrupted, remaining " + buffer.remaining() + " bytes to read."); ++ } ++ ++ // loading was successful ++ return true; + } +- return _regions.get(region); +- } +- +- /** +- * @param filePath +- * @param regionX +- * @param regionY +- * @throws IOException +- */ +- 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")) ++ catch (Exception e) + { +- _regions.set(regionOffset, new Region(raf.getChannel().map(MapMode.READ_ONLY, 0, raf.length()).load().order(ByteOrder.LITTLE_ENDIAN))); ++ // an error occured while loading, load null blocks ++ LOGGER.warning("GeoEngine: Error while loading " + filename + " region file."); ++ LOGGER.warning(e.getMessage()); ++ ++ // replace whole region file with null blocks ++ loadNullBlocks(regionX, regionY); ++ ++ // loading was not successful ++ return false; + } + } + + /** +- * @param regionX +- * @param regionY ++ * 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. + */ +- public void unloadRegion(int regionX, int regionY) ++ private final void loadNullBlocks(int regionX, int regionY) + { +- _regions.set((regionX * GEO_REGIONS_Y) + regionY, NullRegion.INSTANCE); +- } +- +- /** +- * @param geoX +- * @param geoY +- * @return if geodata exist +- */ +- public boolean hasGeoPos(int geoX, int geoY) +- { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) ++ // 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++) + { +- return false; ++ for (int iy = 0; iy < GeoStructure.REGION_BLOCKS_Y; iy++) ++ { ++ _blocks[blockX + ix][blockY + iy] = _nullBlock; ++ } + } +- return region.hasGeo(); + } + ++ // GEODATA - GENERAL ++ + /** +- * Checks the specified position for available geodata. +- * @param x the world x +- * @param y the world y +- * @return {@code true} if there is geodata for the given coordinates, {@code false} otherwise ++ * Converts world X to geodata X. ++ * @param worldX ++ * @return int : Geo X + */ +- public boolean hasGeo(int x, int y) ++ public static int getGeoX(int worldX) + { +- return hasGeoPos(getGeoX(x), getGeoY(y)); ++ return (MathUtil.limit(worldX, World.MAP_MIN_X, World.MAP_MAX_X) - World.MAP_MIN_X) >> 4; + } + + /** +- * @param geoX +- * @param geoY +- * @param worldZ +- * @param nswe +- * @return the nearest nswe check ++ * Converts world Y to geodata Y. ++ * @param worldY ++ * @return int : Geo Y + */ +- public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) ++ public static int getGeoY(int worldY) + { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) +- { +- return true; +- } +- return region.checkNearestNswe(geoX, geoY, worldZ, nswe); ++ return (MathUtil.limit(worldY, World.MAP_MIN_Y, World.MAP_MAX_Y) - World.MAP_MIN_Y) >> 4; + } + + /** ++ * Converts geodata X to world X. + * @param geoX +- * @param geoY +- * @param worldZ +- * @param nswe +- * @return the nearest nswe anti-corner cut ++ * @return int : World X + */ +- public boolean checkNearestNsweAntiCornerCut(int geoX, int geoY, int worldZ, int nswe) ++ public static int getWorldX(int geoX) + { +- boolean can = true; +- if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) +- { +- can = checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_EAST) && checkNearestNswe(geoX + 1, geoY, worldZ, Cell.NSWE_NORTH); +- } +- if (can && ((nswe & Cell.NSWE_NORTH_WEST) == Cell.NSWE_NORTH_WEST)) +- { +- 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 = 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 = checkNearestNswe(geoX, geoY + 1, worldZ, Cell.NSWE_WEST) && checkNearestNswe(geoX - 1, geoY, worldZ, Cell.NSWE_SOUTH); +- } +- return can && checkNearestNswe(geoX, geoY, worldZ, nswe); ++ return (MathUtil.limit(geoX, 0, GeoStructure.GEO_CELLS_X) << 4) + World.MAP_MIN_X + 8; + } + + /** +- * @param geoX ++ * Converts geodata Y to world Y. + * @param geoY +- * @param worldZ +- * @return the nearest Z value ++ * @return int : World Y + */ +- public int getNearestZ(int geoX, int geoY, int worldZ) ++ public static int getWorldY(int geoY) + { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) +- { +- return worldZ; +- } +- return region.getNearestZ(geoX, geoY, worldZ); ++ return (MathUtil.limit(geoY, 0, GeoStructure.GEO_CELLS_Y) << 4) + World.MAP_MIN_Y + 8; + } + + /** +- * @param geoX +- * @param geoY +- * @param worldZ +- * @return the next lower Z value ++ * Returns block of geodata on given coordinates. ++ * @param geoX : Geodata X ++ * @param geoY : Geodata Y ++ * @return {@link ABlock} : Block of geodata. + */ +- public int getNextLowerZ(int geoX, int geoY, int worldZ) ++ private final ABlock getBlock(int geoX, int geoY) + { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) ++ final int x = geoX / GeoStructure.BLOCK_CELLS_X; ++ final int y = geoY / GeoStructure.BLOCK_CELLS_Y; ++ ++ // if x or y is out of array return null ++ if ((x > -1) && (y > -1) && (x < GeoStructure.GEO_BLOCKS_X) && (y < GeoStructure.GEO_BLOCKS_Y)) + { +- return worldZ; ++ return _blocks[x][y]; + } +- return region.getNextLowerZ(geoX, geoY, worldZ); ++ return null; + } + + /** +- * @param geoX +- * @param geoY +- * @param worldZ +- * @return the next higher Z value ++ * Check if geo coordinates has geo. ++ * @param geoX : Geodata X ++ * @param geoY : Geodata Y ++ * @return boolean : True, if given geo coordinates have geodata + */ +- public int getNextHigherZ(int geoX, int geoY, int worldZ) ++ public boolean hasGeoPos(int geoX, int geoY) + { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) ++ final ABlock block = getBlock(geoX, geoY); ++ if (block == null) // null block check + { +- return worldZ; ++ // TODO: Find when this can be null. (Bad geodata? Check World getRegion method.) ++ // LOGGER.warning("Could not find geodata block at " + getWorldX(geoX) + ", " + getWorldY(geoY) + "."); ++ return false; + } +- return region.getNextHigherZ(geoX, geoY, worldZ); ++ return block.hasGeoPos(); + } + + /** +- * Gets the Z height. +- * @param x the world x +- * @param y the world y +- * @param z the world z +- * @return the nearest Z height ++ * 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 int getHeight(int x, int y, int z) ++ public short getHeightNearest(int geoX, int geoY, int worldZ) + { +- return getNearestZ(getGeoX(x), getGeoY(y), z); ++ final ABlock block = getBlock(geoX, geoY); ++ return block != null ? block.getHeightNearest(geoX, geoY, worldZ) : (short) worldZ; + } + + /** +- * Gets the next lower Z height. +- * @param x the world x +- * @param y the world y +- * @param z the world z +- * @return the nearest Z height ++ * 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 int getLowerHeight(int x, int y, int z) ++ public byte getNsweNearest(int geoX, int geoY, int worldZ) + { +- return getNextLowerZ(getGeoX(x), getGeoY(y), z); ++ final ABlock block = getBlock(geoX, geoY); ++ return block != null ? block.getNsweNearest(geoX, geoY, worldZ) : (byte) 0xFF; + } + + /** +- * Gets the next higher Z height. +- * @param x the world x +- * @param y the world y +- * @param z the world z +- * @return the nearest Z height ++ * Check if world coordinates has geo. ++ * @param worldX : World X ++ * @param worldY : World Y ++ * @return boolean : True, if given world coordinates have geodata + */ +- public int getHigherHeight(int x, int y, int z) ++ public boolean hasGeo(int worldX, int worldY) + { +- return getNextHigherZ(getGeoX(x), getGeoY(y), z); ++ return hasGeoPos(getGeoX(worldX), getGeoY(worldY)); + } + + /** +- * @param worldX +- * @return the geo X ++ * 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 int getGeoX(int worldX) ++ public short getHeight(int worldX, int worldY, int worldZ) + { +- return (worldX - WORLD_MIN_X) / 16; ++ return getHeightNearest(getGeoX(worldX), getGeoY(worldY), worldZ); + } + +- /** +- * @param worldY +- * @return the geo Y +- */ +- public int getGeoY(int worldY) +- { +- return (worldY - WORLD_MIN_Y) / 16; +- } ++ // PATHFINDING + + /** +- * @param worldZ +- * @return the geo Z ++ * Check line of sight from {@link WorldObject} to {@link WorldObject}. ++ * @param origin : The origin object. ++ * @param target : The target object. ++ * @return {@code boolean} : True if origin can see target + */ +- public int getGeoZ(int worldZ) ++ public boolean canSeeTarget(WorldObject origin, WorldObject target) + { +- return (worldZ - WORLD_MIN_Z) / 16; +- } +- +- /** +- * @param geoX +- * @return the world X +- */ +- public int getWorldX(int geoX) +- { +- return (geoX * 16) + WORLD_MIN_X + 8; +- } +- +- /** +- * @param geoY +- * @return the world Y +- */ +- public int getWorldY(int geoY) +- { +- return (geoY * 16) + WORLD_MIN_Y + 8; +- } +- +- /** +- * @param geoZ +- * @return the world Z +- */ +- public int getWorldZ(int geoZ) +- { +- return (geoZ * 16) + WORLD_MIN_Z + 8; +- } +- +- /** +- * 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) +- { +- if (target.isDoor()) ++ if (target.isDoor() || target.isArtefact() || (target.isCreature() && ((Creature) target).isFlying())) + { +- // Can always see doors. + return true; + } +- return 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(), cha.getInstanceWorld(), worldPosition.getX(), worldPosition.getY(), worldPosition.getZ()); +- } +- +- /** +- * Can see target. Checks doors between. +- * @param x the x coordinate +- * @param y the y coordinate +- * @param z the z coordinate +- * @param world +- * @param tx the target's x coordinate +- * @param ty the target's y coordinate +- * @param tz the target's z coordinate +- * @param tworld the target's instanceId +- * @return +- */ +- public boolean canSeeTarget(int x, int y, int z, Instance world, int tx, int ty, int tz, Instance tworld) +- { +- return (world != tworld) ? false : canSeeTarget(x, y, z, world, tx, ty, tz); +- } +- +- /** +- * 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 +- * @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, Instance instance, int tx, int ty, int tz) +- { +- if (DoorData.getInstance().checkIfDoorsBetween(x, y, z, tx, ty, tz, instance, true)) ++ ++ // get origin and target world coordinates ++ final int ox = origin.getX(); ++ final int oy = origin.getY(); ++ final int oz = origin.getZ(); ++ final int tx = target.getX(); ++ final int ty = target.getY(); ++ final int tz = target.getZ(); ++ ++ if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld(), true)) + { + return false; + } +- return canSeeTarget(x, y, z, tx, ty, tz); +- } +- +- /** +- * @param prevX +- * @param prevY +- * @param prevGeoZ +- * @param curX +- * @param curY +- * @param nswe +- * @return the LOS Z value +- */ +- 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))) ++ if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld())) + { +- throw new RuntimeException("Multiple directions!"); ++ return false; + } ++ if (WarpedSpaceManager.getInstance().checkForWarpedSpace(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld())) ++ { ++ return false; ++ } + +- if (checkNearestNsweAntiCornerCut(prevX, prevY, prevGeoZ, nswe)) ++ // get origin and check existing geo coordinates ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) + { +- return getNearestZ(curX, curY, prevGeoZ); ++ return true; + } + +- return getNextHigherZ(curX, curY, prevGeoZ); ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coordinates ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) ++ { ++ return true; ++ } ++ ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // origin and target coordinates are same ++ if ((gox == gtx) && (goy == gty)) ++ { ++ return goz == gtz; ++ } ++ ++ // get origin and target height, real height = collision height * 2 ++ double oheight = 0; ++ if (origin.isCreature()) ++ { ++ oheight = ((Creature) origin).getCollisionHeight() * 2; ++ } ++ ++ double theight = 0; ++ if (target.isCreature()) ++ { ++ theight = ((Creature) target).getCollisionHeight() * 2; ++ } ++ ++ // perform geodata check ++ return checkSee(gox, goy, goz, oheight, gtx, gty, gtz, theight, origin.getInstanceWorld()); + } + + /** +- * Can see target. Does not check doors between. +- * @param xValue the x coordinate +- * @param yValue the y coordinate +- * @param zValue the z coordinate +- * @param txValue the target's x coordinate +- * @param tyValue the target's y coordinate +- * @param tzValue the target's z coordinate +- * @return {@code true} if there is line of sight between the given coordinate sets, {@code false} otherwise ++ * Check line of sight from {@link WorldObject} to {@link Location}. ++ * @param origin : The origin object. ++ * @param position : The target position. ++ * @return {@code boolean} : True if object can see position + */ +- public boolean canSeeTarget(int xValue, int yValue, int zValue, int txValue, int tyValue, int tzValue) ++ public boolean canSeeTarget(WorldObject origin, Location position) + { +- int x = xValue; +- int y = yValue; +- int tx = txValue; +- int ty = tyValue; ++ // get origin and target world coordinates ++ final int ox = origin.getX(); ++ final int oy = origin.getY(); ++ final int oz = origin.getZ(); ++ final int tx = position.getX(); ++ final int ty = position.getY(); ++ final int tz = position.getZ(); + +- int geoX = getGeoX(x); +- int geoY = getGeoY(y); +- int tGeoX = getGeoX(tx); +- int tGeoY = getGeoY(ty); ++ if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld(), true)) ++ { ++ return false; ++ } ++ if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld())) ++ { ++ return false; ++ } ++ if (WarpedSpaceManager.getInstance().checkForWarpedSpace(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld())) ++ { ++ return false; ++ } + +- int z = getNearestZ(geoX, geoY, zValue); +- int tz = getNearestZ(tGeoX, tGeoY, tzValue); ++ // get origin and check existing geo coordinates ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) ++ { ++ return true; ++ } + +- // Fastpath. +- if ((geoX == tGeoX) && (geoY == tGeoY)) ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coordinates ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) + { +- if (hasGeoPos(tGeoX, tGeoY)) +- { +- return z == tz; +- } + return true; + } + +- if (tz > z) ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // origin and target coordinates are same ++ if ((gox == gtx) && (goy == gty)) + { +- int tmp = tx; +- tx = x; +- x = tmp; +- +- tmp = ty; +- ty = y; +- y = tmp; +- +- tmp = tz; +- tz = z; +- z = tmp; +- +- tmp = tGeoX; +- tGeoX = geoX; +- geoX = tmp; +- +- tmp = tGeoY; +- tGeoY = geoY; +- geoY = tmp; ++ return goz == gtz; + } + +- final LinePointIterator3D pointIter = new LinePointIterator3D(geoX, geoY, z, tGeoX, tGeoY, tz); +- // 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()) ++ // get origin and target height, real height = collision height * 2 ++ double oheight = 0; ++ if (origin.isCreature()) + { +- final int curX = pointIter.x(); +- final int curY = pointIter.y(); ++ oheight = ((Creature) origin).getTemplate().getCollisionHeight(); ++ } ++ ++ // perform geodata check ++ return checkSee(gox, goy, goz, oheight, gtx, gty, gtz, 0, origin.getInstanceWorld()); ++ } ++ ++ /** ++ * Simple check for origin to target visibility. ++ * @param goxValue : origin X geodata coordinate ++ * @param goyValue : origin Y geodata coordinate ++ * @param gozValue : origin Z geodata coordinate ++ * @param oheight : origin height (if instance of {@link Character}) ++ * @param gtxValue : target X geodata coordinate ++ * @param gtyValue : target Y geodata coordinate ++ * @param gtzValue : target Z geodata coordinate ++ * @param theight : target height (if instance of {@link Character}) ++ * @param instance ++ * @return {@code boolean} : True, when target can be seen. ++ */ ++ private final boolean checkSee(int goxValue, int goyValue, int gozValue, double oheight, int gtxValue, int gtyValue, int gtzValue, double theight, Instance instance) ++ { ++ int goz = gozValue; ++ int gtz = gtzValue; ++ int gox = goxValue; ++ int goy = goyValue; ++ int gtx = gtxValue; ++ int gty = gtyValue; ++ ++ // get line of sight Z coordinates ++ double losoz = goz + ((oheight * Config.PART_OF_CHARACTER_HEIGHT) / 100); ++ double lostz = gtz + ((theight * Config.PART_OF_CHARACTER_HEIGHT) / 100); ++ ++ // get X delta and signum ++ final int dx = Math.abs(gtx - gox); ++ final int sx = gox < gtx ? 1 : -1; ++ final byte dirox = sx > 0 ? GeoStructure.CELL_FLAG_E : GeoStructure.CELL_FLAG_W; ++ final byte dirtx = sx > 0 ? GeoStructure.CELL_FLAG_W : GeoStructure.CELL_FLAG_E; ++ ++ // get Y delta and signum ++ final int dy = Math.abs(gty - goy); ++ final int sy = goy < gty ? 1 : -1; ++ final byte diroy = sy > 0 ? GeoStructure.CELL_FLAG_S : GeoStructure.CELL_FLAG_N; ++ final byte dirty = sy > 0 ? GeoStructure.CELL_FLAG_N : GeoStructure.CELL_FLAG_S; ++ ++ // get Z delta ++ final int dm = Math.max(dx, dy); ++ final double dz = (lostz - losoz) / dm; ++ ++ // get direction flag for diagonal movement ++ final byte diroxy = getDirXY(dirox, diroy); ++ final byte dirtxy = getDirXY(dirtx, dirty); ++ ++ // delta, determines axis to move on (+..X axis, -..Y axis) ++ int d = dx - dy; ++ ++ // NSWE direction of movement ++ byte diro; ++ byte dirt; ++ ++ // initialize node values ++ int nox = gox; ++ int noy = goy; ++ int ntx = gtx; ++ int nty = gty; ++ byte nsweo = getNsweNearest(gox, goy, goz); ++ byte nswet = getNsweNearest(gtx, gty, gtz); ++ ++ // loop ++ ABlock block; ++ int index; ++ for (int i = 0; i < ((dm + 1) / 2); i++) ++ { ++ // reset direction flag ++ diro = 0; ++ dirt = 0; + +- if ((curX == prevX) && (curY == prevY)) ++ // calculate next point coordinates ++ final int e2 = 2 * d; ++ if ((e2 > -dy) && (e2 < dx)) + { +- continue; ++ // calculate next point XY coordinates ++ d -= dy; ++ d += dx; ++ nox += sx; ++ ntx -= sx; ++ noy += sy; ++ nty -= sy; ++ diro |= diroxy; ++ dirt |= dirtxy; + } ++ else if (e2 > -dy) ++ { ++ // calculate next point X coordinate ++ d -= dy; ++ nox += sx; ++ ntx -= sx; ++ diro |= dirox; ++ dirt |= dirtx; ++ } ++ else if (e2 < dx) ++ { ++ // calculate next point Y coordinate ++ d += dx; ++ noy += sy; ++ nty -= sy; ++ diro |= diroy; ++ dirt |= dirty; ++ } + +- final int beeCurZ = pointIter.z(); +- int curGeoZ = prevGeoZ; +- +- // Check if the position has geodata. +- if (hasGeoPos(curX, curY)) + { +- final int beeCurGeoZ = getNearestZ(curX, curY, beeCurZ); +- final int nswe = GeoUtils.computeNswe(prevX, prevY, curX, curY); // .computeDirection(prevX, prevY, curX, curY); +- curGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, curX, curY, nswe); +- int maxHeight; +- if (ptIndex < ELEVATED_SEE_OVER_DISTANCE) ++ // get block of the next cell ++ block = getBlock(nox, noy); ++ ++ // get index of particular layer, based on movement conditions ++ if ((nsweo & diro) == 0) + { +- maxHeight = z + MAX_SEE_OVER_HEIGHT; ++ index = block.getIndexAbove(nox, noy, goz - GeoStructure.CELL_IGNORE_HEIGHT); + } + else + { +- maxHeight = beeCurZ + MAX_SEE_OVER_HEIGHT; ++ index = block.getIndexBelow(nox, noy, goz + GeoStructure.CELL_IGNORE_HEIGHT); + } + +- boolean canSeeThrough = false; +- if ((curGeoZ <= maxHeight) && (curGeoZ <= beeCurGeoZ)) ++ // layer does not exist, return ++ if (index == -1) + { +- if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) +- { +- 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 +- { +- canSeeThrough = true; +- } ++ return false; + } + +- if (!canSeeThrough) ++ // get layer and next line of sight Z coordinate ++ goz = block.getHeight(index); ++ losoz += dz; ++ ++ // perform line of sight check, return when fails ++ if ((goz - losoz) > Config.MAX_OBSTACLE_HEIGHT) + { + return false; + } ++ ++ // get layer nswe ++ nsweo = block.getNswe(index); + } ++ { ++ // get block of the next cell ++ block = getBlock(ntx, nty); ++ ++ // get index of particular layer, based on movement conditions ++ if ((nswet & dirt) == 0) ++ { ++ index = block.getIndexAbove(ntx, nty, gtz - GeoStructure.CELL_IGNORE_HEIGHT); ++ } ++ else ++ { ++ index = block.getIndexBelow(ntx, nty, gtz + GeoStructure.CELL_IGNORE_HEIGHT); ++ } ++ ++ // layer does not exist, return ++ if (index == -1) ++ { ++ return false; ++ } ++ ++ // get layer and next line of sight Z coordinate ++ gtz = block.getHeight(index); ++ lostz -= dz; ++ ++ // perform line of sight check, return when fails ++ if ((gtz - lostz) > Config.MAX_OBSTACLE_HEIGHT) ++ { ++ return false; ++ } ++ ++ // get layer nswe ++ nswet = block.getNswe(index); ++ } + +- prevX = curX; +- prevY = curY; +- prevGeoZ = curGeoZ; +- ++ptIndex; ++ // update coords ++ gox = nox; ++ goy = noy; ++ gtx = ntx; ++ gty = nty; + } + +- return true; ++ // when iteration is completed, compare final Z coordinates ++ return Math.abs(goz - gtz) < (GeoStructure.CELL_HEIGHT * 4); + } + + /** +- * Move check. +- * @param x the x coordinate +- * @param y the y coordinate +- * @param zValue the z coordinate +- * @param tx the target's x coordinate +- * @param ty the target's y coordinate +- * @param tzValue the target's z coordinate +- * @param instance the instance +- * @return the last Location (x,y,z) where player can walk - just before wall ++ * Check movement from coordinates to 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 {code boolean} : True if target coordinates are reachable from origin coordinates + */ +- public Location canMoveToTargetLoc(int x, int y, int zValue, int tx, int ty, int tzValue, Instance instance) ++ public boolean canMoveToTarget(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + { +- final int geoX = getGeoX(x); +- final int geoY = getGeoY(y); +- final int z = getNearestZ(geoX, geoY, zValue); +- final int tGeoX = getGeoX(tx); +- final int tGeoY = getGeoY(ty); +- final int tz = getNearestZ(tGeoX, tGeoY, tzValue); ++ // get origin and check existing geo coordinates ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) ++ { ++ return true; ++ } + +- if (DoorData.getInstance().checkIfDoorsBetween(x, y, z, tx, ty, tz, instance, false)) ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coordinates ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) + { +- return new Location(x, y, getHeight(x, y, z)); ++ return true; + } +- if (FenceData.getInstance().checkIfFenceBetween(x, y, z, tx, ty, tz, instance)) ++ ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // target coordinates reached ++ if ((gox == gtx) && (goy == gty) && (goz == gtz)) + { +- return new Location(x, y, getHeight(x, y, z)); ++ return true; + } +- if (WarpedSpaceManager.getInstance().checkForWarpedSpace(x, y, z, tx, ty, tz, instance)) ++ ++ // perform geodata check ++ final GeoLocation loc = checkMove(gox, goy, goz, gtx, gty, gtz, instance); ++ return (loc.getGeoX() == gtx) && (loc.getGeoY() == gty); ++ } ++ ++ /** ++ * Check movement from origin to target. Returns last available point in the checked path. ++ * @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 {@link Location} : Last point where object can walk (just before wall) ++ */ ++ public Location canMoveToTargetLoc(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) ++ { ++ // Mobius: Double check for doors before normal checkMove to avoid exploiting key movement. ++ if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, instance, false)) + { +- return new Location(x, y, getHeight(x, y, z)); ++ return new Location(ox, oy, oz); + } ++ if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, instance)) ++ { ++ return new Location(ox, oy, oz); ++ } + +- 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 = z; ++ // get origin and check existing geo coordinates ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) ++ { ++ return new Location(tx, ty, tz); ++ } + +- while (pointIter.next()) ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coordinates ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) + { +- final int curX = pointIter.x(); +- final int curY = pointIter.y(); +- final int curZ = getNearestZ(curX, curY, prevZ); +- +- if (hasGeoPos(prevX, prevY)) +- { +- final int nswe = GeoUtils.computeNswe(prevX, prevY, curX, curY); +- if (!checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, nswe)) +- { +- // Can't move, return previous location. +- return new Location(getWorldX(prevX), getWorldY(prevY), prevZ); +- } +- } +- +- prevX = curX; +- prevY = curY; +- prevZ = curZ; ++ return new Location(tx, ty, tz); + } + +- if (hasGeoPos(prevX, prevY) && (prevZ != tz)) ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // target coordinates reached ++ if ((gox == gtx) && (goy == gty) && (goz == gtz)) + { +- // Different floors, return start location. +- return new Location(x, y, z); ++ return new Location(tx, ty, tz); + } + +- return new Location(tx, ty, tz); ++ // perform geodata check ++ return checkMove(gox, goy, goz, gtx, gty, gtz, instance); + } + + /** +- * 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 fromZvalue 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 toZvalue 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 ++ * With this method you can check if a position is visible or can be reached by beeline movement.
++ * 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 gox : origin X geodata coordinate ++ * @param goy : origin Y geodata coordinate ++ * @param goz : origin Z geodata coordinate ++ * @param gtx : target X geodata coordinate ++ * @param gty : target Y geodata coordinate ++ * @param gtz : target Z geodata coordinate ++ * @param instance ++ * @return {@link GeoLocation} : The last allowed point of movement. + */ +- public boolean canMoveToTarget(int fromX, int fromY, int fromZvalue, int toX, int toY, int toZvalue, Instance instance) ++ protected final GeoLocation checkMove(int gox, int goy, int goz, int gtx, int gty, int gtz, Instance instance) + { +- final int geoX = getGeoX(fromX); +- final int geoY = getGeoY(fromY); +- final int fromZ = getNearestZ(geoX, geoY, fromZvalue); +- final int tGeoX = getGeoX(toX); +- final int tGeoY = getGeoY(toY); +- final int toZ = getNearestZ(tGeoX, tGeoY, toZvalue); +- +- if (DoorData.getInstance().checkIfDoorsBetween(fromX, fromY, fromZ, toX, toY, toZ, instance, false)) ++ if (DoorData.getInstance().checkIfDoorsBetween(gox, goy, goz, gtx, gty, gtz, instance, false)) + { +- return false; ++ return new GeoLocation(gox, goy, goz); + } +- if (FenceData.getInstance().checkIfFenceBetween(fromX, fromY, fromZ, toX, toY, toZ, instance)) ++ if (FenceData.getInstance().checkIfFenceBetween(gox, goy, goz, gtx, gty, gtz, instance)) + { +- return false; ++ return new GeoLocation(gox, goy, goz); + } +- if (WarpedSpaceManager.getInstance().checkForWarpedSpace(fromX, fromY, fromZ, toX, toY, toZ, instance)) ++ if (WarpedSpaceManager.getInstance().checkForWarpedSpace(gox, goy, goz, gtx, gty, gtz, instance)) + { +- return false; ++ return new GeoLocation(gox, goy, goz); + } + +- 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 = fromZ; ++ // get X delta, signum and direction flag ++ final int dx = Math.abs(gtx - gox); ++ final int sx = gox < gtx ? 1 : -1; ++ final byte dirX = sx > 0 ? GeoStructure.CELL_FLAG_E : GeoStructure.CELL_FLAG_W; + +- while (pointIter.next()) ++ // get Y delta, signum and direction flag ++ final int dy = Math.abs(gty - goy); ++ final int sy = goy < gty ? 1 : -1; ++ final byte dirY = sy > 0 ? GeoStructure.CELL_FLAG_S : GeoStructure.CELL_FLAG_N; ++ ++ // get direction flag for diagonal movement ++ final byte dirXY = getDirXY(dirX, dirY); ++ ++ // delta, determines axis to move on (+..X axis, -..Y axis) ++ int d = dx - dy; ++ ++ // NSWE direction of movement ++ byte direction; ++ ++ // load pointer coordinates ++ int gpx = gox; ++ int gpy = goy; ++ int gpz = goz; ++ ++ // load next pointer ++ int nx = gpx; ++ int ny = gpy; ++ ++ // loop ++ int count = 0; ++ while (count++ < Config.MAX_ITERATIONS) + { +- final int curX = pointIter.x(); +- final int curY = pointIter.y(); +- final int curZ = getNearestZ(curX, curY, prevZ); ++ direction = 0; + +- if (hasGeoPos(prevX, prevY)) ++ // calculate next point coordinates ++ final int e2 = 2 * d; ++ if ((e2 > -dy) && (e2 < dx)) + { +- final int nswe = GeoUtils.computeNswe(prevX, prevY, curX, curY); +- if (!checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, nswe)) ++ d -= dy; ++ d += dx; ++ nx += sx; ++ ny += sy; ++ direction |= dirXY; ++ } ++ else if (e2 > -dy) ++ { ++ d -= dy; ++ nx += sx; ++ direction |= dirX; ++ } ++ else if (e2 < dx) ++ { ++ d += dx; ++ ny += sy; ++ direction |= dirY; ++ } ++ ++ // obstacle found, return ++ if ((getNsweNearest(gpx, gpy, gpz) & direction) == 0) ++ { ++ return new GeoLocation(gpx, gpy, gpz); ++ } ++ ++ // update pointer coordinates ++ gpx = nx; ++ gpy = ny; ++ gpz = getHeightNearest(nx, ny, gpz); ++ ++ // target coordinates reached ++ if ((gpx == gtx) && (gpy == gty)) ++ { ++ if (gpz == gtz) + { +- return false; ++ // path found, Z coordinates are okay, return target point ++ return new GeoLocation(gtx, gty, gtz); + } ++ ++ // path found, Z coordinates are not okay, return last good point ++ return new GeoLocation(gpx, gpy, gpz); + } ++ } ++ ++ return new GeoLocation(gox, goy, goz); ++ } ++ ++ /** ++ * Returns diagonal NSWE flag format of combined two NSWE flags. ++ * @param dirX : X direction NSWE flag ++ * @param dirY : Y direction NSWE flag ++ * @return byte : NSWE flag of combined direction ++ */ ++ private static byte getDirXY(byte dirX, byte dirY) ++ { ++ // check axis directions ++ if (dirY == GeoStructure.CELL_FLAG_N) ++ { ++ if (dirX == GeoStructure.CELL_FLAG_W) ++ { ++ return GeoStructure.CELL_FLAG_NW; ++ } + +- prevX = curX; +- prevY = curY; +- prevZ = curZ; ++ return GeoStructure.CELL_FLAG_NE; + } + +- if (hasGeoPos(prevX, prevY) && (prevZ != toZ)) ++ if (dirX == GeoStructure.CELL_FLAG_W) + { +- // Different floors. +- return false; ++ return GeoStructure.CELL_FLAG_SW; + } + +- return true; ++ return GeoStructure.CELL_FLAG_SE; + } + ++ /** ++ * 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 ++ */ ++ public List findPath(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) ++ { ++ return null; ++ } ++ ++ /** ++ * Returns the instance of the {@link GeoEngine}. ++ * @return {@link GeoEngine} : The instance. ++ */ + public static GeoEngine getInstance() + { + return SingletonHolder.INSTANCE; +@@ -752,6 +947,6 @@ + + private static class SingletonHolder + { +- protected static final GeoEngine INSTANCE = new GeoEngine(); ++ protected static final GeoEngine INSTANCE = Config.PATHFINDING ? new GeoEnginePathfinding() : new GeoEngine(); + } +-} ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/GeoEnginePathfinding.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/GeoEnginePathfinding.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/GeoEnginePathfinding.java (working copy) +@@ -20,88 +20,84 @@ + import java.util.LinkedList; + import java.util.List; + import java.util.ListIterator; +-import java.util.logging.Level; +-import java.util.logging.Logger; + + import org.l2jmobius.Config; +-import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; +-import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +-import org.l2jmobius.gameserver.geoengine.pathfinding.CellNode; +-import org.l2jmobius.gameserver.geoengine.pathfinding.CellNodeBuffer; +-import org.l2jmobius.gameserver.geoengine.pathfinding.NodeLoc; +-import org.l2jmobius.gameserver.model.World; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoLocation; ++import org.l2jmobius.gameserver.geoengine.pathfinding.Node; ++import org.l2jmobius.gameserver.geoengine.pathfinding.NodeBuffer; ++import org.l2jmobius.gameserver.model.Location; + import org.l2jmobius.gameserver.model.instancezone.Instance; + + /** +- * @author -Nemesiss- ++ * @author Hasha + */ +-public class GeoEnginePathfinding ++final class GeoEnginePathfinding extends GeoEngine + { +- private static final Logger LOGGER = Logger.getLogger(GeoEnginePathfinding.class.getName()); ++ // pre-allocated buffers ++ private final BufferHolder[] _buffers; + +- private BufferInfo[] _buffers; +- + protected GeoEnginePathfinding() + { +- try ++ super(); ++ ++ final String[] array = Config.PATHFIND_BUFFERS.split(";"); ++ _buffers = new BufferHolder[array.length]; ++ int count = 0; ++ for (int i = 0; i < array.length; i++) + { +- final String[] array = Config.PATHFIND_BUFFERS.split(";"); ++ final String buf = array[i]; ++ final String[] args = buf.split("x"); + +- _buffers = new BufferInfo[array.length]; +- +- String buf; +- String[] args; +- for (int i = 0; i < array.length; i++) ++ try + { +- buf = array[i]; +- args = buf.split("x"); +- if (args.length != 2) +- { +- throw new Exception("Invalid buffer definition: " + buf); +- } +- +- _buffers[i] = new BufferInfo(Integer.parseInt(args[0]), Integer.parseInt(args[1])); ++ final int size = Integer.parseInt(args[1]); ++ count += size; ++ _buffers[i] = new BufferHolder(Integer.parseInt(args[0]), size); + } ++ catch (Exception e) ++ { ++ LOGGER.warning("GeoEnginePathfinding: Can not load buffer setting: " + buf); ++ } + } +- catch (Exception e) +- { +- LOGGER.log(Level.WARNING, "CellPathFinding: Problem during buffer init: " + e.getMessage(), e); +- throw new Error("CellPathFinding: load aborted"); +- } ++ ++ LOGGER.info("GeoEnginePathfinding: Loaded " + count + " node buffers."); + } + +- public boolean pathNodesExist(short regionoffset) ++ @Override ++ public List findPath(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + { +- return false; +- } +- +- public List findPath(int x, int y, int z, int tx, int ty, int tz, Instance instance) +- { +- final int gx = GeoEngine.getInstance().getGeoX(x); +- final int gy = GeoEngine.getInstance().getGeoY(y); +- if (!GeoEngine.getInstance().hasGeo(x, y)) ++ // get origin and check existing geo coords ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) + { + 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)) ++ ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coords ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) + { + 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)))); ++ ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // Prepare buffer for pathfinding calculations ++ final NodeBuffer buffer = getBuffer(64 + (2 * Math.max(Math.abs(gox - gtx), Math.abs(goy - gty)))); + if (buffer == null) + { + return null; + } + +- List path = null; ++ // find path ++ List path = null; + try + { +- final CellNode result = buffer.findPath(gx, gy, gz, gtx, gty, gtz); +- ++ final Node result = buffer.findPath(gox, goy, goz, gtx, gty, gtz); + if (result == null) + { + return null; +@@ -125,33 +121,45 @@ + return path; + } + +- int currentX, currentY, currentZ; +- ListIterator middlePoint; ++ // get path list iterator ++ final ListIterator point = path.listIterator(); + +- middlePoint = path.listIterator(); +- currentX = x; +- currentY = y; +- currentZ = z; ++ // get node A (origin) ++ int nodeAx = gox; ++ int nodeAy = goy; ++ short nodeAz = goz; + +- while (middlePoint.hasNext()) ++ // get node B ++ GeoLocation nodeB = (GeoLocation) point.next(); ++ ++ // iterate thought the path to optimize it ++ int count = 0; ++ while (point.hasNext() && (count++ < Config.MAX_ITERATIONS)) + { +- final AbstractNodeLoc locMiddle = middlePoint.next(); +- if (!middlePoint.hasNext()) +- { +- break; +- } ++ // get node C ++ final GeoLocation nodeC = (GeoLocation) path.get(point.nextIndex()); + +- final AbstractNodeLoc locEnd = path.get(middlePoint.nextIndex()); +- if (GeoEngine.getInstance().canMoveToTarget(currentX, currentY, currentZ, locEnd.getX(), locEnd.getY(), locEnd.getZ(), instance)) ++ // check movement from node A to node C ++ final GeoLocation loc = checkMove(nodeAx, nodeAy, nodeAz, nodeC.getGeoX(), nodeC.getGeoY(), nodeC.getZ(), instance); ++ if ((loc.getGeoX() == nodeC.getGeoX()) && (loc.getGeoY() == nodeC.getGeoY())) + { +- middlePoint.remove(); ++ // can move from node A to node C ++ ++ // remove node B ++ point.remove(); + } + else + { +- currentX = locMiddle.getX(); +- currentY = locMiddle.getY(); +- currentZ = locMiddle.getZ(); ++ // can not move from node A to node C ++ ++ // set node A (node B is part of path, update A coordinates) ++ nodeAx = nodeB.getGeoX(); ++ nodeAy = nodeB.getGeoY(); ++ nodeAz = (short) nodeB.getZ(); + } ++ ++ // set node B ++ nodeB = (GeoLocation) point.next(); + } + + return path; +@@ -158,144 +166,102 @@ + } + + /** +- * Convert geodata position to pathnode position +- * @param geo_pos +- * @return pathnode position ++ * Create list of node locations as result of calculated buffer node tree. ++ * @param node : the entry point ++ * @return List : list of node location + */ +- public short getNodePos(int geo_pos) ++ private static List constructPath(Node node) + { +- return (short) (geo_pos >> 3); // OK? +- } +- +- /** +- * Convert node position to pathnode block position +- * @param node_pos +- * @return pathnode block position (0...255) +- */ +- public short getNodeBlock(int node_pos) +- { +- return (short) (node_pos % 256); +- } +- +- public byte getRegionX(int node_pos) +- { +- return (byte) ((node_pos >> 8) + World.TILE_X_MIN); +- } +- +- public byte getRegionY(int node_pos) +- { +- return (byte) ((node_pos >> 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 node_x rx +- * @return +- */ +- public int calculateWorldX(short node_x) +- { +- return World.MAP_MIN_X + (node_x * 128) + 48; +- } +- +- /** +- * Convert pathnode y to World y position +- * @param node_y +- * @return +- */ +- public int calculateWorldY(short node_y) +- { +- return World.MAP_MIN_Y + (node_y * 128) + 48; +- } +- +- private List constructPath(AbstractNode nodeValue) +- { +- final LinkedList path = new LinkedList<>(); +- int previousDirectionX = Integer.MIN_VALUE; +- int previousDirectionY = Integer.MIN_VALUE; +- int directionX, directionY; ++ // create empty list ++ final LinkedList list = new LinkedList<>(); + +- AbstractNode node = nodeValue; +- while (node.getParent() != null) ++ // set direction X/Y ++ int dx = 0; ++ int dy = 0; ++ ++ // get target parent ++ Node target = node; ++ Node parent = target.getParent(); ++ ++ // while parent exists ++ int count = 0; ++ while ((parent != null) && (count++ < Config.MAX_ITERATIONS)) + { +- directionX = node.getLoc().getNodeX() - node.getParent().getLoc().getNodeX(); +- directionY = node.getLoc().getNodeY() - node.getParent().getLoc().getNodeY(); ++ // get parent <> target direction X/Y ++ final int nx = parent.getLoc().getGeoX() - target.getLoc().getGeoX(); ++ final int ny = parent.getLoc().getGeoY() - target.getLoc().getGeoY(); + +- // only add a new route point if moving direction changes +- if ((directionX != previousDirectionX) || (directionY != previousDirectionY)) ++ // direction has changed? ++ if ((dx != nx) || (dy != ny)) + { +- previousDirectionX = directionX; +- previousDirectionY = directionY; ++ // add node to the beginning of the list ++ list.addFirst(target.getLoc()); + +- path.addFirst(node.getLoc()); +- node.setLoc(null); ++ // update direction X/Y ++ dx = nx; ++ dy = ny; + } + +- node = node.getParent(); ++ // move to next node, set target and get its parent ++ target = parent; ++ parent = target.getParent(); + } + +- return path; ++ // return list ++ return list; + } + +- private CellNodeBuffer alloc(int size) ++ /** ++ * Provides optimize selection of the buffer. When all pre-initialized buffer are locked, creates new buffer. ++ * @param size : pre-calculated minimal required size ++ * @return NodeBuffer : buffer ++ */ ++ private final NodeBuffer getBuffer(int size) + { +- CellNodeBuffer current = null; +- for (BufferInfo i : _buffers) ++ NodeBuffer current = null; ++ for (BufferHolder holder : _buffers) + { +- if (i.mapSize >= size) ++ // Find proper size of buffer ++ if (holder._size < size) + { +- for (CellNodeBuffer buf : i.buffer) ++ continue; ++ } ++ ++ // Find unlocked NodeBuffer ++ for (NodeBuffer buffer : holder._buffer) ++ { ++ if (!buffer.isLocked()) + { +- if (buf.lock()) +- { +- current = buf; +- break; +- } ++ continue; + } +- if (current != null) +- { +- break; +- } + +- // not found, allocate temporary buffer +- current = new CellNodeBuffer(i.mapSize); +- current.lock(); +- if (i.buffer.size() < i.count) +- { +- i.buffer.add(current); +- break; +- } ++ return buffer; + } ++ ++ // NodeBuffer not found, allocate temporary buffer ++ current = new NodeBuffer(holder._size); ++ current.isLocked(); + } + + return current; + } + +- private static final class BufferInfo ++ /** ++ * NodeBuffer container with specified size and count of separate buffers. ++ */ ++ private static final class BufferHolder + { +- final int mapSize; +- final int count; +- ArrayList buffer; ++ final int _size; ++ List _buffer; + +- public BufferInfo(int size, int cnt) ++ public BufferHolder(int size, int count) + { +- mapSize = size; +- count = cnt; +- buffer = new ArrayList<>(count); ++ _size = size; ++ _buffer = new ArrayList<>(count); ++ for (int i = 0; i < count; i++) ++ { ++ _buffer.add(new NodeBuffer(size)); ++ } + } + } +- +- public static GeoEnginePathfinding getInstance() +- { +- return SingletonHolder.INSTANCE; +- } +- +- private static class SingletonHolder +- { +- protected static final GeoEnginePathfinding INSTANCE = new GeoEnginePathfinding(); +- } +-} ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java (working copy) +@@ -0,0 +1,197 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++import java.io.IOException; ++ ++/** ++ * @author Hasha ++ */ ++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 height of cell, which is closest to given coordinates.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @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 getHeightNearestOriginal(int geoX, int geoY, int worldZ); ++ ++ /** ++ * Returns the height of cell, which is first above 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, above given coordinates. ++ */ ++ public abstract short getHeightAbove(int geoX, int geoY, int worldZ); ++ ++ /** ++ * Returns the height of cell, which is first below 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, below given coordinates. ++ */ ++ public abstract short getHeightBelow(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 the NSWE flag byte of cell, which is closest to given coordinates.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @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 getNsweNearestOriginal(int geoX, int geoY, int worldZ); ++ ++ /** ++ * Returns the NSWE flag byte of cell, which is first above 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 getNsweAbove(int geoX, int geoY, int worldZ); ++ ++ /** ++ * Returns the NSWE flag byte of cell, which is first below 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 getNsweBelow(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 above given coordinates.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @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 getIndexAboveOriginal(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 index to data of the cell, which is first layer below given coordinates.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @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 getIndexBelowOriginal(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 height of cell given by cell index.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @param index : Index of the cell. ++ * @return short : Cell geodata Z coordinate, below given coordinates. ++ */ ++ public abstract short getHeightOriginal(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); ++ ++ /** ++ * Returns the NSWE flag byte of cell given by cell index.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @param index : Index of the cell. ++ * @return short : Cell geodata Z coordinate, below given coordinates. ++ */ ++ public abstract byte getNsweOriginal(int index); ++ ++ /** ++ * Sets the NSWE flag byte of cell given by cell index. ++ * @param index : Index of the cell. ++ * @param nswe : New NSWE flag byte. ++ */ ++ public abstract void setNswe(int index, byte nswe); ++ ++ /** ++ * Saves the block in L2D format to {@link BufferedOutputStream}. Used only for L2D geodata conversion. ++ * @param stream : The stream. ++ * @throws IOException : Can't save the block to steam. ++ */ ++ public abstract void saveBlock(BufferedOutputStream stream) throws IOException; ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java (working copy) +@@ -0,0 +1,252 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++import java.io.IOException; ++import java.nio.ByteBuffer; ++ ++/** ++ * @author Hasha ++ */ ++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. ++ * @param format : GeoFormat specifying format of loaded data. ++ */ ++ public BlockComplex(ByteBuffer bb, GeoFormat format) ++ { ++ // initialize buffer ++ _buffer = new byte[GeoStructure.BLOCK_CELLS * 3]; ++ ++ // load data ++ for (int i = 0; i < GeoStructure.BLOCK_CELLS; i++) ++ { ++ if (format != GeoFormat.L2D) ++ { ++ // 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); ++ } ++ else ++ { ++ // get nswe ++ final byte nswe = bb.get(); ++ _buffer[i * 3] = nswe; ++ ++ // get height ++ final short height = bb.getShort(); ++ _buffer[(i * 3) + 1] = (byte) (height & 0x00FF); ++ _buffer[(i * 3) + 2] = (byte) (height >> 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 short getHeightNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getHeightNearest(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeightAbove(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 short height = (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ ++ // check and return height ++ return height > worldZ ? height : Short.MIN_VALUE; ++ } ++ ++ @Override ++ public short getHeightBelow(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 short height = (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ ++ // check and return height ++ return height < worldZ ? height : Short.MAX_VALUE; ++ } ++ ++ @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 byte getNsweNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getNsweNearest(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public byte getNsweAbove(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 ? _buffer[index] : 0; ++ } ++ ++ @Override ++ public byte getNsweBelow(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 ? _buffer[index] : 0; ++ } ++ ++ @Override ++ public 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 getIndexAboveOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexAbove(geoX, geoY, worldZ); ++ } ++ ++ @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 int getIndexBelowOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexBelow(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeight(int index) ++ { ++ return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ } ++ ++ @Override ++ public short getHeightOriginal(int index) ++ { ++ return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ } ++ ++ @Override ++ public byte getNswe(int index) ++ { ++ return _buffer[index]; ++ } ++ ++ @Override ++ public byte getNsweOriginal(int index) ++ { ++ return _buffer[index]; ++ } ++ ++ @Override ++ public void setNswe(int index, byte nswe) ++ { ++ _buffer[index] = nswe; ++ } ++ ++ @Override ++ public void saveBlock(BufferedOutputStream stream) throws IOException ++ { ++ // write block type ++ stream.write(GeoStructure.TYPE_COMPLEX_L2D); ++ ++ // write block data ++ stream.write(_buffer, 0, GeoStructure.BLOCK_CELLS * 3); ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java (working copy) +@@ -0,0 +1,176 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++import java.io.IOException; ++import java.nio.ByteBuffer; ++ ++/** ++ * @author Hasha ++ */ ++public class BlockFlat extends ABlock ++{ ++ protected final short _height; ++ protected byte _nswe; ++ ++ /** ++ * Creates FlatBlock. ++ * @param bb : Input byte buffer. ++ * @param format : GeoFormat specifying format of loaded data. ++ */ ++ public BlockFlat(ByteBuffer bb, GeoFormat format) ++ { ++ _height = bb.getShort(); ++ _nswe = format != GeoFormat.L2D ? 0x0F : (byte) (0xFF); ++ if (format == GeoFormat.L2OFF) ++ { ++ bb.getShort(); ++ } ++ } ++ ++ @Override ++ public boolean hasGeoPos() ++ { ++ return true; ++ } ++ ++ @Override ++ public short getHeightNearest(int geoX, int geoY, int worldZ) ++ { ++ return _height; ++ } ++ ++ @Override ++ public short getHeightNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return _height; ++ } ++ ++ @Override ++ public short getHeightAbove(int geoX, int geoY, int worldZ) ++ { ++ // check and return height ++ return _height > worldZ ? _height : Short.MIN_VALUE; ++ } ++ ++ @Override ++ public short getHeightBelow(int geoX, int geoY, int worldZ) ++ { ++ // check and return height ++ return _height < worldZ ? _height : Short.MAX_VALUE; ++ } ++ ++ @Override ++ public byte getNsweNearest(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweAbove(int geoX, int geoY, int worldZ) ++ { ++ // check height and return nswe ++ return _height > worldZ ? _nswe : 0; ++ } ++ ++ @Override ++ public byte getNsweBelow(int geoX, int geoY, int worldZ) ++ { ++ // check height and return nswe ++ return _height < worldZ ? _nswe : 0; ++ } ++ ++ @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 getIndexAboveOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexAbove(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public int getIndexBelow(int geoX, int geoY, int worldZ) ++ { ++ // check height and return index ++ return _height < worldZ ? 0 : -1; ++ } ++ ++ @Override ++ public int getIndexBelowOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexBelow(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeight(int index) ++ { ++ return _height; ++ } ++ ++ @Override ++ public short getHeightOriginal(int index) ++ { ++ return _height; ++ } ++ ++ @Override ++ public byte getNswe(int index) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweOriginal(int index) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public void setNswe(int index, byte nswe) ++ { ++ _nswe = nswe; ++ } ++ ++ @Override ++ public void saveBlock(BufferedOutputStream stream) throws IOException ++ { ++ // write block type ++ stream.write(GeoStructure.TYPE_FLAT_L2D); ++ ++ // write height ++ stream.write((byte) (_height & 0x00FF)); ++ stream.write((byte) (_height >> 8)); ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java (working copy) +@@ -0,0 +1,465 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++import java.io.IOException; ++import java.nio.ByteBuffer; ++import java.nio.ByteOrder; ++import java.util.Arrays; ++ ++/** ++ * @author Hasha ++ */ ++public class BlockMultilayer extends ABlock ++{ ++ private static final int MAX_LAYERS = Byte.MAX_VALUE; ++ ++ private static ByteBuffer _temp; ++ ++ /** ++ * Initializes the temporarily buffer. ++ */ ++ public static void initialize() ++ { ++ // initialize temporarily buffer and sorting mechanism ++ _temp = ByteBuffer.allocate(GeoStructure.BLOCK_CELLS * MAX_LAYERS * 3); ++ _temp.order(ByteOrder.LITTLE_ENDIAN); ++ } ++ ++ /** ++ * Releases temporarily buffer. ++ */ ++ public static void release() ++ { ++ _temp = null; ++ } ++ ++ protected byte[] _buffer; ++ ++ /** ++ * Implicit constructor for children class. ++ */ ++ protected BlockMultilayer() ++ { ++ _buffer = null; ++ } ++ ++ /** ++ * Creates MultilayerBlock. ++ * @param bb : Input byte buffer. ++ * @param format : GeoFormat specifying format of loaded data. ++ */ ++ public BlockMultilayer(ByteBuffer bb, GeoFormat format) ++ { ++ // 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 = format != GeoFormat.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++) ++ { ++ if (format != GeoFormat.L2D) ++ { ++ // get data ++ final short data = bb.getShort(); ++ ++ // add nswe and height ++ _temp.put((byte) (data & 0x000F)); ++ _temp.putShort((short) ((short) (data & 0xFFF0) >> 1)); ++ } ++ else ++ { ++ // add nswe ++ _temp.put(bb.get()); ++ ++ // add height ++ _temp.putShort(bb.getShort()); ++ } ++ } ++ } ++ ++ // 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 short getHeightNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getHeightNearest(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeightAbove(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 height ++ if (height > worldZ) ++ { ++ return (short) height; ++ } ++ ++ // move index to next layer ++ index -= 3; ++ } ++ ++ // none layer found, return minimum value ++ return Short.MIN_VALUE; ++ } ++ ++ @Override ++ public short getHeightBelow(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 height ++ if (height < worldZ) ++ { ++ return (short) height; ++ } ++ ++ // move index to next layer ++ index += 3; ++ } ++ ++ // none layer found, return maximum value ++ return Short.MAX_VALUE; ++ } ++ ++ @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 byte getNsweNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getNsweNearest(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public byte getNsweAbove(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 nswe ++ if (height > worldZ) ++ { ++ return _buffer[index]; ++ } ++ ++ // move index to next layer ++ index -= 3; ++ } ++ ++ // none layer found, block movement ++ return 0; ++ } ++ ++ @Override ++ public byte getNsweBelow(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 nswe ++ if (height < worldZ) ++ { ++ return _buffer[index]; ++ } ++ ++ // move index to next layer ++ index += 3; ++ } ++ ++ // none layer found, block movement ++ return 0; ++ } ++ ++ @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 first layer data (first from bottom) ++ byte layers = _buffer[index++]; ++ ++ // loop though all cell layers, find closest layer ++ 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; ++ } ++ ++ // none layer found ++ return -1; ++ } ++ ++ @Override ++ public int getIndexAboveOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexAbove(geoX, geoY, worldZ); ++ } ++ ++ @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; ++ } ++ ++ // none layer found ++ return -1; ++ } ++ ++ @Override ++ public int getIndexBelowOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexBelow(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeight(int index) ++ { ++ // get height ++ return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ } ++ ++ @Override ++ public short getHeightOriginal(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]; ++ } ++ ++ @Override ++ public byte getNsweOriginal(int index) ++ { ++ // get nswe ++ return _buffer[index]; ++ } ++ ++ @Override ++ public void setNswe(int index, byte nswe) ++ { ++ // set nswe ++ _buffer[index] = nswe; ++ } ++ ++ @Override ++ public void saveBlock(BufferedOutputStream stream) throws IOException ++ { ++ // write block type ++ stream.write(GeoStructure.TYPE_MULTILAYER_L2D); ++ ++ // for each cell ++ int index = 0; ++ for (int i = 0; i < GeoStructure.BLOCK_CELLS; i++) ++ { ++ // write layers count ++ final byte layers = _buffer[index++]; ++ stream.write(layers); ++ ++ // write cell data ++ stream.write(_buffer, index, layers * 3); ++ ++ // move index to next cell ++ index += layers * 3; ++ } ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java (working copy) +@@ -0,0 +1,150 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++ ++/** ++ * @author Hasha ++ */ ++public class BlockNull extends ABlock ++{ ++ private final byte _nswe; ++ ++ public BlockNull() ++ { ++ _nswe = (byte) 0xFF; ++ } ++ ++ @Override ++ public boolean hasGeoPos() ++ { ++ return false; ++ } ++ ++ @Override ++ public short getHeightNearest(int geoX, int geoY, int worldZ) ++ { ++ return (short) worldZ; ++ } ++ ++ @Override ++ public short getHeightNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return (short) worldZ; ++ } ++ ++ @Override ++ public short getHeightAbove(int geoX, int geoY, int worldZ) ++ { ++ return (short) worldZ; ++ } ++ ++ @Override ++ public short getHeightBelow(int geoX, int geoY, int worldZ) ++ { ++ return (short) worldZ; ++ } ++ ++ @Override ++ public byte getNsweNearest(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweAbove(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweBelow(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) ++ { ++ return 0; ++ } ++ ++ @Override ++ public int getIndexAboveOriginal(int geoX, int geoY, int worldZ) ++ { ++ return 0; ++ } ++ ++ @Override ++ public int getIndexBelow(int geoX, int geoY, int worldZ) ++ { ++ return 0; ++ } ++ ++ @Override ++ public int getIndexBelowOriginal(int geoX, int geoY, int worldZ) ++ { ++ return 0; ++ } ++ ++ @Override ++ public short getHeight(int index) ++ { ++ return 0; ++ } ++ ++ @Override ++ public short getHeightOriginal(int index) ++ { ++ return 0; ++ } ++ ++ @Override ++ public byte getNswe(int index) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweOriginal(int index) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public void setNswe(int index, byte nswe) ++ { ++ } ++ ++ @Override ++ public void saveBlock(BufferedOutputStream stream) ++ { ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java (nonexistent) +@@ -1,48 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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() +- { +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/ComplexBlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/ComplexBlock.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/ComplexBlock.java (nonexistent) +@@ -1,77 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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; +- +-/** +- * @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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/FlatBlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/FlatBlock.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/FlatBlock.java (nonexistent) +@@ -1,56 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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; +- +-/** +- * @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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/GeoFormat.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/GeoFormat.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/GeoFormat.java (working copy) +@@ -0,0 +1,39 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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 Hasha ++ */ ++public enum GeoFormat ++{ ++ L2J("%d_%d.l2j"), ++ L2OFF("%d_%d_conv.dat"), ++ L2D("%d_%d.l2d"); ++ ++ private final String _filename; ++ ++ private GeoFormat(String filename) ++ { ++ _filename = filename; ++ } ++ ++ public String getFilename() ++ { ++ return _filename; ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/GeoLocation.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/GeoLocation.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/GeoLocation.java (working copy) +@@ -0,0 +1,67 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.geoengine.GeoEngine; ++import org.l2jmobius.gameserver.model.Location; ++ ++/** ++ * @author Hasha ++ */ ++public class GeoLocation extends Location ++{ ++ private byte _nswe; ++ ++ public GeoLocation(int x, int y, int z) ++ { ++ super(x, y, GeoEngine.getInstance().getHeightNearest(x, y, z)); ++ _nswe = GeoEngine.getInstance().getNsweNearest(x, y, z); ++ } ++ ++ public void set(int x, int y, short z) ++ { ++ super.setXYZ(x, y, GeoEngine.getInstance().getHeightNearest(x, y, z)); ++ _nswe = GeoEngine.getInstance().getNsweNearest(x, y, z); ++ } ++ ++ public int getGeoX() ++ { ++ return _x; ++ } ++ ++ public int getGeoY() ++ { ++ return _y; ++ } ++ ++ @Override ++ public int getX() ++ { ++ return GeoEngine.getWorldX(_x); ++ } ++ ++ @Override ++ public int getY() ++ { ++ return GeoEngine.getWorldY(_y); ++ } ++ ++ public byte getNSWE() ++ { ++ return _nswe; ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java (working copy) +@@ -0,0 +1,70 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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; ++ ++/** ++ * @author Hasha ++ */ ++public class GeoStructure ++{ ++ // cells ++ public static final byte CELL_FLAG_E = 1 << 0; ++ public static final byte CELL_FLAG_W = 1 << 1; ++ public static final byte CELL_FLAG_S = 1 << 2; ++ public static final byte CELL_FLAG_N = 1 << 3; ++ public static final byte CELL_FLAG_SE = 1 << 4; ++ public static final byte CELL_FLAG_SW = 1 << 5; ++ public static final byte CELL_FLAG_NE = 1 << 6; ++ public static final byte CELL_FLAG_NW = (byte) (1 << 7); ++ ++ public static final int CELL_HEIGHT = 8; ++ public static final int CELL_IGNORE_HEIGHT = CELL_HEIGHT * 6; ++ ++ // blocks ++ public static final byte TYPE_FLAT_L2J_L2OFF = 0; ++ public static final byte TYPE_FLAT_L2D = (byte) 0xD0; ++ public static final byte TYPE_COMPLEX_L2J = 1; ++ public static final byte TYPE_COMPLEX_L2OFF = 0x40; ++ public static final byte TYPE_COMPLEX_L2D = (byte) 0xD1; ++ 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) ++ public static final byte TYPE_MULTILAYER_L2D = (byte) 0xD2; ++ ++ 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; ++ ++ // regions ++ 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; ++ ++ // global geodata ++ private static final int GEO_REGIONS_X = ((World.TILE_X_MAX - World.TILE_X_MIN) + 1); ++ private 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 +Index: java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java (nonexistent) +@@ -1,42 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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); +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/IGeoObject.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/IGeoObject.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/IGeoObject.java (working copy) +@@ -0,0 +1,53 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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 Hasha ++ */ ++public interface IGeoObject ++{ ++ /** ++ * Returns geodata X coordinate of the {@link IGeoObject}. ++ * @return int : Geodata X coordinate. ++ */ ++ int getGeoX(); ++ ++ /** ++ * Returns geodata Y coordinate of the {@link IGeoObject}. ++ * @return int : Geodata Y coordinate. ++ */ ++ int getGeoY(); ++ ++ /** ++ * Returns geodata Z coordinate of the {@link IGeoObject}. ++ * @return int : Geodata Z coordinate. ++ */ ++ int getGeoZ(); ++ ++ /** ++ * Returns height of the {@link IGeoObject}. ++ * @return int : Height. ++ */ ++ int getHeight(); ++ ++ /** ++ * Returns {@link IGeoObject} data. ++ * @return byte[][] : {@link IGeoObject} data. ++ */ ++ byte[][] getObjectGeoData(); ++} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java (nonexistent) +@@ -1,47 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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(); +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/MultilayerBlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/MultilayerBlock.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/MultilayerBlock.java (nonexistent) +@@ -1,183 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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; +- +-/** +- * @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); +- +- // 1 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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/NullRegion.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/NullRegion.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/NullRegion.java (nonexistent) +@@ -1,55 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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 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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/Region.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/Region.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/Region.java (nonexistent) +@@ -1,92 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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; +- +-/** +- * @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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java (nonexistent) +@@ -1,87 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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 T _loc; +- private AbstractNode _parent; +- +- public AbstractNode(T loc) +- { +- _loc = loc; +- } +- +- public void setParent(AbstractNode p) +- { +- _parent = p; +- } +- +- public AbstractNode getParent() +- { +- return _parent; +- } +- +- public T getLoc() +- { +- return _loc; +- } +- +- public void setLoc(T l) +- { +- _loc = l; +- } +- +- @Override +- public int hashCode() +- { +- final int prime = 31; +- int result = 1; +- result = (prime * result) + ((_loc == null) ? 0 : _loc.hashCode()); +- return result; +- } +- +- @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 +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java (nonexistent) +@@ -1,33 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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 int getNodeX(); +- +- public abstract int getNodeY(); +-} +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNode.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNode.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNode.java (nonexistent) +@@ -1,67 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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 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 +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNodeBuffer.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNodeBuffer.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNodeBuffer.java (nonexistent) +@@ -1,348 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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.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 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) +- { +- _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(); +- } +- +- 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 void getNeighbors() +- { +- if (!_current.getLoc().canGoAll()) +- { +- 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); +- } +- +- // SouthEast +- if ((nodeE != null) && (nodeS != null)) +- { +- if (nodeE.getLoc().canGoSouth() && nodeS.getLoc().canGoEast()) +- { +- addNode(x + 1, y + 1, z, true); +- } +- } +- +- // SouthWest +- if ((nodeS != null) && (nodeW != null)) +- { +- if (nodeW.getLoc().canGoSouth() && nodeS.getLoc().canGoWest()) +- { +- addNode(x - 1, y + 1, z, true); +- } +- } +- +- // NorthEast +- if ((nodeN != null) && (nodeE != null)) +- { +- if (nodeE.getLoc().canGoNorth() && nodeN.getLoc().canGoEast()) +- { +- addNode(x + 1, y - 1, z, true); +- } +- } +- +- // NorthWest +- if ((nodeN != null) && (nodeW != null)) +- { +- if (nodeW.getLoc().canGoNorth() && nodeN.getLoc().canGoWest()) +- { +- addNode(x - 1, y - 1, z, true); +- } +- } +- } +- +- private 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(); +- // reinit node if needed +- if (result.getLoc() != null) +- { +- result.getLoc().set(x, y, z); +- } +- else +- { +- result.setLoc(new NodeLoc(x, y, z)); +- } +- } +- +- return result; +- } +- +- private 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 boolean isHighWeight(int x, int y, int z) +- { +- final CellNode result = getNode(x, y, z); +- if (result == null) +- { +- return true; +- } +- +- if (!result.getLoc().canGoAll()) +- { +- return true; +- } +- if (Math.abs(result.getLoc().getZ() - z) > 16) +- { +- return true; +- } +- +- return false; +- } +- +- private 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 +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java (working copy) +@@ -0,0 +1,87 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.geodata.GeoLocation; ++ ++/** ++ * @author Hasha ++ */ ++public class Node ++{ ++ // node coords and nswe flag ++ private GeoLocation _loc; ++ ++ // node parent (for reverse path construction) ++ private Node _parent; ++ // node child (for moving over nodes during iteration) ++ private Node _child; ++ ++ // node G cost (movement cost = parent movement cost + current movement cost) ++ private double _cost = -1000; ++ ++ public void setLoc(int x, int y, int z) ++ { ++ _loc = new GeoLocation(x, y, z); ++ } ++ ++ public GeoLocation getLoc() ++ { ++ return _loc; ++ } ++ ++ public void setParent(Node parent) ++ { ++ _parent = parent; ++ } ++ ++ public Node getParent() ++ { ++ return _parent; ++ } ++ ++ public void setChild(Node child) ++ { ++ _child = child; ++ } ++ ++ public Node getChild() ++ { ++ return _child; ++ } ++ ++ public void setCost(double cost) ++ { ++ _cost = cost; ++ } ++ ++ public double getCost() ++ { ++ return _cost; ++ } ++ ++ public void free() ++ { ++ // reset node location ++ _loc = null; ++ ++ // reset node parent, child and cost ++ _parent = null; ++ _child = null; ++ _cost = -1000; ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java (working copy) +@@ -0,0 +1,306 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.concurrent.locks.ReentrantLock; ++ ++import org.l2jmobius.Config; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; ++ ++/** ++ * @author DS, Hasha; Credits to Diamond ++ */ ++public class NodeBuffer ++{ ++ private final ReentrantLock _lock = new ReentrantLock(); ++ private final int _size; ++ private final Node[][] _buffer; ++ ++ // center coordinates ++ private int _cx = 0; ++ private int _cy = 0; ++ ++ // target coordinates ++ private int _gtx = 0; ++ private int _gty = 0; ++ private short _gtz = 0; ++ ++ private Node _current = null; ++ ++ /** ++ * Constructor of NodeBuffer. ++ * @param size : one dimension size of buffer ++ */ ++ public NodeBuffer(int size) ++ { ++ // set size ++ _size = size; ++ ++ // initialize buffer ++ _buffer = new Node[size][size]; ++ for (int x = 0; x < size; x++) ++ { ++ for (int y = 0; y < size; y++) ++ { ++ _buffer[x][y] = 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 Node : first node of path ++ */ ++ public Node findPath(int gox, int goy, short goz, int gtx, int gty, short gtz) ++ { ++ // set coordinates (middle of the line (gox,goy) - (gtx,gty), will be in the center of the buffer) ++ _cx = gox + ((gtx - gox - _size) / 2); ++ _cy = goy + ((gty - goy - _size) / 2); ++ ++ _gtx = gtx; ++ _gty = gty; ++ _gtz = gtz; ++ ++ _current = getNode(gox, goy, goz); ++ _current.setCost(getCostH(gox, goy, goz)); ++ ++ int count = 0; ++ do ++ { ++ // reached target? ++ if ((_current.getLoc().getGeoX() == _gtx) && (_current.getLoc().getGeoY() == _gty) && (Math.abs(_current.getLoc().getZ() - _gtz) < 8)) ++ { ++ return _current; ++ } ++ ++ // expand current node ++ expand(); ++ ++ // move pointer ++ _current = _current.getChild(); ++ } ++ while ((_current != null) && (count++ < Config.MAX_ITERATIONS)); ++ ++ return null; ++ } ++ ++ public boolean isLocked() ++ { ++ return _lock.tryLock(); ++ } ++ ++ public void free() ++ { ++ _current = null; ++ ++ for (Node[] nodes : _buffer) ++ { ++ for (Node node : nodes) ++ { ++ if (node.getLoc() != null) ++ { ++ node.free(); ++ } ++ } ++ } ++ ++ _lock.unlock(); ++ } ++ ++ /** ++ * Check _current Node and add its neighbors to the buffer. ++ */ ++ private final void expand() ++ { ++ // can't move anywhere, don't expand ++ final byte nswe = _current.getLoc().getNSWE(); ++ if (nswe == 0) ++ { ++ return; ++ } ++ ++ // get geo coords of the node to be expanded ++ final int x = _current.getLoc().getGeoX(); ++ final int y = _current.getLoc().getGeoY(); ++ final short z = (short) _current.getLoc().getZ(); ++ ++ // can move north, expand ++ if ((nswe & GeoStructure.CELL_FLAG_N) != 0) ++ { ++ addNode(x, y - 1, z, Config.BASE_WEIGHT); ++ } ++ ++ // can move south, expand ++ if ((nswe & GeoStructure.CELL_FLAG_S) != 0) ++ { ++ addNode(x, y + 1, z, Config.BASE_WEIGHT); ++ } ++ ++ // can move west, expand ++ if ((nswe & GeoStructure.CELL_FLAG_W) != 0) ++ { ++ addNode(x - 1, y, z, Config.BASE_WEIGHT); ++ } ++ ++ // can move east, expand ++ if ((nswe & GeoStructure.CELL_FLAG_E) != 0) ++ { ++ addNode(x + 1, y, z, Config.BASE_WEIGHT); ++ } ++ ++ // can move north-west, expand ++ if ((nswe & GeoStructure.CELL_FLAG_NW) != 0) ++ { ++ addNode(x - 1, y - 1, z, Config.DIAGONAL_WEIGHT); ++ } ++ ++ // can move north-east, expand ++ if ((nswe & GeoStructure.CELL_FLAG_NE) != 0) ++ { ++ addNode(x + 1, y - 1, z, Config.DIAGONAL_WEIGHT); ++ } ++ ++ // can move south-west, expand ++ if ((nswe & GeoStructure.CELL_FLAG_SW) != 0) ++ { ++ addNode(x - 1, y + 1, z, Config.DIAGONAL_WEIGHT); ++ } ++ ++ // can move south-east, expand ++ if ((nswe & GeoStructure.CELL_FLAG_SE) != 0) ++ { ++ addNode(x + 1, y + 1, z, Config.DIAGONAL_WEIGHT); ++ } ++ } ++ ++ /** ++ * Returns node, if it exists in buffer. ++ * @param x : node X coord ++ * @param y : node Y coord ++ * @param z : node Z coord ++ * @return Node : node, if exits in buffer ++ */ ++ private final Node getNode(int x, int y, short z) ++ { ++ // check node X out of coordinates ++ final int ix = x - _cx; ++ if ((ix < 0) || (ix >= _size)) ++ { ++ return null; ++ } ++ ++ // check node Y out of coordinates ++ final int iy = y - _cy; ++ if ((iy < 0) || (iy >= _size)) ++ { ++ return null; ++ } ++ ++ // get node ++ final Node result = _buffer[ix][iy]; ++ ++ // check and update ++ if (result.getLoc() == null) ++ { ++ result.setLoc(x, y, z); ++ } ++ ++ // return node ++ return result; ++ } ++ ++ /** ++ * Add node given by coordinates to the buffer. ++ * @param x : geo X coord ++ * @param y : geo Y coord ++ * @param z : geo Z coord ++ * @param weight : weight of movement to new node ++ */ ++ private final void addNode(int x, int y, short z, int weight) ++ { ++ // get node to be expanded ++ final Node node = getNode(x, y, z); ++ if (node == null) ++ { ++ return; ++ } ++ ++ // Z distance between nearby cells is higher than cell size ++ if (node.getLoc().getZ() > (z + (2 * GeoStructure.CELL_HEIGHT))) ++ { ++ return; ++ } ++ ++ // node was already expanded, return ++ if (node.getCost() >= 0) ++ { ++ return; ++ } ++ ++ node.setParent(_current); ++ if (node.getLoc().getNSWE() != (byte) 0xFF) ++ { ++ node.setCost(getCostH(x, y, node.getLoc().getZ()) + (weight * Config.OBSTACLE_MULTIPLIER)); ++ } ++ else ++ { ++ node.setCost(getCostH(x, y, node.getLoc().getZ()) + weight); ++ } ++ ++ Node current = _current; ++ int count = 0; ++ while ((current.getChild() != null) && (count < (Config.MAX_ITERATIONS * 4))) ++ { ++ count++; ++ if (current.getChild().getCost() > node.getCost()) ++ { ++ node.setChild(current.getChild()); ++ break; ++ } ++ current = current.getChild(); ++ } ++ ++ if (count >= (Config.MAX_ITERATIONS * 4)) ++ { ++ System.err.println("Pathfinding: too long loop detected, cost:" + node.getCost()); ++ } ++ ++ current.setChild(node); ++ } ++ ++ /** ++ * @param x : node X coord ++ * @param y : node Y coord ++ * @param i : node Z coord ++ * @return double : node cost ++ */ ++ private final double getCostH(int x, int y, int i) ++ { ++ final int dX = x - _gtx; ++ final int dY = y - _gty; ++ final int dZ = (i - _gtz) / GeoStructure.CELL_HEIGHT; ++ ++ // return (Math.abs(dX) + Math.abs(dY) + Math.abs(dZ)) * Config.HEURISTIC_WEIGHT; // Manhattan distance ++ return Math.sqrt((dX * dX) + (dY * dY) + (dZ * dZ)) * Config.HEURISTIC_WEIGHT; // Direct distance ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeLoc.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeLoc.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeLoc.java (nonexistent) +@@ -1,183 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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.Cell; +- +-/** +- * @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 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 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; +- // return super.hashCode(); +- } +- +- @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; +- if (_x != other._x) +- { +- return false; +- } +- if (_y != other._y) +- { +- return false; +- } +- if (_goNorth != other._goNorth) +- { +- return false; +- } +- if (_goEast != other._goEast) +- { +- return false; +- } +- if (_goSouth != other._goSouth) +- { +- return false; +- } +- if (_goWest != other._goWest) +- { +- return false; +- } +- if (_geoHeight != other._geoHeight) +- { +- return false; +- } +- return true; +- } +-} +Index: java/org/l2jmobius/gameserver/model/actor/Creature.java +=================================================================== +--- java/org/l2jmobius/gameserver/model/actor/Creature.java (revision 8399) ++++ java/org/l2jmobius/gameserver/model/actor/Creature.java (working copy) +@@ -66,8 +66,6 @@ + import org.l2jmobius.gameserver.enums.TeleportWhereType; + import org.l2jmobius.gameserver.enums.UserInfoType; + import org.l2jmobius.gameserver.geoengine.GeoEngine; +-import org.l2jmobius.gameserver.geoengine.GeoEnginePathfinding; +-import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; + import org.l2jmobius.gameserver.instancemanager.IdManager; + import org.l2jmobius.gameserver.instancemanager.MapRegionManager; + import org.l2jmobius.gameserver.instancemanager.QuestManager; +@@ -2577,7 +2575,7 @@ + + public boolean disregardingGeodata; + public int onGeodataPathIndex; +- public List geoPath; ++ public List geoPath; + public int geoPathAccurateTx; + public int geoPathAccurateTy; + public int geoPathGtx; +@@ -3466,7 +3464,7 @@ + if (((originalDistance - distance) > 30) && !isControlBlocked() && !isInVehicle) + { + // Path calculation -- overrides previous movement check +- m.geoPath = GeoEnginePathfinding.getInstance().findPath(curX, curY, curZ, originalX, originalY, originalZ, getInstanceWorld()); ++ m.geoPath = GeoEngine.getInstance().findPath(curX, curY, curZ, originalX, originalY, originalZ, getInstanceWorld()); + if ((m.geoPath == null) || (m.geoPath.size() < 2)) // No path found + { + if (isPlayer() && !_isFlying && !isInWater) +Index: java/org/l2jmobius/gameserver/model/actor/instance/PlayerInstance.java +=================================================================== +--- java/org/l2jmobius/gameserver/model/actor/instance/PlayerInstance.java (revision 8397) ++++ java/org/l2jmobius/gameserver/model/actor/instance/PlayerInstance.java (working copy) +@@ -12704,7 +12704,7 @@ + { + if (Config.CORRECT_PLAYER_Z) + { +- final int nearestZ = GeoEngine.getInstance().getHigherHeight(getX(), getY(), getZ()); ++ final int nearestZ = GeoEngine.getInstance().getHeightNearest(getX(), getY(), getZ()); + if (getZ() < nearestZ) + { + teleToLocation(new Location(getX(), getY(), nearestZ)); +Index: java/org/l2jmobius/gameserver/util/GeoUtils.java +=================================================================== +--- java/org/l2jmobius/gameserver/util/GeoUtils.java (revision 8397) ++++ java/org/l2jmobius/gameserver/util/GeoUtils.java (working copy) +@@ -19,7 +19,7 @@ + import java.awt.Color; + + import org.l2jmobius.gameserver.geoengine.GeoEngine; +-import org.l2jmobius.gameserver.geoengine.geodata.Cell; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; + import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance; + import org.l2jmobius.gameserver.network.serverpackets.ExServerPrimitive; + +@@ -26,25 +26,25 @@ + /** + * @author HorridoJoho + */ +-public final class GeoUtils ++public class GeoUtils + { + public static void debug2DLine(PlayerInstance player, int x, int y, int tx, int ty, int z) + { +- final int gx = GeoEngine.getInstance().getGeoX(x); +- final int gy = GeoEngine.getInstance().getGeoY(y); ++ final int gx = GeoEngine.getGeoX(x); ++ final int gy = GeoEngine.getGeoY(y); + +- final int tgx = GeoEngine.getInstance().getGeoX(tx); +- final int tgy = GeoEngine.getInstance().getGeoY(ty); ++ final int tgx = GeoEngine.getGeoX(tx); ++ final int tgy = GeoEngine.getGeoY(ty); + + final ExServerPrimitive prim = new ExServerPrimitive("Debug2DLine", x, y, z); +- prim.addLine(Color.BLUE, GeoEngine.getInstance().getWorldX(gx), GeoEngine.getInstance().getWorldY(gy), z, GeoEngine.getInstance().getWorldX(tgx), GeoEngine.getInstance().getWorldY(tgy), z); ++ prim.addLine(Color.BLUE, GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), z, GeoEngine.getWorldX(tgx), GeoEngine.getWorldY(tgy), z); + + final LinePointIterator iter = new LinePointIterator(gx, gy, tgx, tgy); + + while (iter.next()) + { +- final int wx = GeoEngine.getInstance().getWorldX(iter.x()); +- final int wy = GeoEngine.getInstance().getWorldY(iter.y()); ++ final int wx = GeoEngine.getWorldX(iter.x()); ++ final int wy = GeoEngine.getWorldY(iter.y()); + + prim.addPoint(Color.RED, wx, wy, z); + } +@@ -53,21 +53,21 @@ + + public static void debug3DLine(PlayerInstance player, int x, int y, int z, int tx, int ty, int tz) + { +- final int gx = GeoEngine.getInstance().getGeoX(x); +- final int gy = GeoEngine.getInstance().getGeoY(y); ++ final int gx = GeoEngine.getGeoX(x); ++ final int gy = GeoEngine.getGeoY(y); + +- final int tgx = GeoEngine.getInstance().getGeoX(tx); +- final int tgy = GeoEngine.getInstance().getGeoY(ty); ++ final int tgx = GeoEngine.getGeoX(tx); ++ final int tgy = GeoEngine.getGeoY(ty); + + final ExServerPrimitive prim = new ExServerPrimitive("Debug3DLine", x, y, z); +- prim.addLine(Color.BLUE, GeoEngine.getInstance().getWorldX(gx), GeoEngine.getInstance().getWorldY(gy), z, GeoEngine.getInstance().getWorldX(tgx), GeoEngine.getInstance().getWorldY(tgy), tz); ++ prim.addLine(Color.BLUE, GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), z, GeoEngine.getWorldX(tgx), GeoEngine.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.getInstance().getWorldX(prevX); +- int wy = GeoEngine.getInstance().getWorldY(prevY); ++ int wx = GeoEngine.getWorldX(prevX); ++ int wy = GeoEngine.getWorldY(prevY); + int wz = iter.z(); + prim.addPoint(Color.RED, wx, wy, wz); + +@@ -78,8 +78,8 @@ + + if ((curX != prevX) || (curY != prevY)) + { +- wx = GeoEngine.getInstance().getWorldX(curX); +- wy = GeoEngine.getInstance().getWorldY(curY); ++ wx = GeoEngine.getWorldX(curX); ++ wy = GeoEngine.getWorldY(curY); + wz = iter.z(); + + prim.addPoint(Color.RED, wx, wy, wz); +@@ -93,7 +93,7 @@ + + private static Color getDirectionColor(int x, int y, int z, int nswe) + { +- if (GeoEngine.getInstance().checkNearestNswe(x, y, z, nswe)) ++ if ((GeoEngine.getInstance().getNsweNearest(x, y, z) & nswe) == nswe) + { + return Color.GREEN; + } +@@ -109,9 +109,8 @@ + int iPacket = 0; + + ExServerPrimitive exsp = null; +- final GeoEngine ge = GeoEngine.getInstance(); +- final int playerGx = ge.getGeoX(player.getX()); +- final int playerGy = ge.getGeoY(player.getY()); ++ final int playerGx = GeoEngine.getGeoX(player.getX()); ++ final int playerGy = GeoEngine.getGeoY(player.getY()); + for (int dx = -geoRadius; dx <= geoRadius; ++dx) + { + for (int dy = -geoRadius; dy <= geoRadius; ++dy) +@@ -135,12 +134,12 @@ + final int gx = playerGx + dx; + final int gy = playerGy + dy; + +- final int x = ge.getWorldX(gx); +- final int y = ge.getWorldY(gy); +- final int z = ge.getNearestZ(gx, gy, player.getZ()); ++ final int x = GeoEngine.getWorldX(gx); ++ final int y = GeoEngine.getWorldY(gy); ++ final int z = GeoEngine.getInstance().getHeightNearest(gx, gy, player.getZ()); + + // north arrow +- Color col = getDirectionColor(gx, gy, z, Cell.NSWE_NORTH); ++ Color col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_N); + 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); +@@ -147,7 +146,7 @@ + exsp.addLine(col, x - 4, y - 4, z, x + 4, y - 4, z); + + // east arrow +- col = getDirectionColor(gx, gy, z, Cell.NSWE_EAST); ++ col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_E); + 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); +@@ -154,13 +153,13 @@ + exsp.addLine(col, x + 4, y - 4, z, x + 4, y + 4, z); + + // south arrow +- col = getDirectionColor(gx, gy, z, Cell.NSWE_SOUTH); ++ col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_S); + 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, Cell.NSWE_WEST); ++ col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_W); + 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); +@@ -188,15 +187,15 @@ + { + if (y > lastY) + { +- return Cell.NSWE_SOUTH_EAST; ++ return GeoStructure.CELL_FLAG_SE; // Direction.SOUTH_EAST; + } + else if (y < lastY) + { +- return Cell.NSWE_NORTH_EAST; ++ return GeoStructure.CELL_FLAG_NE; // Direction.NORTH_EAST; + } + else + { +- return Cell.NSWE_EAST; ++ return GeoStructure.CELL_FLAG_E; // Direction.EAST; + } + } + else if (x < lastX) // west +@@ -203,26 +202,27 @@ + { + if (y > lastY) + { +- return Cell.NSWE_SOUTH_WEST; ++ return GeoStructure.CELL_FLAG_SW; // Direction.SOUTH_WEST; + } + else if (y < lastY) + { +- return Cell.NSWE_NORTH_WEST; ++ return GeoStructure.CELL_FLAG_NW; // Direction.NORTH_WEST; + } + else + { +- return Cell.NSWE_WEST; ++ return GeoStructure.CELL_FLAG_W; // Direction.WEST; + } + } +- else // unchanged x ++ else ++ // unchanged x + { + if (y > lastY) + { +- return Cell.NSWE_SOUTH; ++ return GeoStructure.CELL_FLAG_S; // Direction.SOUTH; + } + else if (y < lastY) + { +- return Cell.NSWE_NORTH; ++ return GeoStructure.CELL_FLAG_N; // Direction.NORTH; + } + else + { +Index: java/org/l2jmobius/tools/geodataconverter/GeoDataConverter.java +=================================================================== +--- java/org/l2jmobius/tools/geodataconverter/GeoDataConverter.java (nonexistent) ++++ java/org/l2jmobius/tools/geodataconverter/GeoDataConverter.java (working copy) +@@ -0,0 +1,386 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ */ ++package org.l2jmobius.tools.geodataconverter; ++ ++import java.io.BufferedOutputStream; ++import java.io.File; ++import java.io.FileOutputStream; ++import java.io.RandomAccessFile; ++import java.nio.ByteOrder; ++import java.nio.MappedByteBuffer; ++import java.nio.channels.FileChannel; ++import java.util.Scanner; ++ ++import org.l2jmobius.Config; ++import org.l2jmobius.commons.util.PropertiesParser; ++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.GeoFormat; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; ++import org.l2jmobius.gameserver.model.World; ++ ++/** ++ * @author Hasha ++ */ ++public class GeoDataConverter ++{ ++ private static GeoFormat _format; ++ private static ABlock[][] _blocks; ++ ++ public static void main(String[] args) ++ { ++ // initialize config ++ loadGeoengineConfigs(); ++ ++ // get geodata format ++ String type = ""; ++ try (Scanner scn = new Scanner(System.in)) ++ { ++ while (!(type.equalsIgnoreCase("J") || type.equalsIgnoreCase("O") || type.equalsIgnoreCase("E"))) ++ { ++ System.out.println("GeoDataConverter: Select source geodata type:"); ++ System.out.println(" J: L2J (e.g. 23_22.l2j)"); ++ System.out.println(" O: L2OFF (e.g. 23_22_conv.dat)"); ++ System.out.println(" E: Exit"); ++ System.out.print("Choice: "); ++ type = scn.next(); ++ } ++ } ++ if (type.equalsIgnoreCase("E")) ++ { ++ System.exit(0); ++ } ++ ++ _format = type.equalsIgnoreCase("J") ? GeoFormat.L2J : GeoFormat.L2OFF; ++ ++ // start conversion ++ System.out.println("GeoDataConverter: Converting all " + _format + " files."); ++ ++ // initialize geodata container ++ _blocks = new ABlock[GeoStructure.REGION_BLOCKS_X][GeoStructure.REGION_BLOCKS_Y]; ++ ++ // initialize multilayer temporarily buffer ++ BlockMultilayer.initialize(); ++ ++ // load geo files ++ int converted = 0; ++ for (int rx = World.TILE_X_MIN; rx <= World.TILE_X_MAX; rx++) ++ { ++ for (int ry = World.TILE_Y_MIN; ry <= World.TILE_Y_MAX; ry++) ++ { ++ final String input = String.format(_format.getFilename(), rx, ry); ++ final String filepath = Config.GEODATA_PATH; ++ final File f = new File(filepath + input); ++ if (f.exists() && !f.isDirectory()) ++ { ++ // load geodata ++ if (!loadGeoBlocks(input)) ++ { ++ System.out.println("GeoDataConverter: Unable to load " + input + " region file."); ++ continue; ++ } ++ ++ // recalculate nswe ++ if (!recalculateNswe()) ++ { ++ System.out.println("GeoDataConverter: Unable to convert " + input + " region file."); ++ continue; ++ } ++ ++ // save geodata ++ final String output = String.format(GeoFormat.L2D.getFilename(), rx, ry); ++ if (!saveGeoBlocks(output)) ++ { ++ System.out.println("GeoDataConverter: Unable to save " + output + " region file."); ++ continue; ++ } ++ ++ converted++; ++ System.out.println("GeoDataConverter: Created " + output + " region file."); ++ } ++ } ++ } ++ System.out.println("GeoDataConverter: Converted " + converted + " " + _format + " to L2D region file(s)."); ++ ++ // release multilayer block temporarily buffer ++ BlockMultilayer.release(); ++ } ++ ++ /** ++ * Loads geo blocks from buffer of the region file. ++ * @param filename : The name of the to load. ++ * @return boolean : True when successful. ++ */ ++ private static boolean loadGeoBlocks(String filename) ++ { ++ // region file is load-able, try to load it ++ try (RandomAccessFile raf = new RandomAccessFile(Config.GEODATA_PATH + filename, "r"); ++ FileChannel fc = raf.getChannel()) ++ { ++ final MappedByteBuffer buffer = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()).load(); ++ buffer.order(ByteOrder.LITTLE_ENDIAN); ++ ++ // load 18B header for L2off geodata (1st and 2nd byte...region X and Y) ++ if (_format == GeoFormat.L2OFF) ++ { ++ for (int i = 0; i < 18; i++) ++ { ++ buffer.get(); ++ } ++ } ++ ++ // 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 (_format == GeoFormat.L2J) ++ { ++ // get block type ++ final byte type = buffer.get(); ++ ++ // load block according to block type ++ switch (type) ++ { ++ case GeoStructure.TYPE_FLAT_L2J_L2OFF: ++ { ++ _blocks[ix][iy] = new BlockFlat(buffer, _format); ++ break; ++ } ++ case GeoStructure.TYPE_COMPLEX_L2J: ++ { ++ _blocks[ix][iy] = new BlockComplex(buffer, _format); ++ break; ++ } ++ case GeoStructure.TYPE_MULTILAYER_L2J: ++ { ++ _blocks[ix][iy] = new BlockMultilayer(buffer, _format); ++ break; ++ } ++ default: ++ { ++ throw new IllegalArgumentException("Unknown block type: " + type); ++ } ++ } ++ } ++ else ++ { ++ // get block type ++ final short type = buffer.getShort(); ++ ++ // load block according to block type ++ switch (type) ++ { ++ case GeoStructure.TYPE_FLAT_L2J_L2OFF: ++ { ++ _blocks[ix][iy] = new BlockFlat(buffer, _format); ++ break; ++ } ++ case GeoStructure.TYPE_COMPLEX_L2OFF: ++ { ++ _blocks[ix][iy] = new BlockComplex(buffer, _format); ++ break; ++ } ++ default: ++ { ++ _blocks[ix][iy] = new BlockMultilayer(buffer, _format); ++ break; ++ } ++ } ++ } ++ } ++ } ++ ++ if (buffer.remaining() > 0) ++ { ++ System.out.println("GeoDataConverter: Region file " + filename + " can be corrupted, remaining " + buffer.remaining() + " bytes to read."); ++ return false; ++ } ++ ++ return true; ++ } ++ catch (Exception e) ++ { ++ System.out.println("GeoDataConverter: Error while loading " + filename + " region file."); ++ return false; ++ } ++ } ++ ++ /** ++ * Recalculate diagonal flags for the region file. ++ * @return boolean : True when successful. ++ */ ++ private static boolean recalculateNswe() ++ { ++ try ++ { ++ for (int x = 0; x < GeoStructure.REGION_CELLS_X; x++) ++ { ++ for (int y = 0; y < GeoStructure.REGION_CELLS_Y; y++) ++ { ++ // get block ++ final ABlock block = _blocks[x / GeoStructure.BLOCK_CELLS_X][y / GeoStructure.BLOCK_CELLS_Y]; ++ ++ // skip flat blocks ++ if (block instanceof BlockFlat) ++ { ++ continue; ++ } ++ ++ // for complex and multilayer blocks go though all layers ++ short height = Short.MAX_VALUE; ++ int index; ++ while ((index = block.getIndexBelow(x, y, height)) != -1) ++ { ++ // get height and nswe ++ height = block.getHeight(index); ++ byte nswe = block.getNswe(index); ++ ++ // update nswe with diagonal flags ++ nswe = updateNsweBelow(x, y, height, nswe); ++ ++ // set nswe of the cell ++ block.setNswe(index, nswe); ++ } ++ } ++ } ++ ++ return true; ++ } ++ catch (Exception e) ++ { ++ return false; ++ } ++ } ++ ++ /** ++ * Updates the NSWE flag with diagonal flags. ++ * @param x : Geodata X coordinate. ++ * @param y : Geodata Y coordinate. ++ * @param z : Geodata Z coordinate. ++ * @param nsweValue : NSWE flag to be updated. ++ * @return byte : Updated NSWE flag. ++ */ ++ private static byte updateNsweBelow(int x, int y, short z, byte nsweValue) ++ { ++ byte nswe = nsweValue; ++ ++ // calculate virtual layer height ++ final short height = (short) (z + GeoStructure.CELL_IGNORE_HEIGHT); ++ ++ // get NSWE of neighbor cells below virtual layer (NPC/PC can fall down of clif, but can not climb it -> NSWE of cell below) ++ final byte nsweN = getNsweBelow(x, y - 1, height); ++ final byte nsweS = getNsweBelow(x, y + 1, height); ++ final byte nsweW = getNsweBelow(x - 1, y, height); ++ final byte nsweE = getNsweBelow(x + 1, y, height); ++ ++ // north-west ++ if ((((nswe & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) || (((nswe & GeoStructure.CELL_FLAG_W) != 0) && ((nsweW & GeoStructure.CELL_FLAG_N) != 0))) ++ { ++ nswe |= GeoStructure.CELL_FLAG_NW; ++ } ++ ++ // north-east ++ if ((((nswe & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) || (((nswe & GeoStructure.CELL_FLAG_E) != 0) && ((nsweE & GeoStructure.CELL_FLAG_N) != 0))) ++ { ++ nswe |= GeoStructure.CELL_FLAG_NE; ++ } ++ ++ // south-west ++ if ((((nswe & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) || (((nswe & GeoStructure.CELL_FLAG_W) != 0) && ((nsweW & GeoStructure.CELL_FLAG_S) != 0))) ++ { ++ nswe |= GeoStructure.CELL_FLAG_SW; ++ } ++ ++ // south-east ++ if ((((nswe & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) || (((nswe & GeoStructure.CELL_FLAG_E) != 0) && ((nsweE & GeoStructure.CELL_FLAG_S) != 0))) ++ { ++ nswe |= GeoStructure.CELL_FLAG_SE; ++ } ++ ++ return nswe; ++ } ++ ++ private static byte getNsweBelow(int geoX, int geoY, short worldZ) ++ { ++ // out of geo coordinates ++ if ((geoX < 0) || (geoX >= GeoStructure.REGION_CELLS_X)) ++ { ++ return 0; ++ } ++ ++ // out of geo coordinates ++ if ((geoY < 0) || (geoY >= GeoStructure.REGION_CELLS_Y)) ++ { ++ return 0; ++ } ++ ++ // get block ++ final ABlock block = _blocks[geoX / GeoStructure.BLOCK_CELLS_X][geoY / GeoStructure.BLOCK_CELLS_Y]; ++ ++ // get index, when valid, return nswe ++ final int index = block.getIndexBelow(geoX, geoY, worldZ); ++ return index == -1 ? 0 : block.getNswe(index); ++ } ++ ++ /** ++ * Save region file to file. ++ * @param filename : The name of file to save. ++ * @return boolean : True when successful. ++ */ ++ private static boolean saveGeoBlocks(String filename) ++ { ++ try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(Config.GEODATA_PATH + filename), GeoStructure.REGION_BLOCKS * GeoStructure.BLOCK_CELLS * 3)) ++ { ++ // loop over region blocks and save each block ++ for (int ix = 0; ix < GeoStructure.REGION_BLOCKS_X; ix++) ++ { ++ for (int iy = 0; iy < GeoStructure.REGION_BLOCKS_Y; iy++) ++ { ++ _blocks[ix][iy].saveBlock(bos); ++ } ++ } ++ ++ // flush data to file ++ bos.flush(); ++ ++ return true; ++ } ++ catch (Exception e) ++ { ++ return false; ++ } ++ } ++ ++ private static void loadGeoengineConfigs() ++ { ++ final PropertiesParser geoData = new PropertiesParser(Config.GEOENGINE_CONFIG_FILE); ++ Config.GEODATA_PATH = geoData.getString("GeoDataPath", "./data/geodata/"); ++ Config.COORD_SYNCHRONIZE = geoData.getInt("CoordSynchronize", -1); ++ Config.PART_OF_CHARACTER_HEIGHT = geoData.getInt("PartOfCharacterHeight", 75); ++ Config.MAX_OBSTACLE_HEIGHT = geoData.getInt("MaxObstacleHeight", 32); ++ Config.PATHFINDING = geoData.getBoolean("PathFinding", true); ++ Config.PATHFIND_BUFFERS = geoData.getString("PathFindBuffers", "100x6;128x6;192x6;256x4;320x4;384x4;500x2"); ++ Config.BASE_WEIGHT = geoData.getInt("BaseWeight", 10); ++ Config.DIAGONAL_WEIGHT = geoData.getInt("DiagonalWeight", 14); ++ Config.OBSTACLE_MULTIPLIER = geoData.getInt("ObstacleMultiplier", 10); ++ Config.HEURISTIC_WEIGHT = geoData.getInt("HeuristicWeight", 20); ++ Config.MAX_ITERATIONS = geoData.getInt("MaxIterations", 3500); ++ } ++} +\ No newline at end of file diff --git a/L2J_Mobius_9.0_ReturnOfTheQueenAnt/dist/game/data/geodata/L2D_GeoEngine.patch b/L2J_Mobius_9.0_ReturnOfTheQueenAnt/dist/game/data/geodata/L2D_GeoEngine.patch new file mode 100644 index 0000000000..63e2faf0c9 --- /dev/null +++ b/L2J_Mobius_9.0_ReturnOfTheQueenAnt/dist/game/data/geodata/L2D_GeoEngine.patch @@ -0,0 +1,6052 @@ +Index: dist/game/GeoDataConverter.bat +=================================================================== +--- dist/game/GeoDataConverter.bat (nonexistent) ++++ dist/game/GeoDataConverter.bat (working copy) +@@ -0,0 +1,6 @@ ++@echo off ++title L2D geodata converter ++ ++java -Xmx512m -cp ./../libs/* org.l2jmobius.tools.geodataconverter.GeoDataConverter ++ ++pause +Index: dist/game/GeoDataConverter.sh +=================================================================== +--- dist/game/GeoDataConverter.sh (nonexistent) ++++ dist/game/GeoDataConverter.sh (working copy) +@@ -0,0 +1,4 @@ ++#! /bin/sh ++ ++java -Xmx512m -cp ../libs/*: org.l2jmobius.tools.geodataconverter.GeoDataConverter > log/stdout.log 2>&1 ++ +Index: dist/game/config/GeoEngine.ini +=================================================================== +--- dist/game/config/GeoEngine.ini (revision 8397) ++++ dist/game/config/GeoEngine.ini (working copy) +@@ -1,6 +1,10 @@ + # ================================================================= + # Geodata + # ================================================================= ++# Because of real-time performance we are using geodata files only in ++# diagonal L2D format now (using filename e.g. 22_16.l2d). ++# L2D geodata can be obtained by conversion of existing L2J or L2OFF geodata. ++# Launch "GeoDataConverter.bat/sh" and follow instructions to start the conversion. + + # 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/ +@@ -13,6 +17,16 @@ + CoordSynchronize = 2 + + # ================================================================= ++# Path checking ++# ================================================================= ++ ++# 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 finding + # ================================================================= + +@@ -23,19 +37,23 @@ + # Pathfinding array buffers configuration, default: 100x6;128x6;192x6;256x4;320x4;384x4;500x2 + PathFindBuffers = 100x6;128x6;192x6;256x4;320x4;384x4;500x2 + +-# Weight for nodes without obstacles far from walls +-LowWeight = 0.5 ++# Base path weight, when moving from one node to another on axis direction, default: 10 ++BaseWeight = 10 + +-# Weight for nodes near walls +-MediumWeight = 2 ++# Path weight, when moving from one node to another on diagonal direction, default: BaseWeight * sqrt(2) = 14 ++DiagonalWeight = 14 + +-# Weight for nodes with obstacles +-HighWeight = 3 ++# When movement flags of target node is blocked to any direction, multiply movement weight by this multiplier. ++# This causes pathfinding algorithm to avoid path construction exactly near an obstacle, default: 10 ++ObstacleMultiplier = 10 + +-# Weight for diagonal movement. +-# Default: LowWeight * sqrt(2) +-DiagonalWeight = 0.707 ++# Weight of the heuristic algorithm, which is giving estimated cost from node to target, default: 20 ++# For proper function must be higher than BaseWeight and/or DiagonalWeight. ++HeuristicWeight = 20 + ++# Maximum number of generated nodes per one path-finding process, default 3500 ++MaxIterations = 3500 ++ + # ================================================================= + # Other + # ================================================================= +Index: dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java +=================================================================== +--- dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java (revision 8397) ++++ dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java (working copy) +@@ -55,9 +55,8 @@ + final int worldX = activeChar.getX(); + final int worldY = activeChar.getY(); + final int worldZ = activeChar.getZ(); +- final int geoX = GeoEngine.getInstance().getGeoX(worldX); +- final int geoY = GeoEngine.getInstance().getGeoY(worldY); +- ++ final int geoX = GeoEngine.getGeoX(worldX); ++ final int geoY = GeoEngine.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)); +@@ -73,9 +72,8 @@ + final int worldX = activeChar.getX(); + final int worldY = activeChar.getY(); + final int worldZ = activeChar.getZ(); +- final int geoX = GeoEngine.getInstance().getGeoX(worldX); +- final int geoY = GeoEngine.getInstance().getGeoY(worldY); +- ++ final int geoX = GeoEngine.getGeoX(worldX); ++ final int geoY = GeoEngine.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)); +Index: dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java +=================================================================== +--- dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java (revision 8397) ++++ dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java (working copy) +@@ -18,11 +18,11 @@ + + import java.util.List; + +-import org.l2jmobius.Config; +-import org.l2jmobius.gameserver.geoengine.GeoEnginePathfinding; +-import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; ++import org.l2jmobius.gameserver.geoengine.GeoEngine; + import org.l2jmobius.gameserver.handler.IAdminCommandHandler; ++import org.l2jmobius.gameserver.model.Location; + import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance; ++import org.l2jmobius.gameserver.network.SystemMessageId; + import org.l2jmobius.gameserver.util.BuilderUtil; + + public class AdminPathNode implements IAdminCommandHandler +@@ -37,29 +37,30 @@ + { + if (command.equals("admin_path_find")) + { +- if (!Config.PATHFINDING) +- { +- BuilderUtil.sendSysMessage(activeChar, "PathFinding is disabled."); +- return true; +- } + if (activeChar.getTarget() != null) + { +- final List path = GeoEnginePathfinding.getInstance().findPath(activeChar.getX(), activeChar.getY(), (short) activeChar.getZ(), activeChar.getTarget().getX(), activeChar.getTarget().getY(), (short) activeChar.getTarget().getZ(), activeChar.getInstanceWorld()); ++ 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()); + if (path == null) + { +- BuilderUtil.sendSysMessage(activeChar, "No Route!"); +- return true; ++ BuilderUtil.sendSysMessage(activeChar, "No route found or pathfinding disabled."); + } +- for (AbstractNodeLoc a : path) ++ else + { +- BuilderUtil.sendSysMessage(activeChar, "x:" + a.getX() + " y:" + a.getY() + " z:" + a.getZ()); ++ for (Location point : path) ++ { ++ BuilderUtil.sendSysMessage(activeChar, "x:" + point.getX() + " y:" + point.getY() + " z:" + point.getZ()); ++ } + } + } + else + { +- BuilderUtil.sendSysMessage(activeChar, "No Target!"); ++ activeChar.sendPacket(SystemMessageId.INVALID_TARGET); + } + } ++ else ++ { ++ return false; ++ } + return true; + } + +Index: java/org/l2jmobius/Config.java +=================================================================== +--- java/org/l2jmobius/Config.java (revision 8397) ++++ java/org/l2jmobius/Config.java (working copy) +@@ -32,7 +32,6 @@ + import java.net.UnknownHostException; + import java.nio.charset.StandardCharsets; + import java.nio.file.Files; +-import java.nio.file.Path; + import java.nio.file.Paths; + import java.time.Duration; + import java.util.ArrayList; +@@ -927,14 +926,17 @@ + // -------------------------------------------------- + // GeoEngine + // -------------------------------------------------- +- public static Path GEODATA_PATH; ++ public static String GEODATA_PATH; ++ public static int COORD_SYNCHRONIZE; ++ public static int PART_OF_CHARACTER_HEIGHT; ++ public static int MAX_OBSTACLE_HEIGHT; + public static boolean PATHFINDING; + public static String PATHFIND_BUFFERS; +- public static float LOW_WEIGHT; +- public static float MEDIUM_WEIGHT; +- public static float HIGH_WEIGHT; +- public static float DIAGONAL_WEIGHT; +- public static int COORD_SYNCHRONIZE; ++ public static int BASE_WEIGHT; ++ public static int DIAGONAL_WEIGHT; ++ public static int HEURISTIC_WEIGHT; ++ public static int OBSTACLE_MULTIPLIER; ++ public static int MAX_ITERATIONS; + public static boolean CORRECT_PLAYER_Z; + + /** Attribute System */ +@@ -2468,14 +2470,17 @@ + + // Load GeoEngine config file (if exists) + final PropertiesParser GeoEngine = new PropertiesParser(GEOENGINE_CONFIG_FILE); +- GEODATA_PATH = Paths.get(GeoEngine.getString("GeoDataPath", "./data/geodata")); ++ GEODATA_PATH = GeoEngine.getString("GeoDataPath", "./data/geodata/"); ++ COORD_SYNCHRONIZE = GeoEngine.getInt("CoordSynchronize", -1); ++ PART_OF_CHARACTER_HEIGHT = GeoEngine.getInt("PartOfCharacterHeight", 75); ++ MAX_OBSTACLE_HEIGHT = GeoEngine.getInt("MaxObstacleHeight", 32); + PATHFINDING = GeoEngine.getBoolean("PathFinding", true); + PATHFIND_BUFFERS = GeoEngine.getString("PathFindBuffers", "100x6;128x6;192x6;256x4;320x4;384x4;500x2"); +- LOW_WEIGHT = GeoEngine.getFloat("LowWeight", 0.5f); +- MEDIUM_WEIGHT = GeoEngine.getFloat("MediumWeight", 2); +- HIGH_WEIGHT = GeoEngine.getFloat("HighWeight", 3); +- DIAGONAL_WEIGHT = GeoEngine.getFloat("DiagonalWeight", 0.707f); +- COORD_SYNCHRONIZE = GeoEngine.getInt("CoordSynchronize", -1); ++ BASE_WEIGHT = GeoEngine.getInt("BaseWeight", 10); ++ DIAGONAL_WEIGHT = GeoEngine.getInt("DiagonalWeight", 14); ++ OBSTACLE_MULTIPLIER = GeoEngine.getInt("ObstacleMultiplier", 10); ++ HEURISTIC_WEIGHT = GeoEngine.getInt("HeuristicWeight", 20); ++ MAX_ITERATIONS = GeoEngine.getInt("MaxIterations", 3500); + CORRECT_PLAYER_Z = GeoEngine.getBoolean("CorrectPlayerZ", false); + + // Load AllowedPlayerRaces config file (if exists) +Index: java/org/l2jmobius/gameserver/geoengine/GeoEngine.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/GeoEngine.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/GeoEngine.java (working copy) +@@ -16,101 +16,91 @@ + */ + package org.l2jmobius.gameserver.geoengine; + +-import java.io.IOException; ++import java.io.File; + import java.io.RandomAccessFile; + import java.nio.ByteOrder; +-import java.nio.channels.FileChannel.MapMode; +-import java.nio.file.Files; +-import java.nio.file.Path; +-import java.util.concurrent.atomic.AtomicReferenceArray; +-import java.util.logging.Level; ++import java.nio.MappedByteBuffer; ++import java.nio.channels.FileChannel; ++import java.util.List; + 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.geoengine.geodata.Cell; +-import org.l2jmobius.gameserver.geoengine.geodata.IRegion; +-import org.l2jmobius.gameserver.geoengine.geodata.NullRegion; +-import org.l2jmobius.gameserver.geoengine.geodata.Region; ++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.GeoFormat; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoLocation; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; + import org.l2jmobius.gameserver.instancemanager.WarpedSpaceManager; + 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; ++import org.l2jmobius.gameserver.util.MathUtil; + + /** +- * @author -Nemesiss-, HorridoJoho ++ * @author Hasha + */ + public class GeoEngine + { +- private static final Logger LOGGER = Logger.getLogger(GeoEngine.class.getName()); ++ protected static final Logger LOGGER = Logger.getLogger(GeoEngine.class.getName()); + +- private static final int WORLD_MIN_X = -655360; +- private static final int WORLD_MIN_Y = -589824; +- private static final int WORLD_MIN_Z = -16384; ++ private final ABlock[][] _blocks; ++ private final BlockNull _nullBlock; + +- /** 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; +- +- /** The regions array */ +- private final AtomicReferenceArray _regions = new AtomicReferenceArray<>(GEO_REGIONS); +- +- 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; +- +- protected GeoEngine() ++ /** ++ * GeoEngine constructor. Loads all geodata files of chosen geodata format. ++ */ ++ public GeoEngine() + { + LOGGER.info("GeoEngine: Initializing..."); +- for (int i = 0; i < _regions.length(); i++) +- { +- _regions.set(i, NullRegion.INSTANCE); +- } + ++ // 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; +- try ++ long fileSize = 0; ++ for (int rx = World.TILE_X_MIN; rx <= World.TILE_X_MAX; rx++) + { +- for (int regionX = World.TILE_X_MIN; regionX <= World.TILE_X_MAX; regionX++) ++ for (int ry = World.TILE_Y_MIN; ry <= World.TILE_Y_MAX; ry++) + { +- for (int regionY = World.TILE_Y_MIN; regionY <= World.TILE_Y_MAX; regionY++) ++ final File f = new File(Config.GEODATA_PATH + String.format(GeoFormat.L2D.getFilename(), rx, ry)); ++ if (f.exists() && !f.isDirectory()) + { +- 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(rx, ry)) + { +- try (RandomAccessFile raf = new RandomAccessFile(geoFilePath.toFile(), "r")) +- { +- _regions.set((regionX * GEO_REGIONS_Y) + regionY, new Region(raf.getChannel().map(MapMode.READ_ONLY, 0, raf.length()).load().order(ByteOrder.LITTLE_ENDIAN))); +- loaded++; +- } ++ loaded++; ++ fileSize += f.length(); + } + } ++ else ++ { ++ // region file is not load-able, load null blocks ++ loadNullBlocks(rx, ry); ++ } + } + } +- catch (Exception e) ++ ++ LOGGER.info("GeoEngine: Loaded " + loaded + " geodata files."); ++ if (loaded > 0) + { +- LOGGER.log(Level.SEVERE, "GeoEngine: Failed to load geodata!", e); +- System.exit(1); ++ LOGGER.info("GeoEngine: Total geodata file size " + (fileSize / 1024 / 1024) + " MB."); + } + +- LOGGER.info("GeoEngine: Loaded " + loaded + " geodata files."); +- +- // Avoid wrong configs when no files are loaded. ++ // avoid wrong configs when no files are loaded + if (loaded == 0) + { + if (Config.PATHFINDING) +@@ -124,627 +114,832 @@ + LOGGER.info("GeoEngine: Forcing CoordSynchronize setting to -1."); + } + } ++ ++ // release multilayer block temporarily buffer ++ BlockMultilayer.release(); + } + + /** +- * @param geoX +- * @param geoY +- * @return the region ++ * 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 IRegion getRegion(int geoX, int geoY) ++ private final boolean loadGeoBlocks(int regionX, int regionY) + { +- final int region = ((geoX / IRegion.REGION_CELLS_X) * GEO_REGIONS_Y) + (geoY / IRegion.REGION_CELLS_Y); +- if ((region < 0) || (region >= _regions.length())) ++ final String filename = String.format(GeoFormat.L2D.getFilename(), regionX, regionY); ++ ++ // standard load ++ try (RandomAccessFile raf = new RandomAccessFile(Config.GEODATA_PATH + filename, "r"); ++ FileChannel fc = raf.getChannel()) + { +- return null; ++ // initialize file buffer ++ final MappedByteBuffer buffer = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()).load(); ++ buffer.order(ByteOrder.LITTLE_ENDIAN); ++ ++ // 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++) ++ { ++ // get block type ++ final byte type = buffer.get(); ++ ++ // load block according to block type ++ switch (type) ++ { ++ case GeoStructure.TYPE_FLAT_L2D: ++ { ++ _blocks[blockX + ix][blockY + iy] = new BlockFlat(buffer, GeoFormat.L2D); ++ break; ++ } ++ case GeoStructure.TYPE_COMPLEX_L2D: ++ { ++ _blocks[blockX + ix][blockY + iy] = new BlockComplex(buffer, GeoFormat.L2D); ++ break; ++ } ++ case GeoStructure.TYPE_MULTILAYER_L2D: ++ { ++ _blocks[blockX + ix][blockY + iy] = new BlockMultilayer(buffer, GeoFormat.L2D); ++ break; ++ } ++ default: ++ { ++ throw new IllegalArgumentException("Unknown block type: " + type); ++ } ++ } ++ } ++ } ++ ++ // check data consistency ++ if (buffer.remaining() > 0) ++ { ++ LOGGER.warning("GeoEngine: Region file " + filename + " can be corrupted, remaining " + buffer.remaining() + " bytes to read."); ++ } ++ ++ // loading was successful ++ return true; + } +- return _regions.get(region); +- } +- +- /** +- * @param filePath +- * @param regionX +- * @param regionY +- * @throws IOException +- */ +- 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")) ++ catch (Exception e) + { +- _regions.set(regionOffset, new Region(raf.getChannel().map(MapMode.READ_ONLY, 0, raf.length()).load().order(ByteOrder.LITTLE_ENDIAN))); ++ // an error occured while loading, load null blocks ++ LOGGER.warning("GeoEngine: Error while loading " + filename + " region file."); ++ LOGGER.warning(e.getMessage()); ++ ++ // replace whole region file with null blocks ++ loadNullBlocks(regionX, regionY); ++ ++ // loading was not successful ++ return false; + } + } + + /** +- * @param regionX +- * @param regionY ++ * 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. + */ +- public void unloadRegion(int regionX, int regionY) ++ private final void loadNullBlocks(int regionX, int regionY) + { +- _regions.set((regionX * GEO_REGIONS_Y) + regionY, NullRegion.INSTANCE); +- } +- +- /** +- * @param geoX +- * @param geoY +- * @return if geodata exist +- */ +- public boolean hasGeoPos(int geoX, int geoY) +- { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) ++ // 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++) + { +- return false; ++ for (int iy = 0; iy < GeoStructure.REGION_BLOCKS_Y; iy++) ++ { ++ _blocks[blockX + ix][blockY + iy] = _nullBlock; ++ } + } +- return region.hasGeo(); + } + ++ // GEODATA - GENERAL ++ + /** +- * Checks the specified position for available geodata. +- * @param x the world x +- * @param y the world y +- * @return {@code true} if there is geodata for the given coordinates, {@code false} otherwise ++ * Converts world X to geodata X. ++ * @param worldX ++ * @return int : Geo X + */ +- public boolean hasGeo(int x, int y) ++ public static int getGeoX(int worldX) + { +- return hasGeoPos(getGeoX(x), getGeoY(y)); ++ return (MathUtil.limit(worldX, World.MAP_MIN_X, World.MAP_MAX_X) - World.MAP_MIN_X) >> 4; + } + + /** +- * @param geoX +- * @param geoY +- * @param worldZ +- * @param nswe +- * @return the nearest nswe check ++ * Converts world Y to geodata Y. ++ * @param worldY ++ * @return int : Geo Y + */ +- public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) ++ public static int getGeoY(int worldY) + { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) +- { +- return true; +- } +- return region.checkNearestNswe(geoX, geoY, worldZ, nswe); ++ return (MathUtil.limit(worldY, World.MAP_MIN_Y, World.MAP_MAX_Y) - World.MAP_MIN_Y) >> 4; + } + + /** ++ * Converts geodata X to world X. + * @param geoX +- * @param geoY +- * @param worldZ +- * @param nswe +- * @return the nearest nswe anti-corner cut ++ * @return int : World X + */ +- public boolean checkNearestNsweAntiCornerCut(int geoX, int geoY, int worldZ, int nswe) ++ public static int getWorldX(int geoX) + { +- boolean can = true; +- if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) +- { +- can = checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_EAST) && checkNearestNswe(geoX + 1, geoY, worldZ, Cell.NSWE_NORTH); +- } +- if (can && ((nswe & Cell.NSWE_NORTH_WEST) == Cell.NSWE_NORTH_WEST)) +- { +- 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 = 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 = checkNearestNswe(geoX, geoY + 1, worldZ, Cell.NSWE_WEST) && checkNearestNswe(geoX - 1, geoY, worldZ, Cell.NSWE_SOUTH); +- } +- return can && checkNearestNswe(geoX, geoY, worldZ, nswe); ++ return (MathUtil.limit(geoX, 0, GeoStructure.GEO_CELLS_X) << 4) + World.MAP_MIN_X + 8; + } + + /** +- * @param geoX ++ * Converts geodata Y to world Y. + * @param geoY +- * @param worldZ +- * @return the nearest Z value ++ * @return int : World Y + */ +- public int getNearestZ(int geoX, int geoY, int worldZ) ++ public static int getWorldY(int geoY) + { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) +- { +- return worldZ; +- } +- return region.getNearestZ(geoX, geoY, worldZ); ++ return (MathUtil.limit(geoY, 0, GeoStructure.GEO_CELLS_Y) << 4) + World.MAP_MIN_Y + 8; + } + + /** +- * @param geoX +- * @param geoY +- * @param worldZ +- * @return the next lower Z value ++ * Returns block of geodata on given coordinates. ++ * @param geoX : Geodata X ++ * @param geoY : Geodata Y ++ * @return {@link ABlock} : Block of geodata. + */ +- public int getNextLowerZ(int geoX, int geoY, int worldZ) ++ private final ABlock getBlock(int geoX, int geoY) + { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) ++ final int x = geoX / GeoStructure.BLOCK_CELLS_X; ++ final int y = geoY / GeoStructure.BLOCK_CELLS_Y; ++ ++ // if x or y is out of array return null ++ if ((x > -1) && (y > -1) && (x < GeoStructure.GEO_BLOCKS_X) && (y < GeoStructure.GEO_BLOCKS_Y)) + { +- return worldZ; ++ return _blocks[x][y]; + } +- return region.getNextLowerZ(geoX, geoY, worldZ); ++ return null; + } + + /** +- * @param geoX +- * @param geoY +- * @param worldZ +- * @return the next higher Z value ++ * Check if geo coordinates has geo. ++ * @param geoX : Geodata X ++ * @param geoY : Geodata Y ++ * @return boolean : True, if given geo coordinates have geodata + */ +- public int getNextHigherZ(int geoX, int geoY, int worldZ) ++ public boolean hasGeoPos(int geoX, int geoY) + { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) ++ final ABlock block = getBlock(geoX, geoY); ++ if (block == null) // null block check + { +- return worldZ; ++ // TODO: Find when this can be null. (Bad geodata? Check World getRegion method.) ++ // LOGGER.warning("Could not find geodata block at " + getWorldX(geoX) + ", " + getWorldY(geoY) + "."); ++ return false; + } +- return region.getNextHigherZ(geoX, geoY, worldZ); ++ return block.hasGeoPos(); + } + + /** +- * Gets the Z height. +- * @param x the world x +- * @param y the world y +- * @param z the world z +- * @return the nearest Z height ++ * 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 int getHeight(int x, int y, int z) ++ public short getHeightNearest(int geoX, int geoY, int worldZ) + { +- return getNearestZ(getGeoX(x), getGeoY(y), z); ++ final ABlock block = getBlock(geoX, geoY); ++ return block != null ? block.getHeightNearest(geoX, geoY, worldZ) : (short) worldZ; + } + + /** +- * Gets the next lower Z height. +- * @param x the world x +- * @param y the world y +- * @param z the world z +- * @return the nearest Z height ++ * 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 int getLowerHeight(int x, int y, int z) ++ public byte getNsweNearest(int geoX, int geoY, int worldZ) + { +- return getNextLowerZ(getGeoX(x), getGeoY(y), z); ++ final ABlock block = getBlock(geoX, geoY); ++ return block != null ? block.getNsweNearest(geoX, geoY, worldZ) : (byte) 0xFF; + } + + /** +- * Gets the next higher Z height. +- * @param x the world x +- * @param y the world y +- * @param z the world z +- * @return the nearest Z height ++ * Check if world coordinates has geo. ++ * @param worldX : World X ++ * @param worldY : World Y ++ * @return boolean : True, if given world coordinates have geodata + */ +- public int getHigherHeight(int x, int y, int z) ++ public boolean hasGeo(int worldX, int worldY) + { +- return getNextHigherZ(getGeoX(x), getGeoY(y), z); ++ return hasGeoPos(getGeoX(worldX), getGeoY(worldY)); + } + + /** +- * @param worldX +- * @return the geo X ++ * 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 int getGeoX(int worldX) ++ public short getHeight(int worldX, int worldY, int worldZ) + { +- return (worldX - WORLD_MIN_X) / 16; ++ return getHeightNearest(getGeoX(worldX), getGeoY(worldY), worldZ); + } + +- /** +- * @param worldY +- * @return the geo Y +- */ +- public int getGeoY(int worldY) +- { +- return (worldY - WORLD_MIN_Y) / 16; +- } ++ // PATHFINDING + + /** +- * @param worldZ +- * @return the geo Z ++ * Check line of sight from {@link WorldObject} to {@link WorldObject}. ++ * @param origin : The origin object. ++ * @param target : The target object. ++ * @return {@code boolean} : True if origin can see target + */ +- public int getGeoZ(int worldZ) ++ public boolean canSeeTarget(WorldObject origin, WorldObject target) + { +- return (worldZ - WORLD_MIN_Z) / 16; +- } +- +- /** +- * @param geoX +- * @return the world X +- */ +- public int getWorldX(int geoX) +- { +- return (geoX * 16) + WORLD_MIN_X + 8; +- } +- +- /** +- * @param geoY +- * @return the world Y +- */ +- public int getWorldY(int geoY) +- { +- return (geoY * 16) + WORLD_MIN_Y + 8; +- } +- +- /** +- * @param geoZ +- * @return the world Z +- */ +- public int getWorldZ(int geoZ) +- { +- return (geoZ * 16) + WORLD_MIN_Z + 8; +- } +- +- /** +- * 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) +- { +- if (target.isDoor()) ++ if (target.isDoor() || target.isArtefact() || (target.isCreature() && ((Creature) target).isFlying())) + { +- // Can always see doors. + return true; + } +- return 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(), cha.getInstanceWorld(), worldPosition.getX(), worldPosition.getY(), worldPosition.getZ()); +- } +- +- /** +- * Can see target. Checks doors between. +- * @param x the x coordinate +- * @param y the y coordinate +- * @param z the z coordinate +- * @param world +- * @param tx the target's x coordinate +- * @param ty the target's y coordinate +- * @param tz the target's z coordinate +- * @param tworld the target's instanceId +- * @return +- */ +- public boolean canSeeTarget(int x, int y, int z, Instance world, int tx, int ty, int tz, Instance tworld) +- { +- return (world != tworld) ? false : canSeeTarget(x, y, z, world, tx, ty, tz); +- } +- +- /** +- * 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 +- * @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, Instance instance, int tx, int ty, int tz) +- { +- if (DoorData.getInstance().checkIfDoorsBetween(x, y, z, tx, ty, tz, instance, true)) ++ ++ // get origin and target world coordinates ++ final int ox = origin.getX(); ++ final int oy = origin.getY(); ++ final int oz = origin.getZ(); ++ final int tx = target.getX(); ++ final int ty = target.getY(); ++ final int tz = target.getZ(); ++ ++ if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld(), true)) + { + return false; + } +- return canSeeTarget(x, y, z, tx, ty, tz); +- } +- +- /** +- * @param prevX +- * @param prevY +- * @param prevGeoZ +- * @param curX +- * @param curY +- * @param nswe +- * @return the LOS Z value +- */ +- 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))) ++ if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld())) + { +- throw new RuntimeException("Multiple directions!"); ++ return false; + } ++ if (WarpedSpaceManager.getInstance().checkForWarpedSpace(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld())) ++ { ++ return false; ++ } + +- if (checkNearestNsweAntiCornerCut(prevX, prevY, prevGeoZ, nswe)) ++ // get origin and check existing geo coordinates ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) + { +- return getNearestZ(curX, curY, prevGeoZ); ++ return true; + } + +- return getNextHigherZ(curX, curY, prevGeoZ); ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coordinates ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) ++ { ++ return true; ++ } ++ ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // origin and target coordinates are same ++ if ((gox == gtx) && (goy == gty)) ++ { ++ return goz == gtz; ++ } ++ ++ // get origin and target height, real height = collision height * 2 ++ double oheight = 0; ++ if (origin.isCreature()) ++ { ++ oheight = ((Creature) origin).getCollisionHeight() * 2; ++ } ++ ++ double theight = 0; ++ if (target.isCreature()) ++ { ++ theight = ((Creature) target).getCollisionHeight() * 2; ++ } ++ ++ // perform geodata check ++ return checkSee(gox, goy, goz, oheight, gtx, gty, gtz, theight, origin.getInstanceWorld()); + } + + /** +- * Can see target. Does not check doors between. +- * @param xValue the x coordinate +- * @param yValue the y coordinate +- * @param zValue the z coordinate +- * @param txValue the target's x coordinate +- * @param tyValue the target's y coordinate +- * @param tzValue the target's z coordinate +- * @return {@code true} if there is line of sight between the given coordinate sets, {@code false} otherwise ++ * Check line of sight from {@link WorldObject} to {@link Location}. ++ * @param origin : The origin object. ++ * @param position : The target position. ++ * @return {@code boolean} : True if object can see position + */ +- public boolean canSeeTarget(int xValue, int yValue, int zValue, int txValue, int tyValue, int tzValue) ++ public boolean canSeeTarget(WorldObject origin, Location position) + { +- int x = xValue; +- int y = yValue; +- int tx = txValue; +- int ty = tyValue; ++ // get origin and target world coordinates ++ final int ox = origin.getX(); ++ final int oy = origin.getY(); ++ final int oz = origin.getZ(); ++ final int tx = position.getX(); ++ final int ty = position.getY(); ++ final int tz = position.getZ(); + +- int geoX = getGeoX(x); +- int geoY = getGeoY(y); +- int tGeoX = getGeoX(tx); +- int tGeoY = getGeoY(ty); ++ if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld(), true)) ++ { ++ return false; ++ } ++ if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld())) ++ { ++ return false; ++ } ++ if (WarpedSpaceManager.getInstance().checkForWarpedSpace(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld())) ++ { ++ return false; ++ } + +- int z = getNearestZ(geoX, geoY, zValue); +- int tz = getNearestZ(tGeoX, tGeoY, tzValue); ++ // get origin and check existing geo coordinates ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) ++ { ++ return true; ++ } + +- // Fastpath. +- if ((geoX == tGeoX) && (geoY == tGeoY)) ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coordinates ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) + { +- if (hasGeoPos(tGeoX, tGeoY)) +- { +- return z == tz; +- } + return true; + } + +- if (tz > z) ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // origin and target coordinates are same ++ if ((gox == gtx) && (goy == gty)) + { +- int tmp = tx; +- tx = x; +- x = tmp; +- +- tmp = ty; +- ty = y; +- y = tmp; +- +- tmp = tz; +- tz = z; +- z = tmp; +- +- tmp = tGeoX; +- tGeoX = geoX; +- geoX = tmp; +- +- tmp = tGeoY; +- tGeoY = geoY; +- geoY = tmp; ++ return goz == gtz; + } + +- final LinePointIterator3D pointIter = new LinePointIterator3D(geoX, geoY, z, tGeoX, tGeoY, tz); +- // 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()) ++ // get origin and target height, real height = collision height * 2 ++ double oheight = 0; ++ if (origin.isCreature()) + { +- final int curX = pointIter.x(); +- final int curY = pointIter.y(); ++ oheight = ((Creature) origin).getTemplate().getCollisionHeight(); ++ } ++ ++ // perform geodata check ++ return checkSee(gox, goy, goz, oheight, gtx, gty, gtz, 0, origin.getInstanceWorld()); ++ } ++ ++ /** ++ * Simple check for origin to target visibility. ++ * @param goxValue : origin X geodata coordinate ++ * @param goyValue : origin Y geodata coordinate ++ * @param gozValue : origin Z geodata coordinate ++ * @param oheight : origin height (if instance of {@link Character}) ++ * @param gtxValue : target X geodata coordinate ++ * @param gtyValue : target Y geodata coordinate ++ * @param gtzValue : target Z geodata coordinate ++ * @param theight : target height (if instance of {@link Character}) ++ * @param instance ++ * @return {@code boolean} : True, when target can be seen. ++ */ ++ private final boolean checkSee(int goxValue, int goyValue, int gozValue, double oheight, int gtxValue, int gtyValue, int gtzValue, double theight, Instance instance) ++ { ++ int goz = gozValue; ++ int gtz = gtzValue; ++ int gox = goxValue; ++ int goy = goyValue; ++ int gtx = gtxValue; ++ int gty = gtyValue; ++ ++ // get line of sight Z coordinates ++ double losoz = goz + ((oheight * Config.PART_OF_CHARACTER_HEIGHT) / 100); ++ double lostz = gtz + ((theight * Config.PART_OF_CHARACTER_HEIGHT) / 100); ++ ++ // get X delta and signum ++ final int dx = Math.abs(gtx - gox); ++ final int sx = gox < gtx ? 1 : -1; ++ final byte dirox = sx > 0 ? GeoStructure.CELL_FLAG_E : GeoStructure.CELL_FLAG_W; ++ final byte dirtx = sx > 0 ? GeoStructure.CELL_FLAG_W : GeoStructure.CELL_FLAG_E; ++ ++ // get Y delta and signum ++ final int dy = Math.abs(gty - goy); ++ final int sy = goy < gty ? 1 : -1; ++ final byte diroy = sy > 0 ? GeoStructure.CELL_FLAG_S : GeoStructure.CELL_FLAG_N; ++ final byte dirty = sy > 0 ? GeoStructure.CELL_FLAG_N : GeoStructure.CELL_FLAG_S; ++ ++ // get Z delta ++ final int dm = Math.max(dx, dy); ++ final double dz = (lostz - losoz) / dm; ++ ++ // get direction flag for diagonal movement ++ final byte diroxy = getDirXY(dirox, diroy); ++ final byte dirtxy = getDirXY(dirtx, dirty); ++ ++ // delta, determines axis to move on (+..X axis, -..Y axis) ++ int d = dx - dy; ++ ++ // NSWE direction of movement ++ byte diro; ++ byte dirt; ++ ++ // initialize node values ++ int nox = gox; ++ int noy = goy; ++ int ntx = gtx; ++ int nty = gty; ++ byte nsweo = getNsweNearest(gox, goy, goz); ++ byte nswet = getNsweNearest(gtx, gty, gtz); ++ ++ // loop ++ ABlock block; ++ int index; ++ for (int i = 0; i < ((dm + 1) / 2); i++) ++ { ++ // reset direction flag ++ diro = 0; ++ dirt = 0; + +- if ((curX == prevX) && (curY == prevY)) ++ // calculate next point coordinates ++ final int e2 = 2 * d; ++ if ((e2 > -dy) && (e2 < dx)) + { +- continue; ++ // calculate next point XY coordinates ++ d -= dy; ++ d += dx; ++ nox += sx; ++ ntx -= sx; ++ noy += sy; ++ nty -= sy; ++ diro |= diroxy; ++ dirt |= dirtxy; + } ++ else if (e2 > -dy) ++ { ++ // calculate next point X coordinate ++ d -= dy; ++ nox += sx; ++ ntx -= sx; ++ diro |= dirox; ++ dirt |= dirtx; ++ } ++ else if (e2 < dx) ++ { ++ // calculate next point Y coordinate ++ d += dx; ++ noy += sy; ++ nty -= sy; ++ diro |= diroy; ++ dirt |= dirty; ++ } + +- final int beeCurZ = pointIter.z(); +- int curGeoZ = prevGeoZ; +- +- // Check if the position has geodata. +- if (hasGeoPos(curX, curY)) + { +- final int beeCurGeoZ = getNearestZ(curX, curY, beeCurZ); +- final int nswe = GeoUtils.computeNswe(prevX, prevY, curX, curY); // .computeDirection(prevX, prevY, curX, curY); +- curGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, curX, curY, nswe); +- int maxHeight; +- if (ptIndex < ELEVATED_SEE_OVER_DISTANCE) ++ // get block of the next cell ++ block = getBlock(nox, noy); ++ ++ // get index of particular layer, based on movement conditions ++ if ((nsweo & diro) == 0) + { +- maxHeight = z + MAX_SEE_OVER_HEIGHT; ++ index = block.getIndexAbove(nox, noy, goz - GeoStructure.CELL_IGNORE_HEIGHT); + } + else + { +- maxHeight = beeCurZ + MAX_SEE_OVER_HEIGHT; ++ index = block.getIndexBelow(nox, noy, goz + GeoStructure.CELL_IGNORE_HEIGHT); + } + +- boolean canSeeThrough = false; +- if ((curGeoZ <= maxHeight) && (curGeoZ <= beeCurGeoZ)) ++ // layer does not exist, return ++ if (index == -1) + { +- if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) +- { +- 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 +- { +- canSeeThrough = true; +- } ++ return false; + } + +- if (!canSeeThrough) ++ // get layer and next line of sight Z coordinate ++ goz = block.getHeight(index); ++ losoz += dz; ++ ++ // perform line of sight check, return when fails ++ if ((goz - losoz) > Config.MAX_OBSTACLE_HEIGHT) + { + return false; + } ++ ++ // get layer nswe ++ nsweo = block.getNswe(index); + } ++ { ++ // get block of the next cell ++ block = getBlock(ntx, nty); ++ ++ // get index of particular layer, based on movement conditions ++ if ((nswet & dirt) == 0) ++ { ++ index = block.getIndexAbove(ntx, nty, gtz - GeoStructure.CELL_IGNORE_HEIGHT); ++ } ++ else ++ { ++ index = block.getIndexBelow(ntx, nty, gtz + GeoStructure.CELL_IGNORE_HEIGHT); ++ } ++ ++ // layer does not exist, return ++ if (index == -1) ++ { ++ return false; ++ } ++ ++ // get layer and next line of sight Z coordinate ++ gtz = block.getHeight(index); ++ lostz -= dz; ++ ++ // perform line of sight check, return when fails ++ if ((gtz - lostz) > Config.MAX_OBSTACLE_HEIGHT) ++ { ++ return false; ++ } ++ ++ // get layer nswe ++ nswet = block.getNswe(index); ++ } + +- prevX = curX; +- prevY = curY; +- prevGeoZ = curGeoZ; +- ++ptIndex; ++ // update coords ++ gox = nox; ++ goy = noy; ++ gtx = ntx; ++ gty = nty; + } + +- return true; ++ // when iteration is completed, compare final Z coordinates ++ return Math.abs(goz - gtz) < (GeoStructure.CELL_HEIGHT * 4); + } + + /** +- * Move check. +- * @param x the x coordinate +- * @param y the y coordinate +- * @param zValue the z coordinate +- * @param tx the target's x coordinate +- * @param ty the target's y coordinate +- * @param tzValue the target's z coordinate +- * @param instance the instance +- * @return the last Location (x,y,z) where player can walk - just before wall ++ * Check movement from coordinates to 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 {code boolean} : True if target coordinates are reachable from origin coordinates + */ +- public Location canMoveToTargetLoc(int x, int y, int zValue, int tx, int ty, int tzValue, Instance instance) ++ public boolean canMoveToTarget(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + { +- final int geoX = getGeoX(x); +- final int geoY = getGeoY(y); +- final int z = getNearestZ(geoX, geoY, zValue); +- final int tGeoX = getGeoX(tx); +- final int tGeoY = getGeoY(ty); +- final int tz = getNearestZ(tGeoX, tGeoY, tzValue); ++ // get origin and check existing geo coordinates ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) ++ { ++ return true; ++ } + +- if (DoorData.getInstance().checkIfDoorsBetween(x, y, z, tx, ty, tz, instance, false)) ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coordinates ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) + { +- return new Location(x, y, getHeight(x, y, z)); ++ return true; + } +- if (FenceData.getInstance().checkIfFenceBetween(x, y, z, tx, ty, tz, instance)) ++ ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // target coordinates reached ++ if ((gox == gtx) && (goy == gty) && (goz == gtz)) + { +- return new Location(x, y, getHeight(x, y, z)); ++ return true; + } +- if (WarpedSpaceManager.getInstance().checkForWarpedSpace(x, y, z, tx, ty, tz, instance)) ++ ++ // perform geodata check ++ final GeoLocation loc = checkMove(gox, goy, goz, gtx, gty, gtz, instance); ++ return (loc.getGeoX() == gtx) && (loc.getGeoY() == gty); ++ } ++ ++ /** ++ * Check movement from origin to target. Returns last available point in the checked path. ++ * @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 {@link Location} : Last point where object can walk (just before wall) ++ */ ++ public Location canMoveToTargetLoc(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) ++ { ++ // Mobius: Double check for doors before normal checkMove to avoid exploiting key movement. ++ if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, instance, false)) + { +- return new Location(x, y, getHeight(x, y, z)); ++ return new Location(ox, oy, oz); + } ++ if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, instance)) ++ { ++ return new Location(ox, oy, oz); ++ } + +- 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 = z; ++ // get origin and check existing geo coordinates ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) ++ { ++ return new Location(tx, ty, tz); ++ } + +- while (pointIter.next()) ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coordinates ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) + { +- final int curX = pointIter.x(); +- final int curY = pointIter.y(); +- final int curZ = getNearestZ(curX, curY, prevZ); +- +- if (hasGeoPos(prevX, prevY)) +- { +- final int nswe = GeoUtils.computeNswe(prevX, prevY, curX, curY); +- if (!checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, nswe)) +- { +- // Can't move, return previous location. +- return new Location(getWorldX(prevX), getWorldY(prevY), prevZ); +- } +- } +- +- prevX = curX; +- prevY = curY; +- prevZ = curZ; ++ return new Location(tx, ty, tz); + } + +- if (hasGeoPos(prevX, prevY) && (prevZ != tz)) ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // target coordinates reached ++ if ((gox == gtx) && (goy == gty) && (goz == gtz)) + { +- // Different floors, return start location. +- return new Location(x, y, z); ++ return new Location(tx, ty, tz); + } + +- return new Location(tx, ty, tz); ++ // perform geodata check ++ return checkMove(gox, goy, goz, gtx, gty, gtz, instance); + } + + /** +- * 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 fromZvalue 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 toZvalue 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 ++ * With this method you can check if a position is visible or can be reached by beeline movement.
++ * 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 gox : origin X geodata coordinate ++ * @param goy : origin Y geodata coordinate ++ * @param goz : origin Z geodata coordinate ++ * @param gtx : target X geodata coordinate ++ * @param gty : target Y geodata coordinate ++ * @param gtz : target Z geodata coordinate ++ * @param instance ++ * @return {@link GeoLocation} : The last allowed point of movement. + */ +- public boolean canMoveToTarget(int fromX, int fromY, int fromZvalue, int toX, int toY, int toZvalue, Instance instance) ++ protected final GeoLocation checkMove(int gox, int goy, int goz, int gtx, int gty, int gtz, Instance instance) + { +- final int geoX = getGeoX(fromX); +- final int geoY = getGeoY(fromY); +- final int fromZ = getNearestZ(geoX, geoY, fromZvalue); +- final int tGeoX = getGeoX(toX); +- final int tGeoY = getGeoY(toY); +- final int toZ = getNearestZ(tGeoX, tGeoY, toZvalue); +- +- if (DoorData.getInstance().checkIfDoorsBetween(fromX, fromY, fromZ, toX, toY, toZ, instance, false)) ++ if (DoorData.getInstance().checkIfDoorsBetween(gox, goy, goz, gtx, gty, gtz, instance, false)) + { +- return false; ++ return new GeoLocation(gox, goy, goz); + } +- if (FenceData.getInstance().checkIfFenceBetween(fromX, fromY, fromZ, toX, toY, toZ, instance)) ++ if (FenceData.getInstance().checkIfFenceBetween(gox, goy, goz, gtx, gty, gtz, instance)) + { +- return false; ++ return new GeoLocation(gox, goy, goz); + } +- if (WarpedSpaceManager.getInstance().checkForWarpedSpace(fromX, fromY, fromZ, toX, toY, toZ, instance)) ++ if (WarpedSpaceManager.getInstance().checkForWarpedSpace(gox, goy, goz, gtx, gty, gtz, instance)) + { +- return false; ++ return new GeoLocation(gox, goy, goz); + } + +- 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 = fromZ; ++ // get X delta, signum and direction flag ++ final int dx = Math.abs(gtx - gox); ++ final int sx = gox < gtx ? 1 : -1; ++ final byte dirX = sx > 0 ? GeoStructure.CELL_FLAG_E : GeoStructure.CELL_FLAG_W; + +- while (pointIter.next()) ++ // get Y delta, signum and direction flag ++ final int dy = Math.abs(gty - goy); ++ final int sy = goy < gty ? 1 : -1; ++ final byte dirY = sy > 0 ? GeoStructure.CELL_FLAG_S : GeoStructure.CELL_FLAG_N; ++ ++ // get direction flag for diagonal movement ++ final byte dirXY = getDirXY(dirX, dirY); ++ ++ // delta, determines axis to move on (+..X axis, -..Y axis) ++ int d = dx - dy; ++ ++ // NSWE direction of movement ++ byte direction; ++ ++ // load pointer coordinates ++ int gpx = gox; ++ int gpy = goy; ++ int gpz = goz; ++ ++ // load next pointer ++ int nx = gpx; ++ int ny = gpy; ++ ++ // loop ++ int count = 0; ++ while (count++ < Config.MAX_ITERATIONS) + { +- final int curX = pointIter.x(); +- final int curY = pointIter.y(); +- final int curZ = getNearestZ(curX, curY, prevZ); ++ direction = 0; + +- if (hasGeoPos(prevX, prevY)) ++ // calculate next point coordinates ++ final int e2 = 2 * d; ++ if ((e2 > -dy) && (e2 < dx)) + { +- final int nswe = GeoUtils.computeNswe(prevX, prevY, curX, curY); +- if (!checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, nswe)) ++ d -= dy; ++ d += dx; ++ nx += sx; ++ ny += sy; ++ direction |= dirXY; ++ } ++ else if (e2 > -dy) ++ { ++ d -= dy; ++ nx += sx; ++ direction |= dirX; ++ } ++ else if (e2 < dx) ++ { ++ d += dx; ++ ny += sy; ++ direction |= dirY; ++ } ++ ++ // obstacle found, return ++ if ((getNsweNearest(gpx, gpy, gpz) & direction) == 0) ++ { ++ return new GeoLocation(gpx, gpy, gpz); ++ } ++ ++ // update pointer coordinates ++ gpx = nx; ++ gpy = ny; ++ gpz = getHeightNearest(nx, ny, gpz); ++ ++ // target coordinates reached ++ if ((gpx == gtx) && (gpy == gty)) ++ { ++ if (gpz == gtz) + { +- return false; ++ // path found, Z coordinates are okay, return target point ++ return new GeoLocation(gtx, gty, gtz); + } ++ ++ // path found, Z coordinates are not okay, return last good point ++ return new GeoLocation(gpx, gpy, gpz); + } ++ } ++ ++ return new GeoLocation(gox, goy, goz); ++ } ++ ++ /** ++ * Returns diagonal NSWE flag format of combined two NSWE flags. ++ * @param dirX : X direction NSWE flag ++ * @param dirY : Y direction NSWE flag ++ * @return byte : NSWE flag of combined direction ++ */ ++ private static byte getDirXY(byte dirX, byte dirY) ++ { ++ // check axis directions ++ if (dirY == GeoStructure.CELL_FLAG_N) ++ { ++ if (dirX == GeoStructure.CELL_FLAG_W) ++ { ++ return GeoStructure.CELL_FLAG_NW; ++ } + +- prevX = curX; +- prevY = curY; +- prevZ = curZ; ++ return GeoStructure.CELL_FLAG_NE; + } + +- if (hasGeoPos(prevX, prevY) && (prevZ != toZ)) ++ if (dirX == GeoStructure.CELL_FLAG_W) + { +- // Different floors. +- return false; ++ return GeoStructure.CELL_FLAG_SW; + } + +- return true; ++ return GeoStructure.CELL_FLAG_SE; + } + ++ /** ++ * 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 ++ */ ++ public List findPath(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) ++ { ++ return null; ++ } ++ ++ /** ++ * Returns the instance of the {@link GeoEngine}. ++ * @return {@link GeoEngine} : The instance. ++ */ + public static GeoEngine getInstance() + { + return SingletonHolder.INSTANCE; +@@ -752,6 +947,6 @@ + + private static class SingletonHolder + { +- protected static final GeoEngine INSTANCE = new GeoEngine(); ++ protected static final GeoEngine INSTANCE = Config.PATHFINDING ? new GeoEnginePathfinding() : new GeoEngine(); + } +-} ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/GeoEnginePathfinding.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/GeoEnginePathfinding.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/GeoEnginePathfinding.java (working copy) +@@ -20,88 +20,84 @@ + import java.util.LinkedList; + import java.util.List; + import java.util.ListIterator; +-import java.util.logging.Level; +-import java.util.logging.Logger; + + import org.l2jmobius.Config; +-import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; +-import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +-import org.l2jmobius.gameserver.geoengine.pathfinding.CellNode; +-import org.l2jmobius.gameserver.geoengine.pathfinding.CellNodeBuffer; +-import org.l2jmobius.gameserver.geoengine.pathfinding.NodeLoc; +-import org.l2jmobius.gameserver.model.World; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoLocation; ++import org.l2jmobius.gameserver.geoengine.pathfinding.Node; ++import org.l2jmobius.gameserver.geoengine.pathfinding.NodeBuffer; ++import org.l2jmobius.gameserver.model.Location; + import org.l2jmobius.gameserver.model.instancezone.Instance; + + /** +- * @author -Nemesiss- ++ * @author Hasha + */ +-public class GeoEnginePathfinding ++final class GeoEnginePathfinding extends GeoEngine + { +- private static final Logger LOGGER = Logger.getLogger(GeoEnginePathfinding.class.getName()); ++ // pre-allocated buffers ++ private final BufferHolder[] _buffers; + +- private BufferInfo[] _buffers; +- + protected GeoEnginePathfinding() + { +- try ++ super(); ++ ++ final String[] array = Config.PATHFIND_BUFFERS.split(";"); ++ _buffers = new BufferHolder[array.length]; ++ int count = 0; ++ for (int i = 0; i < array.length; i++) + { +- final String[] array = Config.PATHFIND_BUFFERS.split(";"); ++ final String buf = array[i]; ++ final String[] args = buf.split("x"); + +- _buffers = new BufferInfo[array.length]; +- +- String buf; +- String[] args; +- for (int i = 0; i < array.length; i++) ++ try + { +- buf = array[i]; +- args = buf.split("x"); +- if (args.length != 2) +- { +- throw new Exception("Invalid buffer definition: " + buf); +- } +- +- _buffers[i] = new BufferInfo(Integer.parseInt(args[0]), Integer.parseInt(args[1])); ++ final int size = Integer.parseInt(args[1]); ++ count += size; ++ _buffers[i] = new BufferHolder(Integer.parseInt(args[0]), size); + } ++ catch (Exception e) ++ { ++ LOGGER.warning("GeoEnginePathfinding: Can not load buffer setting: " + buf); ++ } + } +- catch (Exception e) +- { +- LOGGER.log(Level.WARNING, "CellPathFinding: Problem during buffer init: " + e.getMessage(), e); +- throw new Error("CellPathFinding: load aborted"); +- } ++ ++ LOGGER.info("GeoEnginePathfinding: Loaded " + count + " node buffers."); + } + +- public boolean pathNodesExist(short regionoffset) ++ @Override ++ public List findPath(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + { +- return false; +- } +- +- public List findPath(int x, int y, int z, int tx, int ty, int tz, Instance instance) +- { +- final int gx = GeoEngine.getInstance().getGeoX(x); +- final int gy = GeoEngine.getInstance().getGeoY(y); +- if (!GeoEngine.getInstance().hasGeo(x, y)) ++ // get origin and check existing geo coords ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) + { + 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)) ++ ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coords ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) + { + 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)))); ++ ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // Prepare buffer for pathfinding calculations ++ final NodeBuffer buffer = getBuffer(64 + (2 * Math.max(Math.abs(gox - gtx), Math.abs(goy - gty)))); + if (buffer == null) + { + return null; + } + +- List path = null; ++ // find path ++ List path = null; + try + { +- final CellNode result = buffer.findPath(gx, gy, gz, gtx, gty, gtz); +- ++ final Node result = buffer.findPath(gox, goy, goz, gtx, gty, gtz); + if (result == null) + { + return null; +@@ -125,33 +121,45 @@ + return path; + } + +- int currentX, currentY, currentZ; +- ListIterator middlePoint; ++ // get path list iterator ++ final ListIterator point = path.listIterator(); + +- middlePoint = path.listIterator(); +- currentX = x; +- currentY = y; +- currentZ = z; ++ // get node A (origin) ++ int nodeAx = gox; ++ int nodeAy = goy; ++ short nodeAz = goz; + +- while (middlePoint.hasNext()) ++ // get node B ++ GeoLocation nodeB = (GeoLocation) point.next(); ++ ++ // iterate thought the path to optimize it ++ int count = 0; ++ while (point.hasNext() && (count++ < Config.MAX_ITERATIONS)) + { +- final AbstractNodeLoc locMiddle = middlePoint.next(); +- if (!middlePoint.hasNext()) +- { +- break; +- } ++ // get node C ++ final GeoLocation nodeC = (GeoLocation) path.get(point.nextIndex()); + +- final AbstractNodeLoc locEnd = path.get(middlePoint.nextIndex()); +- if (GeoEngine.getInstance().canMoveToTarget(currentX, currentY, currentZ, locEnd.getX(), locEnd.getY(), locEnd.getZ(), instance)) ++ // check movement from node A to node C ++ final GeoLocation loc = checkMove(nodeAx, nodeAy, nodeAz, nodeC.getGeoX(), nodeC.getGeoY(), nodeC.getZ(), instance); ++ if ((loc.getGeoX() == nodeC.getGeoX()) && (loc.getGeoY() == nodeC.getGeoY())) + { +- middlePoint.remove(); ++ // can move from node A to node C ++ ++ // remove node B ++ point.remove(); + } + else + { +- currentX = locMiddle.getX(); +- currentY = locMiddle.getY(); +- currentZ = locMiddle.getZ(); ++ // can not move from node A to node C ++ ++ // set node A (node B is part of path, update A coordinates) ++ nodeAx = nodeB.getGeoX(); ++ nodeAy = nodeB.getGeoY(); ++ nodeAz = (short) nodeB.getZ(); + } ++ ++ // set node B ++ nodeB = (GeoLocation) point.next(); + } + + return path; +@@ -158,144 +166,102 @@ + } + + /** +- * Convert geodata position to pathnode position +- * @param geo_pos +- * @return pathnode position ++ * Create list of node locations as result of calculated buffer node tree. ++ * @param node : the entry point ++ * @return List : list of node location + */ +- public short getNodePos(int geo_pos) ++ private static List constructPath(Node node) + { +- return (short) (geo_pos >> 3); // OK? +- } +- +- /** +- * Convert node position to pathnode block position +- * @param node_pos +- * @return pathnode block position (0...255) +- */ +- public short getNodeBlock(int node_pos) +- { +- return (short) (node_pos % 256); +- } +- +- public byte getRegionX(int node_pos) +- { +- return (byte) ((node_pos >> 8) + World.TILE_X_MIN); +- } +- +- public byte getRegionY(int node_pos) +- { +- return (byte) ((node_pos >> 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 node_x rx +- * @return +- */ +- public int calculateWorldX(short node_x) +- { +- return World.MAP_MIN_X + (node_x * 128) + 48; +- } +- +- /** +- * Convert pathnode y to World y position +- * @param node_y +- * @return +- */ +- public int calculateWorldY(short node_y) +- { +- return World.MAP_MIN_Y + (node_y * 128) + 48; +- } +- +- private List constructPath(AbstractNode nodeValue) +- { +- final LinkedList path = new LinkedList<>(); +- int previousDirectionX = Integer.MIN_VALUE; +- int previousDirectionY = Integer.MIN_VALUE; +- int directionX, directionY; ++ // create empty list ++ final LinkedList list = new LinkedList<>(); + +- AbstractNode node = nodeValue; +- while (node.getParent() != null) ++ // set direction X/Y ++ int dx = 0; ++ int dy = 0; ++ ++ // get target parent ++ Node target = node; ++ Node parent = target.getParent(); ++ ++ // while parent exists ++ int count = 0; ++ while ((parent != null) && (count++ < Config.MAX_ITERATIONS)) + { +- directionX = node.getLoc().getNodeX() - node.getParent().getLoc().getNodeX(); +- directionY = node.getLoc().getNodeY() - node.getParent().getLoc().getNodeY(); ++ // get parent <> target direction X/Y ++ final int nx = parent.getLoc().getGeoX() - target.getLoc().getGeoX(); ++ final int ny = parent.getLoc().getGeoY() - target.getLoc().getGeoY(); + +- // only add a new route point if moving direction changes +- if ((directionX != previousDirectionX) || (directionY != previousDirectionY)) ++ // direction has changed? ++ if ((dx != nx) || (dy != ny)) + { +- previousDirectionX = directionX; +- previousDirectionY = directionY; ++ // add node to the beginning of the list ++ list.addFirst(target.getLoc()); + +- path.addFirst(node.getLoc()); +- node.setLoc(null); ++ // update direction X/Y ++ dx = nx; ++ dy = ny; + } + +- node = node.getParent(); ++ // move to next node, set target and get its parent ++ target = parent; ++ parent = target.getParent(); + } + +- return path; ++ // return list ++ return list; + } + +- private CellNodeBuffer alloc(int size) ++ /** ++ * Provides optimize selection of the buffer. When all pre-initialized buffer are locked, creates new buffer. ++ * @param size : pre-calculated minimal required size ++ * @return NodeBuffer : buffer ++ */ ++ private final NodeBuffer getBuffer(int size) + { +- CellNodeBuffer current = null; +- for (BufferInfo i : _buffers) ++ NodeBuffer current = null; ++ for (BufferHolder holder : _buffers) + { +- if (i.mapSize >= size) ++ // Find proper size of buffer ++ if (holder._size < size) + { +- for (CellNodeBuffer buf : i.buffer) ++ continue; ++ } ++ ++ // Find unlocked NodeBuffer ++ for (NodeBuffer buffer : holder._buffer) ++ { ++ if (!buffer.isLocked()) + { +- if (buf.lock()) +- { +- current = buf; +- break; +- } ++ continue; + } +- if (current != null) +- { +- break; +- } + +- // not found, allocate temporary buffer +- current = new CellNodeBuffer(i.mapSize); +- current.lock(); +- if (i.buffer.size() < i.count) +- { +- i.buffer.add(current); +- break; +- } ++ return buffer; + } ++ ++ // NodeBuffer not found, allocate temporary buffer ++ current = new NodeBuffer(holder._size); ++ current.isLocked(); + } + + return current; + } + +- private static final class BufferInfo ++ /** ++ * NodeBuffer container with specified size and count of separate buffers. ++ */ ++ private static final class BufferHolder + { +- final int mapSize; +- final int count; +- ArrayList buffer; ++ final int _size; ++ List _buffer; + +- public BufferInfo(int size, int cnt) ++ public BufferHolder(int size, int count) + { +- mapSize = size; +- count = cnt; +- buffer = new ArrayList<>(count); ++ _size = size; ++ _buffer = new ArrayList<>(count); ++ for (int i = 0; i < count; i++) ++ { ++ _buffer.add(new NodeBuffer(size)); ++ } + } + } +- +- public static GeoEnginePathfinding getInstance() +- { +- return SingletonHolder.INSTANCE; +- } +- +- private static class SingletonHolder +- { +- protected static final GeoEnginePathfinding INSTANCE = new GeoEnginePathfinding(); +- } +-} ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java (working copy) +@@ -0,0 +1,197 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++import java.io.IOException; ++ ++/** ++ * @author Hasha ++ */ ++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 height of cell, which is closest to given coordinates.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @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 getHeightNearestOriginal(int geoX, int geoY, int worldZ); ++ ++ /** ++ * Returns the height of cell, which is first above 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, above given coordinates. ++ */ ++ public abstract short getHeightAbove(int geoX, int geoY, int worldZ); ++ ++ /** ++ * Returns the height of cell, which is first below 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, below given coordinates. ++ */ ++ public abstract short getHeightBelow(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 the NSWE flag byte of cell, which is closest to given coordinates.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @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 getNsweNearestOriginal(int geoX, int geoY, int worldZ); ++ ++ /** ++ * Returns the NSWE flag byte of cell, which is first above 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 getNsweAbove(int geoX, int geoY, int worldZ); ++ ++ /** ++ * Returns the NSWE flag byte of cell, which is first below 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 getNsweBelow(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 above given coordinates.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @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 getIndexAboveOriginal(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 index to data of the cell, which is first layer below given coordinates.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @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 getIndexBelowOriginal(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 height of cell given by cell index.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @param index : Index of the cell. ++ * @return short : Cell geodata Z coordinate, below given coordinates. ++ */ ++ public abstract short getHeightOriginal(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); ++ ++ /** ++ * Returns the NSWE flag byte of cell given by cell index.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @param index : Index of the cell. ++ * @return short : Cell geodata Z coordinate, below given coordinates. ++ */ ++ public abstract byte getNsweOriginal(int index); ++ ++ /** ++ * Sets the NSWE flag byte of cell given by cell index. ++ * @param index : Index of the cell. ++ * @param nswe : New NSWE flag byte. ++ */ ++ public abstract void setNswe(int index, byte nswe); ++ ++ /** ++ * Saves the block in L2D format to {@link BufferedOutputStream}. Used only for L2D geodata conversion. ++ * @param stream : The stream. ++ * @throws IOException : Can't save the block to steam. ++ */ ++ public abstract void saveBlock(BufferedOutputStream stream) throws IOException; ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java (working copy) +@@ -0,0 +1,252 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++import java.io.IOException; ++import java.nio.ByteBuffer; ++ ++/** ++ * @author Hasha ++ */ ++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. ++ * @param format : GeoFormat specifying format of loaded data. ++ */ ++ public BlockComplex(ByteBuffer bb, GeoFormat format) ++ { ++ // initialize buffer ++ _buffer = new byte[GeoStructure.BLOCK_CELLS * 3]; ++ ++ // load data ++ for (int i = 0; i < GeoStructure.BLOCK_CELLS; i++) ++ { ++ if (format != GeoFormat.L2D) ++ { ++ // 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); ++ } ++ else ++ { ++ // get nswe ++ final byte nswe = bb.get(); ++ _buffer[i * 3] = nswe; ++ ++ // get height ++ final short height = bb.getShort(); ++ _buffer[(i * 3) + 1] = (byte) (height & 0x00FF); ++ _buffer[(i * 3) + 2] = (byte) (height >> 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 short getHeightNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getHeightNearest(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeightAbove(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 short height = (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ ++ // check and return height ++ return height > worldZ ? height : Short.MIN_VALUE; ++ } ++ ++ @Override ++ public short getHeightBelow(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 short height = (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ ++ // check and return height ++ return height < worldZ ? height : Short.MAX_VALUE; ++ } ++ ++ @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 byte getNsweNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getNsweNearest(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public byte getNsweAbove(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 ? _buffer[index] : 0; ++ } ++ ++ @Override ++ public byte getNsweBelow(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 ? _buffer[index] : 0; ++ } ++ ++ @Override ++ public 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 getIndexAboveOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexAbove(geoX, geoY, worldZ); ++ } ++ ++ @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 int getIndexBelowOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexBelow(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeight(int index) ++ { ++ return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ } ++ ++ @Override ++ public short getHeightOriginal(int index) ++ { ++ return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ } ++ ++ @Override ++ public byte getNswe(int index) ++ { ++ return _buffer[index]; ++ } ++ ++ @Override ++ public byte getNsweOriginal(int index) ++ { ++ return _buffer[index]; ++ } ++ ++ @Override ++ public void setNswe(int index, byte nswe) ++ { ++ _buffer[index] = nswe; ++ } ++ ++ @Override ++ public void saveBlock(BufferedOutputStream stream) throws IOException ++ { ++ // write block type ++ stream.write(GeoStructure.TYPE_COMPLEX_L2D); ++ ++ // write block data ++ stream.write(_buffer, 0, GeoStructure.BLOCK_CELLS * 3); ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java (working copy) +@@ -0,0 +1,176 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++import java.io.IOException; ++import java.nio.ByteBuffer; ++ ++/** ++ * @author Hasha ++ */ ++public class BlockFlat extends ABlock ++{ ++ protected final short _height; ++ protected byte _nswe; ++ ++ /** ++ * Creates FlatBlock. ++ * @param bb : Input byte buffer. ++ * @param format : GeoFormat specifying format of loaded data. ++ */ ++ public BlockFlat(ByteBuffer bb, GeoFormat format) ++ { ++ _height = bb.getShort(); ++ _nswe = format != GeoFormat.L2D ? 0x0F : (byte) (0xFF); ++ if (format == GeoFormat.L2OFF) ++ { ++ bb.getShort(); ++ } ++ } ++ ++ @Override ++ public boolean hasGeoPos() ++ { ++ return true; ++ } ++ ++ @Override ++ public short getHeightNearest(int geoX, int geoY, int worldZ) ++ { ++ return _height; ++ } ++ ++ @Override ++ public short getHeightNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return _height; ++ } ++ ++ @Override ++ public short getHeightAbove(int geoX, int geoY, int worldZ) ++ { ++ // check and return height ++ return _height > worldZ ? _height : Short.MIN_VALUE; ++ } ++ ++ @Override ++ public short getHeightBelow(int geoX, int geoY, int worldZ) ++ { ++ // check and return height ++ return _height < worldZ ? _height : Short.MAX_VALUE; ++ } ++ ++ @Override ++ public byte getNsweNearest(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweAbove(int geoX, int geoY, int worldZ) ++ { ++ // check height and return nswe ++ return _height > worldZ ? _nswe : 0; ++ } ++ ++ @Override ++ public byte getNsweBelow(int geoX, int geoY, int worldZ) ++ { ++ // check height and return nswe ++ return _height < worldZ ? _nswe : 0; ++ } ++ ++ @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 getIndexAboveOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexAbove(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public int getIndexBelow(int geoX, int geoY, int worldZ) ++ { ++ // check height and return index ++ return _height < worldZ ? 0 : -1; ++ } ++ ++ @Override ++ public int getIndexBelowOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexBelow(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeight(int index) ++ { ++ return _height; ++ } ++ ++ @Override ++ public short getHeightOriginal(int index) ++ { ++ return _height; ++ } ++ ++ @Override ++ public byte getNswe(int index) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweOriginal(int index) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public void setNswe(int index, byte nswe) ++ { ++ _nswe = nswe; ++ } ++ ++ @Override ++ public void saveBlock(BufferedOutputStream stream) throws IOException ++ { ++ // write block type ++ stream.write(GeoStructure.TYPE_FLAT_L2D); ++ ++ // write height ++ stream.write((byte) (_height & 0x00FF)); ++ stream.write((byte) (_height >> 8)); ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java (working copy) +@@ -0,0 +1,465 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++import java.io.IOException; ++import java.nio.ByteBuffer; ++import java.nio.ByteOrder; ++import java.util.Arrays; ++ ++/** ++ * @author Hasha ++ */ ++public class BlockMultilayer extends ABlock ++{ ++ private static final int MAX_LAYERS = Byte.MAX_VALUE; ++ ++ private static ByteBuffer _temp; ++ ++ /** ++ * Initializes the temporarily buffer. ++ */ ++ public static void initialize() ++ { ++ // initialize temporarily buffer and sorting mechanism ++ _temp = ByteBuffer.allocate(GeoStructure.BLOCK_CELLS * MAX_LAYERS * 3); ++ _temp.order(ByteOrder.LITTLE_ENDIAN); ++ } ++ ++ /** ++ * Releases temporarily buffer. ++ */ ++ public static void release() ++ { ++ _temp = null; ++ } ++ ++ protected byte[] _buffer; ++ ++ /** ++ * Implicit constructor for children class. ++ */ ++ protected BlockMultilayer() ++ { ++ _buffer = null; ++ } ++ ++ /** ++ * Creates MultilayerBlock. ++ * @param bb : Input byte buffer. ++ * @param format : GeoFormat specifying format of loaded data. ++ */ ++ public BlockMultilayer(ByteBuffer bb, GeoFormat format) ++ { ++ // 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 = format != GeoFormat.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++) ++ { ++ if (format != GeoFormat.L2D) ++ { ++ // get data ++ final short data = bb.getShort(); ++ ++ // add nswe and height ++ _temp.put((byte) (data & 0x000F)); ++ _temp.putShort((short) ((short) (data & 0xFFF0) >> 1)); ++ } ++ else ++ { ++ // add nswe ++ _temp.put(bb.get()); ++ ++ // add height ++ _temp.putShort(bb.getShort()); ++ } ++ } ++ } ++ ++ // 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 short getHeightNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getHeightNearest(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeightAbove(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 height ++ if (height > worldZ) ++ { ++ return (short) height; ++ } ++ ++ // move index to next layer ++ index -= 3; ++ } ++ ++ // none layer found, return minimum value ++ return Short.MIN_VALUE; ++ } ++ ++ @Override ++ public short getHeightBelow(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 height ++ if (height < worldZ) ++ { ++ return (short) height; ++ } ++ ++ // move index to next layer ++ index += 3; ++ } ++ ++ // none layer found, return maximum value ++ return Short.MAX_VALUE; ++ } ++ ++ @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 byte getNsweNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getNsweNearest(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public byte getNsweAbove(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 nswe ++ if (height > worldZ) ++ { ++ return _buffer[index]; ++ } ++ ++ // move index to next layer ++ index -= 3; ++ } ++ ++ // none layer found, block movement ++ return 0; ++ } ++ ++ @Override ++ public byte getNsweBelow(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 nswe ++ if (height < worldZ) ++ { ++ return _buffer[index]; ++ } ++ ++ // move index to next layer ++ index += 3; ++ } ++ ++ // none layer found, block movement ++ return 0; ++ } ++ ++ @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 first layer data (first from bottom) ++ byte layers = _buffer[index++]; ++ ++ // loop though all cell layers, find closest layer ++ 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; ++ } ++ ++ // none layer found ++ return -1; ++ } ++ ++ @Override ++ public int getIndexAboveOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexAbove(geoX, geoY, worldZ); ++ } ++ ++ @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; ++ } ++ ++ // none layer found ++ return -1; ++ } ++ ++ @Override ++ public int getIndexBelowOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexBelow(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeight(int index) ++ { ++ // get height ++ return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ } ++ ++ @Override ++ public short getHeightOriginal(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]; ++ } ++ ++ @Override ++ public byte getNsweOriginal(int index) ++ { ++ // get nswe ++ return _buffer[index]; ++ } ++ ++ @Override ++ public void setNswe(int index, byte nswe) ++ { ++ // set nswe ++ _buffer[index] = nswe; ++ } ++ ++ @Override ++ public void saveBlock(BufferedOutputStream stream) throws IOException ++ { ++ // write block type ++ stream.write(GeoStructure.TYPE_MULTILAYER_L2D); ++ ++ // for each cell ++ int index = 0; ++ for (int i = 0; i < GeoStructure.BLOCK_CELLS; i++) ++ { ++ // write layers count ++ final byte layers = _buffer[index++]; ++ stream.write(layers); ++ ++ // write cell data ++ stream.write(_buffer, index, layers * 3); ++ ++ // move index to next cell ++ index += layers * 3; ++ } ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java (working copy) +@@ -0,0 +1,150 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++ ++/** ++ * @author Hasha ++ */ ++public class BlockNull extends ABlock ++{ ++ private final byte _nswe; ++ ++ public BlockNull() ++ { ++ _nswe = (byte) 0xFF; ++ } ++ ++ @Override ++ public boolean hasGeoPos() ++ { ++ return false; ++ } ++ ++ @Override ++ public short getHeightNearest(int geoX, int geoY, int worldZ) ++ { ++ return (short) worldZ; ++ } ++ ++ @Override ++ public short getHeightNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return (short) worldZ; ++ } ++ ++ @Override ++ public short getHeightAbove(int geoX, int geoY, int worldZ) ++ { ++ return (short) worldZ; ++ } ++ ++ @Override ++ public short getHeightBelow(int geoX, int geoY, int worldZ) ++ { ++ return (short) worldZ; ++ } ++ ++ @Override ++ public byte getNsweNearest(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweAbove(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweBelow(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) ++ { ++ return 0; ++ } ++ ++ @Override ++ public int getIndexAboveOriginal(int geoX, int geoY, int worldZ) ++ { ++ return 0; ++ } ++ ++ @Override ++ public int getIndexBelow(int geoX, int geoY, int worldZ) ++ { ++ return 0; ++ } ++ ++ @Override ++ public int getIndexBelowOriginal(int geoX, int geoY, int worldZ) ++ { ++ return 0; ++ } ++ ++ @Override ++ public short getHeight(int index) ++ { ++ return 0; ++ } ++ ++ @Override ++ public short getHeightOriginal(int index) ++ { ++ return 0; ++ } ++ ++ @Override ++ public byte getNswe(int index) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweOriginal(int index) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public void setNswe(int index, byte nswe) ++ { ++ } ++ ++ @Override ++ public void saveBlock(BufferedOutputStream stream) ++ { ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java (nonexistent) +@@ -1,48 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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() +- { +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/ComplexBlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/ComplexBlock.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/ComplexBlock.java (nonexistent) +@@ -1,77 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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; +- +-/** +- * @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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/FlatBlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/FlatBlock.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/FlatBlock.java (nonexistent) +@@ -1,56 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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; +- +-/** +- * @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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/GeoFormat.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/GeoFormat.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/GeoFormat.java (working copy) +@@ -0,0 +1,39 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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 Hasha ++ */ ++public enum GeoFormat ++{ ++ L2J("%d_%d.l2j"), ++ L2OFF("%d_%d_conv.dat"), ++ L2D("%d_%d.l2d"); ++ ++ private final String _filename; ++ ++ private GeoFormat(String filename) ++ { ++ _filename = filename; ++ } ++ ++ public String getFilename() ++ { ++ return _filename; ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/GeoLocation.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/GeoLocation.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/GeoLocation.java (working copy) +@@ -0,0 +1,67 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.geoengine.GeoEngine; ++import org.l2jmobius.gameserver.model.Location; ++ ++/** ++ * @author Hasha ++ */ ++public class GeoLocation extends Location ++{ ++ private byte _nswe; ++ ++ public GeoLocation(int x, int y, int z) ++ { ++ super(x, y, GeoEngine.getInstance().getHeightNearest(x, y, z)); ++ _nswe = GeoEngine.getInstance().getNsweNearest(x, y, z); ++ } ++ ++ public void set(int x, int y, short z) ++ { ++ super.setXYZ(x, y, GeoEngine.getInstance().getHeightNearest(x, y, z)); ++ _nswe = GeoEngine.getInstance().getNsweNearest(x, y, z); ++ } ++ ++ public int getGeoX() ++ { ++ return _x; ++ } ++ ++ public int getGeoY() ++ { ++ return _y; ++ } ++ ++ @Override ++ public int getX() ++ { ++ return GeoEngine.getWorldX(_x); ++ } ++ ++ @Override ++ public int getY() ++ { ++ return GeoEngine.getWorldY(_y); ++ } ++ ++ public byte getNSWE() ++ { ++ return _nswe; ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java (working copy) +@@ -0,0 +1,70 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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; ++ ++/** ++ * @author Hasha ++ */ ++public class GeoStructure ++{ ++ // cells ++ public static final byte CELL_FLAG_E = 1 << 0; ++ public static final byte CELL_FLAG_W = 1 << 1; ++ public static final byte CELL_FLAG_S = 1 << 2; ++ public static final byte CELL_FLAG_N = 1 << 3; ++ public static final byte CELL_FLAG_SE = 1 << 4; ++ public static final byte CELL_FLAG_SW = 1 << 5; ++ public static final byte CELL_FLAG_NE = 1 << 6; ++ public static final byte CELL_FLAG_NW = (byte) (1 << 7); ++ ++ public static final int CELL_HEIGHT = 8; ++ public static final int CELL_IGNORE_HEIGHT = CELL_HEIGHT * 6; ++ ++ // blocks ++ public static final byte TYPE_FLAT_L2J_L2OFF = 0; ++ public static final byte TYPE_FLAT_L2D = (byte) 0xD0; ++ public static final byte TYPE_COMPLEX_L2J = 1; ++ public static final byte TYPE_COMPLEX_L2OFF = 0x40; ++ public static final byte TYPE_COMPLEX_L2D = (byte) 0xD1; ++ 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) ++ public static final byte TYPE_MULTILAYER_L2D = (byte) 0xD2; ++ ++ 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; ++ ++ // regions ++ 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; ++ ++ // global geodata ++ private static final int GEO_REGIONS_X = ((World.TILE_X_MAX - World.TILE_X_MIN) + 1); ++ private 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 +Index: java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java (nonexistent) +@@ -1,42 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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); +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/IGeoObject.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/IGeoObject.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/IGeoObject.java (working copy) +@@ -0,0 +1,53 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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 Hasha ++ */ ++public interface IGeoObject ++{ ++ /** ++ * Returns geodata X coordinate of the {@link IGeoObject}. ++ * @return int : Geodata X coordinate. ++ */ ++ int getGeoX(); ++ ++ /** ++ * Returns geodata Y coordinate of the {@link IGeoObject}. ++ * @return int : Geodata Y coordinate. ++ */ ++ int getGeoY(); ++ ++ /** ++ * Returns geodata Z coordinate of the {@link IGeoObject}. ++ * @return int : Geodata Z coordinate. ++ */ ++ int getGeoZ(); ++ ++ /** ++ * Returns height of the {@link IGeoObject}. ++ * @return int : Height. ++ */ ++ int getHeight(); ++ ++ /** ++ * Returns {@link IGeoObject} data. ++ * @return byte[][] : {@link IGeoObject} data. ++ */ ++ byte[][] getObjectGeoData(); ++} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java (nonexistent) +@@ -1,47 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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(); +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/MultilayerBlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/MultilayerBlock.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/MultilayerBlock.java (nonexistent) +@@ -1,183 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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; +- +-/** +- * @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); +- +- // 1 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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/NullRegion.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/NullRegion.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/NullRegion.java (nonexistent) +@@ -1,55 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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 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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/Region.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/Region.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/Region.java (nonexistent) +@@ -1,92 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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; +- +-/** +- * @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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java (nonexistent) +@@ -1,87 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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 T _loc; +- private AbstractNode _parent; +- +- public AbstractNode(T loc) +- { +- _loc = loc; +- } +- +- public void setParent(AbstractNode p) +- { +- _parent = p; +- } +- +- public AbstractNode getParent() +- { +- return _parent; +- } +- +- public T getLoc() +- { +- return _loc; +- } +- +- public void setLoc(T l) +- { +- _loc = l; +- } +- +- @Override +- public int hashCode() +- { +- final int prime = 31; +- int result = 1; +- result = (prime * result) + ((_loc == null) ? 0 : _loc.hashCode()); +- return result; +- } +- +- @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 +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java (nonexistent) +@@ -1,33 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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 int getNodeX(); +- +- public abstract int getNodeY(); +-} +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNode.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNode.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNode.java (nonexistent) +@@ -1,67 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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 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 +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNodeBuffer.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNodeBuffer.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNodeBuffer.java (nonexistent) +@@ -1,348 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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.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 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) +- { +- _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(); +- } +- +- 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 void getNeighbors() +- { +- if (!_current.getLoc().canGoAll()) +- { +- 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); +- } +- +- // SouthEast +- if ((nodeE != null) && (nodeS != null)) +- { +- if (nodeE.getLoc().canGoSouth() && nodeS.getLoc().canGoEast()) +- { +- addNode(x + 1, y + 1, z, true); +- } +- } +- +- // SouthWest +- if ((nodeS != null) && (nodeW != null)) +- { +- if (nodeW.getLoc().canGoSouth() && nodeS.getLoc().canGoWest()) +- { +- addNode(x - 1, y + 1, z, true); +- } +- } +- +- // NorthEast +- if ((nodeN != null) && (nodeE != null)) +- { +- if (nodeE.getLoc().canGoNorth() && nodeN.getLoc().canGoEast()) +- { +- addNode(x + 1, y - 1, z, true); +- } +- } +- +- // NorthWest +- if ((nodeN != null) && (nodeW != null)) +- { +- if (nodeW.getLoc().canGoNorth() && nodeN.getLoc().canGoWest()) +- { +- addNode(x - 1, y - 1, z, true); +- } +- } +- } +- +- private 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(); +- // reinit node if needed +- if (result.getLoc() != null) +- { +- result.getLoc().set(x, y, z); +- } +- else +- { +- result.setLoc(new NodeLoc(x, y, z)); +- } +- } +- +- return result; +- } +- +- private 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 boolean isHighWeight(int x, int y, int z) +- { +- final CellNode result = getNode(x, y, z); +- if (result == null) +- { +- return true; +- } +- +- if (!result.getLoc().canGoAll()) +- { +- return true; +- } +- if (Math.abs(result.getLoc().getZ() - z) > 16) +- { +- return true; +- } +- +- return false; +- } +- +- private 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 +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java (working copy) +@@ -0,0 +1,87 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.geodata.GeoLocation; ++ ++/** ++ * @author Hasha ++ */ ++public class Node ++{ ++ // node coords and nswe flag ++ private GeoLocation _loc; ++ ++ // node parent (for reverse path construction) ++ private Node _parent; ++ // node child (for moving over nodes during iteration) ++ private Node _child; ++ ++ // node G cost (movement cost = parent movement cost + current movement cost) ++ private double _cost = -1000; ++ ++ public void setLoc(int x, int y, int z) ++ { ++ _loc = new GeoLocation(x, y, z); ++ } ++ ++ public GeoLocation getLoc() ++ { ++ return _loc; ++ } ++ ++ public void setParent(Node parent) ++ { ++ _parent = parent; ++ } ++ ++ public Node getParent() ++ { ++ return _parent; ++ } ++ ++ public void setChild(Node child) ++ { ++ _child = child; ++ } ++ ++ public Node getChild() ++ { ++ return _child; ++ } ++ ++ public void setCost(double cost) ++ { ++ _cost = cost; ++ } ++ ++ public double getCost() ++ { ++ return _cost; ++ } ++ ++ public void free() ++ { ++ // reset node location ++ _loc = null; ++ ++ // reset node parent, child and cost ++ _parent = null; ++ _child = null; ++ _cost = -1000; ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java (working copy) +@@ -0,0 +1,306 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.concurrent.locks.ReentrantLock; ++ ++import org.l2jmobius.Config; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; ++ ++/** ++ * @author DS, Hasha; Credits to Diamond ++ */ ++public class NodeBuffer ++{ ++ private final ReentrantLock _lock = new ReentrantLock(); ++ private final int _size; ++ private final Node[][] _buffer; ++ ++ // center coordinates ++ private int _cx = 0; ++ private int _cy = 0; ++ ++ // target coordinates ++ private int _gtx = 0; ++ private int _gty = 0; ++ private short _gtz = 0; ++ ++ private Node _current = null; ++ ++ /** ++ * Constructor of NodeBuffer. ++ * @param size : one dimension size of buffer ++ */ ++ public NodeBuffer(int size) ++ { ++ // set size ++ _size = size; ++ ++ // initialize buffer ++ _buffer = new Node[size][size]; ++ for (int x = 0; x < size; x++) ++ { ++ for (int y = 0; y < size; y++) ++ { ++ _buffer[x][y] = 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 Node : first node of path ++ */ ++ public Node findPath(int gox, int goy, short goz, int gtx, int gty, short gtz) ++ { ++ // set coordinates (middle of the line (gox,goy) - (gtx,gty), will be in the center of the buffer) ++ _cx = gox + ((gtx - gox - _size) / 2); ++ _cy = goy + ((gty - goy - _size) / 2); ++ ++ _gtx = gtx; ++ _gty = gty; ++ _gtz = gtz; ++ ++ _current = getNode(gox, goy, goz); ++ _current.setCost(getCostH(gox, goy, goz)); ++ ++ int count = 0; ++ do ++ { ++ // reached target? ++ if ((_current.getLoc().getGeoX() == _gtx) && (_current.getLoc().getGeoY() == _gty) && (Math.abs(_current.getLoc().getZ() - _gtz) < 8)) ++ { ++ return _current; ++ } ++ ++ // expand current node ++ expand(); ++ ++ // move pointer ++ _current = _current.getChild(); ++ } ++ while ((_current != null) && (count++ < Config.MAX_ITERATIONS)); ++ ++ return null; ++ } ++ ++ public boolean isLocked() ++ { ++ return _lock.tryLock(); ++ } ++ ++ public void free() ++ { ++ _current = null; ++ ++ for (Node[] nodes : _buffer) ++ { ++ for (Node node : nodes) ++ { ++ if (node.getLoc() != null) ++ { ++ node.free(); ++ } ++ } ++ } ++ ++ _lock.unlock(); ++ } ++ ++ /** ++ * Check _current Node and add its neighbors to the buffer. ++ */ ++ private final void expand() ++ { ++ // can't move anywhere, don't expand ++ final byte nswe = _current.getLoc().getNSWE(); ++ if (nswe == 0) ++ { ++ return; ++ } ++ ++ // get geo coords of the node to be expanded ++ final int x = _current.getLoc().getGeoX(); ++ final int y = _current.getLoc().getGeoY(); ++ final short z = (short) _current.getLoc().getZ(); ++ ++ // can move north, expand ++ if ((nswe & GeoStructure.CELL_FLAG_N) != 0) ++ { ++ addNode(x, y - 1, z, Config.BASE_WEIGHT); ++ } ++ ++ // can move south, expand ++ if ((nswe & GeoStructure.CELL_FLAG_S) != 0) ++ { ++ addNode(x, y + 1, z, Config.BASE_WEIGHT); ++ } ++ ++ // can move west, expand ++ if ((nswe & GeoStructure.CELL_FLAG_W) != 0) ++ { ++ addNode(x - 1, y, z, Config.BASE_WEIGHT); ++ } ++ ++ // can move east, expand ++ if ((nswe & GeoStructure.CELL_FLAG_E) != 0) ++ { ++ addNode(x + 1, y, z, Config.BASE_WEIGHT); ++ } ++ ++ // can move north-west, expand ++ if ((nswe & GeoStructure.CELL_FLAG_NW) != 0) ++ { ++ addNode(x - 1, y - 1, z, Config.DIAGONAL_WEIGHT); ++ } ++ ++ // can move north-east, expand ++ if ((nswe & GeoStructure.CELL_FLAG_NE) != 0) ++ { ++ addNode(x + 1, y - 1, z, Config.DIAGONAL_WEIGHT); ++ } ++ ++ // can move south-west, expand ++ if ((nswe & GeoStructure.CELL_FLAG_SW) != 0) ++ { ++ addNode(x - 1, y + 1, z, Config.DIAGONAL_WEIGHT); ++ } ++ ++ // can move south-east, expand ++ if ((nswe & GeoStructure.CELL_FLAG_SE) != 0) ++ { ++ addNode(x + 1, y + 1, z, Config.DIAGONAL_WEIGHT); ++ } ++ } ++ ++ /** ++ * Returns node, if it exists in buffer. ++ * @param x : node X coord ++ * @param y : node Y coord ++ * @param z : node Z coord ++ * @return Node : node, if exits in buffer ++ */ ++ private final Node getNode(int x, int y, short z) ++ { ++ // check node X out of coordinates ++ final int ix = x - _cx; ++ if ((ix < 0) || (ix >= _size)) ++ { ++ return null; ++ } ++ ++ // check node Y out of coordinates ++ final int iy = y - _cy; ++ if ((iy < 0) || (iy >= _size)) ++ { ++ return null; ++ } ++ ++ // get node ++ final Node result = _buffer[ix][iy]; ++ ++ // check and update ++ if (result.getLoc() == null) ++ { ++ result.setLoc(x, y, z); ++ } ++ ++ // return node ++ return result; ++ } ++ ++ /** ++ * Add node given by coordinates to the buffer. ++ * @param x : geo X coord ++ * @param y : geo Y coord ++ * @param z : geo Z coord ++ * @param weight : weight of movement to new node ++ */ ++ private final void addNode(int x, int y, short z, int weight) ++ { ++ // get node to be expanded ++ final Node node = getNode(x, y, z); ++ if (node == null) ++ { ++ return; ++ } ++ ++ // Z distance between nearby cells is higher than cell size ++ if (node.getLoc().getZ() > (z + (2 * GeoStructure.CELL_HEIGHT))) ++ { ++ return; ++ } ++ ++ // node was already expanded, return ++ if (node.getCost() >= 0) ++ { ++ return; ++ } ++ ++ node.setParent(_current); ++ if (node.getLoc().getNSWE() != (byte) 0xFF) ++ { ++ node.setCost(getCostH(x, y, node.getLoc().getZ()) + (weight * Config.OBSTACLE_MULTIPLIER)); ++ } ++ else ++ { ++ node.setCost(getCostH(x, y, node.getLoc().getZ()) + weight); ++ } ++ ++ Node current = _current; ++ int count = 0; ++ while ((current.getChild() != null) && (count < (Config.MAX_ITERATIONS * 4))) ++ { ++ count++; ++ if (current.getChild().getCost() > node.getCost()) ++ { ++ node.setChild(current.getChild()); ++ break; ++ } ++ current = current.getChild(); ++ } ++ ++ if (count >= (Config.MAX_ITERATIONS * 4)) ++ { ++ System.err.println("Pathfinding: too long loop detected, cost:" + node.getCost()); ++ } ++ ++ current.setChild(node); ++ } ++ ++ /** ++ * @param x : node X coord ++ * @param y : node Y coord ++ * @param i : node Z coord ++ * @return double : node cost ++ */ ++ private final double getCostH(int x, int y, int i) ++ { ++ final int dX = x - _gtx; ++ final int dY = y - _gty; ++ final int dZ = (i - _gtz) / GeoStructure.CELL_HEIGHT; ++ ++ // return (Math.abs(dX) + Math.abs(dY) + Math.abs(dZ)) * Config.HEURISTIC_WEIGHT; // Manhattan distance ++ return Math.sqrt((dX * dX) + (dY * dY) + (dZ * dZ)) * Config.HEURISTIC_WEIGHT; // Direct distance ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeLoc.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeLoc.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeLoc.java (nonexistent) +@@ -1,183 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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.Cell; +- +-/** +- * @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 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 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; +- // return super.hashCode(); +- } +- +- @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; +- if (_x != other._x) +- { +- return false; +- } +- if (_y != other._y) +- { +- return false; +- } +- if (_goNorth != other._goNorth) +- { +- return false; +- } +- if (_goEast != other._goEast) +- { +- return false; +- } +- if (_goSouth != other._goSouth) +- { +- return false; +- } +- if (_goWest != other._goWest) +- { +- return false; +- } +- if (_geoHeight != other._geoHeight) +- { +- return false; +- } +- return true; +- } +-} +Index: java/org/l2jmobius/gameserver/model/actor/Creature.java +=================================================================== +--- java/org/l2jmobius/gameserver/model/actor/Creature.java (revision 8399) ++++ java/org/l2jmobius/gameserver/model/actor/Creature.java (working copy) +@@ -66,8 +66,6 @@ + import org.l2jmobius.gameserver.enums.TeleportWhereType; + import org.l2jmobius.gameserver.enums.UserInfoType; + import org.l2jmobius.gameserver.geoengine.GeoEngine; +-import org.l2jmobius.gameserver.geoengine.GeoEnginePathfinding; +-import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; + import org.l2jmobius.gameserver.instancemanager.IdManager; + import org.l2jmobius.gameserver.instancemanager.MapRegionManager; + import org.l2jmobius.gameserver.instancemanager.QuestManager; +@@ -2577,7 +2575,7 @@ + + public boolean disregardingGeodata; + public int onGeodataPathIndex; +- public List geoPath; ++ public List geoPath; + public int geoPathAccurateTx; + public int geoPathAccurateTy; + public int geoPathGtx; +@@ -3466,7 +3464,7 @@ + if (((originalDistance - distance) > 30) && !isControlBlocked() && !isInVehicle) + { + // Path calculation -- overrides previous movement check +- m.geoPath = GeoEnginePathfinding.getInstance().findPath(curX, curY, curZ, originalX, originalY, originalZ, getInstanceWorld()); ++ m.geoPath = GeoEngine.getInstance().findPath(curX, curY, curZ, originalX, originalY, originalZ, getInstanceWorld()); + if ((m.geoPath == null) || (m.geoPath.size() < 2)) // No path found + { + if (isPlayer() && !_isFlying && !isInWater) +Index: java/org/l2jmobius/gameserver/model/actor/instance/PlayerInstance.java +=================================================================== +--- java/org/l2jmobius/gameserver/model/actor/instance/PlayerInstance.java (revision 8397) ++++ java/org/l2jmobius/gameserver/model/actor/instance/PlayerInstance.java (working copy) +@@ -12704,7 +12704,7 @@ + { + if (Config.CORRECT_PLAYER_Z) + { +- final int nearestZ = GeoEngine.getInstance().getHigherHeight(getX(), getY(), getZ()); ++ final int nearestZ = GeoEngine.getInstance().getHeightNearest(getX(), getY(), getZ()); + if (getZ() < nearestZ) + { + teleToLocation(new Location(getX(), getY(), nearestZ)); +Index: java/org/l2jmobius/gameserver/util/GeoUtils.java +=================================================================== +--- java/org/l2jmobius/gameserver/util/GeoUtils.java (revision 8397) ++++ java/org/l2jmobius/gameserver/util/GeoUtils.java (working copy) +@@ -19,7 +19,7 @@ + import java.awt.Color; + + import org.l2jmobius.gameserver.geoengine.GeoEngine; +-import org.l2jmobius.gameserver.geoengine.geodata.Cell; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; + import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance; + import org.l2jmobius.gameserver.network.serverpackets.ExServerPrimitive; + +@@ -26,25 +26,25 @@ + /** + * @author HorridoJoho + */ +-public final class GeoUtils ++public class GeoUtils + { + public static void debug2DLine(PlayerInstance player, int x, int y, int tx, int ty, int z) + { +- final int gx = GeoEngine.getInstance().getGeoX(x); +- final int gy = GeoEngine.getInstance().getGeoY(y); ++ final int gx = GeoEngine.getGeoX(x); ++ final int gy = GeoEngine.getGeoY(y); + +- final int tgx = GeoEngine.getInstance().getGeoX(tx); +- final int tgy = GeoEngine.getInstance().getGeoY(ty); ++ final int tgx = GeoEngine.getGeoX(tx); ++ final int tgy = GeoEngine.getGeoY(ty); + + final ExServerPrimitive prim = new ExServerPrimitive("Debug2DLine", x, y, z); +- prim.addLine(Color.BLUE, GeoEngine.getInstance().getWorldX(gx), GeoEngine.getInstance().getWorldY(gy), z, GeoEngine.getInstance().getWorldX(tgx), GeoEngine.getInstance().getWorldY(tgy), z); ++ prim.addLine(Color.BLUE, GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), z, GeoEngine.getWorldX(tgx), GeoEngine.getWorldY(tgy), z); + + final LinePointIterator iter = new LinePointIterator(gx, gy, tgx, tgy); + + while (iter.next()) + { +- final int wx = GeoEngine.getInstance().getWorldX(iter.x()); +- final int wy = GeoEngine.getInstance().getWorldY(iter.y()); ++ final int wx = GeoEngine.getWorldX(iter.x()); ++ final int wy = GeoEngine.getWorldY(iter.y()); + + prim.addPoint(Color.RED, wx, wy, z); + } +@@ -53,21 +53,21 @@ + + public static void debug3DLine(PlayerInstance player, int x, int y, int z, int tx, int ty, int tz) + { +- final int gx = GeoEngine.getInstance().getGeoX(x); +- final int gy = GeoEngine.getInstance().getGeoY(y); ++ final int gx = GeoEngine.getGeoX(x); ++ final int gy = GeoEngine.getGeoY(y); + +- final int tgx = GeoEngine.getInstance().getGeoX(tx); +- final int tgy = GeoEngine.getInstance().getGeoY(ty); ++ final int tgx = GeoEngine.getGeoX(tx); ++ final int tgy = GeoEngine.getGeoY(ty); + + final ExServerPrimitive prim = new ExServerPrimitive("Debug3DLine", x, y, z); +- prim.addLine(Color.BLUE, GeoEngine.getInstance().getWorldX(gx), GeoEngine.getInstance().getWorldY(gy), z, GeoEngine.getInstance().getWorldX(tgx), GeoEngine.getInstance().getWorldY(tgy), tz); ++ prim.addLine(Color.BLUE, GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), z, GeoEngine.getWorldX(tgx), GeoEngine.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.getInstance().getWorldX(prevX); +- int wy = GeoEngine.getInstance().getWorldY(prevY); ++ int wx = GeoEngine.getWorldX(prevX); ++ int wy = GeoEngine.getWorldY(prevY); + int wz = iter.z(); + prim.addPoint(Color.RED, wx, wy, wz); + +@@ -78,8 +78,8 @@ + + if ((curX != prevX) || (curY != prevY)) + { +- wx = GeoEngine.getInstance().getWorldX(curX); +- wy = GeoEngine.getInstance().getWorldY(curY); ++ wx = GeoEngine.getWorldX(curX); ++ wy = GeoEngine.getWorldY(curY); + wz = iter.z(); + + prim.addPoint(Color.RED, wx, wy, wz); +@@ -93,7 +93,7 @@ + + private static Color getDirectionColor(int x, int y, int z, int nswe) + { +- if (GeoEngine.getInstance().checkNearestNswe(x, y, z, nswe)) ++ if ((GeoEngine.getInstance().getNsweNearest(x, y, z) & nswe) == nswe) + { + return Color.GREEN; + } +@@ -109,9 +109,8 @@ + int iPacket = 0; + + ExServerPrimitive exsp = null; +- final GeoEngine ge = GeoEngine.getInstance(); +- final int playerGx = ge.getGeoX(player.getX()); +- final int playerGy = ge.getGeoY(player.getY()); ++ final int playerGx = GeoEngine.getGeoX(player.getX()); ++ final int playerGy = GeoEngine.getGeoY(player.getY()); + for (int dx = -geoRadius; dx <= geoRadius; ++dx) + { + for (int dy = -geoRadius; dy <= geoRadius; ++dy) +@@ -135,12 +134,12 @@ + final int gx = playerGx + dx; + final int gy = playerGy + dy; + +- final int x = ge.getWorldX(gx); +- final int y = ge.getWorldY(gy); +- final int z = ge.getNearestZ(gx, gy, player.getZ()); ++ final int x = GeoEngine.getWorldX(gx); ++ final int y = GeoEngine.getWorldY(gy); ++ final int z = GeoEngine.getInstance().getHeightNearest(gx, gy, player.getZ()); + + // north arrow +- Color col = getDirectionColor(gx, gy, z, Cell.NSWE_NORTH); ++ Color col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_N); + 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); +@@ -147,7 +146,7 @@ + exsp.addLine(col, x - 4, y - 4, z, x + 4, y - 4, z); + + // east arrow +- col = getDirectionColor(gx, gy, z, Cell.NSWE_EAST); ++ col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_E); + 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); +@@ -154,13 +153,13 @@ + exsp.addLine(col, x + 4, y - 4, z, x + 4, y + 4, z); + + // south arrow +- col = getDirectionColor(gx, gy, z, Cell.NSWE_SOUTH); ++ col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_S); + 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, Cell.NSWE_WEST); ++ col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_W); + 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); +@@ -188,15 +187,15 @@ + { + if (y > lastY) + { +- return Cell.NSWE_SOUTH_EAST; ++ return GeoStructure.CELL_FLAG_SE; // Direction.SOUTH_EAST; + } + else if (y < lastY) + { +- return Cell.NSWE_NORTH_EAST; ++ return GeoStructure.CELL_FLAG_NE; // Direction.NORTH_EAST; + } + else + { +- return Cell.NSWE_EAST; ++ return GeoStructure.CELL_FLAG_E; // Direction.EAST; + } + } + else if (x < lastX) // west +@@ -203,26 +202,27 @@ + { + if (y > lastY) + { +- return Cell.NSWE_SOUTH_WEST; ++ return GeoStructure.CELL_FLAG_SW; // Direction.SOUTH_WEST; + } + else if (y < lastY) + { +- return Cell.NSWE_NORTH_WEST; ++ return GeoStructure.CELL_FLAG_NW; // Direction.NORTH_WEST; + } + else + { +- return Cell.NSWE_WEST; ++ return GeoStructure.CELL_FLAG_W; // Direction.WEST; + } + } +- else // unchanged x ++ else ++ // unchanged x + { + if (y > lastY) + { +- return Cell.NSWE_SOUTH; ++ return GeoStructure.CELL_FLAG_S; // Direction.SOUTH; + } + else if (y < lastY) + { +- return Cell.NSWE_NORTH; ++ return GeoStructure.CELL_FLAG_N; // Direction.NORTH; + } + else + { +Index: java/org/l2jmobius/tools/geodataconverter/GeoDataConverter.java +=================================================================== +--- java/org/l2jmobius/tools/geodataconverter/GeoDataConverter.java (nonexistent) ++++ java/org/l2jmobius/tools/geodataconverter/GeoDataConverter.java (working copy) +@@ -0,0 +1,386 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ */ ++package org.l2jmobius.tools.geodataconverter; ++ ++import java.io.BufferedOutputStream; ++import java.io.File; ++import java.io.FileOutputStream; ++import java.io.RandomAccessFile; ++import java.nio.ByteOrder; ++import java.nio.MappedByteBuffer; ++import java.nio.channels.FileChannel; ++import java.util.Scanner; ++ ++import org.l2jmobius.Config; ++import org.l2jmobius.commons.util.PropertiesParser; ++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.GeoFormat; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; ++import org.l2jmobius.gameserver.model.World; ++ ++/** ++ * @author Hasha ++ */ ++public class GeoDataConverter ++{ ++ private static GeoFormat _format; ++ private static ABlock[][] _blocks; ++ ++ public static void main(String[] args) ++ { ++ // initialize config ++ loadGeoengineConfigs(); ++ ++ // get geodata format ++ String type = ""; ++ try (Scanner scn = new Scanner(System.in)) ++ { ++ while (!(type.equalsIgnoreCase("J") || type.equalsIgnoreCase("O") || type.equalsIgnoreCase("E"))) ++ { ++ System.out.println("GeoDataConverter: Select source geodata type:"); ++ System.out.println(" J: L2J (e.g. 23_22.l2j)"); ++ System.out.println(" O: L2OFF (e.g. 23_22_conv.dat)"); ++ System.out.println(" E: Exit"); ++ System.out.print("Choice: "); ++ type = scn.next(); ++ } ++ } ++ if (type.equalsIgnoreCase("E")) ++ { ++ System.exit(0); ++ } ++ ++ _format = type.equalsIgnoreCase("J") ? GeoFormat.L2J : GeoFormat.L2OFF; ++ ++ // start conversion ++ System.out.println("GeoDataConverter: Converting all " + _format + " files."); ++ ++ // initialize geodata container ++ _blocks = new ABlock[GeoStructure.REGION_BLOCKS_X][GeoStructure.REGION_BLOCKS_Y]; ++ ++ // initialize multilayer temporarily buffer ++ BlockMultilayer.initialize(); ++ ++ // load geo files ++ int converted = 0; ++ for (int rx = World.TILE_X_MIN; rx <= World.TILE_X_MAX; rx++) ++ { ++ for (int ry = World.TILE_Y_MIN; ry <= World.TILE_Y_MAX; ry++) ++ { ++ final String input = String.format(_format.getFilename(), rx, ry); ++ final String filepath = Config.GEODATA_PATH; ++ final File f = new File(filepath + input); ++ if (f.exists() && !f.isDirectory()) ++ { ++ // load geodata ++ if (!loadGeoBlocks(input)) ++ { ++ System.out.println("GeoDataConverter: Unable to load " + input + " region file."); ++ continue; ++ } ++ ++ // recalculate nswe ++ if (!recalculateNswe()) ++ { ++ System.out.println("GeoDataConverter: Unable to convert " + input + " region file."); ++ continue; ++ } ++ ++ // save geodata ++ final String output = String.format(GeoFormat.L2D.getFilename(), rx, ry); ++ if (!saveGeoBlocks(output)) ++ { ++ System.out.println("GeoDataConverter: Unable to save " + output + " region file."); ++ continue; ++ } ++ ++ converted++; ++ System.out.println("GeoDataConverter: Created " + output + " region file."); ++ } ++ } ++ } ++ System.out.println("GeoDataConverter: Converted " + converted + " " + _format + " to L2D region file(s)."); ++ ++ // release multilayer block temporarily buffer ++ BlockMultilayer.release(); ++ } ++ ++ /** ++ * Loads geo blocks from buffer of the region file. ++ * @param filename : The name of the to load. ++ * @return boolean : True when successful. ++ */ ++ private static boolean loadGeoBlocks(String filename) ++ { ++ // region file is load-able, try to load it ++ try (RandomAccessFile raf = new RandomAccessFile(Config.GEODATA_PATH + filename, "r"); ++ FileChannel fc = raf.getChannel()) ++ { ++ final MappedByteBuffer buffer = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()).load(); ++ buffer.order(ByteOrder.LITTLE_ENDIAN); ++ ++ // load 18B header for L2off geodata (1st and 2nd byte...region X and Y) ++ if (_format == GeoFormat.L2OFF) ++ { ++ for (int i = 0; i < 18; i++) ++ { ++ buffer.get(); ++ } ++ } ++ ++ // 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 (_format == GeoFormat.L2J) ++ { ++ // get block type ++ final byte type = buffer.get(); ++ ++ // load block according to block type ++ switch (type) ++ { ++ case GeoStructure.TYPE_FLAT_L2J_L2OFF: ++ { ++ _blocks[ix][iy] = new BlockFlat(buffer, _format); ++ break; ++ } ++ case GeoStructure.TYPE_COMPLEX_L2J: ++ { ++ _blocks[ix][iy] = new BlockComplex(buffer, _format); ++ break; ++ } ++ case GeoStructure.TYPE_MULTILAYER_L2J: ++ { ++ _blocks[ix][iy] = new BlockMultilayer(buffer, _format); ++ break; ++ } ++ default: ++ { ++ throw new IllegalArgumentException("Unknown block type: " + type); ++ } ++ } ++ } ++ else ++ { ++ // get block type ++ final short type = buffer.getShort(); ++ ++ // load block according to block type ++ switch (type) ++ { ++ case GeoStructure.TYPE_FLAT_L2J_L2OFF: ++ { ++ _blocks[ix][iy] = new BlockFlat(buffer, _format); ++ break; ++ } ++ case GeoStructure.TYPE_COMPLEX_L2OFF: ++ { ++ _blocks[ix][iy] = new BlockComplex(buffer, _format); ++ break; ++ } ++ default: ++ { ++ _blocks[ix][iy] = new BlockMultilayer(buffer, _format); ++ break; ++ } ++ } ++ } ++ } ++ } ++ ++ if (buffer.remaining() > 0) ++ { ++ System.out.println("GeoDataConverter: Region file " + filename + " can be corrupted, remaining " + buffer.remaining() + " bytes to read."); ++ return false; ++ } ++ ++ return true; ++ } ++ catch (Exception e) ++ { ++ System.out.println("GeoDataConverter: Error while loading " + filename + " region file."); ++ return false; ++ } ++ } ++ ++ /** ++ * Recalculate diagonal flags for the region file. ++ * @return boolean : True when successful. ++ */ ++ private static boolean recalculateNswe() ++ { ++ try ++ { ++ for (int x = 0; x < GeoStructure.REGION_CELLS_X; x++) ++ { ++ for (int y = 0; y < GeoStructure.REGION_CELLS_Y; y++) ++ { ++ // get block ++ final ABlock block = _blocks[x / GeoStructure.BLOCK_CELLS_X][y / GeoStructure.BLOCK_CELLS_Y]; ++ ++ // skip flat blocks ++ if (block instanceof BlockFlat) ++ { ++ continue; ++ } ++ ++ // for complex and multilayer blocks go though all layers ++ short height = Short.MAX_VALUE; ++ int index; ++ while ((index = block.getIndexBelow(x, y, height)) != -1) ++ { ++ // get height and nswe ++ height = block.getHeight(index); ++ byte nswe = block.getNswe(index); ++ ++ // update nswe with diagonal flags ++ nswe = updateNsweBelow(x, y, height, nswe); ++ ++ // set nswe of the cell ++ block.setNswe(index, nswe); ++ } ++ } ++ } ++ ++ return true; ++ } ++ catch (Exception e) ++ { ++ return false; ++ } ++ } ++ ++ /** ++ * Updates the NSWE flag with diagonal flags. ++ * @param x : Geodata X coordinate. ++ * @param y : Geodata Y coordinate. ++ * @param z : Geodata Z coordinate. ++ * @param nsweValue : NSWE flag to be updated. ++ * @return byte : Updated NSWE flag. ++ */ ++ private static byte updateNsweBelow(int x, int y, short z, byte nsweValue) ++ { ++ byte nswe = nsweValue; ++ ++ // calculate virtual layer height ++ final short height = (short) (z + GeoStructure.CELL_IGNORE_HEIGHT); ++ ++ // get NSWE of neighbor cells below virtual layer (NPC/PC can fall down of clif, but can not climb it -> NSWE of cell below) ++ final byte nsweN = getNsweBelow(x, y - 1, height); ++ final byte nsweS = getNsweBelow(x, y + 1, height); ++ final byte nsweW = getNsweBelow(x - 1, y, height); ++ final byte nsweE = getNsweBelow(x + 1, y, height); ++ ++ // north-west ++ if ((((nswe & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) || (((nswe & GeoStructure.CELL_FLAG_W) != 0) && ((nsweW & GeoStructure.CELL_FLAG_N) != 0))) ++ { ++ nswe |= GeoStructure.CELL_FLAG_NW; ++ } ++ ++ // north-east ++ if ((((nswe & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) || (((nswe & GeoStructure.CELL_FLAG_E) != 0) && ((nsweE & GeoStructure.CELL_FLAG_N) != 0))) ++ { ++ nswe |= GeoStructure.CELL_FLAG_NE; ++ } ++ ++ // south-west ++ if ((((nswe & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) || (((nswe & GeoStructure.CELL_FLAG_W) != 0) && ((nsweW & GeoStructure.CELL_FLAG_S) != 0))) ++ { ++ nswe |= GeoStructure.CELL_FLAG_SW; ++ } ++ ++ // south-east ++ if ((((nswe & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) || (((nswe & GeoStructure.CELL_FLAG_E) != 0) && ((nsweE & GeoStructure.CELL_FLAG_S) != 0))) ++ { ++ nswe |= GeoStructure.CELL_FLAG_SE; ++ } ++ ++ return nswe; ++ } ++ ++ private static byte getNsweBelow(int geoX, int geoY, short worldZ) ++ { ++ // out of geo coordinates ++ if ((geoX < 0) || (geoX >= GeoStructure.REGION_CELLS_X)) ++ { ++ return 0; ++ } ++ ++ // out of geo coordinates ++ if ((geoY < 0) || (geoY >= GeoStructure.REGION_CELLS_Y)) ++ { ++ return 0; ++ } ++ ++ // get block ++ final ABlock block = _blocks[geoX / GeoStructure.BLOCK_CELLS_X][geoY / GeoStructure.BLOCK_CELLS_Y]; ++ ++ // get index, when valid, return nswe ++ final int index = block.getIndexBelow(geoX, geoY, worldZ); ++ return index == -1 ? 0 : block.getNswe(index); ++ } ++ ++ /** ++ * Save region file to file. ++ * @param filename : The name of file to save. ++ * @return boolean : True when successful. ++ */ ++ private static boolean saveGeoBlocks(String filename) ++ { ++ try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(Config.GEODATA_PATH + filename), GeoStructure.REGION_BLOCKS * GeoStructure.BLOCK_CELLS * 3)) ++ { ++ // loop over region blocks and save each block ++ for (int ix = 0; ix < GeoStructure.REGION_BLOCKS_X; ix++) ++ { ++ for (int iy = 0; iy < GeoStructure.REGION_BLOCKS_Y; iy++) ++ { ++ _blocks[ix][iy].saveBlock(bos); ++ } ++ } ++ ++ // flush data to file ++ bos.flush(); ++ ++ return true; ++ } ++ catch (Exception e) ++ { ++ return false; ++ } ++ } ++ ++ private static void loadGeoengineConfigs() ++ { ++ final PropertiesParser geoData = new PropertiesParser(Config.GEOENGINE_CONFIG_FILE); ++ Config.GEODATA_PATH = geoData.getString("GeoDataPath", "./data/geodata/"); ++ Config.COORD_SYNCHRONIZE = geoData.getInt("CoordSynchronize", -1); ++ Config.PART_OF_CHARACTER_HEIGHT = geoData.getInt("PartOfCharacterHeight", 75); ++ Config.MAX_OBSTACLE_HEIGHT = geoData.getInt("MaxObstacleHeight", 32); ++ Config.PATHFINDING = geoData.getBoolean("PathFinding", true); ++ Config.PATHFIND_BUFFERS = geoData.getString("PathFindBuffers", "100x6;128x6;192x6;256x4;320x4;384x4;500x2"); ++ Config.BASE_WEIGHT = geoData.getInt("BaseWeight", 10); ++ Config.DIAGONAL_WEIGHT = geoData.getInt("DiagonalWeight", 14); ++ Config.OBSTACLE_MULTIPLIER = geoData.getInt("ObstacleMultiplier", 10); ++ Config.HEURISTIC_WEIGHT = geoData.getInt("HeuristicWeight", 20); ++ Config.MAX_ITERATIONS = geoData.getInt("MaxIterations", 3500); ++ } ++} +\ No newline at end of file diff --git a/L2J_Mobius_C4_ScionsOfDestiny/dist/game/data/geodata/L2D_GeoEngine.patch b/L2J_Mobius_C4_ScionsOfDestiny/dist/game/data/geodata/L2D_GeoEngine.patch new file mode 100644 index 0000000000..473e18592e --- /dev/null +++ b/L2J_Mobius_C4_ScionsOfDestiny/dist/game/data/geodata/L2D_GeoEngine.patch @@ -0,0 +1,5951 @@ +Index: dist/game/GeoDataConverter.bat +=================================================================== +--- dist/game/GeoDataConverter.bat (nonexistent) ++++ dist/game/GeoDataConverter.bat (working copy) +@@ -0,0 +1,6 @@ ++@echo off ++title L2D geodata converter ++ ++java -Xmx512m -cp ./../libs/* org.l2jmobius.tools.geodataconverter.GeoDataConverter ++ ++pause +Index: dist/game/GeoDataConverter.sh +=================================================================== +--- dist/game/GeoDataConverter.sh (nonexistent) ++++ dist/game/GeoDataConverter.sh (working copy) +@@ -0,0 +1,4 @@ ++#! /bin/sh ++ ++java -Xmx512m -cp ../libs/*: org.l2jmobius.tools.geodataconverter.GeoDataConverter > log/stdout.log 2>&1 ++ +Index: dist/game/config/main/GeoEngine.ini +=================================================================== +--- dist/game/config/main/GeoEngine.ini (revision 8319) ++++ dist/game/config/main/GeoEngine.ini (working copy) +@@ -1,6 +1,10 @@ + # ================================================================= + # Geodata + # ================================================================= ++# Because of real-time performance we are using geodata files only in ++# diagonal L2D format now (using filename e.g. 22_16.l2d). ++# L2D geodata can be obtained by conversion of existing L2J or L2OFF geodata. ++# Launch "GeoDataConverter.bat/sh" and follow instructions to start the conversion. + + # 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/ +@@ -13,6 +17,16 @@ + CoordSynchronize = 2 + + # ================================================================= ++# Path checking ++# ================================================================= ++ ++# 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 finding + # ================================================================= + +@@ -23,19 +37,23 @@ + # Pathfinding array buffers configuration, default: 100x6;128x6;192x6;256x4;320x4;384x4;500x2 + PathFindBuffers = 100x6;128x6;192x6;256x4;320x4;384x4;500x2 + +-# Weight for nodes without obstacles far from walls +-LowWeight = 0.5 ++# Base path weight, when moving from one node to another on axis direction, default: 10 ++BaseWeight = 10 + +-# Weight for nodes near walls +-MediumWeight = 2 ++# Path weight, when moving from one node to another on diagonal direction, default: BaseWeight * sqrt(2) = 14 ++DiagonalWeight = 14 + +-# Weight for nodes with obstacles +-HighWeight = 3 ++# When movement flags of target node is blocked to any direction, multiply movement weight by this multiplier. ++# This causes pathfinding algorithm to avoid path construction exactly near an obstacle, default: 10 ++ObstacleMultiplier = 10 + +-# Weight for diagonal movement. +-# Default: LowWeight * sqrt(2) +-DiagonalWeight = 0.707 ++# Weight of the heuristic algorithm, which is giving estimated cost from node to target, default: 20 ++# For proper function must be higher than BaseWeight and/or DiagonalWeight. ++HeuristicWeight = 20 + ++# Maximum number of generated nodes per one path-finding process, default 3500 ++MaxIterations = 3500 ++ + # ================================================================= + # Other + # ================================================================= +Index: java/org/l2jmobius/Config.java +=================================================================== +--- java/org/l2jmobius/Config.java (revision 8388) ++++ java/org/l2jmobius/Config.java (working copy) +@@ -27,8 +27,6 @@ + import java.io.LineNumberReader; + import java.io.OutputStream; + import java.math.BigInteger; +-import java.nio.file.Path; +-import java.nio.file.Paths; + import java.util.ArrayList; + import java.util.HashMap; + import java.util.List; +@@ -930,14 +928,17 @@ + public static boolean LEAVE_BUFFS_ON_DIE; + public static boolean ALT_RAIDS_STATS_BONUS; + +- public static Path GEODATA_PATH; ++ public static String GEODATA_PATH; ++ public static int COORD_SYNCHRONIZE; ++ public static int PART_OF_CHARACTER_HEIGHT; ++ public static int MAX_OBSTACLE_HEIGHT; + public static boolean PATHFINDING; + public static String PATHFIND_BUFFERS; +- public static float LOW_WEIGHT; +- public static float MEDIUM_WEIGHT; +- public static float HIGH_WEIGHT; +- public static float DIAGONAL_WEIGHT; +- public static int COORD_SYNCHRONIZE; ++ public static int BASE_WEIGHT; ++ public static int DIAGONAL_WEIGHT; ++ public static int HEURISTIC_WEIGHT; ++ public static int OBSTACLE_MULTIPLIER; ++ public static int MAX_ITERATIONS; + public static boolean CORRECT_PLAYER_Z; + public static boolean FALL_DAMAGE; + public static boolean ALLOW_WATER; +@@ -2495,14 +2496,17 @@ + public static void loadgeodataConfig() + { + final PropertiesParser geoengineConfig = new PropertiesParser(GEOENGINE_CONFIG_FILE); +- GEODATA_PATH = Paths.get(geoengineConfig.getString("GeoDataPath", "./data/geodata")); ++ GEODATA_PATH = geoengineConfig.getString("GeoDataPath", "./data/geodata/"); ++ COORD_SYNCHRONIZE = geoengineConfig.getInt("CoordSynchronize", -1); ++ PART_OF_CHARACTER_HEIGHT = geoengineConfig.getInt("PartOfCharacterHeight", 75); ++ MAX_OBSTACLE_HEIGHT = geoengineConfig.getInt("MaxObstacleHeight", 32); + PATHFINDING = geoengineConfig.getBoolean("PathFinding", true); + 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); +- DIAGONAL_WEIGHT = geoengineConfig.getFloat("DiagonalWeight", 0.707f); +- COORD_SYNCHRONIZE = geoengineConfig.getInt("CoordSynchronize", -1); ++ BASE_WEIGHT = geoengineConfig.getInt("BaseWeight", 10); ++ DIAGONAL_WEIGHT = geoengineConfig.getInt("DiagonalWeight", 14); ++ OBSTACLE_MULTIPLIER = geoengineConfig.getInt("ObstacleMultiplier", 10); ++ HEURISTIC_WEIGHT = geoengineConfig.getInt("HeuristicWeight", 20); ++ MAX_ITERATIONS = geoengineConfig.getInt("MaxIterations", 3500); + CORRECT_PLAYER_Z = geoengineConfig.getBoolean("CorrectPlayerZ", false); + FALL_DAMAGE = geoengineConfig.getBoolean("FallDamage", false); + ALLOW_WATER = geoengineConfig.getBoolean("AllowWater", false); +Index: java/org/l2jmobius/gameserver/geoengine/GeoEngine.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/GeoEngine.java (revision 8352) ++++ java/org/l2jmobius/gameserver/geoengine/GeoEngine.java (working copy) +@@ -16,98 +16,89 @@ + */ + package org.l2jmobius.gameserver.geoengine; + +-import java.io.IOException; ++import java.io.File; + import java.io.RandomAccessFile; + import java.nio.ByteOrder; +-import java.nio.channels.FileChannel.MapMode; +-import java.nio.file.Files; +-import java.nio.file.Path; +-import java.util.concurrent.atomic.AtomicReferenceArray; +-import java.util.logging.Level; ++import java.nio.MappedByteBuffer; ++import java.nio.channels.FileChannel; ++import java.util.List; + 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.geoengine.geodata.Cell; +-import org.l2jmobius.gameserver.geoengine.geodata.IRegion; +-import org.l2jmobius.gameserver.geoengine.geodata.NullRegion; +-import org.l2jmobius.gameserver.geoengine.geodata.Region; ++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.GeoFormat; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoLocation; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; + import org.l2jmobius.gameserver.model.Location; + import org.l2jmobius.gameserver.model.World; + import org.l2jmobius.gameserver.model.WorldObject; +-import org.l2jmobius.gameserver.util.GeoUtils; +-import org.l2jmobius.gameserver.util.LinePointIterator; +-import org.l2jmobius.gameserver.util.LinePointIterator3D; ++import org.l2jmobius.gameserver.model.actor.Creature; ++import org.l2jmobius.gameserver.util.MathUtil; + + /** +- * @author -Nemesiss-, HorridoJoho ++ * @author Hasha + */ + public class GeoEngine + { +- private static final Logger LOGGER = Logger.getLogger(GeoEngine.class.getName()); ++ protected static final Logger LOGGER = Logger.getLogger(GeoEngine.class.getName()); + +- private static final int WORLD_MIN_X = -655360; +- private static final int WORLD_MIN_Y = -589824; +- private static final int WORLD_MIN_Z = -16384; ++ private final ABlock[][] _blocks; ++ private final BlockNull _nullBlock; + +- /** 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; +- +- /** The regions array */ +- private final AtomicReferenceArray _regions = new AtomicReferenceArray<>(GEO_REGIONS); +- +- 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; +- +- protected GeoEngine() ++ /** ++ * GeoEngine constructor. Loads all geodata files of chosen geodata format. ++ */ ++ public GeoEngine() + { + LOGGER.info("GeoEngine: Initializing..."); +- for (int i = 0; i < _regions.length(); i++) +- { +- _regions.set(i, NullRegion.INSTANCE); +- } + ++ // 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; +- try ++ long fileSize = 0; ++ for (int rx = World.TILE_X_MIN; rx <= World.TILE_X_MAX; rx++) + { +- for (int regionX = World.TILE_X_MIN; regionX <= World.TILE_X_MAX; regionX++) ++ for (int ry = World.TILE_Y_MIN; ry <= World.TILE_Y_MAX; ry++) + { +- for (int regionY = World.TILE_Y_MIN; regionY <= World.TILE_Y_MAX; regionY++) ++ final File f = new File(Config.GEODATA_PATH + String.format(GeoFormat.L2D.getFilename(), rx, ry)); ++ if (f.exists() && !f.isDirectory()) + { +- 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(rx, ry)) + { +- try (RandomAccessFile raf = new RandomAccessFile(geoFilePath.toFile(), "r")) +- { +- _regions.set((regionX * GEO_REGIONS_Y) + regionY, new Region(raf.getChannel().map(MapMode.READ_ONLY, 0, raf.length()).load().order(ByteOrder.LITTLE_ENDIAN))); +- loaded++; +- } ++ loaded++; ++ fileSize += f.length(); + } + } ++ else ++ { ++ // region file is not load-able, load null blocks ++ loadNullBlocks(rx, ry); ++ } + } + } +- catch (Exception e) ++ ++ LOGGER.info("GeoEngine: Loaded " + loaded + " geodata files."); ++ if (loaded > 0) + { +- LOGGER.log(Level.SEVERE, "GeoEngine: Failed to load geodata!", e); +- System.exit(1); ++ LOGGER.info("GeoEngine: Total geodata file size " + (fileSize / 1024 / 1024) + " MB."); + } + +- LOGGER.info("GeoEngine: Loaded " + loaded + " geodata files."); +- +- // Avoid wrong configs when no files are loaded. ++ // avoid wrong configs when no files are loaded + if (loaded == 0) + { + if (Config.PATHFINDING) +@@ -121,619 +112,820 @@ + LOGGER.info("GeoEngine: Forcing CoordSynchronize setting to -1."); + } + } ++ ++ // release multilayer block temporarily buffer ++ BlockMultilayer.release(); + } + + /** +- * @param geoX +- * @param geoY +- * @return the region ++ * 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 IRegion getRegion(int geoX, int geoY) ++ private final boolean loadGeoBlocks(int regionX, int regionY) + { +- final int region = ((geoX / IRegion.REGION_CELLS_X) * GEO_REGIONS_Y) + (geoY / IRegion.REGION_CELLS_Y); +- if ((region < 0) || (region >= _regions.length())) ++ final String filename = String.format(GeoFormat.L2D.getFilename(), regionX, regionY); ++ ++ // standard load ++ try (RandomAccessFile raf = new RandomAccessFile(Config.GEODATA_PATH + filename, "r"); ++ FileChannel fc = raf.getChannel()) + { +- return null; ++ // initialize file buffer ++ final MappedByteBuffer buffer = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()).load(); ++ buffer.order(ByteOrder.LITTLE_ENDIAN); ++ ++ // 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++) ++ { ++ // get block type ++ final byte type = buffer.get(); ++ ++ // load block according to block type ++ switch (type) ++ { ++ case GeoStructure.TYPE_FLAT_L2D: ++ { ++ _blocks[blockX + ix][blockY + iy] = new BlockFlat(buffer, GeoFormat.L2D); ++ break; ++ } ++ case GeoStructure.TYPE_COMPLEX_L2D: ++ { ++ _blocks[blockX + ix][blockY + iy] = new BlockComplex(buffer, GeoFormat.L2D); ++ break; ++ } ++ case GeoStructure.TYPE_MULTILAYER_L2D: ++ { ++ _blocks[blockX + ix][blockY + iy] = new BlockMultilayer(buffer, GeoFormat.L2D); ++ break; ++ } ++ default: ++ { ++ throw new IllegalArgumentException("Unknown block type: " + type); ++ } ++ } ++ } ++ } ++ ++ // check data consistency ++ if (buffer.remaining() > 0) ++ { ++ LOGGER.warning("GeoEngine: Region file " + filename + " can be corrupted, remaining " + buffer.remaining() + " bytes to read."); ++ } ++ ++ // loading was successful ++ return true; + } +- return _regions.get(region); +- } +- +- /** +- * @param filePath +- * @param regionX +- * @param regionY +- * @throws IOException +- */ +- 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")) ++ catch (Exception e) + { +- _regions.set(regionOffset, new Region(raf.getChannel().map(MapMode.READ_ONLY, 0, raf.length()).load().order(ByteOrder.LITTLE_ENDIAN))); ++ // an error occured while loading, load null blocks ++ LOGGER.warning("GeoEngine: Error while loading " + filename + " region file."); ++ LOGGER.warning(e.getMessage()); ++ ++ // replace whole region file with null blocks ++ loadNullBlocks(regionX, regionY); ++ ++ // loading was not successful ++ return false; + } + } + + /** +- * @param regionX +- * @param regionY ++ * 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. + */ +- public void unloadRegion(int regionX, int regionY) ++ private final void loadNullBlocks(int regionX, int regionY) + { +- _regions.set((regionX * GEO_REGIONS_Y) + regionY, NullRegion.INSTANCE); +- } +- +- /** +- * @param geoX +- * @param geoY +- * @return if geodata exist +- */ +- public boolean hasGeoPos(int geoX, int geoY) +- { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) ++ // 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++) + { +- return false; ++ for (int iy = 0; iy < GeoStructure.REGION_BLOCKS_Y; iy++) ++ { ++ _blocks[blockX + ix][blockY + iy] = _nullBlock; ++ } + } +- return region.hasGeo(); + } + ++ // GEODATA - GENERAL ++ + /** +- * Checks the specified position for available geodata. +- * @param x the world x +- * @param y the world y +- * @return {@code true} if there is geodata for the given coordinates, {@code false} otherwise ++ * Converts world X to geodata X. ++ * @param worldX ++ * @return int : Geo X + */ +- public boolean hasGeo(int x, int y) ++ public static int getGeoX(int worldX) + { +- return hasGeoPos(getGeoX(x), getGeoY(y)); ++ return (MathUtil.limit(worldX, World.MAP_MIN_X, World.MAP_MAX_X) - World.MAP_MIN_X) >> 4; + } + + /** +- * @param geoX +- * @param geoY +- * @param worldZ +- * @param nswe +- * @return the nearest nswe check ++ * Converts world Y to geodata Y. ++ * @param worldY ++ * @return int : Geo Y + */ +- public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) ++ public static int getGeoY(int worldY) + { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) +- { +- return true; +- } +- return region.checkNearestNswe(geoX, geoY, worldZ, nswe); ++ return (MathUtil.limit(worldY, World.MAP_MIN_Y, World.MAP_MAX_Y) - World.MAP_MIN_Y) >> 4; + } + + /** ++ * Converts geodata X to world X. + * @param geoX +- * @param geoY +- * @param worldZ +- * @param nswe +- * @return the nearest nswe anti-corner cut ++ * @return int : World X + */ +- public boolean checkNearestNsweAntiCornerCut(int geoX, int geoY, int worldZ, int nswe) ++ public static int getWorldX(int geoX) + { +- boolean can = true; +- if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) +- { +- can = checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_EAST) && checkNearestNswe(geoX + 1, geoY, worldZ, Cell.NSWE_NORTH); +- } +- if (can && ((nswe & Cell.NSWE_NORTH_WEST) == Cell.NSWE_NORTH_WEST)) +- { +- 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 = 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 = checkNearestNswe(geoX, geoY + 1, worldZ, Cell.NSWE_WEST) && checkNearestNswe(geoX - 1, geoY, worldZ, Cell.NSWE_SOUTH); +- } +- return can && checkNearestNswe(geoX, geoY, worldZ, nswe); ++ return (MathUtil.limit(geoX, 0, GeoStructure.GEO_CELLS_X) << 4) + World.MAP_MIN_X + 8; + } + + /** +- * @param geoX ++ * Converts geodata Y to world Y. + * @param geoY +- * @param worldZ +- * @return the nearest Z value ++ * @return int : World Y + */ +- public int getNearestZ(int geoX, int geoY, int worldZ) ++ public static int getWorldY(int geoY) + { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) +- { +- return worldZ; +- } +- return region.getNearestZ(geoX, geoY, worldZ); ++ return (MathUtil.limit(geoY, 0, GeoStructure.GEO_CELLS_Y) << 4) + World.MAP_MIN_Y + 8; + } + + /** +- * @param geoX +- * @param geoY +- * @param worldZ +- * @return the next lower Z value ++ * Returns block of geodata on given coordinates. ++ * @param geoX : Geodata X ++ * @param geoY : Geodata Y ++ * @return {@link ABlock} : Block of geodata. + */ +- public int getNextLowerZ(int geoX, int geoY, int worldZ) ++ private final ABlock getBlock(int geoX, int geoY) + { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) ++ final int x = geoX / GeoStructure.BLOCK_CELLS_X; ++ final int y = geoY / GeoStructure.BLOCK_CELLS_Y; ++ ++ // if x or y is out of array return null ++ if ((x > -1) && (y > -1) && (x < GeoStructure.GEO_BLOCKS_X) && (y < GeoStructure.GEO_BLOCKS_Y)) + { +- return worldZ; ++ return _blocks[x][y]; + } +- return region.getNextLowerZ(geoX, geoY, worldZ); ++ return null; + } + + /** +- * @param geoX +- * @param geoY +- * @param worldZ +- * @return the next higher Z value ++ * Check if geo coordinates has geo. ++ * @param geoX : Geodata X ++ * @param geoY : Geodata Y ++ * @return boolean : True, if given geo coordinates have geodata + */ +- public int getNextHigherZ(int geoX, int geoY, int worldZ) ++ public boolean hasGeoPos(int geoX, int geoY) + { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) ++ final ABlock block = getBlock(geoX, geoY); ++ if (block == null) // null block check + { +- return worldZ; ++ // TODO: Find when this can be null. (Bad geodata? Check World getRegion method.) ++ // LOGGER.warning("Could not find geodata block at " + getWorldX(geoX) + ", " + getWorldY(geoY) + "."); ++ return false; + } +- return region.getNextHigherZ(geoX, geoY, worldZ); ++ return block.hasGeoPos(); + } + + /** +- * Gets the Z height. +- * @param x the world x +- * @param y the world y +- * @param z the world z +- * @return the nearest Z height ++ * 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 int getHeight(int x, int y, int z) ++ public short getHeightNearest(int geoX, int geoY, int worldZ) + { +- return getNearestZ(getGeoX(x), getGeoY(y), z); ++ final ABlock block = getBlock(geoX, geoY); ++ return block != null ? block.getHeightNearest(geoX, geoY, worldZ) : (short) worldZ; + } + + /** +- * Gets the next lower Z height. +- * @param x the world x +- * @param y the world y +- * @param z the world z +- * @return the nearest Z height ++ * 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 int getLowerHeight(int x, int y, int z) ++ public byte getNsweNearest(int geoX, int geoY, int worldZ) + { +- return getNextLowerZ(getGeoX(x), getGeoY(y), z); ++ final ABlock block = getBlock(geoX, geoY); ++ return block != null ? block.getNsweNearest(geoX, geoY, worldZ) : (byte) 0xFF; + } + + /** +- * Gets the next higher Z height. +- * @param x the world x +- * @param y the world y +- * @param z the world z +- * @return the nearest Z height ++ * Check if world coordinates has geo. ++ * @param worldX : World X ++ * @param worldY : World Y ++ * @return boolean : True, if given world coordinates have geodata + */ +- public int getHigherHeight(int x, int y, int z) ++ public boolean hasGeo(int worldX, int worldY) + { +- return getNextHigherZ(getGeoX(x), getGeoY(y), z); ++ return hasGeoPos(getGeoX(worldX), getGeoY(worldY)); + } + + /** +- * @param worldX +- * @return the geo X ++ * 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 int getGeoX(int worldX) ++ public short getHeight(int worldX, int worldY, int worldZ) + { +- return (worldX - WORLD_MIN_X) / 16; ++ return getHeightNearest(getGeoX(worldX), getGeoY(worldY), worldZ); + } + +- /** +- * @param worldY +- * @return the geo Y +- */ +- public int getGeoY(int worldY) +- { +- return (worldY - WORLD_MIN_Y) / 16; +- } ++ // PATHFINDING + + /** +- * @param worldZ +- * @return the geo Z ++ * Check line of sight from {@link WorldObject} to {@link WorldObject}. ++ * @param origin : The origin object. ++ * @param target : The target object. ++ * @return {@code boolean} : True if origin can see target + */ +- public int getGeoZ(int worldZ) ++ public boolean canSeeTarget(WorldObject origin, WorldObject target) + { +- return (worldZ - WORLD_MIN_Z) / 16; +- } +- +- /** +- * @param geoX +- * @return the world X +- */ +- public int getWorldX(int geoX) +- { +- return (geoX * 16) + WORLD_MIN_X + 8; +- } +- +- /** +- * @param geoY +- * @return the world Y +- */ +- public int getWorldY(int geoY) +- { +- return (geoY * 16) + WORLD_MIN_Y + 8; +- } +- +- /** +- * @param geoZ +- * @return the world Z +- */ +- public int getWorldZ(int geoZ) +- { +- return (geoZ * 16) + WORLD_MIN_Z + 8; +- } +- +- /** +- * 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) +- { +- if (target.isDoor()) ++ if (target.isDoor() || target.isArtefact() || (target.isCreature() && ((Creature) target).isFlying())) + { +- // Can always see doors. + return true; + } +- return 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(), cha.getInstanceId(), worldPosition.getX(), worldPosition.getY(), worldPosition.getZ()); +- } +- +- /** +- * 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 instanceId +- * @return +- */ +- public boolean canSeeTarget(int x, int y, int z, int instanceId, int tx, int ty, int tz, int tInstanceId) +- { +- return (instanceId != tInstanceId) ? false : canSeeTarget(x, y, z, instanceId, tx, ty, tz); +- } +- +- /** +- * 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 +- * @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 instanceId, int tx, int ty, int tz) +- { +- if (DoorData.getInstance().checkIfDoorsBetween(x, y, z, tx, ty, tz)) ++ ++ // get origin and target world coordinates ++ final int ox = origin.getX(); ++ final int oy = origin.getY(); ++ final int oz = origin.getZ(); ++ final int tx = target.getX(); ++ final int ty = target.getY(); ++ final int tz = target.getZ(); ++ ++ if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz)) + { + return false; + } +- return canSeeTarget(x, y, z, tx, ty, tz); +- } +- +- /** +- * @param prevX +- * @param prevY +- * @param prevGeoZ +- * @param curX +- * @param curY +- * @param nswe +- * @return the LOS Z value +- */ +- 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))) ++ if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz)) + { +- throw new RuntimeException("Multiple directions!"); ++ return false; + } + +- if (checkNearestNsweAntiCornerCut(prevX, prevY, prevGeoZ, nswe)) ++ // get origin and check existing geo coordinates ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) + { +- return getNearestZ(curX, curY, prevGeoZ); ++ return true; + } + +- return getNextHigherZ(curX, curY, prevGeoZ); ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coordinates ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) ++ { ++ return true; ++ } ++ ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // origin and target coordinates are same ++ if ((gox == gtx) && (goy == gty)) ++ { ++ return goz == gtz; ++ } ++ ++ // get origin and target height, real height = collision height * 2 ++ double oheight = 0; ++ if (origin.isCreature()) ++ { ++ oheight = ((Creature) origin).getTemplate().getCollisionHeight() * 2; ++ } ++ ++ double theight = 0; ++ if (target.isCreature()) ++ { ++ theight = ((Creature) target).getTemplate().getCollisionHeight() * 2; ++ } ++ ++ // perform geodata check ++ return checkSee(gox, goy, goz, oheight, gtx, gty, gtz, theight, origin.getInstanceId()); + } + + /** +- * Can see target. Does not check doors between. +- * @param xValue the x coordinate +- * @param yValue the y coordinate +- * @param zValue the z coordinate +- * @param txValue the target's x coordinate +- * @param tyValue the target's y coordinate +- * @param tzValue the target's z coordinate +- * @return {@code true} if there is line of sight between the given coordinate sets, {@code false} otherwise ++ * Check line of sight from {@link WorldObject} to {@link Location}. ++ * @param origin : The origin object. ++ * @param position : The target position. ++ * @return {@code boolean} : True if object can see position + */ +- public boolean canSeeTarget(int xValue, int yValue, int zValue, int txValue, int tyValue, int tzValue) ++ public boolean canSeeTarget(WorldObject origin, Location position) + { +- int x = xValue; +- int y = yValue; +- int tx = txValue; +- int ty = tyValue; ++ // get origin and target world coordinates ++ final int ox = origin.getX(); ++ final int oy = origin.getY(); ++ final int oz = origin.getZ(); ++ final int tx = position.getX(); ++ final int ty = position.getY(); ++ final int tz = position.getZ(); + +- int geoX = getGeoX(x); +- int geoY = getGeoY(y); +- int tGeoX = getGeoX(tx); +- int tGeoY = getGeoY(ty); ++ if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz)) ++ { ++ return false; ++ } ++ if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz)) ++ { ++ return false; ++ } + +- int z = getNearestZ(geoX, geoY, zValue); +- int tz = getNearestZ(tGeoX, tGeoY, tzValue); ++ // get origin and check existing geo coordinates ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) ++ { ++ return true; ++ } + +- // Fastpath. +- if ((geoX == tGeoX) && (geoY == tGeoY)) ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coordinates ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) + { +- if (hasGeoPos(tGeoX, tGeoY)) +- { +- return z == tz; +- } + return true; + } + +- if (tz > z) ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // origin and target coordinates are same ++ if ((gox == gtx) && (goy == gty)) + { +- int tmp = tx; +- tx = x; +- x = tmp; +- +- tmp = ty; +- ty = y; +- y = tmp; +- +- tmp = tz; +- tz = z; +- z = tmp; +- +- tmp = tGeoX; +- tGeoX = geoX; +- geoX = tmp; +- +- tmp = tGeoY; +- tGeoY = geoY; +- geoY = tmp; ++ return goz == gtz; + } + +- final LinePointIterator3D pointIter = new LinePointIterator3D(geoX, geoY, z, tGeoX, tGeoY, tz); +- // 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()) ++ // get origin and target height, real height = collision height * 2 ++ double oheight = 0; ++ if (origin.isCreature()) + { +- final int curX = pointIter.x(); +- final int curY = pointIter.y(); ++ oheight = ((Creature) origin).getTemplate().getCollisionHeight(); ++ } ++ ++ // perform geodata check ++ return checkSee(gox, goy, goz, oheight, gtx, gty, gtz, 0, origin.getInstanceId()); ++ } ++ ++ /** ++ * Simple check for origin to target visibility. ++ * @param goxValue : origin X geodata coordinate ++ * @param goyValue : origin Y geodata coordinate ++ * @param gozValue : origin Z geodata coordinate ++ * @param oheight : origin height (if instance of {@link Character}) ++ * @param gtxValue : target X geodata coordinate ++ * @param gtyValue : target Y geodata coordinate ++ * @param gtzValue : target Z geodata coordinate ++ * @param theight : target height (if instance of {@link Character}) ++ * @param instanceId ++ * @return {@code boolean} : True, when target can be seen. ++ */ ++ private final boolean checkSee(int goxValue, int goyValue, int gozValue, double oheight, int gtxValue, int gtyValue, int gtzValue, double theight, int instanceId) ++ { ++ int goz = gozValue; ++ int gtz = gtzValue; ++ int gox = goxValue; ++ int goy = goyValue; ++ int gtx = gtxValue; ++ int gty = gtyValue; ++ ++ // get line of sight Z coordinates ++ double losoz = goz + ((oheight * Config.PART_OF_CHARACTER_HEIGHT) / 100); ++ double lostz = gtz + ((theight * Config.PART_OF_CHARACTER_HEIGHT) / 100); ++ ++ // get X delta and signum ++ final int dx = Math.abs(gtx - gox); ++ final int sx = gox < gtx ? 1 : -1; ++ final byte dirox = sx > 0 ? GeoStructure.CELL_FLAG_E : GeoStructure.CELL_FLAG_W; ++ final byte dirtx = sx > 0 ? GeoStructure.CELL_FLAG_W : GeoStructure.CELL_FLAG_E; ++ ++ // get Y delta and signum ++ final int dy = Math.abs(gty - goy); ++ final int sy = goy < gty ? 1 : -1; ++ final byte diroy = sy > 0 ? GeoStructure.CELL_FLAG_S : GeoStructure.CELL_FLAG_N; ++ final byte dirty = sy > 0 ? GeoStructure.CELL_FLAG_N : GeoStructure.CELL_FLAG_S; ++ ++ // get Z delta ++ final int dm = Math.max(dx, dy); ++ final double dz = (lostz - losoz) / dm; ++ ++ // get direction flag for diagonal movement ++ final byte diroxy = getDirXY(dirox, diroy); ++ final byte dirtxy = getDirXY(dirtx, dirty); ++ ++ // delta, determines axis to move on (+..X axis, -..Y axis) ++ int d = dx - dy; ++ ++ // NSWE direction of movement ++ byte diro; ++ byte dirt; ++ ++ // initialize node values ++ int nox = gox; ++ int noy = goy; ++ int ntx = gtx; ++ int nty = gty; ++ byte nsweo = getNsweNearest(gox, goy, goz); ++ byte nswet = getNsweNearest(gtx, gty, gtz); ++ ++ // loop ++ ABlock block; ++ int index; ++ for (int i = 0; i < ((dm + 1) / 2); i++) ++ { ++ // reset direction flag ++ diro = 0; ++ dirt = 0; + +- if ((curX == prevX) && (curY == prevY)) ++ // calculate next point coordinates ++ final int e2 = 2 * d; ++ if ((e2 > -dy) && (e2 < dx)) + { +- continue; ++ // calculate next point XY coordinates ++ d -= dy; ++ d += dx; ++ nox += sx; ++ ntx -= sx; ++ noy += sy; ++ nty -= sy; ++ diro |= diroxy; ++ dirt |= dirtxy; + } ++ else if (e2 > -dy) ++ { ++ // calculate next point X coordinate ++ d -= dy; ++ nox += sx; ++ ntx -= sx; ++ diro |= dirox; ++ dirt |= dirtx; ++ } ++ else if (e2 < dx) ++ { ++ // calculate next point Y coordinate ++ d += dx; ++ noy += sy; ++ nty -= sy; ++ diro |= diroy; ++ dirt |= dirty; ++ } + +- final int beeCurZ = pointIter.z(); +- int curGeoZ = prevGeoZ; +- +- // Check if the position has geodata. +- if (hasGeoPos(curX, curY)) + { +- final int beeCurGeoZ = getNearestZ(curX, curY, beeCurZ); +- final int nswe = GeoUtils.computeNswe(prevX, prevY, curX, curY); // .computeDirection(prevX, prevY, curX, curY); +- curGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, curX, curY, nswe); +- int maxHeight; +- if (ptIndex < ELEVATED_SEE_OVER_DISTANCE) ++ // get block of the next cell ++ block = getBlock(nox, noy); ++ ++ // get index of particular layer, based on movement conditions ++ if ((nsweo & diro) == 0) + { +- maxHeight = z + MAX_SEE_OVER_HEIGHT; ++ index = block.getIndexAbove(nox, noy, goz - GeoStructure.CELL_IGNORE_HEIGHT); + } + else + { +- maxHeight = beeCurZ + MAX_SEE_OVER_HEIGHT; ++ index = block.getIndexBelow(nox, noy, goz + GeoStructure.CELL_IGNORE_HEIGHT); + } + +- boolean canSeeThrough = false; +- if ((curGeoZ <= maxHeight) && (curGeoZ <= beeCurGeoZ)) ++ // layer does not exist, return ++ if (index == -1) + { +- if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) +- { +- 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 +- { +- canSeeThrough = true; +- } ++ return false; + } + +- if (!canSeeThrough) ++ // get layer and next line of sight Z coordinate ++ goz = block.getHeight(index); ++ losoz += dz; ++ ++ // perform line of sight check, return when fails ++ if ((goz - losoz) > Config.MAX_OBSTACLE_HEIGHT) + { + return false; + } ++ ++ // get layer nswe ++ nsweo = block.getNswe(index); + } ++ { ++ // get block of the next cell ++ block = getBlock(ntx, nty); ++ ++ // get index of particular layer, based on movement conditions ++ if ((nswet & dirt) == 0) ++ { ++ index = block.getIndexAbove(ntx, nty, gtz - GeoStructure.CELL_IGNORE_HEIGHT); ++ } ++ else ++ { ++ index = block.getIndexBelow(ntx, nty, gtz + GeoStructure.CELL_IGNORE_HEIGHT); ++ } ++ ++ // layer does not exist, return ++ if (index == -1) ++ { ++ return false; ++ } ++ ++ // get layer and next line of sight Z coordinate ++ gtz = block.getHeight(index); ++ lostz -= dz; ++ ++ // perform line of sight check, return when fails ++ if ((gtz - lostz) > Config.MAX_OBSTACLE_HEIGHT) ++ { ++ return false; ++ } ++ ++ // get layer nswe ++ nswet = block.getNswe(index); ++ } + +- prevX = curX; +- prevY = curY; +- prevGeoZ = curGeoZ; +- ++ptIndex; ++ // update coords ++ gox = nox; ++ goy = noy; ++ gtx = ntx; ++ gty = nty; + } + +- return true; ++ // when iteration is completed, compare final Z coordinates ++ return Math.abs(goz - gtz) < (GeoStructure.CELL_HEIGHT * 4); + } + + /** +- * Move check. +- * @param x the x coordinate +- * @param y the y coordinate +- * @param zValue the z coordinate +- * @param tx the target's x coordinate +- * @param ty the target's y coordinate +- * @param tzValue the target's z coordinate +- * @param instanceId the instance id +- * @return the last Location (x,y,z) where player can walk - just before wall ++ * Check movement from coordinates to 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 {code boolean} : True if target coordinates are reachable from origin coordinates + */ +- public Location canMoveToTargetLoc(int x, int y, int zValue, int tx, int ty, int tzValue, int instanceId) ++ public boolean canMoveToTarget(int ox, int oy, int oz, int tx, int ty, int tz, int instanceId) + { +- final int geoX = getGeoX(x); +- final int geoY = getGeoY(y); +- final int z = getNearestZ(geoX, geoY, zValue); +- final int tGeoX = getGeoX(tx); +- final int tGeoY = getGeoY(ty); +- final int tz = getNearestZ(tGeoX, tGeoY, tzValue); ++ // get origin and check existing geo coordinates ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) ++ { ++ return true; ++ } + +- if (DoorData.getInstance().checkIfDoorsBetween(x, y, z, tx, ty, tz)) ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coordinates ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) + { +- return new Location(x, y, getHeight(x, y, z)); ++ return true; + } +- if (FenceData.getInstance().checkIfFenceBetween(x, y, z, tx, ty, tz)) ++ ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // target coordinates reached ++ if ((gox == gtx) && (goy == gty) && (goz == gtz)) + { +- return new Location(x, y, getHeight(x, y, z)); ++ return true; + } + +- 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 = z; ++ // perform geodata check ++ final GeoLocation loc = checkMove(gox, goy, goz, gtx, gty, gtz, instanceId); ++ return (loc.getGeoX() == gtx) && (loc.getGeoY() == gty); ++ } ++ ++ /** ++ * Check movement from origin to target. Returns last available point in the checked path. ++ * @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 {@link Location} : Last point where object can walk (just before wall) ++ */ ++ public Location canMoveToTargetLoc(int ox, int oy, int oz, int tx, int ty, int tz, int instanceId) ++ { ++ // Mobius: Double check for doors before normal checkMove to avoid exploiting key movement. ++ if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz)) ++ { ++ return new Location(ox, oy, oz); ++ } ++ if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz)) ++ { ++ return new Location(ox, oy, oz); ++ } + +- while (pointIter.next()) ++ // get origin and check existing geo coordinates ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) + { +- final int curX = pointIter.x(); +- final int curY = pointIter.y(); +- final int curZ = getNearestZ(curX, curY, prevZ); +- +- if (hasGeoPos(prevX, prevY)) +- { +- final int nswe = GeoUtils.computeNswe(prevX, prevY, curX, curY); +- if (!checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, nswe)) +- { +- // Can't move, return previous location. +- return new Location(getWorldX(prevX), getWorldY(prevY), prevZ); +- } +- } +- +- prevX = curX; +- prevY = curY; +- prevZ = curZ; ++ return new Location(tx, ty, tz); + } + +- if (hasGeoPos(prevX, prevY) && (prevZ != tz)) ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coordinates ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) + { +- // Different floors, return start location. +- return new Location(x, y, z); ++ return new Location(tx, ty, tz); + } + +- return new Location(tx, ty, tz); ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // target coordinates reached ++ if ((gox == gtx) && (goy == gty) && (goz == gtz)) ++ { ++ return new Location(tx, ty, tz); ++ } ++ ++ // perform geodata check ++ return checkMove(gox, goy, goz, gtx, gty, gtz, instanceId); + } + + /** +- * 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 fromZvalue 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 toZvalue the Z coordinate to end checking at +- * @param instanceId the instance +- * @return {@code true} if the character at start coordinates can move to end coordinates, {@code false} otherwise ++ * With this method you can check if a position is visible or can be reached by beeline movement.
++ * 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 gox : origin X geodata coordinate ++ * @param goy : origin Y geodata coordinate ++ * @param goz : origin Z geodata coordinate ++ * @param gtx : target X geodata coordinate ++ * @param gty : target Y geodata coordinate ++ * @param gtz : target Z geodata coordinate ++ * @param instanceId ++ * @return {@link GeoLocation} : The last allowed point of movement. + */ +- public boolean canMoveToTarget(int fromX, int fromY, int fromZvalue, int toX, int toY, int toZvalue, int instanceId) ++ protected final GeoLocation checkMove(int gox, int goy, int goz, int gtx, int gty, int gtz, int instanceId) + { +- final int geoX = getGeoX(fromX); +- final int geoY = getGeoY(fromY); +- final int fromZ = getNearestZ(geoX, geoY, fromZvalue); +- final int tGeoX = getGeoX(toX); +- final int tGeoY = getGeoY(toY); +- final int toZ = getNearestZ(tGeoX, tGeoY, toZvalue); +- +- if (DoorData.getInstance().checkIfDoorsBetween(fromX, fromY, fromZ, toX, toY, toZ)) ++ if (DoorData.getInstance().checkIfDoorsBetween(gox, goy, goz, gtx, gty, gtz)) + { +- return false; ++ return new GeoLocation(gox, goy, goz); + } +- if (FenceData.getInstance().checkIfFenceBetween(fromX, fromY, fromZ, toX, toY, toZ)) ++ if (FenceData.getInstance().checkIfFenceBetween(gox, goy, goz, gtx, gty, gtz)) + { +- return false; ++ return new GeoLocation(gox, goy, goz); + } + +- 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 = fromZ; ++ // get X delta, signum and direction flag ++ final int dx = Math.abs(gtx - gox); ++ final int sx = gox < gtx ? 1 : -1; ++ final byte dirX = sx > 0 ? GeoStructure.CELL_FLAG_E : GeoStructure.CELL_FLAG_W; + +- while (pointIter.next()) ++ // get Y delta, signum and direction flag ++ final int dy = Math.abs(gty - goy); ++ final int sy = goy < gty ? 1 : -1; ++ final byte dirY = sy > 0 ? GeoStructure.CELL_FLAG_S : GeoStructure.CELL_FLAG_N; ++ ++ // get direction flag for diagonal movement ++ final byte dirXY = getDirXY(dirX, dirY); ++ ++ // delta, determines axis to move on (+..X axis, -..Y axis) ++ int d = dx - dy; ++ ++ // NSWE direction of movement ++ byte direction; ++ ++ // load pointer coordinates ++ int gpx = gox; ++ int gpy = goy; ++ int gpz = goz; ++ ++ // load next pointer ++ int nx = gpx; ++ int ny = gpy; ++ ++ // loop ++ int count = 0; ++ while (count++ < Config.MAX_ITERATIONS) + { +- final int curX = pointIter.x(); +- final int curY = pointIter.y(); +- final int curZ = getNearestZ(curX, curY, prevZ); ++ direction = 0; + +- if (hasGeoPos(prevX, prevY)) ++ // calculate next point coordinates ++ final int e2 = 2 * d; ++ if ((e2 > -dy) && (e2 < dx)) + { +- final int nswe = GeoUtils.computeNswe(prevX, prevY, curX, curY); +- if (!checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, nswe)) ++ d -= dy; ++ d += dx; ++ nx += sx; ++ ny += sy; ++ direction |= dirXY; ++ } ++ else if (e2 > -dy) ++ { ++ d -= dy; ++ nx += sx; ++ direction |= dirX; ++ } ++ else if (e2 < dx) ++ { ++ d += dx; ++ ny += sy; ++ direction |= dirY; ++ } ++ ++ // obstacle found, return ++ if ((getNsweNearest(gpx, gpy, gpz) & direction) == 0) ++ { ++ return new GeoLocation(gpx, gpy, gpz); ++ } ++ ++ // update pointer coordinates ++ gpx = nx; ++ gpy = ny; ++ gpz = getHeightNearest(nx, ny, gpz); ++ ++ // target coordinates reached ++ if ((gpx == gtx) && (gpy == gty)) ++ { ++ if (gpz == gtz) + { +- return false; ++ // path found, Z coordinates are okay, return target point ++ return new GeoLocation(gtx, gty, gtz); + } ++ ++ // path found, Z coordinates are not okay, return last good point ++ return new GeoLocation(gpx, gpy, gpz); + } ++ } ++ ++ return new GeoLocation(gox, goy, goz); ++ } ++ ++ /** ++ * Returns diagonal NSWE flag format of combined two NSWE flags. ++ * @param dirX : X direction NSWE flag ++ * @param dirY : Y direction NSWE flag ++ * @return byte : NSWE flag of combined direction ++ */ ++ private static byte getDirXY(byte dirX, byte dirY) ++ { ++ // check axis directions ++ if (dirY == GeoStructure.CELL_FLAG_N) ++ { ++ if (dirX == GeoStructure.CELL_FLAG_W) ++ { ++ return GeoStructure.CELL_FLAG_NW; ++ } + +- prevX = curX; +- prevY = curY; +- prevZ = curZ; ++ return GeoStructure.CELL_FLAG_NE; + } + +- if (hasGeoPos(prevX, prevY) && (prevZ != toZ)) ++ if (dirX == GeoStructure.CELL_FLAG_W) + { +- // Different floors. +- return false; ++ return GeoStructure.CELL_FLAG_SW; + } + +- return true; ++ return GeoStructure.CELL_FLAG_SE; + } + ++ /** ++ * 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 ++ */ ++ public List findPath(int ox, int oy, int oz, int tx, int ty, int tz, int instanceId) ++ { ++ return null; ++ } ++ ++ /** ++ * Returns the instance of the {@link GeoEngine}. ++ * @return {@link GeoEngine} : The instance. ++ */ + public static GeoEngine getInstance() + { + return SingletonHolder.INSTANCE; +@@ -741,6 +933,6 @@ + + private static class SingletonHolder + { +- protected static final GeoEngine INSTANCE = new GeoEngine(); ++ protected static final GeoEngine INSTANCE = Config.PATHFINDING ? new GeoEnginePathfinding() : new GeoEngine(); + } +-} ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/GeoEnginePathfinding.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/GeoEnginePathfinding.java (revision 8319) ++++ java/org/l2jmobius/gameserver/geoengine/GeoEnginePathfinding.java (working copy) +@@ -20,87 +20,83 @@ + import java.util.LinkedList; + import java.util.List; + import java.util.ListIterator; +-import java.util.logging.Level; +-import java.util.logging.Logger; + + import org.l2jmobius.Config; +-import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; +-import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +-import org.l2jmobius.gameserver.geoengine.pathfinding.CellNode; +-import org.l2jmobius.gameserver.geoengine.pathfinding.CellNodeBuffer; +-import org.l2jmobius.gameserver.geoengine.pathfinding.NodeLoc; +-import org.l2jmobius.gameserver.model.World; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoLocation; ++import org.l2jmobius.gameserver.geoengine.pathfinding.Node; ++import org.l2jmobius.gameserver.geoengine.pathfinding.NodeBuffer; ++import org.l2jmobius.gameserver.model.Location; + + /** +- * @author -Nemesiss- ++ * @author Hasha + */ +-public class GeoEnginePathfinding ++final class GeoEnginePathfinding extends GeoEngine + { +- private static final Logger LOGGER = Logger.getLogger(GeoEnginePathfinding.class.getName()); ++ // pre-allocated buffers ++ private final BufferHolder[] _buffers; + +- private BufferInfo[] _buffers; +- + protected GeoEnginePathfinding() + { +- try ++ super(); ++ ++ final String[] array = Config.PATHFIND_BUFFERS.split(";"); ++ _buffers = new BufferHolder[array.length]; ++ int count = 0; ++ for (int i = 0; i < array.length; i++) + { +- final String[] array = Config.PATHFIND_BUFFERS.split(";"); ++ final String buf = array[i]; ++ final String[] args = buf.split("x"); + +- _buffers = new BufferInfo[array.length]; +- +- String buf; +- String[] args; +- for (int i = 0; i < array.length; i++) ++ try + { +- buf = array[i]; +- args = buf.split("x"); +- if (args.length != 2) +- { +- throw new Exception("Invalid buffer definition: " + buf); +- } +- +- _buffers[i] = new BufferInfo(Integer.parseInt(args[0]), Integer.parseInt(args[1])); ++ final int size = Integer.parseInt(args[1]); ++ count += size; ++ _buffers[i] = new BufferHolder(Integer.parseInt(args[0]), size); + } ++ catch (Exception e) ++ { ++ LOGGER.warning("GeoEnginePathfinding: Can not load buffer setting: " + buf); ++ } + } +- catch (Exception e) +- { +- LOGGER.log(Level.WARNING, "CellPathFinding: Problem during buffer init: " + e.getMessage(), e); +- throw new Error("CellPathFinding: load aborted"); +- } ++ ++ LOGGER.info("GeoEnginePathfinding: Loaded " + count + " node buffers."); + } + +- public boolean pathNodesExist(short regionoffset) ++ @Override ++ public List findPath(int ox, int oy, int oz, int tx, int ty, int tz, int instanceId) + { +- return false; +- } +- +- public List findPath(int x, int y, int z, int tx, int ty, int tz, int instanceId) +- { +- final int gx = GeoEngine.getInstance().getGeoX(x); +- final int gy = GeoEngine.getInstance().getGeoY(y); +- if (!GeoEngine.getInstance().hasGeo(x, y)) ++ // get origin and check existing geo coords ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) + { + 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)) ++ ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coords ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) + { + 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)))); ++ ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // Prepare buffer for pathfinding calculations ++ final NodeBuffer buffer = getBuffer(64 + (2 * Math.max(Math.abs(gox - gtx), Math.abs(goy - gty)))); + if (buffer == null) + { + return null; + } + +- List path = null; ++ // find path ++ List path = null; + try + { +- final CellNode result = buffer.findPath(gx, gy, gz, gtx, gty, gtz); +- ++ final Node result = buffer.findPath(gox, goy, goz, gtx, gty, gtz); + if (result == null) + { + return null; +@@ -124,33 +120,45 @@ + return path; + } + +- int currentX, currentY, currentZ; +- ListIterator middlePoint; ++ // get path list iterator ++ final ListIterator point = path.listIterator(); + +- middlePoint = path.listIterator(); +- currentX = x; +- currentY = y; +- currentZ = z; ++ // get node A (origin) ++ int nodeAx = gox; ++ int nodeAy = goy; ++ short nodeAz = goz; + +- while (middlePoint.hasNext()) ++ // get node B ++ GeoLocation nodeB = (GeoLocation) point.next(); ++ ++ // iterate thought the path to optimize it ++ int count = 0; ++ while (point.hasNext() && (count++ < Config.MAX_ITERATIONS)) + { +- final AbstractNodeLoc locMiddle = middlePoint.next(); +- if (!middlePoint.hasNext()) +- { +- break; +- } ++ // get node C ++ final GeoLocation nodeC = (GeoLocation) path.get(point.nextIndex()); + +- final AbstractNodeLoc locEnd = path.get(middlePoint.nextIndex()); +- if (GeoEngine.getInstance().canMoveToTarget(currentX, currentY, currentZ, locEnd.getX(), locEnd.getY(), locEnd.getZ(), instanceId)) ++ // check movement from node A to node C ++ final GeoLocation loc = checkMove(nodeAx, nodeAy, nodeAz, nodeC.getGeoX(), nodeC.getGeoY(), nodeC.getZ(), instanceId); ++ if ((loc.getGeoX() == nodeC.getGeoX()) && (loc.getGeoY() == nodeC.getGeoY())) + { +- middlePoint.remove(); ++ // can move from node A to node C ++ ++ // remove node B ++ point.remove(); + } + else + { +- currentX = locMiddle.getX(); +- currentY = locMiddle.getY(); +- currentZ = locMiddle.getZ(); ++ // can not move from node A to node C ++ ++ // set node A (node B is part of path, update A coordinates) ++ nodeAx = nodeB.getGeoX(); ++ nodeAy = nodeB.getGeoY(); ++ nodeAz = (short) nodeB.getZ(); + } ++ ++ // set node B ++ nodeB = (GeoLocation) point.next(); + } + + return path; +@@ -157,144 +165,102 @@ + } + + /** +- * Convert geodata position to pathnode position +- * @param geo_pos +- * @return pathnode position ++ * Create list of node locations as result of calculated buffer node tree. ++ * @param node : the entry point ++ * @return List : list of node location + */ +- public short getNodePos(int geo_pos) ++ private static List constructPath(Node node) + { +- return (short) (geo_pos >> 3); // OK? +- } +- +- /** +- * Convert node position to pathnode block position +- * @param node_pos +- * @return pathnode block position (0...255) +- */ +- public short getNodeBlock(int node_pos) +- { +- return (short) (node_pos % 256); +- } +- +- public byte getRegionX(int node_pos) +- { +- return (byte) ((node_pos >> 8) + World.TILE_X_MIN); +- } +- +- public byte getRegionY(int node_pos) +- { +- return (byte) ((node_pos >> 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 node_x rx +- * @return +- */ +- public int calculateWorldX(short node_x) +- { +- return World.MAP_MIN_X + (node_x * 128) + 48; +- } +- +- /** +- * Convert pathnode y to World y position +- * @param node_y +- * @return +- */ +- public int calculateWorldY(short node_y) +- { +- return World.MAP_MIN_Y + (node_y * 128) + 48; +- } +- +- private List constructPath(AbstractNode nodeValue) +- { +- final LinkedList path = new LinkedList<>(); +- int previousDirectionX = Integer.MIN_VALUE; +- int previousDirectionY = Integer.MIN_VALUE; +- int directionX, directionY; ++ // create empty list ++ final LinkedList list = new LinkedList<>(); + +- AbstractNode node = nodeValue; +- while (node.getParent() != null) ++ // set direction X/Y ++ int dx = 0; ++ int dy = 0; ++ ++ // get target parent ++ Node target = node; ++ Node parent = target.getParent(); ++ ++ // while parent exists ++ int count = 0; ++ while ((parent != null) && (count++ < Config.MAX_ITERATIONS)) + { +- directionX = node.getLoc().getNodeX() - node.getParent().getLoc().getNodeX(); +- directionY = node.getLoc().getNodeY() - node.getParent().getLoc().getNodeY(); ++ // get parent <> target direction X/Y ++ final int nx = parent.getLoc().getGeoX() - target.getLoc().getGeoX(); ++ final int ny = parent.getLoc().getGeoY() - target.getLoc().getGeoY(); + +- // only add a new route point if moving direction changes +- if ((directionX != previousDirectionX) || (directionY != previousDirectionY)) ++ // direction has changed? ++ if ((dx != nx) || (dy != ny)) + { +- previousDirectionX = directionX; +- previousDirectionY = directionY; ++ // add node to the beginning of the list ++ list.addFirst(target.getLoc()); + +- path.addFirst(node.getLoc()); +- node.setLoc(null); ++ // update direction X/Y ++ dx = nx; ++ dy = ny; + } + +- node = node.getParent(); ++ // move to next node, set target and get its parent ++ target = parent; ++ parent = target.getParent(); + } + +- return path; ++ // return list ++ return list; + } + +- private CellNodeBuffer alloc(int size) ++ /** ++ * Provides optimize selection of the buffer. When all pre-initialized buffer are locked, creates new buffer. ++ * @param size : pre-calculated minimal required size ++ * @return NodeBuffer : buffer ++ */ ++ private final NodeBuffer getBuffer(int size) + { +- CellNodeBuffer current = null; +- for (BufferInfo i : _buffers) ++ NodeBuffer current = null; ++ for (BufferHolder holder : _buffers) + { +- if (i.mapSize >= size) ++ // Find proper size of buffer ++ if (holder._size < size) + { +- for (CellNodeBuffer buf : i.buffer) ++ continue; ++ } ++ ++ // Find unlocked NodeBuffer ++ for (NodeBuffer buffer : holder._buffer) ++ { ++ if (!buffer.isLocked()) + { +- if (buf.lock()) +- { +- current = buf; +- break; +- } ++ continue; + } +- if (current != null) +- { +- break; +- } + +- // not found, allocate temporary buffer +- current = new CellNodeBuffer(i.mapSize); +- current.lock(); +- if (i.buffer.size() < i.count) +- { +- i.buffer.add(current); +- break; +- } ++ return buffer; + } ++ ++ // NodeBuffer not found, allocate temporary buffer ++ current = new NodeBuffer(holder._size); ++ current.isLocked(); + } + + return current; + } + +- private static final class BufferInfo ++ /** ++ * NodeBuffer container with specified size and count of separate buffers. ++ */ ++ private static final class BufferHolder + { +- final int mapSize; +- final int count; +- ArrayList buffer; ++ final int _size; ++ List _buffer; + +- public BufferInfo(int size, int cnt) ++ public BufferHolder(int size, int count) + { +- mapSize = size; +- count = cnt; +- buffer = new ArrayList<>(count); ++ _size = size; ++ _buffer = new ArrayList<>(count); ++ for (int i = 0; i < count; i++) ++ { ++ _buffer.add(new NodeBuffer(size)); ++ } + } + } +- +- public static GeoEnginePathfinding getInstance() +- { +- return SingletonHolder.INSTANCE; +- } +- +- private static class SingletonHolder +- { +- protected static final GeoEnginePathfinding INSTANCE = new GeoEnginePathfinding(); +- } +-} ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java (working copy) +@@ -0,0 +1,197 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++import java.io.IOException; ++ ++/** ++ * @author Hasha ++ */ ++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 height of cell, which is closest to given coordinates.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @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 getHeightNearestOriginal(int geoX, int geoY, int worldZ); ++ ++ /** ++ * Returns the height of cell, which is first above 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, above given coordinates. ++ */ ++ public abstract short getHeightAbove(int geoX, int geoY, int worldZ); ++ ++ /** ++ * Returns the height of cell, which is first below 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, below given coordinates. ++ */ ++ public abstract short getHeightBelow(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 the NSWE flag byte of cell, which is closest to given coordinates.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @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 getNsweNearestOriginal(int geoX, int geoY, int worldZ); ++ ++ /** ++ * Returns the NSWE flag byte of cell, which is first above 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 getNsweAbove(int geoX, int geoY, int worldZ); ++ ++ /** ++ * Returns the NSWE flag byte of cell, which is first below 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 getNsweBelow(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 above given coordinates.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @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 getIndexAboveOriginal(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 index to data of the cell, which is first layer below given coordinates.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @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 getIndexBelowOriginal(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 height of cell given by cell index.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @param index : Index of the cell. ++ * @return short : Cell geodata Z coordinate, below given coordinates. ++ */ ++ public abstract short getHeightOriginal(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); ++ ++ /** ++ * Returns the NSWE flag byte of cell given by cell index.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @param index : Index of the cell. ++ * @return short : Cell geodata Z coordinate, below given coordinates. ++ */ ++ public abstract byte getNsweOriginal(int index); ++ ++ /** ++ * Sets the NSWE flag byte of cell given by cell index. ++ * @param index : Index of the cell. ++ * @param nswe : New NSWE flag byte. ++ */ ++ public abstract void setNswe(int index, byte nswe); ++ ++ /** ++ * Saves the block in L2D format to {@link BufferedOutputStream}. Used only for L2D geodata conversion. ++ * @param stream : The stream. ++ * @throws IOException : Can't save the block to steam. ++ */ ++ public abstract void saveBlock(BufferedOutputStream stream) throws IOException; ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java (working copy) +@@ -0,0 +1,252 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++import java.io.IOException; ++import java.nio.ByteBuffer; ++ ++/** ++ * @author Hasha ++ */ ++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. ++ * @param format : GeoFormat specifying format of loaded data. ++ */ ++ public BlockComplex(ByteBuffer bb, GeoFormat format) ++ { ++ // initialize buffer ++ _buffer = new byte[GeoStructure.BLOCK_CELLS * 3]; ++ ++ // load data ++ for (int i = 0; i < GeoStructure.BLOCK_CELLS; i++) ++ { ++ if (format != GeoFormat.L2D) ++ { ++ // 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); ++ } ++ else ++ { ++ // get nswe ++ final byte nswe = bb.get(); ++ _buffer[i * 3] = nswe; ++ ++ // get height ++ final short height = bb.getShort(); ++ _buffer[(i * 3) + 1] = (byte) (height & 0x00FF); ++ _buffer[(i * 3) + 2] = (byte) (height >> 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 short getHeightNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getHeightNearest(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeightAbove(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 short height = (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ ++ // check and return height ++ return height > worldZ ? height : Short.MIN_VALUE; ++ } ++ ++ @Override ++ public short getHeightBelow(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 short height = (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ ++ // check and return height ++ return height < worldZ ? height : Short.MAX_VALUE; ++ } ++ ++ @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 byte getNsweNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getNsweNearest(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public byte getNsweAbove(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 ? _buffer[index] : 0; ++ } ++ ++ @Override ++ public byte getNsweBelow(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 ? _buffer[index] : 0; ++ } ++ ++ @Override ++ public 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 getIndexAboveOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexAbove(geoX, geoY, worldZ); ++ } ++ ++ @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 int getIndexBelowOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexBelow(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeight(int index) ++ { ++ return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ } ++ ++ @Override ++ public short getHeightOriginal(int index) ++ { ++ return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ } ++ ++ @Override ++ public byte getNswe(int index) ++ { ++ return _buffer[index]; ++ } ++ ++ @Override ++ public byte getNsweOriginal(int index) ++ { ++ return _buffer[index]; ++ } ++ ++ @Override ++ public void setNswe(int index, byte nswe) ++ { ++ _buffer[index] = nswe; ++ } ++ ++ @Override ++ public void saveBlock(BufferedOutputStream stream) throws IOException ++ { ++ // write block type ++ stream.write(GeoStructure.TYPE_COMPLEX_L2D); ++ ++ // write block data ++ stream.write(_buffer, 0, GeoStructure.BLOCK_CELLS * 3); ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java (working copy) +@@ -0,0 +1,176 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++import java.io.IOException; ++import java.nio.ByteBuffer; ++ ++/** ++ * @author Hasha ++ */ ++public class BlockFlat extends ABlock ++{ ++ protected final short _height; ++ protected byte _nswe; ++ ++ /** ++ * Creates FlatBlock. ++ * @param bb : Input byte buffer. ++ * @param format : GeoFormat specifying format of loaded data. ++ */ ++ public BlockFlat(ByteBuffer bb, GeoFormat format) ++ { ++ _height = bb.getShort(); ++ _nswe = format != GeoFormat.L2D ? 0x0F : (byte) (0xFF); ++ if (format == GeoFormat.L2OFF) ++ { ++ bb.getShort(); ++ } ++ } ++ ++ @Override ++ public boolean hasGeoPos() ++ { ++ return true; ++ } ++ ++ @Override ++ public short getHeightNearest(int geoX, int geoY, int worldZ) ++ { ++ return _height; ++ } ++ ++ @Override ++ public short getHeightNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return _height; ++ } ++ ++ @Override ++ public short getHeightAbove(int geoX, int geoY, int worldZ) ++ { ++ // check and return height ++ return _height > worldZ ? _height : Short.MIN_VALUE; ++ } ++ ++ @Override ++ public short getHeightBelow(int geoX, int geoY, int worldZ) ++ { ++ // check and return height ++ return _height < worldZ ? _height : Short.MAX_VALUE; ++ } ++ ++ @Override ++ public byte getNsweNearest(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweAbove(int geoX, int geoY, int worldZ) ++ { ++ // check height and return nswe ++ return _height > worldZ ? _nswe : 0; ++ } ++ ++ @Override ++ public byte getNsweBelow(int geoX, int geoY, int worldZ) ++ { ++ // check height and return nswe ++ return _height < worldZ ? _nswe : 0; ++ } ++ ++ @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 getIndexAboveOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexAbove(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public int getIndexBelow(int geoX, int geoY, int worldZ) ++ { ++ // check height and return index ++ return _height < worldZ ? 0 : -1; ++ } ++ ++ @Override ++ public int getIndexBelowOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexBelow(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeight(int index) ++ { ++ return _height; ++ } ++ ++ @Override ++ public short getHeightOriginal(int index) ++ { ++ return _height; ++ } ++ ++ @Override ++ public byte getNswe(int index) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweOriginal(int index) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public void setNswe(int index, byte nswe) ++ { ++ _nswe = nswe; ++ } ++ ++ @Override ++ public void saveBlock(BufferedOutputStream stream) throws IOException ++ { ++ // write block type ++ stream.write(GeoStructure.TYPE_FLAT_L2D); ++ ++ // write height ++ stream.write((byte) (_height & 0x00FF)); ++ stream.write((byte) (_height >> 8)); ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java (working copy) +@@ -0,0 +1,465 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++import java.io.IOException; ++import java.nio.ByteBuffer; ++import java.nio.ByteOrder; ++import java.util.Arrays; ++ ++/** ++ * @author Hasha ++ */ ++public class BlockMultilayer extends ABlock ++{ ++ private static final int MAX_LAYERS = Byte.MAX_VALUE; ++ ++ private static ByteBuffer _temp; ++ ++ /** ++ * Initializes the temporarily buffer. ++ */ ++ public static void initialize() ++ { ++ // initialize temporarily buffer and sorting mechanism ++ _temp = ByteBuffer.allocate(GeoStructure.BLOCK_CELLS * MAX_LAYERS * 3); ++ _temp.order(ByteOrder.LITTLE_ENDIAN); ++ } ++ ++ /** ++ * Releases temporarily buffer. ++ */ ++ public static void release() ++ { ++ _temp = null; ++ } ++ ++ protected byte[] _buffer; ++ ++ /** ++ * Implicit constructor for children class. ++ */ ++ protected BlockMultilayer() ++ { ++ _buffer = null; ++ } ++ ++ /** ++ * Creates MultilayerBlock. ++ * @param bb : Input byte buffer. ++ * @param format : GeoFormat specifying format of loaded data. ++ */ ++ public BlockMultilayer(ByteBuffer bb, GeoFormat format) ++ { ++ // 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 = format != GeoFormat.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++) ++ { ++ if (format != GeoFormat.L2D) ++ { ++ // get data ++ final short data = bb.getShort(); ++ ++ // add nswe and height ++ _temp.put((byte) (data & 0x000F)); ++ _temp.putShort((short) ((short) (data & 0xFFF0) >> 1)); ++ } ++ else ++ { ++ // add nswe ++ _temp.put(bb.get()); ++ ++ // add height ++ _temp.putShort(bb.getShort()); ++ } ++ } ++ } ++ ++ // 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 short getHeightNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getHeightNearest(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeightAbove(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 height ++ if (height > worldZ) ++ { ++ return (short) height; ++ } ++ ++ // move index to next layer ++ index -= 3; ++ } ++ ++ // none layer found, return minimum value ++ return Short.MIN_VALUE; ++ } ++ ++ @Override ++ public short getHeightBelow(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 height ++ if (height < worldZ) ++ { ++ return (short) height; ++ } ++ ++ // move index to next layer ++ index += 3; ++ } ++ ++ // none layer found, return maximum value ++ return Short.MAX_VALUE; ++ } ++ ++ @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 byte getNsweNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getNsweNearest(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public byte getNsweAbove(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 nswe ++ if (height > worldZ) ++ { ++ return _buffer[index]; ++ } ++ ++ // move index to next layer ++ index -= 3; ++ } ++ ++ // none layer found, block movement ++ return 0; ++ } ++ ++ @Override ++ public byte getNsweBelow(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 nswe ++ if (height < worldZ) ++ { ++ return _buffer[index]; ++ } ++ ++ // move index to next layer ++ index += 3; ++ } ++ ++ // none layer found, block movement ++ return 0; ++ } ++ ++ @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 first layer data (first from bottom) ++ byte layers = _buffer[index++]; ++ ++ // loop though all cell layers, find closest layer ++ 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; ++ } ++ ++ // none layer found ++ return -1; ++ } ++ ++ @Override ++ public int getIndexAboveOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexAbove(geoX, geoY, worldZ); ++ } ++ ++ @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; ++ } ++ ++ // none layer found ++ return -1; ++ } ++ ++ @Override ++ public int getIndexBelowOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexBelow(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeight(int index) ++ { ++ // get height ++ return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ } ++ ++ @Override ++ public short getHeightOriginal(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]; ++ } ++ ++ @Override ++ public byte getNsweOriginal(int index) ++ { ++ // get nswe ++ return _buffer[index]; ++ } ++ ++ @Override ++ public void setNswe(int index, byte nswe) ++ { ++ // set nswe ++ _buffer[index] = nswe; ++ } ++ ++ @Override ++ public void saveBlock(BufferedOutputStream stream) throws IOException ++ { ++ // write block type ++ stream.write(GeoStructure.TYPE_MULTILAYER_L2D); ++ ++ // for each cell ++ int index = 0; ++ for (int i = 0; i < GeoStructure.BLOCK_CELLS; i++) ++ { ++ // write layers count ++ final byte layers = _buffer[index++]; ++ stream.write(layers); ++ ++ // write cell data ++ stream.write(_buffer, index, layers * 3); ++ ++ // move index to next cell ++ index += layers * 3; ++ } ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java (working copy) +@@ -0,0 +1,150 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++ ++/** ++ * @author Hasha ++ */ ++public class BlockNull extends ABlock ++{ ++ private final byte _nswe; ++ ++ public BlockNull() ++ { ++ _nswe = (byte) 0xFF; ++ } ++ ++ @Override ++ public boolean hasGeoPos() ++ { ++ return false; ++ } ++ ++ @Override ++ public short getHeightNearest(int geoX, int geoY, int worldZ) ++ { ++ return (short) worldZ; ++ } ++ ++ @Override ++ public short getHeightNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return (short) worldZ; ++ } ++ ++ @Override ++ public short getHeightAbove(int geoX, int geoY, int worldZ) ++ { ++ return (short) worldZ; ++ } ++ ++ @Override ++ public short getHeightBelow(int geoX, int geoY, int worldZ) ++ { ++ return (short) worldZ; ++ } ++ ++ @Override ++ public byte getNsweNearest(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweAbove(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweBelow(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) ++ { ++ return 0; ++ } ++ ++ @Override ++ public int getIndexAboveOriginal(int geoX, int geoY, int worldZ) ++ { ++ return 0; ++ } ++ ++ @Override ++ public int getIndexBelow(int geoX, int geoY, int worldZ) ++ { ++ return 0; ++ } ++ ++ @Override ++ public int getIndexBelowOriginal(int geoX, int geoY, int worldZ) ++ { ++ return 0; ++ } ++ ++ @Override ++ public short getHeight(int index) ++ { ++ return 0; ++ } ++ ++ @Override ++ public short getHeightOriginal(int index) ++ { ++ return 0; ++ } ++ ++ @Override ++ public byte getNswe(int index) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweOriginal(int index) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public void setNswe(int index, byte nswe) ++ { ++ } ++ ++ @Override ++ public void saveBlock(BufferedOutputStream stream) ++ { ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java (revision 8319) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java (nonexistent) +@@ -1,48 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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() +- { +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/ComplexBlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/ComplexBlock.java (revision 8319) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/ComplexBlock.java (nonexistent) +@@ -1,77 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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; +- +-/** +- * @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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/FlatBlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/FlatBlock.java (revision 8319) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/FlatBlock.java (nonexistent) +@@ -1,56 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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; +- +-/** +- * @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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/GeoFormat.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/GeoFormat.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/GeoFormat.java (working copy) +@@ -0,0 +1,39 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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 Hasha ++ */ ++public enum GeoFormat ++{ ++ L2J("%d_%d.l2j"), ++ L2OFF("%d_%d_conv.dat"), ++ L2D("%d_%d.l2d"); ++ ++ private final String _filename; ++ ++ private GeoFormat(String filename) ++ { ++ _filename = filename; ++ } ++ ++ public String getFilename() ++ { ++ return _filename; ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/GeoLocation.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/GeoLocation.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/GeoLocation.java (working copy) +@@ -0,0 +1,67 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.geoengine.GeoEngine; ++import org.l2jmobius.gameserver.model.Location; ++ ++/** ++ * @author Hasha ++ */ ++public class GeoLocation extends Location ++{ ++ private byte _nswe; ++ ++ public GeoLocation(int x, int y, int z) ++ { ++ super(x, y, GeoEngine.getInstance().getHeightNearest(x, y, z)); ++ _nswe = GeoEngine.getInstance().getNsweNearest(x, y, z); ++ } ++ ++ public void set(int x, int y, short z) ++ { ++ super.setXYZ(x, y, GeoEngine.getInstance().getHeightNearest(x, y, z)); ++ _nswe = GeoEngine.getInstance().getNsweNearest(x, y, z); ++ } ++ ++ public int getGeoX() ++ { ++ return _x; ++ } ++ ++ public int getGeoY() ++ { ++ return _y; ++ } ++ ++ @Override ++ public int getX() ++ { ++ return GeoEngine.getWorldX(_x); ++ } ++ ++ @Override ++ public int getY() ++ { ++ return GeoEngine.getWorldY(_y); ++ } ++ ++ public byte getNSWE() ++ { ++ return _nswe; ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java (working copy) +@@ -0,0 +1,70 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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; ++ ++/** ++ * @author Hasha ++ */ ++public class GeoStructure ++{ ++ // cells ++ public static final byte CELL_FLAG_E = 1 << 0; ++ public static final byte CELL_FLAG_W = 1 << 1; ++ public static final byte CELL_FLAG_S = 1 << 2; ++ public static final byte CELL_FLAG_N = 1 << 3; ++ public static final byte CELL_FLAG_SE = 1 << 4; ++ public static final byte CELL_FLAG_SW = 1 << 5; ++ public static final byte CELL_FLAG_NE = 1 << 6; ++ public static final byte CELL_FLAG_NW = (byte) (1 << 7); ++ ++ public static final int CELL_HEIGHT = 8; ++ public static final int CELL_IGNORE_HEIGHT = CELL_HEIGHT * 6; ++ ++ // blocks ++ public static final byte TYPE_FLAT_L2J_L2OFF = 0; ++ public static final byte TYPE_FLAT_L2D = (byte) 0xD0; ++ public static final byte TYPE_COMPLEX_L2J = 1; ++ public static final byte TYPE_COMPLEX_L2OFF = 0x40; ++ public static final byte TYPE_COMPLEX_L2D = (byte) 0xD1; ++ 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) ++ public static final byte TYPE_MULTILAYER_L2D = (byte) 0xD2; ++ ++ 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; ++ ++ // regions ++ 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; ++ ++ // global geodata ++ private static final int GEO_REGIONS_X = ((World.TILE_X_MAX - World.TILE_X_MIN) + 1); ++ private 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 +Index: java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java (revision 8319) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java (nonexistent) +@@ -1,42 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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); +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/IGeoObject.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/IGeoObject.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/IGeoObject.java (working copy) +@@ -0,0 +1,53 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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 Hasha ++ */ ++public interface IGeoObject ++{ ++ /** ++ * Returns geodata X coordinate of the {@link IGeoObject}. ++ * @return int : Geodata X coordinate. ++ */ ++ int getGeoX(); ++ ++ /** ++ * Returns geodata Y coordinate of the {@link IGeoObject}. ++ * @return int : Geodata Y coordinate. ++ */ ++ int getGeoY(); ++ ++ /** ++ * Returns geodata Z coordinate of the {@link IGeoObject}. ++ * @return int : Geodata Z coordinate. ++ */ ++ int getGeoZ(); ++ ++ /** ++ * Returns height of the {@link IGeoObject}. ++ * @return int : Height. ++ */ ++ int getHeight(); ++ ++ /** ++ * Returns {@link IGeoObject} data. ++ * @return byte[][] : {@link IGeoObject} data. ++ */ ++ byte[][] getObjectGeoData(); ++} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java (revision 8319) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java (nonexistent) +@@ -1,47 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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(); +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/MultilayerBlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/MultilayerBlock.java (revision 8319) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/MultilayerBlock.java (nonexistent) +@@ -1,183 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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; +- +-/** +- * @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); +- +- // 1 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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/NullRegion.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/NullRegion.java (revision 8319) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/NullRegion.java (nonexistent) +@@ -1,55 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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 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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/Region.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/Region.java (revision 8319) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/Region.java (nonexistent) +@@ -1,92 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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; +- +-/** +- * @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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java (revision 8319) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java (nonexistent) +@@ -1,87 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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 T _loc; +- private AbstractNode _parent; +- +- public AbstractNode(T loc) +- { +- _loc = loc; +- } +- +- public void setParent(AbstractNode p) +- { +- _parent = p; +- } +- +- public AbstractNode getParent() +- { +- return _parent; +- } +- +- public T getLoc() +- { +- return _loc; +- } +- +- public void setLoc(T l) +- { +- _loc = l; +- } +- +- @Override +- public int hashCode() +- { +- final int prime = 31; +- int result = 1; +- result = (prime * result) + ((_loc == null) ? 0 : _loc.hashCode()); +- return result; +- } +- +- @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 +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java (revision 8319) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java (nonexistent) +@@ -1,33 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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 int getNodeX(); +- +- public abstract int getNodeY(); +-} +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNode.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNode.java (revision 8319) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNode.java (nonexistent) +@@ -1,67 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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 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 +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNodeBuffer.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNodeBuffer.java (revision 8319) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNodeBuffer.java (nonexistent) +@@ -1,348 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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.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 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) +- { +- _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(); +- } +- +- 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 void getNeighbors() +- { +- if (!_current.getLoc().canGoAll()) +- { +- 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); +- } +- +- // SouthEast +- if ((nodeE != null) && (nodeS != null)) +- { +- if (nodeE.getLoc().canGoSouth() && nodeS.getLoc().canGoEast()) +- { +- addNode(x + 1, y + 1, z, true); +- } +- } +- +- // SouthWest +- if ((nodeS != null) && (nodeW != null)) +- { +- if (nodeW.getLoc().canGoSouth() && nodeS.getLoc().canGoWest()) +- { +- addNode(x - 1, y + 1, z, true); +- } +- } +- +- // NorthEast +- if ((nodeN != null) && (nodeE != null)) +- { +- if (nodeE.getLoc().canGoNorth() && nodeN.getLoc().canGoEast()) +- { +- addNode(x + 1, y - 1, z, true); +- } +- } +- +- // NorthWest +- if ((nodeN != null) && (nodeW != null)) +- { +- if (nodeW.getLoc().canGoNorth() && nodeN.getLoc().canGoWest()) +- { +- addNode(x - 1, y - 1, z, true); +- } +- } +- } +- +- private 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(); +- // reinit node if needed +- if (result.getLoc() != null) +- { +- result.getLoc().set(x, y, z); +- } +- else +- { +- result.setLoc(new NodeLoc(x, y, z)); +- } +- } +- +- return result; +- } +- +- private 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 boolean isHighWeight(int x, int y, int z) +- { +- final CellNode result = getNode(x, y, z); +- if (result == null) +- { +- return true; +- } +- +- if (!result.getLoc().canGoAll()) +- { +- return true; +- } +- if (Math.abs(result.getLoc().getZ() - z) > 16) +- { +- return true; +- } +- +- return false; +- } +- +- private 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 +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java (working copy) +@@ -0,0 +1,87 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.geodata.GeoLocation; ++ ++/** ++ * @author Hasha ++ */ ++public class Node ++{ ++ // node coords and nswe flag ++ private GeoLocation _loc; ++ ++ // node parent (for reverse path construction) ++ private Node _parent; ++ // node child (for moving over nodes during iteration) ++ private Node _child; ++ ++ // node G cost (movement cost = parent movement cost + current movement cost) ++ private double _cost = -1000; ++ ++ public void setLoc(int x, int y, int z) ++ { ++ _loc = new GeoLocation(x, y, z); ++ } ++ ++ public GeoLocation getLoc() ++ { ++ return _loc; ++ } ++ ++ public void setParent(Node parent) ++ { ++ _parent = parent; ++ } ++ ++ public Node getParent() ++ { ++ return _parent; ++ } ++ ++ public void setChild(Node child) ++ { ++ _child = child; ++ } ++ ++ public Node getChild() ++ { ++ return _child; ++ } ++ ++ public void setCost(double cost) ++ { ++ _cost = cost; ++ } ++ ++ public double getCost() ++ { ++ return _cost; ++ } ++ ++ public void free() ++ { ++ // reset node location ++ _loc = null; ++ ++ // reset node parent, child and cost ++ _parent = null; ++ _child = null; ++ _cost = -1000; ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java (working copy) +@@ -0,0 +1,306 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.concurrent.locks.ReentrantLock; ++ ++import org.l2jmobius.Config; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; ++ ++/** ++ * @author DS, Hasha; Credits to Diamond ++ */ ++public class NodeBuffer ++{ ++ private final ReentrantLock _lock = new ReentrantLock(); ++ private final int _size; ++ private final Node[][] _buffer; ++ ++ // center coordinates ++ private int _cx = 0; ++ private int _cy = 0; ++ ++ // target coordinates ++ private int _gtx = 0; ++ private int _gty = 0; ++ private short _gtz = 0; ++ ++ private Node _current = null; ++ ++ /** ++ * Constructor of NodeBuffer. ++ * @param size : one dimension size of buffer ++ */ ++ public NodeBuffer(int size) ++ { ++ // set size ++ _size = size; ++ ++ // initialize buffer ++ _buffer = new Node[size][size]; ++ for (int x = 0; x < size; x++) ++ { ++ for (int y = 0; y < size; y++) ++ { ++ _buffer[x][y] = 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 Node : first node of path ++ */ ++ public Node findPath(int gox, int goy, short goz, int gtx, int gty, short gtz) ++ { ++ // set coordinates (middle of the line (gox,goy) - (gtx,gty), will be in the center of the buffer) ++ _cx = gox + ((gtx - gox - _size) / 2); ++ _cy = goy + ((gty - goy - _size) / 2); ++ ++ _gtx = gtx; ++ _gty = gty; ++ _gtz = gtz; ++ ++ _current = getNode(gox, goy, goz); ++ _current.setCost(getCostH(gox, goy, goz)); ++ ++ int count = 0; ++ do ++ { ++ // reached target? ++ if ((_current.getLoc().getGeoX() == _gtx) && (_current.getLoc().getGeoY() == _gty) && (Math.abs(_current.getLoc().getZ() - _gtz) < 8)) ++ { ++ return _current; ++ } ++ ++ // expand current node ++ expand(); ++ ++ // move pointer ++ _current = _current.getChild(); ++ } ++ while ((_current != null) && (count++ < Config.MAX_ITERATIONS)); ++ ++ return null; ++ } ++ ++ public boolean isLocked() ++ { ++ return _lock.tryLock(); ++ } ++ ++ public void free() ++ { ++ _current = null; ++ ++ for (Node[] nodes : _buffer) ++ { ++ for (Node node : nodes) ++ { ++ if (node.getLoc() != null) ++ { ++ node.free(); ++ } ++ } ++ } ++ ++ _lock.unlock(); ++ } ++ ++ /** ++ * Check _current Node and add its neighbors to the buffer. ++ */ ++ private final void expand() ++ { ++ // can't move anywhere, don't expand ++ final byte nswe = _current.getLoc().getNSWE(); ++ if (nswe == 0) ++ { ++ return; ++ } ++ ++ // get geo coords of the node to be expanded ++ final int x = _current.getLoc().getGeoX(); ++ final int y = _current.getLoc().getGeoY(); ++ final short z = (short) _current.getLoc().getZ(); ++ ++ // can move north, expand ++ if ((nswe & GeoStructure.CELL_FLAG_N) != 0) ++ { ++ addNode(x, y - 1, z, Config.BASE_WEIGHT); ++ } ++ ++ // can move south, expand ++ if ((nswe & GeoStructure.CELL_FLAG_S) != 0) ++ { ++ addNode(x, y + 1, z, Config.BASE_WEIGHT); ++ } ++ ++ // can move west, expand ++ if ((nswe & GeoStructure.CELL_FLAG_W) != 0) ++ { ++ addNode(x - 1, y, z, Config.BASE_WEIGHT); ++ } ++ ++ // can move east, expand ++ if ((nswe & GeoStructure.CELL_FLAG_E) != 0) ++ { ++ addNode(x + 1, y, z, Config.BASE_WEIGHT); ++ } ++ ++ // can move north-west, expand ++ if ((nswe & GeoStructure.CELL_FLAG_NW) != 0) ++ { ++ addNode(x - 1, y - 1, z, Config.DIAGONAL_WEIGHT); ++ } ++ ++ // can move north-east, expand ++ if ((nswe & GeoStructure.CELL_FLAG_NE) != 0) ++ { ++ addNode(x + 1, y - 1, z, Config.DIAGONAL_WEIGHT); ++ } ++ ++ // can move south-west, expand ++ if ((nswe & GeoStructure.CELL_FLAG_SW) != 0) ++ { ++ addNode(x - 1, y + 1, z, Config.DIAGONAL_WEIGHT); ++ } ++ ++ // can move south-east, expand ++ if ((nswe & GeoStructure.CELL_FLAG_SE) != 0) ++ { ++ addNode(x + 1, y + 1, z, Config.DIAGONAL_WEIGHT); ++ } ++ } ++ ++ /** ++ * Returns node, if it exists in buffer. ++ * @param x : node X coord ++ * @param y : node Y coord ++ * @param z : node Z coord ++ * @return Node : node, if exits in buffer ++ */ ++ private final Node getNode(int x, int y, short z) ++ { ++ // check node X out of coordinates ++ final int ix = x - _cx; ++ if ((ix < 0) || (ix >= _size)) ++ { ++ return null; ++ } ++ ++ // check node Y out of coordinates ++ final int iy = y - _cy; ++ if ((iy < 0) || (iy >= _size)) ++ { ++ return null; ++ } ++ ++ // get node ++ final Node result = _buffer[ix][iy]; ++ ++ // check and update ++ if (result.getLoc() == null) ++ { ++ result.setLoc(x, y, z); ++ } ++ ++ // return node ++ return result; ++ } ++ ++ /** ++ * Add node given by coordinates to the buffer. ++ * @param x : geo X coord ++ * @param y : geo Y coord ++ * @param z : geo Z coord ++ * @param weight : weight of movement to new node ++ */ ++ private final void addNode(int x, int y, short z, int weight) ++ { ++ // get node to be expanded ++ final Node node = getNode(x, y, z); ++ if (node == null) ++ { ++ return; ++ } ++ ++ // Z distance between nearby cells is higher than cell size ++ if (node.getLoc().getZ() > (z + (2 * GeoStructure.CELL_HEIGHT))) ++ { ++ return; ++ } ++ ++ // node was already expanded, return ++ if (node.getCost() >= 0) ++ { ++ return; ++ } ++ ++ node.setParent(_current); ++ if (node.getLoc().getNSWE() != (byte) 0xFF) ++ { ++ node.setCost(getCostH(x, y, node.getLoc().getZ()) + (weight * Config.OBSTACLE_MULTIPLIER)); ++ } ++ else ++ { ++ node.setCost(getCostH(x, y, node.getLoc().getZ()) + weight); ++ } ++ ++ Node current = _current; ++ int count = 0; ++ while ((current.getChild() != null) && (count < (Config.MAX_ITERATIONS * 4))) ++ { ++ count++; ++ if (current.getChild().getCost() > node.getCost()) ++ { ++ node.setChild(current.getChild()); ++ break; ++ } ++ current = current.getChild(); ++ } ++ ++ if (count >= (Config.MAX_ITERATIONS * 4)) ++ { ++ System.err.println("Pathfinding: too long loop detected, cost:" + node.getCost()); ++ } ++ ++ current.setChild(node); ++ } ++ ++ /** ++ * @param x : node X coord ++ * @param y : node Y coord ++ * @param i : node Z coord ++ * @return double : node cost ++ */ ++ private final double getCostH(int x, int y, int i) ++ { ++ final int dX = x - _gtx; ++ final int dY = y - _gty; ++ final int dZ = (i - _gtz) / GeoStructure.CELL_HEIGHT; ++ ++ // return (Math.abs(dX) + Math.abs(dY) + Math.abs(dZ)) * Config.HEURISTIC_WEIGHT; // Manhattan distance ++ return Math.sqrt((dX * dX) + (dY * dY) + (dZ * dZ)) * Config.HEURISTIC_WEIGHT; // Direct distance ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeLoc.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeLoc.java (revision 8319) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeLoc.java (nonexistent) +@@ -1,183 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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.Cell; +- +-/** +- * @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 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 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; +- // return super.hashCode(); +- } +- +- @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; +- if (_x != other._x) +- { +- return false; +- } +- if (_y != other._y) +- { +- return false; +- } +- if (_goNorth != other._goNorth) +- { +- return false; +- } +- if (_goEast != other._goEast) +- { +- return false; +- } +- if (_goSouth != other._goSouth) +- { +- return false; +- } +- if (_goWest != other._goWest) +- { +- return false; +- } +- if (_geoHeight != other._geoHeight) +- { +- return false; +- } +- return true; +- } +-} +Index: java/org/l2jmobius/gameserver/handler/admincommandhandlers/AdminGeodata.java +=================================================================== +--- java/org/l2jmobius/gameserver/handler/admincommandhandlers/AdminGeodata.java (revision 8319) ++++ java/org/l2jmobius/gameserver/handler/admincommandhandlers/AdminGeodata.java (working copy) +@@ -47,8 +47,8 @@ + final int worldX = activeChar.getX(); + final int worldY = activeChar.getY(); + final int worldZ = activeChar.getZ(); +- final int geoX = GeoEngine.getInstance().getGeoX(worldX); +- final int geoY = GeoEngine.getInstance().getGeoY(worldY); ++ final int geoX = GeoEngine.getGeoX(worldX); ++ final int geoY = GeoEngine.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 @@ + final int worldX = activeChar.getX(); + final int worldY = activeChar.getY(); + final int worldZ = activeChar.getZ(); +- final int geoX = GeoEngine.getInstance().getGeoX(worldX); +- final int geoY = GeoEngine.getInstance().getGeoY(worldY); ++ final int geoX = GeoEngine.getGeoX(worldX); ++ final int geoY = GeoEngine.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)); +Index: java/org/l2jmobius/gameserver/model/actor/Creature.java +=================================================================== +--- java/org/l2jmobius/gameserver/model/actor/Creature.java (revision 8399) ++++ java/org/l2jmobius/gameserver/model/actor/Creature.java (working copy) +@@ -44,8 +44,6 @@ + import org.l2jmobius.gameserver.data.xml.ZoneData; + import org.l2jmobius.gameserver.enums.TeleportWhereType; + import org.l2jmobius.gameserver.geoengine.GeoEngine; +-import org.l2jmobius.gameserver.geoengine.GeoEnginePathfinding; +-import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; + import org.l2jmobius.gameserver.handler.ISkillHandler; + import org.l2jmobius.gameserver.handler.SkillHandler; + import org.l2jmobius.gameserver.handler.itemhandlers.Potions; +@@ -4298,7 +4296,7 @@ + public int _heading; + public boolean disregardingGeodata; + public int onGeodataPathIndex; +- public List geoPath; ++ public List geoPath; + public int geoPathAccurateTx; + public int geoPathAccurateTy; + public int geoPathGtx; +@@ -5578,7 +5576,7 @@ + if (((originalDistance - distance) > 30) && !_isAfraid && !isInBoat) + { + // Path calculation -- overrides previous movement check +- m.geoPath = GeoEnginePathfinding.getInstance().findPath(curX, curY, curZ, originalX, originalY, originalZ, getInstanceId()); ++ m.geoPath = GeoEngine.getInstance().findPath(curX, curY, curZ, originalX, originalY, originalZ, getInstanceId()); + if ((m.geoPath == null) || (m.geoPath.size() < 2)) // No path found + { + if (isPlayer() && !_isFlying && !isInWater) +Index: java/org/l2jmobius/gameserver/model/actor/instance/PlayerInstance.java +=================================================================== +--- java/org/l2jmobius/gameserver/model/actor/instance/PlayerInstance.java (revision 8352) ++++ java/org/l2jmobius/gameserver/model/actor/instance/PlayerInstance.java (working copy) +@@ -15483,7 +15483,7 @@ + { + if (Config.CORRECT_PLAYER_Z) + { +- final int nearestZ = GeoEngine.getInstance().getHigherHeight(getX(), getY(), getZ()); ++ final int nearestZ = GeoEngine.getInstance().getHeightNearest(getX(), getY(), getZ()); + if (getZ() < nearestZ) + { + teleToLocation(new Location(getX(), getY(), nearestZ), false); +Index: java/org/l2jmobius/gameserver/util/GeoUtils.java +=================================================================== +--- java/org/l2jmobius/gameserver/util/GeoUtils.java (revision 8319) ++++ java/org/l2jmobius/gameserver/util/GeoUtils.java (working copy) +@@ -19,7 +19,7 @@ + import java.awt.Color; + + import org.l2jmobius.gameserver.geoengine.GeoEngine; +-import org.l2jmobius.gameserver.geoengine.geodata.Cell; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; + import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance; + import org.l2jmobius.gameserver.network.serverpackets.ExServerPrimitive; + +@@ -26,25 +26,25 @@ + /** + * @author HorridoJoho + */ +-public final class GeoUtils ++public class GeoUtils + { + public static void debug2DLine(PlayerInstance player, int x, int y, int tx, int ty, int z) + { +- final int gx = GeoEngine.getInstance().getGeoX(x); +- final int gy = GeoEngine.getInstance().getGeoY(y); ++ final int gx = GeoEngine.getGeoX(x); ++ final int gy = GeoEngine.getGeoY(y); + +- final int tgx = GeoEngine.getInstance().getGeoX(tx); +- final int tgy = GeoEngine.getInstance().getGeoY(ty); ++ final int tgx = GeoEngine.getGeoX(tx); ++ final int tgy = GeoEngine.getGeoY(ty); + + final ExServerPrimitive prim = new ExServerPrimitive("Debug2DLine", x, y, z); +- prim.addLine(Color.BLUE, GeoEngine.getInstance().getWorldX(gx), GeoEngine.getInstance().getWorldY(gy), z, GeoEngine.getInstance().getWorldX(tgx), GeoEngine.getInstance().getWorldY(tgy), z); ++ prim.addLine(Color.BLUE, GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), z, GeoEngine.getWorldX(tgx), GeoEngine.getWorldY(tgy), z); + + final LinePointIterator iter = new LinePointIterator(gx, gy, tgx, tgy); + + while (iter.next()) + { +- final int wx = GeoEngine.getInstance().getWorldX(iter.x()); +- final int wy = GeoEngine.getInstance().getWorldY(iter.y()); ++ final int wx = GeoEngine.getWorldX(iter.x()); ++ final int wy = GeoEngine.getWorldY(iter.y()); + + prim.addPoint(Color.RED, wx, wy, z); + } +@@ -53,21 +53,21 @@ + + public static void debug3DLine(PlayerInstance player, int x, int y, int z, int tx, int ty, int tz) + { +- final int gx = GeoEngine.getInstance().getGeoX(x); +- final int gy = GeoEngine.getInstance().getGeoY(y); ++ final int gx = GeoEngine.getGeoX(x); ++ final int gy = GeoEngine.getGeoY(y); + +- final int tgx = GeoEngine.getInstance().getGeoX(tx); +- final int tgy = GeoEngine.getInstance().getGeoY(ty); ++ final int tgx = GeoEngine.getGeoX(tx); ++ final int tgy = GeoEngine.getGeoY(ty); + + final ExServerPrimitive prim = new ExServerPrimitive("Debug3DLine", x, y, z); +- prim.addLine(Color.BLUE, GeoEngine.getInstance().getWorldX(gx), GeoEngine.getInstance().getWorldY(gy), z, GeoEngine.getInstance().getWorldX(tgx), GeoEngine.getInstance().getWorldY(tgy), tz); ++ prim.addLine(Color.BLUE, GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), z, GeoEngine.getWorldX(tgx), GeoEngine.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.getInstance().getWorldX(prevX); +- int wy = GeoEngine.getInstance().getWorldY(prevY); ++ int wx = GeoEngine.getWorldX(prevX); ++ int wy = GeoEngine.getWorldY(prevY); + int wz = iter.z(); + prim.addPoint(Color.RED, wx, wy, wz); + +@@ -78,8 +78,8 @@ + + if ((curX != prevX) || (curY != prevY)) + { +- wx = GeoEngine.getInstance().getWorldX(curX); +- wy = GeoEngine.getInstance().getWorldY(curY); ++ wx = GeoEngine.getWorldX(curX); ++ wy = GeoEngine.getWorldY(curY); + wz = iter.z(); + + prim.addPoint(Color.RED, wx, wy, wz); +@@ -93,7 +93,7 @@ + + private static Color getDirectionColor(int x, int y, int z, int nswe) + { +- if (GeoEngine.getInstance().checkNearestNswe(x, y, z, nswe)) ++ if ((GeoEngine.getInstance().getNsweNearest(x, y, z) & nswe) == nswe) + { + return Color.GREEN; + } +@@ -109,9 +109,8 @@ + int iPacket = 0; + + ExServerPrimitive exsp = null; +- final GeoEngine ge = GeoEngine.getInstance(); +- final int playerGx = ge.getGeoX(player.getX()); +- final int playerGy = ge.getGeoY(player.getY()); ++ final int playerGx = GeoEngine.getGeoX(player.getX()); ++ final int playerGy = GeoEngine.getGeoY(player.getY()); + for (int dx = -geoRadius; dx <= geoRadius; ++dx) + { + for (int dy = -geoRadius; dy <= geoRadius; ++dy) +@@ -135,12 +134,12 @@ + final int gx = playerGx + dx; + final int gy = playerGy + dy; + +- final int x = ge.getWorldX(gx); +- final int y = ge.getWorldY(gy); +- final int z = ge.getNearestZ(gx, gy, player.getZ()); ++ final int x = GeoEngine.getWorldX(gx); ++ final int y = GeoEngine.getWorldY(gy); ++ final int z = GeoEngine.getInstance().getHeightNearest(gx, gy, player.getZ()); + + // north arrow +- Color col = getDirectionColor(gx, gy, z, Cell.NSWE_NORTH); ++ Color col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_N); + 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); +@@ -147,7 +146,7 @@ + exsp.addLine(col, x - 4, y - 4, z, x + 4, y - 4, z); + + // east arrow +- col = getDirectionColor(gx, gy, z, Cell.NSWE_EAST); ++ col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_E); + 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); +@@ -154,13 +153,13 @@ + exsp.addLine(col, x + 4, y - 4, z, x + 4, y + 4, z); + + // south arrow +- col = getDirectionColor(gx, gy, z, Cell.NSWE_SOUTH); ++ col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_S); + 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, Cell.NSWE_WEST); ++ col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_W); + 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); +@@ -188,15 +187,15 @@ + { + if (y > lastY) + { +- return Cell.NSWE_SOUTH_EAST; ++ return GeoStructure.CELL_FLAG_SE; // Direction.SOUTH_EAST; + } + else if (y < lastY) + { +- return Cell.NSWE_NORTH_EAST; ++ return GeoStructure.CELL_FLAG_NE; // Direction.NORTH_EAST; + } + else + { +- return Cell.NSWE_EAST; ++ return GeoStructure.CELL_FLAG_E; // Direction.EAST; + } + } + else if (x < lastX) // west +@@ -203,26 +202,27 @@ + { + if (y > lastY) + { +- return Cell.NSWE_SOUTH_WEST; ++ return GeoStructure.CELL_FLAG_SW; // Direction.SOUTH_WEST; + } + else if (y < lastY) + { +- return Cell.NSWE_NORTH_WEST; ++ return GeoStructure.CELL_FLAG_NW; // Direction.NORTH_WEST; + } + else + { +- return Cell.NSWE_WEST; ++ return GeoStructure.CELL_FLAG_W; // Direction.WEST; + } + } +- else // unchanged x ++ else ++ // unchanged x + { + if (y > lastY) + { +- return Cell.NSWE_SOUTH; ++ return GeoStructure.CELL_FLAG_S; // Direction.SOUTH; + } + else if (y < lastY) + { +- return Cell.NSWE_NORTH; ++ return GeoStructure.CELL_FLAG_N; // Direction.NORTH; + } + else + { +Index: java/org/l2jmobius/tools/geodataconverter/GeoDataConverter.java +=================================================================== +--- java/org/l2jmobius/tools/geodataconverter/GeoDataConverter.java (nonexistent) ++++ java/org/l2jmobius/tools/geodataconverter/GeoDataConverter.java (working copy) +@@ -0,0 +1,367 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ */ ++package org.l2jmobius.tools.geodataconverter; ++ ++import java.io.BufferedOutputStream; ++import java.io.File; ++import java.io.FileOutputStream; ++import java.io.RandomAccessFile; ++import java.nio.ByteOrder; ++import java.nio.MappedByteBuffer; ++import java.nio.channels.FileChannel; ++import java.util.Scanner; ++ ++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.GeoFormat; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; ++import org.l2jmobius.gameserver.model.World; ++ ++/** ++ * @author Hasha ++ */ ++public class GeoDataConverter ++{ ++ final static String GEODATA_PATH = "./data/geodata/"; ++ ++ private static GeoFormat _format; ++ private static ABlock[][] _blocks; ++ ++ public static void main(String[] args) ++ { ++ // get geodata format ++ String type = ""; ++ try (Scanner scn = new Scanner(System.in)) ++ { ++ while (!(type.equalsIgnoreCase("J") || type.equalsIgnoreCase("O") || type.equalsIgnoreCase("E"))) ++ { ++ System.out.println("GeoDataConverter: Select source geodata type:"); ++ System.out.println(" J: L2J (e.g. 23_22.l2j)"); ++ System.out.println(" O: L2OFF (e.g. 23_22_conv.dat)"); ++ System.out.println(" E: Exit"); ++ System.out.print("Choice: "); ++ type = scn.next(); ++ } ++ } ++ if (type.equalsIgnoreCase("E")) ++ { ++ System.exit(0); ++ } ++ ++ _format = type.equalsIgnoreCase("J") ? GeoFormat.L2J : GeoFormat.L2OFF; ++ ++ // start conversion ++ System.out.println("GeoDataConverter: Converting all " + _format + " files."); ++ ++ // initialize geodata container ++ _blocks = new ABlock[GeoStructure.REGION_BLOCKS_X][GeoStructure.REGION_BLOCKS_Y]; ++ ++ // initialize multilayer temporarily buffer ++ BlockMultilayer.initialize(); ++ ++ // load geo files ++ int converted = 0; ++ for (int rx = World.TILE_X_MIN; rx <= World.TILE_X_MAX; rx++) ++ { ++ for (int ry = World.TILE_Y_MIN; ry <= World.TILE_Y_MAX; ry++) ++ { ++ final String input = String.format(_format.getFilename(), rx, ry); ++ final String filepath = GEODATA_PATH; ++ final File f = new File(filepath + input); ++ if (f.exists() && !f.isDirectory()) ++ { ++ // load geodata ++ if (!loadGeoBlocks(input)) ++ { ++ System.out.println("GeoDataConverter: Unable to load " + input + " region file."); ++ continue; ++ } ++ ++ // recalculate nswe ++ if (!recalculateNswe()) ++ { ++ System.out.println("GeoDataConverter: Unable to convert " + input + " region file."); ++ continue; ++ } ++ ++ // save geodata ++ final String output = String.format(GeoFormat.L2D.getFilename(), rx, ry); ++ if (!saveGeoBlocks(output)) ++ { ++ System.out.println("GeoDataConverter: Unable to save " + output + " region file."); ++ continue; ++ } ++ ++ converted++; ++ System.out.println("GeoDataConverter: Created " + output + " region file."); ++ } ++ } ++ } ++ System.out.println("GeoDataConverter: Converted " + converted + " " + _format + " to L2D region file(s)."); ++ ++ // release multilayer block temporarily buffer ++ BlockMultilayer.release(); ++ } ++ ++ /** ++ * Loads geo blocks from buffer of the region file. ++ * @param filename : The name of the to load. ++ * @return boolean : True when successful. ++ */ ++ private static boolean loadGeoBlocks(String filename) ++ { ++ // region file is load-able, try to load it ++ try (RandomAccessFile raf = new RandomAccessFile(GEODATA_PATH + filename, "r"); ++ FileChannel fc = raf.getChannel()) ++ { ++ final MappedByteBuffer buffer = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()).load(); ++ buffer.order(ByteOrder.LITTLE_ENDIAN); ++ ++ // load 18B header for L2off geodata (1st and 2nd byte...region X and Y) ++ if (_format == GeoFormat.L2OFF) ++ { ++ for (int i = 0; i < 18; i++) ++ { ++ buffer.get(); ++ } ++ } ++ ++ // 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 (_format == GeoFormat.L2J) ++ { ++ // get block type ++ final byte type = buffer.get(); ++ ++ // load block according to block type ++ switch (type) ++ { ++ case GeoStructure.TYPE_FLAT_L2J_L2OFF: ++ { ++ _blocks[ix][iy] = new BlockFlat(buffer, _format); ++ break; ++ } ++ case GeoStructure.TYPE_COMPLEX_L2J: ++ { ++ _blocks[ix][iy] = new BlockComplex(buffer, _format); ++ break; ++ } ++ case GeoStructure.TYPE_MULTILAYER_L2J: ++ { ++ _blocks[ix][iy] = new BlockMultilayer(buffer, _format); ++ break; ++ } ++ default: ++ { ++ throw new IllegalArgumentException("Unknown block type: " + type); ++ } ++ } ++ } ++ else ++ { ++ // get block type ++ final short type = buffer.getShort(); ++ ++ // load block according to block type ++ switch (type) ++ { ++ case GeoStructure.TYPE_FLAT_L2J_L2OFF: ++ { ++ _blocks[ix][iy] = new BlockFlat(buffer, _format); ++ break; ++ } ++ case GeoStructure.TYPE_COMPLEX_L2OFF: ++ { ++ _blocks[ix][iy] = new BlockComplex(buffer, _format); ++ break; ++ } ++ default: ++ { ++ _blocks[ix][iy] = new BlockMultilayer(buffer, _format); ++ break; ++ } ++ } ++ } ++ } ++ } ++ ++ if (buffer.remaining() > 0) ++ { ++ System.out.println("GeoDataConverter: Region file " + filename + " can be corrupted, remaining " + buffer.remaining() + " bytes to read."); ++ return false; ++ } ++ ++ return true; ++ } ++ catch (Exception e) ++ { ++ System.out.println("GeoDataConverter: Error while loading " + filename + " region file."); ++ return false; ++ } ++ } ++ ++ /** ++ * Recalculate diagonal flags for the region file. ++ * @return boolean : True when successful. ++ */ ++ private static boolean recalculateNswe() ++ { ++ try ++ { ++ for (int x = 0; x < GeoStructure.REGION_CELLS_X; x++) ++ { ++ for (int y = 0; y < GeoStructure.REGION_CELLS_Y; y++) ++ { ++ // get block ++ final ABlock block = _blocks[x / GeoStructure.BLOCK_CELLS_X][y / GeoStructure.BLOCK_CELLS_Y]; ++ ++ // skip flat blocks ++ if (block instanceof BlockFlat) ++ { ++ continue; ++ } ++ ++ // for complex and multilayer blocks go though all layers ++ short height = Short.MAX_VALUE; ++ int index; ++ while ((index = block.getIndexBelow(x, y, height)) != -1) ++ { ++ // get height and nswe ++ height = block.getHeight(index); ++ byte nswe = block.getNswe(index); ++ ++ // update nswe with diagonal flags ++ nswe = updateNsweBelow(x, y, height, nswe); ++ ++ // set nswe of the cell ++ block.setNswe(index, nswe); ++ } ++ } ++ } ++ ++ return true; ++ } ++ catch (Exception e) ++ { ++ return false; ++ } ++ } ++ ++ /** ++ * Updates the NSWE flag with diagonal flags. ++ * @param x : Geodata X coordinate. ++ * @param y : Geodata Y coordinate. ++ * @param z : Geodata Z coordinate. ++ * @param nsweValue : NSWE flag to be updated. ++ * @return byte : Updated NSWE flag. ++ */ ++ private static byte updateNsweBelow(int x, int y, short z, byte nsweValue) ++ { ++ byte nswe = nsweValue; ++ ++ // calculate virtual layer height ++ final short height = (short) (z + GeoStructure.CELL_IGNORE_HEIGHT); ++ ++ // get NSWE of neighbor cells below virtual layer (NPC/PC can fall down of clif, but can not climb it -> NSWE of cell below) ++ final byte nsweN = getNsweBelow(x, y - 1, height); ++ final byte nsweS = getNsweBelow(x, y + 1, height); ++ final byte nsweW = getNsweBelow(x - 1, y, height); ++ final byte nsweE = getNsweBelow(x + 1, y, height); ++ ++ // north-west ++ if ((((nswe & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) || (((nswe & GeoStructure.CELL_FLAG_W) != 0) && ((nsweW & GeoStructure.CELL_FLAG_N) != 0))) ++ { ++ nswe |= GeoStructure.CELL_FLAG_NW; ++ } ++ ++ // north-east ++ if ((((nswe & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) || (((nswe & GeoStructure.CELL_FLAG_E) != 0) && ((nsweE & GeoStructure.CELL_FLAG_N) != 0))) ++ { ++ nswe |= GeoStructure.CELL_FLAG_NE; ++ } ++ ++ // south-west ++ if ((((nswe & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) || (((nswe & GeoStructure.CELL_FLAG_W) != 0) && ((nsweW & GeoStructure.CELL_FLAG_S) != 0))) ++ { ++ nswe |= GeoStructure.CELL_FLAG_SW; ++ } ++ ++ // south-east ++ if ((((nswe & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) || (((nswe & GeoStructure.CELL_FLAG_E) != 0) && ((nsweE & GeoStructure.CELL_FLAG_S) != 0))) ++ { ++ nswe |= GeoStructure.CELL_FLAG_SE; ++ } ++ ++ return nswe; ++ } ++ ++ private static byte getNsweBelow(int geoX, int geoY, short worldZ) ++ { ++ // out of geo coordinates ++ if ((geoX < 0) || (geoX >= GeoStructure.REGION_CELLS_X)) ++ { ++ return 0; ++ } ++ ++ // out of geo coordinates ++ if ((geoY < 0) || (geoY >= GeoStructure.REGION_CELLS_Y)) ++ { ++ return 0; ++ } ++ ++ // get block ++ final ABlock block = _blocks[geoX / GeoStructure.BLOCK_CELLS_X][geoY / GeoStructure.BLOCK_CELLS_Y]; ++ ++ // get index, when valid, return nswe ++ final int index = block.getIndexBelow(geoX, geoY, worldZ); ++ return index == -1 ? 0 : block.getNswe(index); ++ } ++ ++ /** ++ * Save region file to file. ++ * @param filename : The name of file to save. ++ * @return boolean : True when successful. ++ */ ++ private static boolean saveGeoBlocks(String filename) ++ { ++ try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(GEODATA_PATH + filename), GeoStructure.REGION_BLOCKS * GeoStructure.BLOCK_CELLS * 3)) ++ { ++ // loop over region blocks and save each block ++ for (int ix = 0; ix < GeoStructure.REGION_BLOCKS_X; ix++) ++ { ++ for (int iy = 0; iy < GeoStructure.REGION_BLOCKS_Y; iy++) ++ { ++ _blocks[ix][iy].saveBlock(bos); ++ } ++ } ++ ++ // flush data to file ++ bos.flush(); ++ ++ return true; ++ } ++ catch (Exception e) ++ { ++ return false; ++ } ++ } ++} +\ No newline at end of file diff --git a/L2J_Mobius_C6_Interlude/dist/game/data/geodata/L2D_GeoEngine.patch b/L2J_Mobius_C6_Interlude/dist/game/data/geodata/L2D_GeoEngine.patch new file mode 100644 index 0000000000..e59fbf584a --- /dev/null +++ b/L2J_Mobius_C6_Interlude/dist/game/data/geodata/L2D_GeoEngine.patch @@ -0,0 +1,5951 @@ +Index: dist/game/GeoDataConverter.bat +=================================================================== +--- dist/game/GeoDataConverter.bat (nonexistent) ++++ dist/game/GeoDataConverter.bat (working copy) +@@ -0,0 +1,6 @@ ++@echo off ++title L2D geodata converter ++ ++java -Xmx512m -cp ./../libs/* org.l2jmobius.tools.geodataconverter.GeoDataConverter ++ ++pause +Index: dist/game/GeoDataConverter.sh +=================================================================== +--- dist/game/GeoDataConverter.sh (nonexistent) ++++ dist/game/GeoDataConverter.sh (working copy) +@@ -0,0 +1,4 @@ ++#! /bin/sh ++ ++java -Xmx512m -cp ../libs/*: org.l2jmobius.tools.geodataconverter.GeoDataConverter > log/stdout.log 2>&1 ++ +Index: dist/game/config/main/GeoEngine.ini +=================================================================== +--- dist/game/config/main/GeoEngine.ini (revision 8319) ++++ dist/game/config/main/GeoEngine.ini (working copy) +@@ -1,6 +1,10 @@ + # ================================================================= + # Geodata + # ================================================================= ++# Because of real-time performance we are using geodata files only in ++# diagonal L2D format now (using filename e.g. 22_16.l2d). ++# L2D geodata can be obtained by conversion of existing L2J or L2OFF geodata. ++# Launch "GeoDataConverter.bat/sh" and follow instructions to start the conversion. + + # 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/ +@@ -13,6 +17,16 @@ + CoordSynchronize = 2 + + # ================================================================= ++# Path checking ++# ================================================================= ++ ++# 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 finding + # ================================================================= + +@@ -23,19 +37,23 @@ + # Pathfinding array buffers configuration, default: 100x6;128x6;192x6;256x4;320x4;384x4;500x2 + PathFindBuffers = 100x6;128x6;192x6;256x4;320x4;384x4;500x2 + +-# Weight for nodes without obstacles far from walls +-LowWeight = 0.5 ++# Base path weight, when moving from one node to another on axis direction, default: 10 ++BaseWeight = 10 + +-# Weight for nodes near walls +-MediumWeight = 2 ++# Path weight, when moving from one node to another on diagonal direction, default: BaseWeight * sqrt(2) = 14 ++DiagonalWeight = 14 + +-# Weight for nodes with obstacles +-HighWeight = 3 ++# When movement flags of target node is blocked to any direction, multiply movement weight by this multiplier. ++# This causes pathfinding algorithm to avoid path construction exactly near an obstacle, default: 10 ++ObstacleMultiplier = 10 + +-# Weight for diagonal movement. +-# Default: LowWeight * sqrt(2) +-DiagonalWeight = 0.707 ++# Weight of the heuristic algorithm, which is giving estimated cost from node to target, default: 20 ++# For proper function must be higher than BaseWeight and/or DiagonalWeight. ++HeuristicWeight = 20 + ++# Maximum number of generated nodes per one path-finding process, default 3500 ++MaxIterations = 3500 ++ + # ================================================================= + # Other + # ================================================================= +Index: java/org/l2jmobius/Config.java +=================================================================== +--- java/org/l2jmobius/Config.java (revision 8388) ++++ java/org/l2jmobius/Config.java (working copy) +@@ -28,8 +28,6 @@ + import java.io.OutputStream; + import java.math.BigInteger; + import java.net.UnknownHostException; +-import java.nio.file.Path; +-import java.nio.file.Paths; + import java.util.ArrayList; + import java.util.HashMap; + import java.util.List; +@@ -964,14 +962,17 @@ + public static boolean LEAVE_BUFFS_ON_DIE; + public static boolean ALT_RAIDS_STATS_BONUS; + +- public static Path GEODATA_PATH; ++ public static String GEODATA_PATH; ++ public static int COORD_SYNCHRONIZE; ++ public static int PART_OF_CHARACTER_HEIGHT; ++ public static int MAX_OBSTACLE_HEIGHT; + public static boolean PATHFINDING; + public static String PATHFIND_BUFFERS; +- public static float LOW_WEIGHT; +- public static float MEDIUM_WEIGHT; +- public static float HIGH_WEIGHT; +- public static float DIAGONAL_WEIGHT; +- public static int COORD_SYNCHRONIZE; ++ public static int BASE_WEIGHT; ++ public static int DIAGONAL_WEIGHT; ++ public static int HEURISTIC_WEIGHT; ++ public static int OBSTACLE_MULTIPLIER; ++ public static int MAX_ITERATIONS; + public static boolean CORRECT_PLAYER_Z; + public static boolean FALL_DAMAGE; + public static boolean ALLOW_WATER; +@@ -2560,14 +2561,17 @@ + public static void loadgeodataConfig() + { + final PropertiesParser geoengineConfig = new PropertiesParser(GEOENGINE_CONFIG_FILE); +- GEODATA_PATH = Paths.get(geoengineConfig.getString("GeoDataPath", "./data/geodata")); ++ GEODATA_PATH = geoengineConfig.getString("GeoDataPath", "./data/geodata/"); ++ COORD_SYNCHRONIZE = geoengineConfig.getInt("CoordSynchronize", -1); ++ PART_OF_CHARACTER_HEIGHT = geoengineConfig.getInt("PartOfCharacterHeight", 75); ++ MAX_OBSTACLE_HEIGHT = geoengineConfig.getInt("MaxObstacleHeight", 32); + PATHFINDING = geoengineConfig.getBoolean("PathFinding", true); + 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); +- DIAGONAL_WEIGHT = geoengineConfig.getFloat("DiagonalWeight", 0.707f); +- COORD_SYNCHRONIZE = geoengineConfig.getInt("CoordSynchronize", -1); ++ BASE_WEIGHT = geoengineConfig.getInt("BaseWeight", 10); ++ DIAGONAL_WEIGHT = geoengineConfig.getInt("DiagonalWeight", 14); ++ OBSTACLE_MULTIPLIER = geoengineConfig.getInt("ObstacleMultiplier", 10); ++ HEURISTIC_WEIGHT = geoengineConfig.getInt("HeuristicWeight", 20); ++ MAX_ITERATIONS = geoengineConfig.getInt("MaxIterations", 3500); + CORRECT_PLAYER_Z = geoengineConfig.getBoolean("CorrectPlayerZ", false); + FALL_DAMAGE = geoengineConfig.getBoolean("FallDamage", false); + ALLOW_WATER = geoengineConfig.getBoolean("AllowWater", false); +Index: java/org/l2jmobius/gameserver/geoengine/GeoEngine.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/GeoEngine.java (revision 8352) ++++ java/org/l2jmobius/gameserver/geoengine/GeoEngine.java (working copy) +@@ -16,98 +16,89 @@ + */ + package org.l2jmobius.gameserver.geoengine; + +-import java.io.IOException; ++import java.io.File; + import java.io.RandomAccessFile; + import java.nio.ByteOrder; +-import java.nio.channels.FileChannel.MapMode; +-import java.nio.file.Files; +-import java.nio.file.Path; +-import java.util.concurrent.atomic.AtomicReferenceArray; +-import java.util.logging.Level; ++import java.nio.MappedByteBuffer; ++import java.nio.channels.FileChannel; ++import java.util.List; + 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.geoengine.geodata.Cell; +-import org.l2jmobius.gameserver.geoengine.geodata.IRegion; +-import org.l2jmobius.gameserver.geoengine.geodata.NullRegion; +-import org.l2jmobius.gameserver.geoengine.geodata.Region; ++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.GeoFormat; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoLocation; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; + import org.l2jmobius.gameserver.model.Location; + import org.l2jmobius.gameserver.model.World; + import org.l2jmobius.gameserver.model.WorldObject; +-import org.l2jmobius.gameserver.util.GeoUtils; +-import org.l2jmobius.gameserver.util.LinePointIterator; +-import org.l2jmobius.gameserver.util.LinePointIterator3D; ++import org.l2jmobius.gameserver.model.actor.Creature; ++import org.l2jmobius.gameserver.util.MathUtil; + + /** +- * @author -Nemesiss-, HorridoJoho ++ * @author Hasha + */ + public class GeoEngine + { +- private static final Logger LOGGER = Logger.getLogger(GeoEngine.class.getName()); ++ protected static final Logger LOGGER = Logger.getLogger(GeoEngine.class.getName()); + +- private static final int WORLD_MIN_X = -655360; +- private static final int WORLD_MIN_Y = -589824; +- private static final int WORLD_MIN_Z = -16384; ++ private final ABlock[][] _blocks; ++ private final BlockNull _nullBlock; + +- /** 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; +- +- /** The regions array */ +- private final AtomicReferenceArray _regions = new AtomicReferenceArray<>(GEO_REGIONS); +- +- 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; +- +- protected GeoEngine() ++ /** ++ * GeoEngine constructor. Loads all geodata files of chosen geodata format. ++ */ ++ public GeoEngine() + { + LOGGER.info("GeoEngine: Initializing..."); +- for (int i = 0; i < _regions.length(); i++) +- { +- _regions.set(i, NullRegion.INSTANCE); +- } + ++ // 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; +- try ++ long fileSize = 0; ++ for (int rx = World.TILE_X_MIN; rx <= World.TILE_X_MAX; rx++) + { +- for (int regionX = World.TILE_X_MIN; regionX <= World.TILE_X_MAX; regionX++) ++ for (int ry = World.TILE_Y_MIN; ry <= World.TILE_Y_MAX; ry++) + { +- for (int regionY = World.TILE_Y_MIN; regionY <= World.TILE_Y_MAX; regionY++) ++ final File f = new File(Config.GEODATA_PATH + String.format(GeoFormat.L2D.getFilename(), rx, ry)); ++ if (f.exists() && !f.isDirectory()) + { +- 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(rx, ry)) + { +- try (RandomAccessFile raf = new RandomAccessFile(geoFilePath.toFile(), "r")) +- { +- _regions.set((regionX * GEO_REGIONS_Y) + regionY, new Region(raf.getChannel().map(MapMode.READ_ONLY, 0, raf.length()).load().order(ByteOrder.LITTLE_ENDIAN))); +- loaded++; +- } ++ loaded++; ++ fileSize += f.length(); + } + } ++ else ++ { ++ // region file is not load-able, load null blocks ++ loadNullBlocks(rx, ry); ++ } + } + } +- catch (Exception e) ++ ++ LOGGER.info("GeoEngine: Loaded " + loaded + " geodata files."); ++ if (loaded > 0) + { +- LOGGER.log(Level.SEVERE, "GeoEngine: Failed to load geodata!", e); +- System.exit(1); ++ LOGGER.info("GeoEngine: Total geodata file size " + (fileSize / 1024 / 1024) + " MB."); + } + +- LOGGER.info("GeoEngine: Loaded " + loaded + " geodata files."); +- +- // Avoid wrong configs when no files are loaded. ++ // avoid wrong configs when no files are loaded + if (loaded == 0) + { + if (Config.PATHFINDING) +@@ -121,619 +112,820 @@ + LOGGER.info("GeoEngine: Forcing CoordSynchronize setting to -1."); + } + } ++ ++ // release multilayer block temporarily buffer ++ BlockMultilayer.release(); + } + + /** +- * @param geoX +- * @param geoY +- * @return the region ++ * 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 IRegion getRegion(int geoX, int geoY) ++ private final boolean loadGeoBlocks(int regionX, int regionY) + { +- final int region = ((geoX / IRegion.REGION_CELLS_X) * GEO_REGIONS_Y) + (geoY / IRegion.REGION_CELLS_Y); +- if ((region < 0) || (region >= _regions.length())) ++ final String filename = String.format(GeoFormat.L2D.getFilename(), regionX, regionY); ++ ++ // standard load ++ try (RandomAccessFile raf = new RandomAccessFile(Config.GEODATA_PATH + filename, "r"); ++ FileChannel fc = raf.getChannel()) + { +- return null; ++ // initialize file buffer ++ final MappedByteBuffer buffer = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()).load(); ++ buffer.order(ByteOrder.LITTLE_ENDIAN); ++ ++ // 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++) ++ { ++ // get block type ++ final byte type = buffer.get(); ++ ++ // load block according to block type ++ switch (type) ++ { ++ case GeoStructure.TYPE_FLAT_L2D: ++ { ++ _blocks[blockX + ix][blockY + iy] = new BlockFlat(buffer, GeoFormat.L2D); ++ break; ++ } ++ case GeoStructure.TYPE_COMPLEX_L2D: ++ { ++ _blocks[blockX + ix][blockY + iy] = new BlockComplex(buffer, GeoFormat.L2D); ++ break; ++ } ++ case GeoStructure.TYPE_MULTILAYER_L2D: ++ { ++ _blocks[blockX + ix][blockY + iy] = new BlockMultilayer(buffer, GeoFormat.L2D); ++ break; ++ } ++ default: ++ { ++ throw new IllegalArgumentException("Unknown block type: " + type); ++ } ++ } ++ } ++ } ++ ++ // check data consistency ++ if (buffer.remaining() > 0) ++ { ++ LOGGER.warning("GeoEngine: Region file " + filename + " can be corrupted, remaining " + buffer.remaining() + " bytes to read."); ++ } ++ ++ // loading was successful ++ return true; + } +- return _regions.get(region); +- } +- +- /** +- * @param filePath +- * @param regionX +- * @param regionY +- * @throws IOException +- */ +- 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")) ++ catch (Exception e) + { +- _regions.set(regionOffset, new Region(raf.getChannel().map(MapMode.READ_ONLY, 0, raf.length()).load().order(ByteOrder.LITTLE_ENDIAN))); ++ // an error occured while loading, load null blocks ++ LOGGER.warning("GeoEngine: Error while loading " + filename + " region file."); ++ LOGGER.warning(e.getMessage()); ++ ++ // replace whole region file with null blocks ++ loadNullBlocks(regionX, regionY); ++ ++ // loading was not successful ++ return false; + } + } + + /** +- * @param regionX +- * @param regionY ++ * 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. + */ +- public void unloadRegion(int regionX, int regionY) ++ private final void loadNullBlocks(int regionX, int regionY) + { +- _regions.set((regionX * GEO_REGIONS_Y) + regionY, NullRegion.INSTANCE); +- } +- +- /** +- * @param geoX +- * @param geoY +- * @return if geodata exist +- */ +- public boolean hasGeoPos(int geoX, int geoY) +- { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) ++ // 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++) + { +- return false; ++ for (int iy = 0; iy < GeoStructure.REGION_BLOCKS_Y; iy++) ++ { ++ _blocks[blockX + ix][blockY + iy] = _nullBlock; ++ } + } +- return region.hasGeo(); + } + ++ // GEODATA - GENERAL ++ + /** +- * Checks the specified position for available geodata. +- * @param x the world x +- * @param y the world y +- * @return {@code true} if there is geodata for the given coordinates, {@code false} otherwise ++ * Converts world X to geodata X. ++ * @param worldX ++ * @return int : Geo X + */ +- public boolean hasGeo(int x, int y) ++ public static int getGeoX(int worldX) + { +- return hasGeoPos(getGeoX(x), getGeoY(y)); ++ return (MathUtil.limit(worldX, World.MAP_MIN_X, World.MAP_MAX_X) - World.MAP_MIN_X) >> 4; + } + + /** +- * @param geoX +- * @param geoY +- * @param worldZ +- * @param nswe +- * @return the nearest nswe check ++ * Converts world Y to geodata Y. ++ * @param worldY ++ * @return int : Geo Y + */ +- public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) ++ public static int getGeoY(int worldY) + { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) +- { +- return true; +- } +- return region.checkNearestNswe(geoX, geoY, worldZ, nswe); ++ return (MathUtil.limit(worldY, World.MAP_MIN_Y, World.MAP_MAX_Y) - World.MAP_MIN_Y) >> 4; + } + + /** ++ * Converts geodata X to world X. + * @param geoX +- * @param geoY +- * @param worldZ +- * @param nswe +- * @return the nearest nswe anti-corner cut ++ * @return int : World X + */ +- public boolean checkNearestNsweAntiCornerCut(int geoX, int geoY, int worldZ, int nswe) ++ public static int getWorldX(int geoX) + { +- boolean can = true; +- if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) +- { +- can = checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_EAST) && checkNearestNswe(geoX + 1, geoY, worldZ, Cell.NSWE_NORTH); +- } +- if (can && ((nswe & Cell.NSWE_NORTH_WEST) == Cell.NSWE_NORTH_WEST)) +- { +- 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 = 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 = checkNearestNswe(geoX, geoY + 1, worldZ, Cell.NSWE_WEST) && checkNearestNswe(geoX - 1, geoY, worldZ, Cell.NSWE_SOUTH); +- } +- return can && checkNearestNswe(geoX, geoY, worldZ, nswe); ++ return (MathUtil.limit(geoX, 0, GeoStructure.GEO_CELLS_X) << 4) + World.MAP_MIN_X + 8; + } + + /** +- * @param geoX ++ * Converts geodata Y to world Y. + * @param geoY +- * @param worldZ +- * @return the nearest Z value ++ * @return int : World Y + */ +- public int getNearestZ(int geoX, int geoY, int worldZ) ++ public static int getWorldY(int geoY) + { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) +- { +- return worldZ; +- } +- return region.getNearestZ(geoX, geoY, worldZ); ++ return (MathUtil.limit(geoY, 0, GeoStructure.GEO_CELLS_Y) << 4) + World.MAP_MIN_Y + 8; + } + + /** +- * @param geoX +- * @param geoY +- * @param worldZ +- * @return the next lower Z value ++ * Returns block of geodata on given coordinates. ++ * @param geoX : Geodata X ++ * @param geoY : Geodata Y ++ * @return {@link ABlock} : Block of geodata. + */ +- public int getNextLowerZ(int geoX, int geoY, int worldZ) ++ private final ABlock getBlock(int geoX, int geoY) + { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) ++ final int x = geoX / GeoStructure.BLOCK_CELLS_X; ++ final int y = geoY / GeoStructure.BLOCK_CELLS_Y; ++ ++ // if x or y is out of array return null ++ if ((x > -1) && (y > -1) && (x < GeoStructure.GEO_BLOCKS_X) && (y < GeoStructure.GEO_BLOCKS_Y)) + { +- return worldZ; ++ return _blocks[x][y]; + } +- return region.getNextLowerZ(geoX, geoY, worldZ); ++ return null; + } + + /** +- * @param geoX +- * @param geoY +- * @param worldZ +- * @return the next higher Z value ++ * Check if geo coordinates has geo. ++ * @param geoX : Geodata X ++ * @param geoY : Geodata Y ++ * @return boolean : True, if given geo coordinates have geodata + */ +- public int getNextHigherZ(int geoX, int geoY, int worldZ) ++ public boolean hasGeoPos(int geoX, int geoY) + { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) ++ final ABlock block = getBlock(geoX, geoY); ++ if (block == null) // null block check + { +- return worldZ; ++ // TODO: Find when this can be null. (Bad geodata? Check World getRegion method.) ++ // LOGGER.warning("Could not find geodata block at " + getWorldX(geoX) + ", " + getWorldY(geoY) + "."); ++ return false; + } +- return region.getNextHigherZ(geoX, geoY, worldZ); ++ return block.hasGeoPos(); + } + + /** +- * Gets the Z height. +- * @param x the world x +- * @param y the world y +- * @param z the world z +- * @return the nearest Z height ++ * 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 int getHeight(int x, int y, int z) ++ public short getHeightNearest(int geoX, int geoY, int worldZ) + { +- return getNearestZ(getGeoX(x), getGeoY(y), z); ++ final ABlock block = getBlock(geoX, geoY); ++ return block != null ? block.getHeightNearest(geoX, geoY, worldZ) : (short) worldZ; + } + + /** +- * Gets the next lower Z height. +- * @param x the world x +- * @param y the world y +- * @param z the world z +- * @return the nearest Z height ++ * 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 int getLowerHeight(int x, int y, int z) ++ public byte getNsweNearest(int geoX, int geoY, int worldZ) + { +- return getNextLowerZ(getGeoX(x), getGeoY(y), z); ++ final ABlock block = getBlock(geoX, geoY); ++ return block != null ? block.getNsweNearest(geoX, geoY, worldZ) : (byte) 0xFF; + } + + /** +- * Gets the next higher Z height. +- * @param x the world x +- * @param y the world y +- * @param z the world z +- * @return the nearest Z height ++ * Check if world coordinates has geo. ++ * @param worldX : World X ++ * @param worldY : World Y ++ * @return boolean : True, if given world coordinates have geodata + */ +- public int getHigherHeight(int x, int y, int z) ++ public boolean hasGeo(int worldX, int worldY) + { +- return getNextHigherZ(getGeoX(x), getGeoY(y), z); ++ return hasGeoPos(getGeoX(worldX), getGeoY(worldY)); + } + + /** +- * @param worldX +- * @return the geo X ++ * 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 int getGeoX(int worldX) ++ public short getHeight(int worldX, int worldY, int worldZ) + { +- return (worldX - WORLD_MIN_X) / 16; ++ return getHeightNearest(getGeoX(worldX), getGeoY(worldY), worldZ); + } + +- /** +- * @param worldY +- * @return the geo Y +- */ +- public int getGeoY(int worldY) +- { +- return (worldY - WORLD_MIN_Y) / 16; +- } ++ // PATHFINDING + + /** +- * @param worldZ +- * @return the geo Z ++ * Check line of sight from {@link WorldObject} to {@link WorldObject}. ++ * @param origin : The origin object. ++ * @param target : The target object. ++ * @return {@code boolean} : True if origin can see target + */ +- public int getGeoZ(int worldZ) ++ public boolean canSeeTarget(WorldObject origin, WorldObject target) + { +- return (worldZ - WORLD_MIN_Z) / 16; +- } +- +- /** +- * @param geoX +- * @return the world X +- */ +- public int getWorldX(int geoX) +- { +- return (geoX * 16) + WORLD_MIN_X + 8; +- } +- +- /** +- * @param geoY +- * @return the world Y +- */ +- public int getWorldY(int geoY) +- { +- return (geoY * 16) + WORLD_MIN_Y + 8; +- } +- +- /** +- * @param geoZ +- * @return the world Z +- */ +- public int getWorldZ(int geoZ) +- { +- return (geoZ * 16) + WORLD_MIN_Z + 8; +- } +- +- /** +- * 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) +- { +- if (target.isDoor()) ++ if (target.isDoor() || target.isArtefact() || (target.isCreature() && ((Creature) target).isFlying())) + { +- // Can always see doors. + return true; + } +- return 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(), cha.getInstanceId(), worldPosition.getX(), worldPosition.getY(), worldPosition.getZ()); +- } +- +- /** +- * 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 instanceId +- * @return +- */ +- public boolean canSeeTarget(int x, int y, int z, int instanceId, int tx, int ty, int tz, int tInstanceId) +- { +- return (instanceId != tInstanceId) ? false : canSeeTarget(x, y, z, instanceId, tx, ty, tz); +- } +- +- /** +- * 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 +- * @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 instanceId, int tx, int ty, int tz) +- { +- if (DoorData.getInstance().checkIfDoorsBetween(x, y, z, tx, ty, tz)) ++ ++ // get origin and target world coordinates ++ final int ox = origin.getX(); ++ final int oy = origin.getY(); ++ final int oz = origin.getZ(); ++ final int tx = target.getX(); ++ final int ty = target.getY(); ++ final int tz = target.getZ(); ++ ++ if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz)) + { + return false; + } +- return canSeeTarget(x, y, z, tx, ty, tz); +- } +- +- /** +- * @param prevX +- * @param prevY +- * @param prevGeoZ +- * @param curX +- * @param curY +- * @param nswe +- * @return the LOS Z value +- */ +- 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))) ++ if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz)) + { +- throw new RuntimeException("Multiple directions!"); ++ return false; + } + +- if (checkNearestNsweAntiCornerCut(prevX, prevY, prevGeoZ, nswe)) ++ // get origin and check existing geo coordinates ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) + { +- return getNearestZ(curX, curY, prevGeoZ); ++ return true; + } + +- return getNextHigherZ(curX, curY, prevGeoZ); ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coordinates ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) ++ { ++ return true; ++ } ++ ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // origin and target coordinates are same ++ if ((gox == gtx) && (goy == gty)) ++ { ++ return goz == gtz; ++ } ++ ++ // get origin and target height, real height = collision height * 2 ++ double oheight = 0; ++ if (origin.isCreature()) ++ { ++ oheight = ((Creature) origin).getTemplate().getCollisionHeight() * 2; ++ } ++ ++ double theight = 0; ++ if (target.isCreature()) ++ { ++ theight = ((Creature) target).getTemplate().getCollisionHeight() * 2; ++ } ++ ++ // perform geodata check ++ return checkSee(gox, goy, goz, oheight, gtx, gty, gtz, theight, origin.getInstanceId()); + } + + /** +- * Can see target. Does not check doors between. +- * @param xValue the x coordinate +- * @param yValue the y coordinate +- * @param zValue the z coordinate +- * @param txValue the target's x coordinate +- * @param tyValue the target's y coordinate +- * @param tzValue the target's z coordinate +- * @return {@code true} if there is line of sight between the given coordinate sets, {@code false} otherwise ++ * Check line of sight from {@link WorldObject} to {@link Location}. ++ * @param origin : The origin object. ++ * @param position : The target position. ++ * @return {@code boolean} : True if object can see position + */ +- public boolean canSeeTarget(int xValue, int yValue, int zValue, int txValue, int tyValue, int tzValue) ++ public boolean canSeeTarget(WorldObject origin, Location position) + { +- int x = xValue; +- int y = yValue; +- int tx = txValue; +- int ty = tyValue; ++ // get origin and target world coordinates ++ final int ox = origin.getX(); ++ final int oy = origin.getY(); ++ final int oz = origin.getZ(); ++ final int tx = position.getX(); ++ final int ty = position.getY(); ++ final int tz = position.getZ(); + +- int geoX = getGeoX(x); +- int geoY = getGeoY(y); +- int tGeoX = getGeoX(tx); +- int tGeoY = getGeoY(ty); ++ if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz)) ++ { ++ return false; ++ } ++ if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz)) ++ { ++ return false; ++ } + +- int z = getNearestZ(geoX, geoY, zValue); +- int tz = getNearestZ(tGeoX, tGeoY, tzValue); ++ // get origin and check existing geo coordinates ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) ++ { ++ return true; ++ } + +- // Fastpath. +- if ((geoX == tGeoX) && (geoY == tGeoY)) ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coordinates ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) + { +- if (hasGeoPos(tGeoX, tGeoY)) +- { +- return z == tz; +- } + return true; + } + +- if (tz > z) ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // origin and target coordinates are same ++ if ((gox == gtx) && (goy == gty)) + { +- int tmp = tx; +- tx = x; +- x = tmp; +- +- tmp = ty; +- ty = y; +- y = tmp; +- +- tmp = tz; +- tz = z; +- z = tmp; +- +- tmp = tGeoX; +- tGeoX = geoX; +- geoX = tmp; +- +- tmp = tGeoY; +- tGeoY = geoY; +- geoY = tmp; ++ return goz == gtz; + } + +- final LinePointIterator3D pointIter = new LinePointIterator3D(geoX, geoY, z, tGeoX, tGeoY, tz); +- // 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()) ++ // get origin and target height, real height = collision height * 2 ++ double oheight = 0; ++ if (origin.isCreature()) + { +- final int curX = pointIter.x(); +- final int curY = pointIter.y(); ++ oheight = ((Creature) origin).getTemplate().getCollisionHeight(); ++ } ++ ++ // perform geodata check ++ return checkSee(gox, goy, goz, oheight, gtx, gty, gtz, 0, origin.getInstanceId()); ++ } ++ ++ /** ++ * Simple check for origin to target visibility. ++ * @param goxValue : origin X geodata coordinate ++ * @param goyValue : origin Y geodata coordinate ++ * @param gozValue : origin Z geodata coordinate ++ * @param oheight : origin height (if instance of {@link Character}) ++ * @param gtxValue : target X geodata coordinate ++ * @param gtyValue : target Y geodata coordinate ++ * @param gtzValue : target Z geodata coordinate ++ * @param theight : target height (if instance of {@link Character}) ++ * @param instanceId ++ * @return {@code boolean} : True, when target can be seen. ++ */ ++ private final boolean checkSee(int goxValue, int goyValue, int gozValue, double oheight, int gtxValue, int gtyValue, int gtzValue, double theight, int instanceId) ++ { ++ int goz = gozValue; ++ int gtz = gtzValue; ++ int gox = goxValue; ++ int goy = goyValue; ++ int gtx = gtxValue; ++ int gty = gtyValue; ++ ++ // get line of sight Z coordinates ++ double losoz = goz + ((oheight * Config.PART_OF_CHARACTER_HEIGHT) / 100); ++ double lostz = gtz + ((theight * Config.PART_OF_CHARACTER_HEIGHT) / 100); ++ ++ // get X delta and signum ++ final int dx = Math.abs(gtx - gox); ++ final int sx = gox < gtx ? 1 : -1; ++ final byte dirox = sx > 0 ? GeoStructure.CELL_FLAG_E : GeoStructure.CELL_FLAG_W; ++ final byte dirtx = sx > 0 ? GeoStructure.CELL_FLAG_W : GeoStructure.CELL_FLAG_E; ++ ++ // get Y delta and signum ++ final int dy = Math.abs(gty - goy); ++ final int sy = goy < gty ? 1 : -1; ++ final byte diroy = sy > 0 ? GeoStructure.CELL_FLAG_S : GeoStructure.CELL_FLAG_N; ++ final byte dirty = sy > 0 ? GeoStructure.CELL_FLAG_N : GeoStructure.CELL_FLAG_S; ++ ++ // get Z delta ++ final int dm = Math.max(dx, dy); ++ final double dz = (lostz - losoz) / dm; ++ ++ // get direction flag for diagonal movement ++ final byte diroxy = getDirXY(dirox, diroy); ++ final byte dirtxy = getDirXY(dirtx, dirty); ++ ++ // delta, determines axis to move on (+..X axis, -..Y axis) ++ int d = dx - dy; ++ ++ // NSWE direction of movement ++ byte diro; ++ byte dirt; ++ ++ // initialize node values ++ int nox = gox; ++ int noy = goy; ++ int ntx = gtx; ++ int nty = gty; ++ byte nsweo = getNsweNearest(gox, goy, goz); ++ byte nswet = getNsweNearest(gtx, gty, gtz); ++ ++ // loop ++ ABlock block; ++ int index; ++ for (int i = 0; i < ((dm + 1) / 2); i++) ++ { ++ // reset direction flag ++ diro = 0; ++ dirt = 0; + +- if ((curX == prevX) && (curY == prevY)) ++ // calculate next point coordinates ++ final int e2 = 2 * d; ++ if ((e2 > -dy) && (e2 < dx)) + { +- continue; ++ // calculate next point XY coordinates ++ d -= dy; ++ d += dx; ++ nox += sx; ++ ntx -= sx; ++ noy += sy; ++ nty -= sy; ++ diro |= diroxy; ++ dirt |= dirtxy; + } ++ else if (e2 > -dy) ++ { ++ // calculate next point X coordinate ++ d -= dy; ++ nox += sx; ++ ntx -= sx; ++ diro |= dirox; ++ dirt |= dirtx; ++ } ++ else if (e2 < dx) ++ { ++ // calculate next point Y coordinate ++ d += dx; ++ noy += sy; ++ nty -= sy; ++ diro |= diroy; ++ dirt |= dirty; ++ } + +- final int beeCurZ = pointIter.z(); +- int curGeoZ = prevGeoZ; +- +- // Check if the position has geodata. +- if (hasGeoPos(curX, curY)) + { +- final int beeCurGeoZ = getNearestZ(curX, curY, beeCurZ); +- final int nswe = GeoUtils.computeNswe(prevX, prevY, curX, curY); // .computeDirection(prevX, prevY, curX, curY); +- curGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, curX, curY, nswe); +- int maxHeight; +- if (ptIndex < ELEVATED_SEE_OVER_DISTANCE) ++ // get block of the next cell ++ block = getBlock(nox, noy); ++ ++ // get index of particular layer, based on movement conditions ++ if ((nsweo & diro) == 0) + { +- maxHeight = z + MAX_SEE_OVER_HEIGHT; ++ index = block.getIndexAbove(nox, noy, goz - GeoStructure.CELL_IGNORE_HEIGHT); + } + else + { +- maxHeight = beeCurZ + MAX_SEE_OVER_HEIGHT; ++ index = block.getIndexBelow(nox, noy, goz + GeoStructure.CELL_IGNORE_HEIGHT); + } + +- boolean canSeeThrough = false; +- if ((curGeoZ <= maxHeight) && (curGeoZ <= beeCurGeoZ)) ++ // layer does not exist, return ++ if (index == -1) + { +- if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) +- { +- 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 +- { +- canSeeThrough = true; +- } ++ return false; + } + +- if (!canSeeThrough) ++ // get layer and next line of sight Z coordinate ++ goz = block.getHeight(index); ++ losoz += dz; ++ ++ // perform line of sight check, return when fails ++ if ((goz - losoz) > Config.MAX_OBSTACLE_HEIGHT) + { + return false; + } ++ ++ // get layer nswe ++ nsweo = block.getNswe(index); + } ++ { ++ // get block of the next cell ++ block = getBlock(ntx, nty); ++ ++ // get index of particular layer, based on movement conditions ++ if ((nswet & dirt) == 0) ++ { ++ index = block.getIndexAbove(ntx, nty, gtz - GeoStructure.CELL_IGNORE_HEIGHT); ++ } ++ else ++ { ++ index = block.getIndexBelow(ntx, nty, gtz + GeoStructure.CELL_IGNORE_HEIGHT); ++ } ++ ++ // layer does not exist, return ++ if (index == -1) ++ { ++ return false; ++ } ++ ++ // get layer and next line of sight Z coordinate ++ gtz = block.getHeight(index); ++ lostz -= dz; ++ ++ // perform line of sight check, return when fails ++ if ((gtz - lostz) > Config.MAX_OBSTACLE_HEIGHT) ++ { ++ return false; ++ } ++ ++ // get layer nswe ++ nswet = block.getNswe(index); ++ } + +- prevX = curX; +- prevY = curY; +- prevGeoZ = curGeoZ; +- ++ptIndex; ++ // update coords ++ gox = nox; ++ goy = noy; ++ gtx = ntx; ++ gty = nty; + } + +- return true; ++ // when iteration is completed, compare final Z coordinates ++ return Math.abs(goz - gtz) < (GeoStructure.CELL_HEIGHT * 4); + } + + /** +- * Move check. +- * @param x the x coordinate +- * @param y the y coordinate +- * @param zValue the z coordinate +- * @param tx the target's x coordinate +- * @param ty the target's y coordinate +- * @param tzValue the target's z coordinate +- * @param instanceId the instance id +- * @return the last Location (x,y,z) where player can walk - just before wall ++ * Check movement from coordinates to 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 {code boolean} : True if target coordinates are reachable from origin coordinates + */ +- public Location canMoveToTargetLoc(int x, int y, int zValue, int tx, int ty, int tzValue, int instanceId) ++ public boolean canMoveToTarget(int ox, int oy, int oz, int tx, int ty, int tz, int instanceId) + { +- final int geoX = getGeoX(x); +- final int geoY = getGeoY(y); +- final int z = getNearestZ(geoX, geoY, zValue); +- final int tGeoX = getGeoX(tx); +- final int tGeoY = getGeoY(ty); +- final int tz = getNearestZ(tGeoX, tGeoY, tzValue); ++ // get origin and check existing geo coordinates ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) ++ { ++ return true; ++ } + +- if (DoorData.getInstance().checkIfDoorsBetween(x, y, z, tx, ty, tz)) ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coordinates ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) + { +- return new Location(x, y, getHeight(x, y, z)); ++ return true; + } +- if (FenceData.getInstance().checkIfFenceBetween(x, y, z, tx, ty, tz)) ++ ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // target coordinates reached ++ if ((gox == gtx) && (goy == gty) && (goz == gtz)) + { +- return new Location(x, y, getHeight(x, y, z)); ++ return true; + } + +- 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 = z; ++ // perform geodata check ++ final GeoLocation loc = checkMove(gox, goy, goz, gtx, gty, gtz, instanceId); ++ return (loc.getGeoX() == gtx) && (loc.getGeoY() == gty); ++ } ++ ++ /** ++ * Check movement from origin to target. Returns last available point in the checked path. ++ * @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 {@link Location} : Last point where object can walk (just before wall) ++ */ ++ public Location canMoveToTargetLoc(int ox, int oy, int oz, int tx, int ty, int tz, int instanceId) ++ { ++ // Mobius: Double check for doors before normal checkMove to avoid exploiting key movement. ++ if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz)) ++ { ++ return new Location(ox, oy, oz); ++ } ++ if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz)) ++ { ++ return new Location(ox, oy, oz); ++ } + +- while (pointIter.next()) ++ // get origin and check existing geo coordinates ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) + { +- final int curX = pointIter.x(); +- final int curY = pointIter.y(); +- final int curZ = getNearestZ(curX, curY, prevZ); +- +- if (hasGeoPos(prevX, prevY)) +- { +- final int nswe = GeoUtils.computeNswe(prevX, prevY, curX, curY); +- if (!checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, nswe)) +- { +- // Can't move, return previous location. +- return new Location(getWorldX(prevX), getWorldY(prevY), prevZ); +- } +- } +- +- prevX = curX; +- prevY = curY; +- prevZ = curZ; ++ return new Location(tx, ty, tz); + } + +- if (hasGeoPos(prevX, prevY) && (prevZ != tz)) ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coordinates ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) + { +- // Different floors, return start location. +- return new Location(x, y, z); ++ return new Location(tx, ty, tz); + } + +- return new Location(tx, ty, tz); ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // target coordinates reached ++ if ((gox == gtx) && (goy == gty) && (goz == gtz)) ++ { ++ return new Location(tx, ty, tz); ++ } ++ ++ // perform geodata check ++ return checkMove(gox, goy, goz, gtx, gty, gtz, instanceId); + } + + /** +- * 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 fromZvalue 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 toZvalue the Z coordinate to end checking at +- * @param instanceId the instance +- * @return {@code true} if the character at start coordinates can move to end coordinates, {@code false} otherwise ++ * With this method you can check if a position is visible or can be reached by beeline movement.
++ * 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 gox : origin X geodata coordinate ++ * @param goy : origin Y geodata coordinate ++ * @param goz : origin Z geodata coordinate ++ * @param gtx : target X geodata coordinate ++ * @param gty : target Y geodata coordinate ++ * @param gtz : target Z geodata coordinate ++ * @param instanceId ++ * @return {@link GeoLocation} : The last allowed point of movement. + */ +- public boolean canMoveToTarget(int fromX, int fromY, int fromZvalue, int toX, int toY, int toZvalue, int instanceId) ++ protected final GeoLocation checkMove(int gox, int goy, int goz, int gtx, int gty, int gtz, int instanceId) + { +- final int geoX = getGeoX(fromX); +- final int geoY = getGeoY(fromY); +- final int fromZ = getNearestZ(geoX, geoY, fromZvalue); +- final int tGeoX = getGeoX(toX); +- final int tGeoY = getGeoY(toY); +- final int toZ = getNearestZ(tGeoX, tGeoY, toZvalue); +- +- if (DoorData.getInstance().checkIfDoorsBetween(fromX, fromY, fromZ, toX, toY, toZ)) ++ if (DoorData.getInstance().checkIfDoorsBetween(gox, goy, goz, gtx, gty, gtz)) + { +- return false; ++ return new GeoLocation(gox, goy, goz); + } +- if (FenceData.getInstance().checkIfFenceBetween(fromX, fromY, fromZ, toX, toY, toZ)) ++ if (FenceData.getInstance().checkIfFenceBetween(gox, goy, goz, gtx, gty, gtz)) + { +- return false; ++ return new GeoLocation(gox, goy, goz); + } + +- 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 = fromZ; ++ // get X delta, signum and direction flag ++ final int dx = Math.abs(gtx - gox); ++ final int sx = gox < gtx ? 1 : -1; ++ final byte dirX = sx > 0 ? GeoStructure.CELL_FLAG_E : GeoStructure.CELL_FLAG_W; + +- while (pointIter.next()) ++ // get Y delta, signum and direction flag ++ final int dy = Math.abs(gty - goy); ++ final int sy = goy < gty ? 1 : -1; ++ final byte dirY = sy > 0 ? GeoStructure.CELL_FLAG_S : GeoStructure.CELL_FLAG_N; ++ ++ // get direction flag for diagonal movement ++ final byte dirXY = getDirXY(dirX, dirY); ++ ++ // delta, determines axis to move on (+..X axis, -..Y axis) ++ int d = dx - dy; ++ ++ // NSWE direction of movement ++ byte direction; ++ ++ // load pointer coordinates ++ int gpx = gox; ++ int gpy = goy; ++ int gpz = goz; ++ ++ // load next pointer ++ int nx = gpx; ++ int ny = gpy; ++ ++ // loop ++ int count = 0; ++ while (count++ < Config.MAX_ITERATIONS) + { +- final int curX = pointIter.x(); +- final int curY = pointIter.y(); +- final int curZ = getNearestZ(curX, curY, prevZ); ++ direction = 0; + +- if (hasGeoPos(prevX, prevY)) ++ // calculate next point coordinates ++ final int e2 = 2 * d; ++ if ((e2 > -dy) && (e2 < dx)) + { +- final int nswe = GeoUtils.computeNswe(prevX, prevY, curX, curY); +- if (!checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, nswe)) ++ d -= dy; ++ d += dx; ++ nx += sx; ++ ny += sy; ++ direction |= dirXY; ++ } ++ else if (e2 > -dy) ++ { ++ d -= dy; ++ nx += sx; ++ direction |= dirX; ++ } ++ else if (e2 < dx) ++ { ++ d += dx; ++ ny += sy; ++ direction |= dirY; ++ } ++ ++ // obstacle found, return ++ if ((getNsweNearest(gpx, gpy, gpz) & direction) == 0) ++ { ++ return new GeoLocation(gpx, gpy, gpz); ++ } ++ ++ // update pointer coordinates ++ gpx = nx; ++ gpy = ny; ++ gpz = getHeightNearest(nx, ny, gpz); ++ ++ // target coordinates reached ++ if ((gpx == gtx) && (gpy == gty)) ++ { ++ if (gpz == gtz) + { +- return false; ++ // path found, Z coordinates are okay, return target point ++ return new GeoLocation(gtx, gty, gtz); + } ++ ++ // path found, Z coordinates are not okay, return last good point ++ return new GeoLocation(gpx, gpy, gpz); + } ++ } ++ ++ return new GeoLocation(gox, goy, goz); ++ } ++ ++ /** ++ * Returns diagonal NSWE flag format of combined two NSWE flags. ++ * @param dirX : X direction NSWE flag ++ * @param dirY : Y direction NSWE flag ++ * @return byte : NSWE flag of combined direction ++ */ ++ private static byte getDirXY(byte dirX, byte dirY) ++ { ++ // check axis directions ++ if (dirY == GeoStructure.CELL_FLAG_N) ++ { ++ if (dirX == GeoStructure.CELL_FLAG_W) ++ { ++ return GeoStructure.CELL_FLAG_NW; ++ } + +- prevX = curX; +- prevY = curY; +- prevZ = curZ; ++ return GeoStructure.CELL_FLAG_NE; + } + +- if (hasGeoPos(prevX, prevY) && (prevZ != toZ)) ++ if (dirX == GeoStructure.CELL_FLAG_W) + { +- // Different floors. +- return false; ++ return GeoStructure.CELL_FLAG_SW; + } + +- return true; ++ return GeoStructure.CELL_FLAG_SE; + } + ++ /** ++ * 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 ++ */ ++ public List findPath(int ox, int oy, int oz, int tx, int ty, int tz, int instanceId) ++ { ++ return null; ++ } ++ ++ /** ++ * Returns the instance of the {@link GeoEngine}. ++ * @return {@link GeoEngine} : The instance. ++ */ + public static GeoEngine getInstance() + { + return SingletonHolder.INSTANCE; +@@ -741,6 +933,6 @@ + + private static class SingletonHolder + { +- protected static final GeoEngine INSTANCE = new GeoEngine(); ++ protected static final GeoEngine INSTANCE = Config.PATHFINDING ? new GeoEnginePathfinding() : new GeoEngine(); + } +-} ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/GeoEnginePathfinding.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/GeoEnginePathfinding.java (revision 8319) ++++ java/org/l2jmobius/gameserver/geoengine/GeoEnginePathfinding.java (working copy) +@@ -20,87 +20,83 @@ + import java.util.LinkedList; + import java.util.List; + import java.util.ListIterator; +-import java.util.logging.Level; +-import java.util.logging.Logger; + + import org.l2jmobius.Config; +-import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; +-import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +-import org.l2jmobius.gameserver.geoengine.pathfinding.CellNode; +-import org.l2jmobius.gameserver.geoengine.pathfinding.CellNodeBuffer; +-import org.l2jmobius.gameserver.geoengine.pathfinding.NodeLoc; +-import org.l2jmobius.gameserver.model.World; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoLocation; ++import org.l2jmobius.gameserver.geoengine.pathfinding.Node; ++import org.l2jmobius.gameserver.geoengine.pathfinding.NodeBuffer; ++import org.l2jmobius.gameserver.model.Location; + + /** +- * @author -Nemesiss- ++ * @author Hasha + */ +-public class GeoEnginePathfinding ++final class GeoEnginePathfinding extends GeoEngine + { +- private static final Logger LOGGER = Logger.getLogger(GeoEnginePathfinding.class.getName()); ++ // pre-allocated buffers ++ private final BufferHolder[] _buffers; + +- private BufferInfo[] _buffers; +- + protected GeoEnginePathfinding() + { +- try ++ super(); ++ ++ final String[] array = Config.PATHFIND_BUFFERS.split(";"); ++ _buffers = new BufferHolder[array.length]; ++ int count = 0; ++ for (int i = 0; i < array.length; i++) + { +- final String[] array = Config.PATHFIND_BUFFERS.split(";"); ++ final String buf = array[i]; ++ final String[] args = buf.split("x"); + +- _buffers = new BufferInfo[array.length]; +- +- String buf; +- String[] args; +- for (int i = 0; i < array.length; i++) ++ try + { +- buf = array[i]; +- args = buf.split("x"); +- if (args.length != 2) +- { +- throw new Exception("Invalid buffer definition: " + buf); +- } +- +- _buffers[i] = new BufferInfo(Integer.parseInt(args[0]), Integer.parseInt(args[1])); ++ final int size = Integer.parseInt(args[1]); ++ count += size; ++ _buffers[i] = new BufferHolder(Integer.parseInt(args[0]), size); + } ++ catch (Exception e) ++ { ++ LOGGER.warning("GeoEnginePathfinding: Can not load buffer setting: " + buf); ++ } + } +- catch (Exception e) +- { +- LOGGER.log(Level.WARNING, "CellPathFinding: Problem during buffer init: " + e.getMessage(), e); +- throw new Error("CellPathFinding: load aborted"); +- } ++ ++ LOGGER.info("GeoEnginePathfinding: Loaded " + count + " node buffers."); + } + +- public boolean pathNodesExist(short regionoffset) ++ @Override ++ public List findPath(int ox, int oy, int oz, int tx, int ty, int tz, int instanceId) + { +- return false; +- } +- +- public List findPath(int x, int y, int z, int tx, int ty, int tz, int instanceId) +- { +- final int gx = GeoEngine.getInstance().getGeoX(x); +- final int gy = GeoEngine.getInstance().getGeoY(y); +- if (!GeoEngine.getInstance().hasGeo(x, y)) ++ // get origin and check existing geo coords ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) + { + 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)) ++ ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coords ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) + { + 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)))); ++ ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // Prepare buffer for pathfinding calculations ++ final NodeBuffer buffer = getBuffer(64 + (2 * Math.max(Math.abs(gox - gtx), Math.abs(goy - gty)))); + if (buffer == null) + { + return null; + } + +- List path = null; ++ // find path ++ List path = null; + try + { +- final CellNode result = buffer.findPath(gx, gy, gz, gtx, gty, gtz); +- ++ final Node result = buffer.findPath(gox, goy, goz, gtx, gty, gtz); + if (result == null) + { + return null; +@@ -124,33 +120,45 @@ + return path; + } + +- int currentX, currentY, currentZ; +- ListIterator middlePoint; ++ // get path list iterator ++ final ListIterator point = path.listIterator(); + +- middlePoint = path.listIterator(); +- currentX = x; +- currentY = y; +- currentZ = z; ++ // get node A (origin) ++ int nodeAx = gox; ++ int nodeAy = goy; ++ short nodeAz = goz; + +- while (middlePoint.hasNext()) ++ // get node B ++ GeoLocation nodeB = (GeoLocation) point.next(); ++ ++ // iterate thought the path to optimize it ++ int count = 0; ++ while (point.hasNext() && (count++ < Config.MAX_ITERATIONS)) + { +- final AbstractNodeLoc locMiddle = middlePoint.next(); +- if (!middlePoint.hasNext()) +- { +- break; +- } ++ // get node C ++ final GeoLocation nodeC = (GeoLocation) path.get(point.nextIndex()); + +- final AbstractNodeLoc locEnd = path.get(middlePoint.nextIndex()); +- if (GeoEngine.getInstance().canMoveToTarget(currentX, currentY, currentZ, locEnd.getX(), locEnd.getY(), locEnd.getZ(), instanceId)) ++ // check movement from node A to node C ++ final GeoLocation loc = checkMove(nodeAx, nodeAy, nodeAz, nodeC.getGeoX(), nodeC.getGeoY(), nodeC.getZ(), instanceId); ++ if ((loc.getGeoX() == nodeC.getGeoX()) && (loc.getGeoY() == nodeC.getGeoY())) + { +- middlePoint.remove(); ++ // can move from node A to node C ++ ++ // remove node B ++ point.remove(); + } + else + { +- currentX = locMiddle.getX(); +- currentY = locMiddle.getY(); +- currentZ = locMiddle.getZ(); ++ // can not move from node A to node C ++ ++ // set node A (node B is part of path, update A coordinates) ++ nodeAx = nodeB.getGeoX(); ++ nodeAy = nodeB.getGeoY(); ++ nodeAz = (short) nodeB.getZ(); + } ++ ++ // set node B ++ nodeB = (GeoLocation) point.next(); + } + + return path; +@@ -157,144 +165,102 @@ + } + + /** +- * Convert geodata position to pathnode position +- * @param geo_pos +- * @return pathnode position ++ * Create list of node locations as result of calculated buffer node tree. ++ * @param node : the entry point ++ * @return List : list of node location + */ +- public short getNodePos(int geo_pos) ++ private static List constructPath(Node node) + { +- return (short) (geo_pos >> 3); // OK? +- } +- +- /** +- * Convert node position to pathnode block position +- * @param node_pos +- * @return pathnode block position (0...255) +- */ +- public short getNodeBlock(int node_pos) +- { +- return (short) (node_pos % 256); +- } +- +- public byte getRegionX(int node_pos) +- { +- return (byte) ((node_pos >> 8) + World.TILE_X_MIN); +- } +- +- public byte getRegionY(int node_pos) +- { +- return (byte) ((node_pos >> 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 node_x rx +- * @return +- */ +- public int calculateWorldX(short node_x) +- { +- return World.MAP_MIN_X + (node_x * 128) + 48; +- } +- +- /** +- * Convert pathnode y to World y position +- * @param node_y +- * @return +- */ +- public int calculateWorldY(short node_y) +- { +- return World.MAP_MIN_Y + (node_y * 128) + 48; +- } +- +- private List constructPath(AbstractNode nodeValue) +- { +- final LinkedList path = new LinkedList<>(); +- int previousDirectionX = Integer.MIN_VALUE; +- int previousDirectionY = Integer.MIN_VALUE; +- int directionX, directionY; ++ // create empty list ++ final LinkedList list = new LinkedList<>(); + +- AbstractNode node = nodeValue; +- while (node.getParent() != null) ++ // set direction X/Y ++ int dx = 0; ++ int dy = 0; ++ ++ // get target parent ++ Node target = node; ++ Node parent = target.getParent(); ++ ++ // while parent exists ++ int count = 0; ++ while ((parent != null) && (count++ < Config.MAX_ITERATIONS)) + { +- directionX = node.getLoc().getNodeX() - node.getParent().getLoc().getNodeX(); +- directionY = node.getLoc().getNodeY() - node.getParent().getLoc().getNodeY(); ++ // get parent <> target direction X/Y ++ final int nx = parent.getLoc().getGeoX() - target.getLoc().getGeoX(); ++ final int ny = parent.getLoc().getGeoY() - target.getLoc().getGeoY(); + +- // only add a new route point if moving direction changes +- if ((directionX != previousDirectionX) || (directionY != previousDirectionY)) ++ // direction has changed? ++ if ((dx != nx) || (dy != ny)) + { +- previousDirectionX = directionX; +- previousDirectionY = directionY; ++ // add node to the beginning of the list ++ list.addFirst(target.getLoc()); + +- path.addFirst(node.getLoc()); +- node.setLoc(null); ++ // update direction X/Y ++ dx = nx; ++ dy = ny; + } + +- node = node.getParent(); ++ // move to next node, set target and get its parent ++ target = parent; ++ parent = target.getParent(); + } + +- return path; ++ // return list ++ return list; + } + +- private CellNodeBuffer alloc(int size) ++ /** ++ * Provides optimize selection of the buffer. When all pre-initialized buffer are locked, creates new buffer. ++ * @param size : pre-calculated minimal required size ++ * @return NodeBuffer : buffer ++ */ ++ private final NodeBuffer getBuffer(int size) + { +- CellNodeBuffer current = null; +- for (BufferInfo i : _buffers) ++ NodeBuffer current = null; ++ for (BufferHolder holder : _buffers) + { +- if (i.mapSize >= size) ++ // Find proper size of buffer ++ if (holder._size < size) + { +- for (CellNodeBuffer buf : i.buffer) ++ continue; ++ } ++ ++ // Find unlocked NodeBuffer ++ for (NodeBuffer buffer : holder._buffer) ++ { ++ if (!buffer.isLocked()) + { +- if (buf.lock()) +- { +- current = buf; +- break; +- } ++ continue; + } +- if (current != null) +- { +- break; +- } + +- // not found, allocate temporary buffer +- current = new CellNodeBuffer(i.mapSize); +- current.lock(); +- if (i.buffer.size() < i.count) +- { +- i.buffer.add(current); +- break; +- } ++ return buffer; + } ++ ++ // NodeBuffer not found, allocate temporary buffer ++ current = new NodeBuffer(holder._size); ++ current.isLocked(); + } + + return current; + } + +- private static final class BufferInfo ++ /** ++ * NodeBuffer container with specified size and count of separate buffers. ++ */ ++ private static final class BufferHolder + { +- final int mapSize; +- final int count; +- ArrayList buffer; ++ final int _size; ++ List _buffer; + +- public BufferInfo(int size, int cnt) ++ public BufferHolder(int size, int count) + { +- mapSize = size; +- count = cnt; +- buffer = new ArrayList<>(count); ++ _size = size; ++ _buffer = new ArrayList<>(count); ++ for (int i = 0; i < count; i++) ++ { ++ _buffer.add(new NodeBuffer(size)); ++ } + } + } +- +- public static GeoEnginePathfinding getInstance() +- { +- return SingletonHolder.INSTANCE; +- } +- +- private static class SingletonHolder +- { +- protected static final GeoEnginePathfinding INSTANCE = new GeoEnginePathfinding(); +- } +-} ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java (working copy) +@@ -0,0 +1,197 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++import java.io.IOException; ++ ++/** ++ * @author Hasha ++ */ ++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 height of cell, which is closest to given coordinates.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @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 getHeightNearestOriginal(int geoX, int geoY, int worldZ); ++ ++ /** ++ * Returns the height of cell, which is first above 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, above given coordinates. ++ */ ++ public abstract short getHeightAbove(int geoX, int geoY, int worldZ); ++ ++ /** ++ * Returns the height of cell, which is first below 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, below given coordinates. ++ */ ++ public abstract short getHeightBelow(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 the NSWE flag byte of cell, which is closest to given coordinates.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @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 getNsweNearestOriginal(int geoX, int geoY, int worldZ); ++ ++ /** ++ * Returns the NSWE flag byte of cell, which is first above 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 getNsweAbove(int geoX, int geoY, int worldZ); ++ ++ /** ++ * Returns the NSWE flag byte of cell, which is first below 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 getNsweBelow(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 above given coordinates.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @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 getIndexAboveOriginal(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 index to data of the cell, which is first layer below given coordinates.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @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 getIndexBelowOriginal(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 height of cell given by cell index.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @param index : Index of the cell. ++ * @return short : Cell geodata Z coordinate, below given coordinates. ++ */ ++ public abstract short getHeightOriginal(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); ++ ++ /** ++ * Returns the NSWE flag byte of cell given by cell index.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @param index : Index of the cell. ++ * @return short : Cell geodata Z coordinate, below given coordinates. ++ */ ++ public abstract byte getNsweOriginal(int index); ++ ++ /** ++ * Sets the NSWE flag byte of cell given by cell index. ++ * @param index : Index of the cell. ++ * @param nswe : New NSWE flag byte. ++ */ ++ public abstract void setNswe(int index, byte nswe); ++ ++ /** ++ * Saves the block in L2D format to {@link BufferedOutputStream}. Used only for L2D geodata conversion. ++ * @param stream : The stream. ++ * @throws IOException : Can't save the block to steam. ++ */ ++ public abstract void saveBlock(BufferedOutputStream stream) throws IOException; ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java (working copy) +@@ -0,0 +1,252 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++import java.io.IOException; ++import java.nio.ByteBuffer; ++ ++/** ++ * @author Hasha ++ */ ++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. ++ * @param format : GeoFormat specifying format of loaded data. ++ */ ++ public BlockComplex(ByteBuffer bb, GeoFormat format) ++ { ++ // initialize buffer ++ _buffer = new byte[GeoStructure.BLOCK_CELLS * 3]; ++ ++ // load data ++ for (int i = 0; i < GeoStructure.BLOCK_CELLS; i++) ++ { ++ if (format != GeoFormat.L2D) ++ { ++ // 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); ++ } ++ else ++ { ++ // get nswe ++ final byte nswe = bb.get(); ++ _buffer[i * 3] = nswe; ++ ++ // get height ++ final short height = bb.getShort(); ++ _buffer[(i * 3) + 1] = (byte) (height & 0x00FF); ++ _buffer[(i * 3) + 2] = (byte) (height >> 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 short getHeightNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getHeightNearest(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeightAbove(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 short height = (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ ++ // check and return height ++ return height > worldZ ? height : Short.MIN_VALUE; ++ } ++ ++ @Override ++ public short getHeightBelow(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 short height = (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ ++ // check and return height ++ return height < worldZ ? height : Short.MAX_VALUE; ++ } ++ ++ @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 byte getNsweNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getNsweNearest(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public byte getNsweAbove(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 ? _buffer[index] : 0; ++ } ++ ++ @Override ++ public byte getNsweBelow(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 ? _buffer[index] : 0; ++ } ++ ++ @Override ++ public 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 getIndexAboveOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexAbove(geoX, geoY, worldZ); ++ } ++ ++ @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 int getIndexBelowOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexBelow(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeight(int index) ++ { ++ return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ } ++ ++ @Override ++ public short getHeightOriginal(int index) ++ { ++ return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ } ++ ++ @Override ++ public byte getNswe(int index) ++ { ++ return _buffer[index]; ++ } ++ ++ @Override ++ public byte getNsweOriginal(int index) ++ { ++ return _buffer[index]; ++ } ++ ++ @Override ++ public void setNswe(int index, byte nswe) ++ { ++ _buffer[index] = nswe; ++ } ++ ++ @Override ++ public void saveBlock(BufferedOutputStream stream) throws IOException ++ { ++ // write block type ++ stream.write(GeoStructure.TYPE_COMPLEX_L2D); ++ ++ // write block data ++ stream.write(_buffer, 0, GeoStructure.BLOCK_CELLS * 3); ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java (working copy) +@@ -0,0 +1,176 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++import java.io.IOException; ++import java.nio.ByteBuffer; ++ ++/** ++ * @author Hasha ++ */ ++public class BlockFlat extends ABlock ++{ ++ protected final short _height; ++ protected byte _nswe; ++ ++ /** ++ * Creates FlatBlock. ++ * @param bb : Input byte buffer. ++ * @param format : GeoFormat specifying format of loaded data. ++ */ ++ public BlockFlat(ByteBuffer bb, GeoFormat format) ++ { ++ _height = bb.getShort(); ++ _nswe = format != GeoFormat.L2D ? 0x0F : (byte) (0xFF); ++ if (format == GeoFormat.L2OFF) ++ { ++ bb.getShort(); ++ } ++ } ++ ++ @Override ++ public boolean hasGeoPos() ++ { ++ return true; ++ } ++ ++ @Override ++ public short getHeightNearest(int geoX, int geoY, int worldZ) ++ { ++ return _height; ++ } ++ ++ @Override ++ public short getHeightNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return _height; ++ } ++ ++ @Override ++ public short getHeightAbove(int geoX, int geoY, int worldZ) ++ { ++ // check and return height ++ return _height > worldZ ? _height : Short.MIN_VALUE; ++ } ++ ++ @Override ++ public short getHeightBelow(int geoX, int geoY, int worldZ) ++ { ++ // check and return height ++ return _height < worldZ ? _height : Short.MAX_VALUE; ++ } ++ ++ @Override ++ public byte getNsweNearest(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweAbove(int geoX, int geoY, int worldZ) ++ { ++ // check height and return nswe ++ return _height > worldZ ? _nswe : 0; ++ } ++ ++ @Override ++ public byte getNsweBelow(int geoX, int geoY, int worldZ) ++ { ++ // check height and return nswe ++ return _height < worldZ ? _nswe : 0; ++ } ++ ++ @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 getIndexAboveOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexAbove(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public int getIndexBelow(int geoX, int geoY, int worldZ) ++ { ++ // check height and return index ++ return _height < worldZ ? 0 : -1; ++ } ++ ++ @Override ++ public int getIndexBelowOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexBelow(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeight(int index) ++ { ++ return _height; ++ } ++ ++ @Override ++ public short getHeightOriginal(int index) ++ { ++ return _height; ++ } ++ ++ @Override ++ public byte getNswe(int index) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweOriginal(int index) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public void setNswe(int index, byte nswe) ++ { ++ _nswe = nswe; ++ } ++ ++ @Override ++ public void saveBlock(BufferedOutputStream stream) throws IOException ++ { ++ // write block type ++ stream.write(GeoStructure.TYPE_FLAT_L2D); ++ ++ // write height ++ stream.write((byte) (_height & 0x00FF)); ++ stream.write((byte) (_height >> 8)); ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java (working copy) +@@ -0,0 +1,465 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++import java.io.IOException; ++import java.nio.ByteBuffer; ++import java.nio.ByteOrder; ++import java.util.Arrays; ++ ++/** ++ * @author Hasha ++ */ ++public class BlockMultilayer extends ABlock ++{ ++ private static final int MAX_LAYERS = Byte.MAX_VALUE; ++ ++ private static ByteBuffer _temp; ++ ++ /** ++ * Initializes the temporarily buffer. ++ */ ++ public static void initialize() ++ { ++ // initialize temporarily buffer and sorting mechanism ++ _temp = ByteBuffer.allocate(GeoStructure.BLOCK_CELLS * MAX_LAYERS * 3); ++ _temp.order(ByteOrder.LITTLE_ENDIAN); ++ } ++ ++ /** ++ * Releases temporarily buffer. ++ */ ++ public static void release() ++ { ++ _temp = null; ++ } ++ ++ protected byte[] _buffer; ++ ++ /** ++ * Implicit constructor for children class. ++ */ ++ protected BlockMultilayer() ++ { ++ _buffer = null; ++ } ++ ++ /** ++ * Creates MultilayerBlock. ++ * @param bb : Input byte buffer. ++ * @param format : GeoFormat specifying format of loaded data. ++ */ ++ public BlockMultilayer(ByteBuffer bb, GeoFormat format) ++ { ++ // 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 = format != GeoFormat.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++) ++ { ++ if (format != GeoFormat.L2D) ++ { ++ // get data ++ final short data = bb.getShort(); ++ ++ // add nswe and height ++ _temp.put((byte) (data & 0x000F)); ++ _temp.putShort((short) ((short) (data & 0xFFF0) >> 1)); ++ } ++ else ++ { ++ // add nswe ++ _temp.put(bb.get()); ++ ++ // add height ++ _temp.putShort(bb.getShort()); ++ } ++ } ++ } ++ ++ // 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 short getHeightNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getHeightNearest(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeightAbove(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 height ++ if (height > worldZ) ++ { ++ return (short) height; ++ } ++ ++ // move index to next layer ++ index -= 3; ++ } ++ ++ // none layer found, return minimum value ++ return Short.MIN_VALUE; ++ } ++ ++ @Override ++ public short getHeightBelow(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 height ++ if (height < worldZ) ++ { ++ return (short) height; ++ } ++ ++ // move index to next layer ++ index += 3; ++ } ++ ++ // none layer found, return maximum value ++ return Short.MAX_VALUE; ++ } ++ ++ @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 byte getNsweNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getNsweNearest(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public byte getNsweAbove(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 nswe ++ if (height > worldZ) ++ { ++ return _buffer[index]; ++ } ++ ++ // move index to next layer ++ index -= 3; ++ } ++ ++ // none layer found, block movement ++ return 0; ++ } ++ ++ @Override ++ public byte getNsweBelow(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 nswe ++ if (height < worldZ) ++ { ++ return _buffer[index]; ++ } ++ ++ // move index to next layer ++ index += 3; ++ } ++ ++ // none layer found, block movement ++ return 0; ++ } ++ ++ @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 first layer data (first from bottom) ++ byte layers = _buffer[index++]; ++ ++ // loop though all cell layers, find closest layer ++ 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; ++ } ++ ++ // none layer found ++ return -1; ++ } ++ ++ @Override ++ public int getIndexAboveOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexAbove(geoX, geoY, worldZ); ++ } ++ ++ @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; ++ } ++ ++ // none layer found ++ return -1; ++ } ++ ++ @Override ++ public int getIndexBelowOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexBelow(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeight(int index) ++ { ++ // get height ++ return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ } ++ ++ @Override ++ public short getHeightOriginal(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]; ++ } ++ ++ @Override ++ public byte getNsweOriginal(int index) ++ { ++ // get nswe ++ return _buffer[index]; ++ } ++ ++ @Override ++ public void setNswe(int index, byte nswe) ++ { ++ // set nswe ++ _buffer[index] = nswe; ++ } ++ ++ @Override ++ public void saveBlock(BufferedOutputStream stream) throws IOException ++ { ++ // write block type ++ stream.write(GeoStructure.TYPE_MULTILAYER_L2D); ++ ++ // for each cell ++ int index = 0; ++ for (int i = 0; i < GeoStructure.BLOCK_CELLS; i++) ++ { ++ // write layers count ++ final byte layers = _buffer[index++]; ++ stream.write(layers); ++ ++ // write cell data ++ stream.write(_buffer, index, layers * 3); ++ ++ // move index to next cell ++ index += layers * 3; ++ } ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java (working copy) +@@ -0,0 +1,150 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++ ++/** ++ * @author Hasha ++ */ ++public class BlockNull extends ABlock ++{ ++ private final byte _nswe; ++ ++ public BlockNull() ++ { ++ _nswe = (byte) 0xFF; ++ } ++ ++ @Override ++ public boolean hasGeoPos() ++ { ++ return false; ++ } ++ ++ @Override ++ public short getHeightNearest(int geoX, int geoY, int worldZ) ++ { ++ return (short) worldZ; ++ } ++ ++ @Override ++ public short getHeightNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return (short) worldZ; ++ } ++ ++ @Override ++ public short getHeightAbove(int geoX, int geoY, int worldZ) ++ { ++ return (short) worldZ; ++ } ++ ++ @Override ++ public short getHeightBelow(int geoX, int geoY, int worldZ) ++ { ++ return (short) worldZ; ++ } ++ ++ @Override ++ public byte getNsweNearest(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweAbove(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweBelow(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) ++ { ++ return 0; ++ } ++ ++ @Override ++ public int getIndexAboveOriginal(int geoX, int geoY, int worldZ) ++ { ++ return 0; ++ } ++ ++ @Override ++ public int getIndexBelow(int geoX, int geoY, int worldZ) ++ { ++ return 0; ++ } ++ ++ @Override ++ public int getIndexBelowOriginal(int geoX, int geoY, int worldZ) ++ { ++ return 0; ++ } ++ ++ @Override ++ public short getHeight(int index) ++ { ++ return 0; ++ } ++ ++ @Override ++ public short getHeightOriginal(int index) ++ { ++ return 0; ++ } ++ ++ @Override ++ public byte getNswe(int index) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweOriginal(int index) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public void setNswe(int index, byte nswe) ++ { ++ } ++ ++ @Override ++ public void saveBlock(BufferedOutputStream stream) ++ { ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java (revision 8319) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java (nonexistent) +@@ -1,48 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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() +- { +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/ComplexBlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/ComplexBlock.java (revision 8319) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/ComplexBlock.java (nonexistent) +@@ -1,77 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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; +- +-/** +- * @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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/FlatBlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/FlatBlock.java (revision 8319) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/FlatBlock.java (nonexistent) +@@ -1,56 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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; +- +-/** +- * @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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/GeoFormat.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/GeoFormat.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/GeoFormat.java (working copy) +@@ -0,0 +1,39 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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 Hasha ++ */ ++public enum GeoFormat ++{ ++ L2J("%d_%d.l2j"), ++ L2OFF("%d_%d_conv.dat"), ++ L2D("%d_%d.l2d"); ++ ++ private final String _filename; ++ ++ private GeoFormat(String filename) ++ { ++ _filename = filename; ++ } ++ ++ public String getFilename() ++ { ++ return _filename; ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/GeoLocation.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/GeoLocation.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/GeoLocation.java (working copy) +@@ -0,0 +1,67 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.geoengine.GeoEngine; ++import org.l2jmobius.gameserver.model.Location; ++ ++/** ++ * @author Hasha ++ */ ++public class GeoLocation extends Location ++{ ++ private byte _nswe; ++ ++ public GeoLocation(int x, int y, int z) ++ { ++ super(x, y, GeoEngine.getInstance().getHeightNearest(x, y, z)); ++ _nswe = GeoEngine.getInstance().getNsweNearest(x, y, z); ++ } ++ ++ public void set(int x, int y, short z) ++ { ++ super.setXYZ(x, y, GeoEngine.getInstance().getHeightNearest(x, y, z)); ++ _nswe = GeoEngine.getInstance().getNsweNearest(x, y, z); ++ } ++ ++ public int getGeoX() ++ { ++ return _x; ++ } ++ ++ public int getGeoY() ++ { ++ return _y; ++ } ++ ++ @Override ++ public int getX() ++ { ++ return GeoEngine.getWorldX(_x); ++ } ++ ++ @Override ++ public int getY() ++ { ++ return GeoEngine.getWorldY(_y); ++ } ++ ++ public byte getNSWE() ++ { ++ return _nswe; ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java (working copy) +@@ -0,0 +1,70 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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; ++ ++/** ++ * @author Hasha ++ */ ++public class GeoStructure ++{ ++ // cells ++ public static final byte CELL_FLAG_E = 1 << 0; ++ public static final byte CELL_FLAG_W = 1 << 1; ++ public static final byte CELL_FLAG_S = 1 << 2; ++ public static final byte CELL_FLAG_N = 1 << 3; ++ public static final byte CELL_FLAG_SE = 1 << 4; ++ public static final byte CELL_FLAG_SW = 1 << 5; ++ public static final byte CELL_FLAG_NE = 1 << 6; ++ public static final byte CELL_FLAG_NW = (byte) (1 << 7); ++ ++ public static final int CELL_HEIGHT = 8; ++ public static final int CELL_IGNORE_HEIGHT = CELL_HEIGHT * 6; ++ ++ // blocks ++ public static final byte TYPE_FLAT_L2J_L2OFF = 0; ++ public static final byte TYPE_FLAT_L2D = (byte) 0xD0; ++ public static final byte TYPE_COMPLEX_L2J = 1; ++ public static final byte TYPE_COMPLEX_L2OFF = 0x40; ++ public static final byte TYPE_COMPLEX_L2D = (byte) 0xD1; ++ 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) ++ public static final byte TYPE_MULTILAYER_L2D = (byte) 0xD2; ++ ++ 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; ++ ++ // regions ++ 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; ++ ++ // global geodata ++ private static final int GEO_REGIONS_X = ((World.TILE_X_MAX - World.TILE_X_MIN) + 1); ++ private 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 +Index: java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java (revision 8319) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java (nonexistent) +@@ -1,42 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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); +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/IGeoObject.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/IGeoObject.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/IGeoObject.java (working copy) +@@ -0,0 +1,53 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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 Hasha ++ */ ++public interface IGeoObject ++{ ++ /** ++ * Returns geodata X coordinate of the {@link IGeoObject}. ++ * @return int : Geodata X coordinate. ++ */ ++ int getGeoX(); ++ ++ /** ++ * Returns geodata Y coordinate of the {@link IGeoObject}. ++ * @return int : Geodata Y coordinate. ++ */ ++ int getGeoY(); ++ ++ /** ++ * Returns geodata Z coordinate of the {@link IGeoObject}. ++ * @return int : Geodata Z coordinate. ++ */ ++ int getGeoZ(); ++ ++ /** ++ * Returns height of the {@link IGeoObject}. ++ * @return int : Height. ++ */ ++ int getHeight(); ++ ++ /** ++ * Returns {@link IGeoObject} data. ++ * @return byte[][] : {@link IGeoObject} data. ++ */ ++ byte[][] getObjectGeoData(); ++} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java (revision 8319) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java (nonexistent) +@@ -1,47 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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(); +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/MultilayerBlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/MultilayerBlock.java (revision 8319) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/MultilayerBlock.java (nonexistent) +@@ -1,183 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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; +- +-/** +- * @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); +- +- // 1 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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/NullRegion.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/NullRegion.java (revision 8319) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/NullRegion.java (nonexistent) +@@ -1,55 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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 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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/Region.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/Region.java (revision 8319) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/Region.java (nonexistent) +@@ -1,92 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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; +- +-/** +- * @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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java (revision 8319) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java (nonexistent) +@@ -1,87 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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 T _loc; +- private AbstractNode _parent; +- +- public AbstractNode(T loc) +- { +- _loc = loc; +- } +- +- public void setParent(AbstractNode p) +- { +- _parent = p; +- } +- +- public AbstractNode getParent() +- { +- return _parent; +- } +- +- public T getLoc() +- { +- return _loc; +- } +- +- public void setLoc(T l) +- { +- _loc = l; +- } +- +- @Override +- public int hashCode() +- { +- final int prime = 31; +- int result = 1; +- result = (prime * result) + ((_loc == null) ? 0 : _loc.hashCode()); +- return result; +- } +- +- @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 +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java (revision 8319) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java (nonexistent) +@@ -1,33 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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 int getNodeX(); +- +- public abstract int getNodeY(); +-} +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNode.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNode.java (revision 8319) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNode.java (nonexistent) +@@ -1,67 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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 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 +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNodeBuffer.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNodeBuffer.java (revision 8319) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNodeBuffer.java (nonexistent) +@@ -1,348 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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.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 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) +- { +- _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(); +- } +- +- 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 void getNeighbors() +- { +- if (!_current.getLoc().canGoAll()) +- { +- 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); +- } +- +- // SouthEast +- if ((nodeE != null) && (nodeS != null)) +- { +- if (nodeE.getLoc().canGoSouth() && nodeS.getLoc().canGoEast()) +- { +- addNode(x + 1, y + 1, z, true); +- } +- } +- +- // SouthWest +- if ((nodeS != null) && (nodeW != null)) +- { +- if (nodeW.getLoc().canGoSouth() && nodeS.getLoc().canGoWest()) +- { +- addNode(x - 1, y + 1, z, true); +- } +- } +- +- // NorthEast +- if ((nodeN != null) && (nodeE != null)) +- { +- if (nodeE.getLoc().canGoNorth() && nodeN.getLoc().canGoEast()) +- { +- addNode(x + 1, y - 1, z, true); +- } +- } +- +- // NorthWest +- if ((nodeN != null) && (nodeW != null)) +- { +- if (nodeW.getLoc().canGoNorth() && nodeN.getLoc().canGoWest()) +- { +- addNode(x - 1, y - 1, z, true); +- } +- } +- } +- +- private 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(); +- // reinit node if needed +- if (result.getLoc() != null) +- { +- result.getLoc().set(x, y, z); +- } +- else +- { +- result.setLoc(new NodeLoc(x, y, z)); +- } +- } +- +- return result; +- } +- +- private 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 boolean isHighWeight(int x, int y, int z) +- { +- final CellNode result = getNode(x, y, z); +- if (result == null) +- { +- return true; +- } +- +- if (!result.getLoc().canGoAll()) +- { +- return true; +- } +- if (Math.abs(result.getLoc().getZ() - z) > 16) +- { +- return true; +- } +- +- return false; +- } +- +- private 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 +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java (working copy) +@@ -0,0 +1,87 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.geodata.GeoLocation; ++ ++/** ++ * @author Hasha ++ */ ++public class Node ++{ ++ // node coords and nswe flag ++ private GeoLocation _loc; ++ ++ // node parent (for reverse path construction) ++ private Node _parent; ++ // node child (for moving over nodes during iteration) ++ private Node _child; ++ ++ // node G cost (movement cost = parent movement cost + current movement cost) ++ private double _cost = -1000; ++ ++ public void setLoc(int x, int y, int z) ++ { ++ _loc = new GeoLocation(x, y, z); ++ } ++ ++ public GeoLocation getLoc() ++ { ++ return _loc; ++ } ++ ++ public void setParent(Node parent) ++ { ++ _parent = parent; ++ } ++ ++ public Node getParent() ++ { ++ return _parent; ++ } ++ ++ public void setChild(Node child) ++ { ++ _child = child; ++ } ++ ++ public Node getChild() ++ { ++ return _child; ++ } ++ ++ public void setCost(double cost) ++ { ++ _cost = cost; ++ } ++ ++ public double getCost() ++ { ++ return _cost; ++ } ++ ++ public void free() ++ { ++ // reset node location ++ _loc = null; ++ ++ // reset node parent, child and cost ++ _parent = null; ++ _child = null; ++ _cost = -1000; ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java (working copy) +@@ -0,0 +1,306 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.concurrent.locks.ReentrantLock; ++ ++import org.l2jmobius.Config; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; ++ ++/** ++ * @author DS, Hasha; Credits to Diamond ++ */ ++public class NodeBuffer ++{ ++ private final ReentrantLock _lock = new ReentrantLock(); ++ private final int _size; ++ private final Node[][] _buffer; ++ ++ // center coordinates ++ private int _cx = 0; ++ private int _cy = 0; ++ ++ // target coordinates ++ private int _gtx = 0; ++ private int _gty = 0; ++ private short _gtz = 0; ++ ++ private Node _current = null; ++ ++ /** ++ * Constructor of NodeBuffer. ++ * @param size : one dimension size of buffer ++ */ ++ public NodeBuffer(int size) ++ { ++ // set size ++ _size = size; ++ ++ // initialize buffer ++ _buffer = new Node[size][size]; ++ for (int x = 0; x < size; x++) ++ { ++ for (int y = 0; y < size; y++) ++ { ++ _buffer[x][y] = 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 Node : first node of path ++ */ ++ public Node findPath(int gox, int goy, short goz, int gtx, int gty, short gtz) ++ { ++ // set coordinates (middle of the line (gox,goy) - (gtx,gty), will be in the center of the buffer) ++ _cx = gox + ((gtx - gox - _size) / 2); ++ _cy = goy + ((gty - goy - _size) / 2); ++ ++ _gtx = gtx; ++ _gty = gty; ++ _gtz = gtz; ++ ++ _current = getNode(gox, goy, goz); ++ _current.setCost(getCostH(gox, goy, goz)); ++ ++ int count = 0; ++ do ++ { ++ // reached target? ++ if ((_current.getLoc().getGeoX() == _gtx) && (_current.getLoc().getGeoY() == _gty) && (Math.abs(_current.getLoc().getZ() - _gtz) < 8)) ++ { ++ return _current; ++ } ++ ++ // expand current node ++ expand(); ++ ++ // move pointer ++ _current = _current.getChild(); ++ } ++ while ((_current != null) && (count++ < Config.MAX_ITERATIONS)); ++ ++ return null; ++ } ++ ++ public boolean isLocked() ++ { ++ return _lock.tryLock(); ++ } ++ ++ public void free() ++ { ++ _current = null; ++ ++ for (Node[] nodes : _buffer) ++ { ++ for (Node node : nodes) ++ { ++ if (node.getLoc() != null) ++ { ++ node.free(); ++ } ++ } ++ } ++ ++ _lock.unlock(); ++ } ++ ++ /** ++ * Check _current Node and add its neighbors to the buffer. ++ */ ++ private final void expand() ++ { ++ // can't move anywhere, don't expand ++ final byte nswe = _current.getLoc().getNSWE(); ++ if (nswe == 0) ++ { ++ return; ++ } ++ ++ // get geo coords of the node to be expanded ++ final int x = _current.getLoc().getGeoX(); ++ final int y = _current.getLoc().getGeoY(); ++ final short z = (short) _current.getLoc().getZ(); ++ ++ // can move north, expand ++ if ((nswe & GeoStructure.CELL_FLAG_N) != 0) ++ { ++ addNode(x, y - 1, z, Config.BASE_WEIGHT); ++ } ++ ++ // can move south, expand ++ if ((nswe & GeoStructure.CELL_FLAG_S) != 0) ++ { ++ addNode(x, y + 1, z, Config.BASE_WEIGHT); ++ } ++ ++ // can move west, expand ++ if ((nswe & GeoStructure.CELL_FLAG_W) != 0) ++ { ++ addNode(x - 1, y, z, Config.BASE_WEIGHT); ++ } ++ ++ // can move east, expand ++ if ((nswe & GeoStructure.CELL_FLAG_E) != 0) ++ { ++ addNode(x + 1, y, z, Config.BASE_WEIGHT); ++ } ++ ++ // can move north-west, expand ++ if ((nswe & GeoStructure.CELL_FLAG_NW) != 0) ++ { ++ addNode(x - 1, y - 1, z, Config.DIAGONAL_WEIGHT); ++ } ++ ++ // can move north-east, expand ++ if ((nswe & GeoStructure.CELL_FLAG_NE) != 0) ++ { ++ addNode(x + 1, y - 1, z, Config.DIAGONAL_WEIGHT); ++ } ++ ++ // can move south-west, expand ++ if ((nswe & GeoStructure.CELL_FLAG_SW) != 0) ++ { ++ addNode(x - 1, y + 1, z, Config.DIAGONAL_WEIGHT); ++ } ++ ++ // can move south-east, expand ++ if ((nswe & GeoStructure.CELL_FLAG_SE) != 0) ++ { ++ addNode(x + 1, y + 1, z, Config.DIAGONAL_WEIGHT); ++ } ++ } ++ ++ /** ++ * Returns node, if it exists in buffer. ++ * @param x : node X coord ++ * @param y : node Y coord ++ * @param z : node Z coord ++ * @return Node : node, if exits in buffer ++ */ ++ private final Node getNode(int x, int y, short z) ++ { ++ // check node X out of coordinates ++ final int ix = x - _cx; ++ if ((ix < 0) || (ix >= _size)) ++ { ++ return null; ++ } ++ ++ // check node Y out of coordinates ++ final int iy = y - _cy; ++ if ((iy < 0) || (iy >= _size)) ++ { ++ return null; ++ } ++ ++ // get node ++ final Node result = _buffer[ix][iy]; ++ ++ // check and update ++ if (result.getLoc() == null) ++ { ++ result.setLoc(x, y, z); ++ } ++ ++ // return node ++ return result; ++ } ++ ++ /** ++ * Add node given by coordinates to the buffer. ++ * @param x : geo X coord ++ * @param y : geo Y coord ++ * @param z : geo Z coord ++ * @param weight : weight of movement to new node ++ */ ++ private final void addNode(int x, int y, short z, int weight) ++ { ++ // get node to be expanded ++ final Node node = getNode(x, y, z); ++ if (node == null) ++ { ++ return; ++ } ++ ++ // Z distance between nearby cells is higher than cell size ++ if (node.getLoc().getZ() > (z + (2 * GeoStructure.CELL_HEIGHT))) ++ { ++ return; ++ } ++ ++ // node was already expanded, return ++ if (node.getCost() >= 0) ++ { ++ return; ++ } ++ ++ node.setParent(_current); ++ if (node.getLoc().getNSWE() != (byte) 0xFF) ++ { ++ node.setCost(getCostH(x, y, node.getLoc().getZ()) + (weight * Config.OBSTACLE_MULTIPLIER)); ++ } ++ else ++ { ++ node.setCost(getCostH(x, y, node.getLoc().getZ()) + weight); ++ } ++ ++ Node current = _current; ++ int count = 0; ++ while ((current.getChild() != null) && (count < (Config.MAX_ITERATIONS * 4))) ++ { ++ count++; ++ if (current.getChild().getCost() > node.getCost()) ++ { ++ node.setChild(current.getChild()); ++ break; ++ } ++ current = current.getChild(); ++ } ++ ++ if (count >= (Config.MAX_ITERATIONS * 4)) ++ { ++ System.err.println("Pathfinding: too long loop detected, cost:" + node.getCost()); ++ } ++ ++ current.setChild(node); ++ } ++ ++ /** ++ * @param x : node X coord ++ * @param y : node Y coord ++ * @param i : node Z coord ++ * @return double : node cost ++ */ ++ private final double getCostH(int x, int y, int i) ++ { ++ final int dX = x - _gtx; ++ final int dY = y - _gty; ++ final int dZ = (i - _gtz) / GeoStructure.CELL_HEIGHT; ++ ++ // return (Math.abs(dX) + Math.abs(dY) + Math.abs(dZ)) * Config.HEURISTIC_WEIGHT; // Manhattan distance ++ return Math.sqrt((dX * dX) + (dY * dY) + (dZ * dZ)) * Config.HEURISTIC_WEIGHT; // Direct distance ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeLoc.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeLoc.java (revision 8319) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeLoc.java (nonexistent) +@@ -1,183 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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.Cell; +- +-/** +- * @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 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 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; +- // return super.hashCode(); +- } +- +- @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; +- if (_x != other._x) +- { +- return false; +- } +- if (_y != other._y) +- { +- return false; +- } +- if (_goNorth != other._goNorth) +- { +- return false; +- } +- if (_goEast != other._goEast) +- { +- return false; +- } +- if (_goSouth != other._goSouth) +- { +- return false; +- } +- if (_goWest != other._goWest) +- { +- return false; +- } +- if (_geoHeight != other._geoHeight) +- { +- return false; +- } +- return true; +- } +-} +Index: java/org/l2jmobius/gameserver/handler/admincommandhandlers/AdminGeodata.java +=================================================================== +--- java/org/l2jmobius/gameserver/handler/admincommandhandlers/AdminGeodata.java (revision 8319) ++++ java/org/l2jmobius/gameserver/handler/admincommandhandlers/AdminGeodata.java (working copy) +@@ -47,8 +47,8 @@ + final int worldX = activeChar.getX(); + final int worldY = activeChar.getY(); + final int worldZ = activeChar.getZ(); +- final int geoX = GeoEngine.getInstance().getGeoX(worldX); +- final int geoY = GeoEngine.getInstance().getGeoY(worldY); ++ final int geoX = GeoEngine.getGeoX(worldX); ++ final int geoY = GeoEngine.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 @@ + final int worldX = activeChar.getX(); + final int worldY = activeChar.getY(); + final int worldZ = activeChar.getZ(); +- final int geoX = GeoEngine.getInstance().getGeoX(worldX); +- final int geoY = GeoEngine.getInstance().getGeoY(worldY); ++ final int geoX = GeoEngine.getGeoX(worldX); ++ final int geoY = GeoEngine.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)); +Index: java/org/l2jmobius/gameserver/model/actor/Creature.java +=================================================================== +--- java/org/l2jmobius/gameserver/model/actor/Creature.java (revision 8399) ++++ java/org/l2jmobius/gameserver/model/actor/Creature.java (working copy) +@@ -44,8 +44,6 @@ + import org.l2jmobius.gameserver.data.xml.ZoneData; + import org.l2jmobius.gameserver.enums.TeleportWhereType; + import org.l2jmobius.gameserver.geoengine.GeoEngine; +-import org.l2jmobius.gameserver.geoengine.GeoEnginePathfinding; +-import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; + import org.l2jmobius.gameserver.handler.ISkillHandler; + import org.l2jmobius.gameserver.handler.SkillHandler; + import org.l2jmobius.gameserver.handler.itemhandlers.Potions; +@@ -4344,7 +4342,7 @@ + public int _heading; + public boolean disregardingGeodata; + public int onGeodataPathIndex; +- public List geoPath; ++ public List geoPath; + public int geoPathAccurateTx; + public int geoPathAccurateTy; + public int geoPathGtx; +@@ -5624,7 +5622,7 @@ + if (((originalDistance - distance) > 30) && !_isAfraid && !isInBoat) + { + // Path calculation -- overrides previous movement check +- m.geoPath = GeoEnginePathfinding.getInstance().findPath(curX, curY, curZ, originalX, originalY, originalZ, getInstanceId()); ++ m.geoPath = GeoEngine.getInstance().findPath(curX, curY, curZ, originalX, originalY, originalZ, getInstanceId()); + if ((m.geoPath == null) || (m.geoPath.size() < 2)) // No path found + { + if (isPlayer() && !_isFlying && !isInWater) +Index: java/org/l2jmobius/gameserver/model/actor/instance/PlayerInstance.java +=================================================================== +--- java/org/l2jmobius/gameserver/model/actor/instance/PlayerInstance.java (revision 8352) ++++ java/org/l2jmobius/gameserver/model/actor/instance/PlayerInstance.java (working copy) +@@ -15849,7 +15849,7 @@ + { + if (Config.CORRECT_PLAYER_Z) + { +- final int nearestZ = GeoEngine.getInstance().getHigherHeight(getX(), getY(), getZ()); ++ final int nearestZ = GeoEngine.getInstance().getHeightNearest(getX(), getY(), getZ()); + if (getZ() < nearestZ) + { + teleToLocation(new Location(getX(), getY(), nearestZ), false); +Index: java/org/l2jmobius/gameserver/util/GeoUtils.java +=================================================================== +--- java/org/l2jmobius/gameserver/util/GeoUtils.java (revision 8319) ++++ java/org/l2jmobius/gameserver/util/GeoUtils.java (working copy) +@@ -19,7 +19,7 @@ + import java.awt.Color; + + import org.l2jmobius.gameserver.geoengine.GeoEngine; +-import org.l2jmobius.gameserver.geoengine.geodata.Cell; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; + import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance; + import org.l2jmobius.gameserver.network.serverpackets.ExServerPrimitive; + +@@ -26,25 +26,25 @@ + /** + * @author HorridoJoho + */ +-public final class GeoUtils ++public class GeoUtils + { + public static void debug2DLine(PlayerInstance player, int x, int y, int tx, int ty, int z) + { +- final int gx = GeoEngine.getInstance().getGeoX(x); +- final int gy = GeoEngine.getInstance().getGeoY(y); ++ final int gx = GeoEngine.getGeoX(x); ++ final int gy = GeoEngine.getGeoY(y); + +- final int tgx = GeoEngine.getInstance().getGeoX(tx); +- final int tgy = GeoEngine.getInstance().getGeoY(ty); ++ final int tgx = GeoEngine.getGeoX(tx); ++ final int tgy = GeoEngine.getGeoY(ty); + + final ExServerPrimitive prim = new ExServerPrimitive("Debug2DLine", x, y, z); +- prim.addLine(Color.BLUE, GeoEngine.getInstance().getWorldX(gx), GeoEngine.getInstance().getWorldY(gy), z, GeoEngine.getInstance().getWorldX(tgx), GeoEngine.getInstance().getWorldY(tgy), z); ++ prim.addLine(Color.BLUE, GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), z, GeoEngine.getWorldX(tgx), GeoEngine.getWorldY(tgy), z); + + final LinePointIterator iter = new LinePointIterator(gx, gy, tgx, tgy); + + while (iter.next()) + { +- final int wx = GeoEngine.getInstance().getWorldX(iter.x()); +- final int wy = GeoEngine.getInstance().getWorldY(iter.y()); ++ final int wx = GeoEngine.getWorldX(iter.x()); ++ final int wy = GeoEngine.getWorldY(iter.y()); + + prim.addPoint(Color.RED, wx, wy, z); + } +@@ -53,21 +53,21 @@ + + public static void debug3DLine(PlayerInstance player, int x, int y, int z, int tx, int ty, int tz) + { +- final int gx = GeoEngine.getInstance().getGeoX(x); +- final int gy = GeoEngine.getInstance().getGeoY(y); ++ final int gx = GeoEngine.getGeoX(x); ++ final int gy = GeoEngine.getGeoY(y); + +- final int tgx = GeoEngine.getInstance().getGeoX(tx); +- final int tgy = GeoEngine.getInstance().getGeoY(ty); ++ final int tgx = GeoEngine.getGeoX(tx); ++ final int tgy = GeoEngine.getGeoY(ty); + + final ExServerPrimitive prim = new ExServerPrimitive("Debug3DLine", x, y, z); +- prim.addLine(Color.BLUE, GeoEngine.getInstance().getWorldX(gx), GeoEngine.getInstance().getWorldY(gy), z, GeoEngine.getInstance().getWorldX(tgx), GeoEngine.getInstance().getWorldY(tgy), tz); ++ prim.addLine(Color.BLUE, GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), z, GeoEngine.getWorldX(tgx), GeoEngine.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.getInstance().getWorldX(prevX); +- int wy = GeoEngine.getInstance().getWorldY(prevY); ++ int wx = GeoEngine.getWorldX(prevX); ++ int wy = GeoEngine.getWorldY(prevY); + int wz = iter.z(); + prim.addPoint(Color.RED, wx, wy, wz); + +@@ -78,8 +78,8 @@ + + if ((curX != prevX) || (curY != prevY)) + { +- wx = GeoEngine.getInstance().getWorldX(curX); +- wy = GeoEngine.getInstance().getWorldY(curY); ++ wx = GeoEngine.getWorldX(curX); ++ wy = GeoEngine.getWorldY(curY); + wz = iter.z(); + + prim.addPoint(Color.RED, wx, wy, wz); +@@ -93,7 +93,7 @@ + + private static Color getDirectionColor(int x, int y, int z, int nswe) + { +- if (GeoEngine.getInstance().checkNearestNswe(x, y, z, nswe)) ++ if ((GeoEngine.getInstance().getNsweNearest(x, y, z) & nswe) == nswe) + { + return Color.GREEN; + } +@@ -109,9 +109,8 @@ + int iPacket = 0; + + ExServerPrimitive exsp = null; +- final GeoEngine ge = GeoEngine.getInstance(); +- final int playerGx = ge.getGeoX(player.getX()); +- final int playerGy = ge.getGeoY(player.getY()); ++ final int playerGx = GeoEngine.getGeoX(player.getX()); ++ final int playerGy = GeoEngine.getGeoY(player.getY()); + for (int dx = -geoRadius; dx <= geoRadius; ++dx) + { + for (int dy = -geoRadius; dy <= geoRadius; ++dy) +@@ -135,12 +134,12 @@ + final int gx = playerGx + dx; + final int gy = playerGy + dy; + +- final int x = ge.getWorldX(gx); +- final int y = ge.getWorldY(gy); +- final int z = ge.getNearestZ(gx, gy, player.getZ()); ++ final int x = GeoEngine.getWorldX(gx); ++ final int y = GeoEngine.getWorldY(gy); ++ final int z = GeoEngine.getInstance().getHeightNearest(gx, gy, player.getZ()); + + // north arrow +- Color col = getDirectionColor(gx, gy, z, Cell.NSWE_NORTH); ++ Color col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_N); + 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); +@@ -147,7 +146,7 @@ + exsp.addLine(col, x - 4, y - 4, z, x + 4, y - 4, z); + + // east arrow +- col = getDirectionColor(gx, gy, z, Cell.NSWE_EAST); ++ col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_E); + 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); +@@ -154,13 +153,13 @@ + exsp.addLine(col, x + 4, y - 4, z, x + 4, y + 4, z); + + // south arrow +- col = getDirectionColor(gx, gy, z, Cell.NSWE_SOUTH); ++ col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_S); + 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, Cell.NSWE_WEST); ++ col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_W); + 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); +@@ -188,15 +187,15 @@ + { + if (y > lastY) + { +- return Cell.NSWE_SOUTH_EAST; ++ return GeoStructure.CELL_FLAG_SE; // Direction.SOUTH_EAST; + } + else if (y < lastY) + { +- return Cell.NSWE_NORTH_EAST; ++ return GeoStructure.CELL_FLAG_NE; // Direction.NORTH_EAST; + } + else + { +- return Cell.NSWE_EAST; ++ return GeoStructure.CELL_FLAG_E; // Direction.EAST; + } + } + else if (x < lastX) // west +@@ -203,26 +202,27 @@ + { + if (y > lastY) + { +- return Cell.NSWE_SOUTH_WEST; ++ return GeoStructure.CELL_FLAG_SW; // Direction.SOUTH_WEST; + } + else if (y < lastY) + { +- return Cell.NSWE_NORTH_WEST; ++ return GeoStructure.CELL_FLAG_NW; // Direction.NORTH_WEST; + } + else + { +- return Cell.NSWE_WEST; ++ return GeoStructure.CELL_FLAG_W; // Direction.WEST; + } + } +- else // unchanged x ++ else ++ // unchanged x + { + if (y > lastY) + { +- return Cell.NSWE_SOUTH; ++ return GeoStructure.CELL_FLAG_S; // Direction.SOUTH; + } + else if (y < lastY) + { +- return Cell.NSWE_NORTH; ++ return GeoStructure.CELL_FLAG_N; // Direction.NORTH; + } + else + { +Index: java/org/l2jmobius/tools/geodataconverter/GeoDataConverter.java +=================================================================== +--- java/org/l2jmobius/tools/geodataconverter/GeoDataConverter.java (nonexistent) ++++ java/org/l2jmobius/tools/geodataconverter/GeoDataConverter.java (working copy) +@@ -0,0 +1,367 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ */ ++package org.l2jmobius.tools.geodataconverter; ++ ++import java.io.BufferedOutputStream; ++import java.io.File; ++import java.io.FileOutputStream; ++import java.io.RandomAccessFile; ++import java.nio.ByteOrder; ++import java.nio.MappedByteBuffer; ++import java.nio.channels.FileChannel; ++import java.util.Scanner; ++ ++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.GeoFormat; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; ++import org.l2jmobius.gameserver.model.World; ++ ++/** ++ * @author Hasha ++ */ ++public class GeoDataConverter ++{ ++ final static String GEODATA_PATH = "./data/geodata/"; ++ ++ private static GeoFormat _format; ++ private static ABlock[][] _blocks; ++ ++ public static void main(String[] args) ++ { ++ // get geodata format ++ String type = ""; ++ try (Scanner scn = new Scanner(System.in)) ++ { ++ while (!(type.equalsIgnoreCase("J") || type.equalsIgnoreCase("O") || type.equalsIgnoreCase("E"))) ++ { ++ System.out.println("GeoDataConverter: Select source geodata type:"); ++ System.out.println(" J: L2J (e.g. 23_22.l2j)"); ++ System.out.println(" O: L2OFF (e.g. 23_22_conv.dat)"); ++ System.out.println(" E: Exit"); ++ System.out.print("Choice: "); ++ type = scn.next(); ++ } ++ } ++ if (type.equalsIgnoreCase("E")) ++ { ++ System.exit(0); ++ } ++ ++ _format = type.equalsIgnoreCase("J") ? GeoFormat.L2J : GeoFormat.L2OFF; ++ ++ // start conversion ++ System.out.println("GeoDataConverter: Converting all " + _format + " files."); ++ ++ // initialize geodata container ++ _blocks = new ABlock[GeoStructure.REGION_BLOCKS_X][GeoStructure.REGION_BLOCKS_Y]; ++ ++ // initialize multilayer temporarily buffer ++ BlockMultilayer.initialize(); ++ ++ // load geo files ++ int converted = 0; ++ for (int rx = World.TILE_X_MIN; rx <= World.TILE_X_MAX; rx++) ++ { ++ for (int ry = World.TILE_Y_MIN; ry <= World.TILE_Y_MAX; ry++) ++ { ++ final String input = String.format(_format.getFilename(), rx, ry); ++ final String filepath = GEODATA_PATH; ++ final File f = new File(filepath + input); ++ if (f.exists() && !f.isDirectory()) ++ { ++ // load geodata ++ if (!loadGeoBlocks(input)) ++ { ++ System.out.println("GeoDataConverter: Unable to load " + input + " region file."); ++ continue; ++ } ++ ++ // recalculate nswe ++ if (!recalculateNswe()) ++ { ++ System.out.println("GeoDataConverter: Unable to convert " + input + " region file."); ++ continue; ++ } ++ ++ // save geodata ++ final String output = String.format(GeoFormat.L2D.getFilename(), rx, ry); ++ if (!saveGeoBlocks(output)) ++ { ++ System.out.println("GeoDataConverter: Unable to save " + output + " region file."); ++ continue; ++ } ++ ++ converted++; ++ System.out.println("GeoDataConverter: Created " + output + " region file."); ++ } ++ } ++ } ++ System.out.println("GeoDataConverter: Converted " + converted + " " + _format + " to L2D region file(s)."); ++ ++ // release multilayer block temporarily buffer ++ BlockMultilayer.release(); ++ } ++ ++ /** ++ * Loads geo blocks from buffer of the region file. ++ * @param filename : The name of the to load. ++ * @return boolean : True when successful. ++ */ ++ private static boolean loadGeoBlocks(String filename) ++ { ++ // region file is load-able, try to load it ++ try (RandomAccessFile raf = new RandomAccessFile(GEODATA_PATH + filename, "r"); ++ FileChannel fc = raf.getChannel()) ++ { ++ final MappedByteBuffer buffer = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()).load(); ++ buffer.order(ByteOrder.LITTLE_ENDIAN); ++ ++ // load 18B header for L2off geodata (1st and 2nd byte...region X and Y) ++ if (_format == GeoFormat.L2OFF) ++ { ++ for (int i = 0; i < 18; i++) ++ { ++ buffer.get(); ++ } ++ } ++ ++ // 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 (_format == GeoFormat.L2J) ++ { ++ // get block type ++ final byte type = buffer.get(); ++ ++ // load block according to block type ++ switch (type) ++ { ++ case GeoStructure.TYPE_FLAT_L2J_L2OFF: ++ { ++ _blocks[ix][iy] = new BlockFlat(buffer, _format); ++ break; ++ } ++ case GeoStructure.TYPE_COMPLEX_L2J: ++ { ++ _blocks[ix][iy] = new BlockComplex(buffer, _format); ++ break; ++ } ++ case GeoStructure.TYPE_MULTILAYER_L2J: ++ { ++ _blocks[ix][iy] = new BlockMultilayer(buffer, _format); ++ break; ++ } ++ default: ++ { ++ throw new IllegalArgumentException("Unknown block type: " + type); ++ } ++ } ++ } ++ else ++ { ++ // get block type ++ final short type = buffer.getShort(); ++ ++ // load block according to block type ++ switch (type) ++ { ++ case GeoStructure.TYPE_FLAT_L2J_L2OFF: ++ { ++ _blocks[ix][iy] = new BlockFlat(buffer, _format); ++ break; ++ } ++ case GeoStructure.TYPE_COMPLEX_L2OFF: ++ { ++ _blocks[ix][iy] = new BlockComplex(buffer, _format); ++ break; ++ } ++ default: ++ { ++ _blocks[ix][iy] = new BlockMultilayer(buffer, _format); ++ break; ++ } ++ } ++ } ++ } ++ } ++ ++ if (buffer.remaining() > 0) ++ { ++ System.out.println("GeoDataConverter: Region file " + filename + " can be corrupted, remaining " + buffer.remaining() + " bytes to read."); ++ return false; ++ } ++ ++ return true; ++ } ++ catch (Exception e) ++ { ++ System.out.println("GeoDataConverter: Error while loading " + filename + " region file."); ++ return false; ++ } ++ } ++ ++ /** ++ * Recalculate diagonal flags for the region file. ++ * @return boolean : True when successful. ++ */ ++ private static boolean recalculateNswe() ++ { ++ try ++ { ++ for (int x = 0; x < GeoStructure.REGION_CELLS_X; x++) ++ { ++ for (int y = 0; y < GeoStructure.REGION_CELLS_Y; y++) ++ { ++ // get block ++ final ABlock block = _blocks[x / GeoStructure.BLOCK_CELLS_X][y / GeoStructure.BLOCK_CELLS_Y]; ++ ++ // skip flat blocks ++ if (block instanceof BlockFlat) ++ { ++ continue; ++ } ++ ++ // for complex and multilayer blocks go though all layers ++ short height = Short.MAX_VALUE; ++ int index; ++ while ((index = block.getIndexBelow(x, y, height)) != -1) ++ { ++ // get height and nswe ++ height = block.getHeight(index); ++ byte nswe = block.getNswe(index); ++ ++ // update nswe with diagonal flags ++ nswe = updateNsweBelow(x, y, height, nswe); ++ ++ // set nswe of the cell ++ block.setNswe(index, nswe); ++ } ++ } ++ } ++ ++ return true; ++ } ++ catch (Exception e) ++ { ++ return false; ++ } ++ } ++ ++ /** ++ * Updates the NSWE flag with diagonal flags. ++ * @param x : Geodata X coordinate. ++ * @param y : Geodata Y coordinate. ++ * @param z : Geodata Z coordinate. ++ * @param nsweValue : NSWE flag to be updated. ++ * @return byte : Updated NSWE flag. ++ */ ++ private static byte updateNsweBelow(int x, int y, short z, byte nsweValue) ++ { ++ byte nswe = nsweValue; ++ ++ // calculate virtual layer height ++ final short height = (short) (z + GeoStructure.CELL_IGNORE_HEIGHT); ++ ++ // get NSWE of neighbor cells below virtual layer (NPC/PC can fall down of clif, but can not climb it -> NSWE of cell below) ++ final byte nsweN = getNsweBelow(x, y - 1, height); ++ final byte nsweS = getNsweBelow(x, y + 1, height); ++ final byte nsweW = getNsweBelow(x - 1, y, height); ++ final byte nsweE = getNsweBelow(x + 1, y, height); ++ ++ // north-west ++ if ((((nswe & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) || (((nswe & GeoStructure.CELL_FLAG_W) != 0) && ((nsweW & GeoStructure.CELL_FLAG_N) != 0))) ++ { ++ nswe |= GeoStructure.CELL_FLAG_NW; ++ } ++ ++ // north-east ++ if ((((nswe & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) || (((nswe & GeoStructure.CELL_FLAG_E) != 0) && ((nsweE & GeoStructure.CELL_FLAG_N) != 0))) ++ { ++ nswe |= GeoStructure.CELL_FLAG_NE; ++ } ++ ++ // south-west ++ if ((((nswe & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) || (((nswe & GeoStructure.CELL_FLAG_W) != 0) && ((nsweW & GeoStructure.CELL_FLAG_S) != 0))) ++ { ++ nswe |= GeoStructure.CELL_FLAG_SW; ++ } ++ ++ // south-east ++ if ((((nswe & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) || (((nswe & GeoStructure.CELL_FLAG_E) != 0) && ((nsweE & GeoStructure.CELL_FLAG_S) != 0))) ++ { ++ nswe |= GeoStructure.CELL_FLAG_SE; ++ } ++ ++ return nswe; ++ } ++ ++ private static byte getNsweBelow(int geoX, int geoY, short worldZ) ++ { ++ // out of geo coordinates ++ if ((geoX < 0) || (geoX >= GeoStructure.REGION_CELLS_X)) ++ { ++ return 0; ++ } ++ ++ // out of geo coordinates ++ if ((geoY < 0) || (geoY >= GeoStructure.REGION_CELLS_Y)) ++ { ++ return 0; ++ } ++ ++ // get block ++ final ABlock block = _blocks[geoX / GeoStructure.BLOCK_CELLS_X][geoY / GeoStructure.BLOCK_CELLS_Y]; ++ ++ // get index, when valid, return nswe ++ final int index = block.getIndexBelow(geoX, geoY, worldZ); ++ return index == -1 ? 0 : block.getNswe(index); ++ } ++ ++ /** ++ * Save region file to file. ++ * @param filename : The name of file to save. ++ * @return boolean : True when successful. ++ */ ++ private static boolean saveGeoBlocks(String filename) ++ { ++ try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(GEODATA_PATH + filename), GeoStructure.REGION_BLOCKS * GeoStructure.BLOCK_CELLS * 3)) ++ { ++ // loop over region blocks and save each block ++ for (int ix = 0; ix < GeoStructure.REGION_BLOCKS_X; ix++) ++ { ++ for (int iy = 0; iy < GeoStructure.REGION_BLOCKS_Y; iy++) ++ { ++ _blocks[ix][iy].saveBlock(bos); ++ } ++ } ++ ++ // flush data to file ++ bos.flush(); ++ ++ return true; ++ } ++ catch (Exception e) ++ { ++ return false; ++ } ++ } ++} +\ No newline at end of file diff --git a/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/geodata/L2D_GeoEngine.patch b/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/geodata/L2D_GeoEngine.patch new file mode 100644 index 0000000000..b11c767ce4 --- /dev/null +++ b/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/geodata/L2D_GeoEngine.patch @@ -0,0 +1,6075 @@ +Index: dist/game/GeoDataConverter.bat +=================================================================== +--- dist/game/GeoDataConverter.bat (nonexistent) ++++ dist/game/GeoDataConverter.bat (working copy) +@@ -0,0 +1,6 @@ ++@echo off ++title L2D geodata converter ++ ++java -Xmx512m -cp ./../libs/* org.l2jmobius.tools.geodataconverter.GeoDataConverter ++ ++pause +Index: dist/game/GeoDataConverter.sh +=================================================================== +--- dist/game/GeoDataConverter.sh (nonexistent) ++++ dist/game/GeoDataConverter.sh (working copy) +@@ -0,0 +1,4 @@ ++#! /bin/sh ++ ++java -Xmx512m -cp ../libs/*: org.l2jmobius.tools.geodataconverter.GeoDataConverter > log/stdout.log 2>&1 ++ +Index: dist/game/config/GeoEngine.ini +=================================================================== +--- dist/game/config/GeoEngine.ini (revision 8319) ++++ dist/game/config/GeoEngine.ini (working copy) +@@ -1,6 +1,10 @@ + # ================================================================= + # Geodata + # ================================================================= ++# Because of real-time performance we are using geodata files only in ++# diagonal L2D format now (using filename e.g. 22_16.l2d). ++# L2D geodata can be obtained by conversion of existing L2J or L2OFF geodata. ++# Launch "GeoDataConverter.bat/sh" and follow instructions to start the conversion. + + # 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/ +@@ -13,6 +17,16 @@ + CoordSynchronize = 2 + + # ================================================================= ++# Path checking ++# ================================================================= ++ ++# 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 finding + # ================================================================= + +@@ -23,19 +37,23 @@ + # Pathfinding array buffers configuration, default: 100x6;128x6;192x6;256x4;320x4;384x4;500x2 + PathFindBuffers = 100x6;128x6;192x6;256x4;320x4;384x4;500x2 + +-# Weight for nodes without obstacles far from walls +-LowWeight = 0.5 ++# Base path weight, when moving from one node to another on axis direction, default: 10 ++BaseWeight = 10 + +-# Weight for nodes near walls +-MediumWeight = 2 ++# Path weight, when moving from one node to another on diagonal direction, default: BaseWeight * sqrt(2) = 14 ++DiagonalWeight = 14 + +-# Weight for nodes with obstacles +-HighWeight = 3 ++# When movement flags of target node is blocked to any direction, multiply movement weight by this multiplier. ++# This causes pathfinding algorithm to avoid path construction exactly near an obstacle, default: 10 ++ObstacleMultiplier = 10 + +-# Weight for diagonal movement. +-# Default: LowWeight * sqrt(2) +-DiagonalWeight = 0.707 ++# Weight of the heuristic algorithm, which is giving estimated cost from node to target, default: 20 ++# For proper function must be higher than BaseWeight and/or DiagonalWeight. ++HeuristicWeight = 20 + ++# Maximum number of generated nodes per one path-finding process, default 3500 ++MaxIterations = 3500 ++ + # ================================================================= + # Other + # ================================================================= +Index: dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java +=================================================================== +--- dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java (revision 8319) ++++ dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java (working copy) +@@ -55,9 +55,8 @@ + final int worldX = activeChar.getX(); + final int worldY = activeChar.getY(); + final int worldZ = activeChar.getZ(); +- final int geoX = GeoEngine.getInstance().getGeoX(worldX); +- final int geoY = GeoEngine.getInstance().getGeoY(worldY); +- ++ final int geoX = GeoEngine.getGeoX(worldX); ++ final int geoY = GeoEngine.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)); +@@ -73,9 +72,8 @@ + final int worldX = activeChar.getX(); + final int worldY = activeChar.getY(); + final int worldZ = activeChar.getZ(); +- final int geoX = GeoEngine.getInstance().getGeoX(worldX); +- final int geoY = GeoEngine.getInstance().getGeoY(worldY); +- ++ final int geoX = GeoEngine.getGeoX(worldX); ++ final int geoY = GeoEngine.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)); +Index: dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java +=================================================================== +--- dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java (revision 8319) ++++ dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java (working copy) +@@ -18,11 +18,11 @@ + + import java.util.List; + +-import org.l2jmobius.Config; +-import org.l2jmobius.gameserver.geoengine.GeoEnginePathfinding; +-import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; ++import org.l2jmobius.gameserver.geoengine.GeoEngine; + import org.l2jmobius.gameserver.handler.IAdminCommandHandler; ++import org.l2jmobius.gameserver.model.Location; + import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance; ++import org.l2jmobius.gameserver.network.SystemMessageId; + import org.l2jmobius.gameserver.util.BuilderUtil; + + public class AdminPathNode implements IAdminCommandHandler +@@ -37,29 +37,30 @@ + { + if (command.equals("admin_path_find")) + { +- if (!Config.PATHFINDING) +- { +- BuilderUtil.sendSysMessage(activeChar, "PathFinding is disabled."); +- return true; +- } + if (activeChar.getTarget() != null) + { +- final List path = GeoEnginePathfinding.getInstance().findPath(activeChar.getX(), activeChar.getY(), (short) activeChar.getZ(), activeChar.getTarget().getX(), activeChar.getTarget().getY(), (short) activeChar.getTarget().getZ(), activeChar.getInstanceId()); ++ 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()); + if (path == null) + { +- BuilderUtil.sendSysMessage(activeChar, "No Route!"); +- return true; ++ BuilderUtil.sendSysMessage(activeChar, "No route found or pathfinding disabled."); + } +- for (AbstractNodeLoc a : path) ++ else + { +- BuilderUtil.sendSysMessage(activeChar, "x:" + a.getX() + " y:" + a.getY() + " z:" + a.getZ()); ++ for (Location point : path) ++ { ++ BuilderUtil.sendSysMessage(activeChar, "x:" + point.getX() + " y:" + point.getY() + " z:" + point.getZ()); ++ } + } + } + else + { +- BuilderUtil.sendSysMessage(activeChar, "No Target!"); ++ activeChar.sendPacket(SystemMessageId.INVALID_TARGET); + } + } ++ else ++ { ++ return false; ++ } + return true; + } + +Index: java/org/l2jmobius/Config.java +=================================================================== +--- java/org/l2jmobius/Config.java (revision 8388) ++++ java/org/l2jmobius/Config.java (working copy) +@@ -32,7 +32,6 @@ + import java.net.UnknownHostException; + import java.nio.charset.StandardCharsets; + import java.nio.file.Files; +-import java.nio.file.Path; + import java.nio.file.Paths; + import java.util.ArrayList; + import java.util.Arrays; +@@ -1059,14 +1058,17 @@ + // -------------------------------------------------- + // GeoEngine + // -------------------------------------------------- +- public static Path GEODATA_PATH; ++ public static String GEODATA_PATH; ++ public static int COORD_SYNCHRONIZE; ++ public static int PART_OF_CHARACTER_HEIGHT; ++ public static int MAX_OBSTACLE_HEIGHT; + public static boolean PATHFINDING; + public static String PATHFIND_BUFFERS; +- public static float LOW_WEIGHT; +- public static float MEDIUM_WEIGHT; +- public static float HIGH_WEIGHT; +- public static float DIAGONAL_WEIGHT; +- public static int COORD_SYNCHRONIZE; ++ public static int BASE_WEIGHT; ++ public static int DIAGONAL_WEIGHT; ++ public static int HEURISTIC_WEIGHT; ++ public static int OBSTACLE_MULTIPLIER; ++ public static int MAX_ITERATIONS; + public static boolean CORRECT_PLAYER_Z; + + // -------------------------------------------------- +@@ -2595,14 +2597,17 @@ + + // Load GeoEngine config file (if exists) + final PropertiesParser GeoEngine = new PropertiesParser(GEOENGINE_CONFIG_FILE); +- GEODATA_PATH = Paths.get(GeoEngine.getString("GeoDataPath", "./data/geodata")); ++ GEODATA_PATH = GeoEngine.getString("GeoDataPath", "./data/geodata/"); ++ COORD_SYNCHRONIZE = GeoEngine.getInt("CoordSynchronize", -1); ++ PART_OF_CHARACTER_HEIGHT = GeoEngine.getInt("PartOfCharacterHeight", 75); ++ MAX_OBSTACLE_HEIGHT = GeoEngine.getInt("MaxObstacleHeight", 32); + PATHFINDING = GeoEngine.getBoolean("PathFinding", true); + PATHFIND_BUFFERS = GeoEngine.getString("PathFindBuffers", "100x6;128x6;192x6;256x4;320x4;384x4;500x2"); +- LOW_WEIGHT = GeoEngine.getFloat("LowWeight", 0.5f); +- MEDIUM_WEIGHT = GeoEngine.getFloat("MediumWeight", 2); +- HIGH_WEIGHT = GeoEngine.getFloat("HighWeight", 3); +- DIAGONAL_WEIGHT = GeoEngine.getFloat("DiagonalWeight", 0.707f); +- COORD_SYNCHRONIZE = GeoEngine.getInt("CoordSynchronize", -1); ++ BASE_WEIGHT = GeoEngine.getInt("BaseWeight", 10); ++ DIAGONAL_WEIGHT = GeoEngine.getInt("DiagonalWeight", 14); ++ OBSTACLE_MULTIPLIER = GeoEngine.getInt("ObstacleMultiplier", 10); ++ HEURISTIC_WEIGHT = GeoEngine.getInt("HeuristicWeight", 20); ++ MAX_ITERATIONS = GeoEngine.getInt("MaxIterations", 3500); + CORRECT_PLAYER_Z = GeoEngine.getBoolean("CorrectPlayerZ", false); + + // Load AllowedPlayerRaces config file (if exists) +Index: java/org/l2jmobius/gameserver/ai/SummonAI.java +=================================================================== +--- java/org/l2jmobius/gameserver/ai/SummonAI.java (revision 8319) ++++ java/org/l2jmobius/gameserver/ai/SummonAI.java (working copy) +@@ -26,7 +26,6 @@ + import org.l2jmobius.commons.concurrent.ThreadPool; + import org.l2jmobius.commons.util.Rnd; + import org.l2jmobius.gameserver.geoengine.GeoEngine; +-import org.l2jmobius.gameserver.geoengine.GeoEnginePathfinding; + import org.l2jmobius.gameserver.model.WorldObject; + import org.l2jmobius.gameserver.model.actor.Creature; + import org.l2jmobius.gameserver.model.actor.Summon; +@@ -51,7 +50,7 @@ + @Override + protected void onIntentionAttack(Creature target) + { +- if (Config.PATHFINDING && (GeoEnginePathfinding.getInstance().findPath(_actor.getX(), _actor.getY(), _actor.getZ(), target.getX(), target.getY(), target.getZ(), _actor.getInstanceId()) == null)) ++ if (Config.PATHFINDING && (GeoEngine.getInstance().findPath(_actor.getX(), _actor.getY(), _actor.getZ(), target.getX(), target.getY(), target.getZ(), _actor.getInstanceId()) == null)) + { + return; + } +Index: java/org/l2jmobius/gameserver/geoengine/GeoEngine.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/GeoEngine.java (revision 8352) ++++ java/org/l2jmobius/gameserver/geoengine/GeoEngine.java (working copy) +@@ -16,99 +16,89 @@ + */ + package org.l2jmobius.gameserver.geoengine; + +-import java.io.IOException; ++import java.io.File; + import java.io.RandomAccessFile; + import java.nio.ByteOrder; +-import java.nio.channels.FileChannel.MapMode; +-import java.nio.file.Files; +-import java.nio.file.Path; +-import java.util.concurrent.atomic.AtomicReferenceArray; +-import java.util.logging.Level; ++import java.nio.MappedByteBuffer; ++import java.nio.channels.FileChannel; ++import java.util.List; + 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.geoengine.geodata.Cell; +-import org.l2jmobius.gameserver.geoengine.geodata.IRegion; +-import org.l2jmobius.gameserver.geoengine.geodata.NullRegion; +-import org.l2jmobius.gameserver.geoengine.geodata.Region; ++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.GeoFormat; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoLocation; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; + import org.l2jmobius.gameserver.model.Location; + import org.l2jmobius.gameserver.model.World; + import org.l2jmobius.gameserver.model.WorldObject; +-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; ++import org.l2jmobius.gameserver.model.actor.Creature; ++import org.l2jmobius.gameserver.util.MathUtil; + + /** +- * @author -Nemesiss-, HorridoJoho ++ * @author Hasha + */ + public class GeoEngine + { +- private static final Logger LOGGER = Logger.getLogger(GeoEngine.class.getName()); ++ protected static final Logger LOGGER = Logger.getLogger(GeoEngine.class.getName()); + +- private static final int WORLD_MIN_X = -655360; +- private static final int WORLD_MIN_Y = -589824; +- private static final int WORLD_MIN_Z = -16384; ++ private final ABlock[][] _blocks; ++ private final BlockNull _nullBlock; + +- /** 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; +- +- /** The regions array */ +- private final AtomicReferenceArray _regions = new AtomicReferenceArray<>(GEO_REGIONS); +- +- 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; +- +- protected GeoEngine() ++ /** ++ * GeoEngine constructor. Loads all geodata files of chosen geodata format. ++ */ ++ public GeoEngine() + { + LOGGER.info("GeoEngine: Initializing..."); +- for (int i = 0; i < _regions.length(); i++) +- { +- _regions.set(i, NullRegion.INSTANCE); +- } + ++ // 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; +- try ++ long fileSize = 0; ++ for (int rx = World.TILE_X_MIN; rx <= World.TILE_X_MAX; rx++) + { +- for (int regionX = World.TILE_X_MIN; regionX <= World.TILE_X_MAX; regionX++) ++ for (int ry = World.TILE_Y_MIN; ry <= World.TILE_Y_MAX; ry++) + { +- for (int regionY = World.TILE_Y_MIN; regionY <= World.TILE_Y_MAX; regionY++) ++ final File f = new File(Config.GEODATA_PATH + String.format(GeoFormat.L2D.getFilename(), rx, ry)); ++ if (f.exists() && !f.isDirectory()) + { +- 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(rx, ry)) + { +- try (RandomAccessFile raf = new RandomAccessFile(geoFilePath.toFile(), "r")) +- { +- _regions.set((regionX * GEO_REGIONS_Y) + regionY, new Region(raf.getChannel().map(MapMode.READ_ONLY, 0, raf.length()).load().order(ByteOrder.LITTLE_ENDIAN))); +- loaded++; +- } ++ loaded++; ++ fileSize += f.length(); + } + } ++ else ++ { ++ // region file is not load-able, load null blocks ++ loadNullBlocks(rx, ry); ++ } + } + } +- catch (Exception e) ++ ++ LOGGER.info("GeoEngine: Loaded " + loaded + " geodata files."); ++ if (loaded > 0) + { +- LOGGER.log(Level.SEVERE, "GeoEngine: Failed to load geodata!", e); +- System.exit(1); ++ LOGGER.info("GeoEngine: Total geodata file size " + (fileSize / 1024 / 1024) + " MB."); + } + +- LOGGER.info("GeoEngine: Loaded " + loaded + " geodata files."); +- +- // Avoid wrong configs when no files are loaded. ++ // avoid wrong configs when no files are loaded + if (loaded == 0) + { + if (Config.PATHFINDING) +@@ -122,619 +112,820 @@ + LOGGER.info("GeoEngine: Forcing CoordSynchronize setting to -1."); + } + } ++ ++ // release multilayer block temporarily buffer ++ BlockMultilayer.release(); + } + + /** +- * @param geoX +- * @param geoY +- * @return the region ++ * 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 IRegion getRegion(int geoX, int geoY) ++ private final boolean loadGeoBlocks(int regionX, int regionY) + { +- final int region = ((geoX / IRegion.REGION_CELLS_X) * GEO_REGIONS_Y) + (geoY / IRegion.REGION_CELLS_Y); +- if ((region < 0) || (region >= _regions.length())) ++ final String filename = String.format(GeoFormat.L2D.getFilename(), regionX, regionY); ++ ++ // standard load ++ try (RandomAccessFile raf = new RandomAccessFile(Config.GEODATA_PATH + filename, "r"); ++ FileChannel fc = raf.getChannel()) + { +- return null; ++ // initialize file buffer ++ final MappedByteBuffer buffer = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()).load(); ++ buffer.order(ByteOrder.LITTLE_ENDIAN); ++ ++ // 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++) ++ { ++ // get block type ++ final byte type = buffer.get(); ++ ++ // load block according to block type ++ switch (type) ++ { ++ case GeoStructure.TYPE_FLAT_L2D: ++ { ++ _blocks[blockX + ix][blockY + iy] = new BlockFlat(buffer, GeoFormat.L2D); ++ break; ++ } ++ case GeoStructure.TYPE_COMPLEX_L2D: ++ { ++ _blocks[blockX + ix][blockY + iy] = new BlockComplex(buffer, GeoFormat.L2D); ++ break; ++ } ++ case GeoStructure.TYPE_MULTILAYER_L2D: ++ { ++ _blocks[blockX + ix][blockY + iy] = new BlockMultilayer(buffer, GeoFormat.L2D); ++ break; ++ } ++ default: ++ { ++ throw new IllegalArgumentException("Unknown block type: " + type); ++ } ++ } ++ } ++ } ++ ++ // check data consistency ++ if (buffer.remaining() > 0) ++ { ++ LOGGER.warning("GeoEngine: Region file " + filename + " can be corrupted, remaining " + buffer.remaining() + " bytes to read."); ++ } ++ ++ // loading was successful ++ return true; + } +- return _regions.get(region); +- } +- +- /** +- * @param filePath +- * @param regionX +- * @param regionY +- * @throws IOException +- */ +- 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")) ++ catch (Exception e) + { +- _regions.set(regionOffset, new Region(raf.getChannel().map(MapMode.READ_ONLY, 0, raf.length()).load().order(ByteOrder.LITTLE_ENDIAN))); ++ // an error occured while loading, load null blocks ++ LOGGER.warning("GeoEngine: Error while loading " + filename + " region file."); ++ LOGGER.warning(e.getMessage()); ++ ++ // replace whole region file with null blocks ++ loadNullBlocks(regionX, regionY); ++ ++ // loading was not successful ++ return false; + } + } + + /** +- * @param regionX +- * @param regionY ++ * 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. + */ +- public void unloadRegion(int regionX, int regionY) ++ private final void loadNullBlocks(int regionX, int regionY) + { +- _regions.set((regionX * GEO_REGIONS_Y) + regionY, NullRegion.INSTANCE); +- } +- +- /** +- * @param geoX +- * @param geoY +- * @return if geodata exist +- */ +- public boolean hasGeoPos(int geoX, int geoY) +- { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) ++ // 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++) + { +- return false; ++ for (int iy = 0; iy < GeoStructure.REGION_BLOCKS_Y; iy++) ++ { ++ _blocks[blockX + ix][blockY + iy] = _nullBlock; ++ } + } +- return region.hasGeo(); + } + ++ // GEODATA - GENERAL ++ + /** +- * Checks the specified position for available geodata. +- * @param x the world x +- * @param y the world y +- * @return {@code true} if there is geodata for the given coordinates, {@code false} otherwise ++ * Converts world X to geodata X. ++ * @param worldX ++ * @return int : Geo X + */ +- public boolean hasGeo(int x, int y) ++ public static int getGeoX(int worldX) + { +- return hasGeoPos(getGeoX(x), getGeoY(y)); ++ return (MathUtil.limit(worldX, World.MAP_MIN_X, World.MAP_MAX_X) - World.MAP_MIN_X) >> 4; + } + + /** +- * @param geoX +- * @param geoY +- * @param worldZ +- * @param nswe +- * @return the nearest nswe check ++ * Converts world Y to geodata Y. ++ * @param worldY ++ * @return int : Geo Y + */ +- public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) ++ public static int getGeoY(int worldY) + { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) +- { +- return true; +- } +- return region.checkNearestNswe(geoX, geoY, worldZ, nswe); ++ return (MathUtil.limit(worldY, World.MAP_MIN_Y, World.MAP_MAX_Y) - World.MAP_MIN_Y) >> 4; + } + + /** ++ * Converts geodata X to world X. + * @param geoX +- * @param geoY +- * @param worldZ +- * @param nswe +- * @return the nearest nswe anti-corner cut ++ * @return int : World X + */ +- public boolean checkNearestNsweAntiCornerCut(int geoX, int geoY, int worldZ, int nswe) ++ public static int getWorldX(int geoX) + { +- boolean can = true; +- if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) +- { +- can = checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_EAST) && checkNearestNswe(geoX + 1, geoY, worldZ, Cell.NSWE_NORTH); +- } +- if (can && ((nswe & Cell.NSWE_NORTH_WEST) == Cell.NSWE_NORTH_WEST)) +- { +- 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 = 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 = checkNearestNswe(geoX, geoY + 1, worldZ, Cell.NSWE_WEST) && checkNearestNswe(geoX - 1, geoY, worldZ, Cell.NSWE_SOUTH); +- } +- return can && checkNearestNswe(geoX, geoY, worldZ, nswe); ++ return (MathUtil.limit(geoX, 0, GeoStructure.GEO_CELLS_X) << 4) + World.MAP_MIN_X + 8; + } + + /** +- * @param geoX ++ * Converts geodata Y to world Y. + * @param geoY +- * @param worldZ +- * @return the nearest Z value ++ * @return int : World Y + */ +- public int getNearestZ(int geoX, int geoY, int worldZ) ++ public static int getWorldY(int geoY) + { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) +- { +- return worldZ; +- } +- return region.getNearestZ(geoX, geoY, worldZ); ++ return (MathUtil.limit(geoY, 0, GeoStructure.GEO_CELLS_Y) << 4) + World.MAP_MIN_Y + 8; + } + + /** +- * @param geoX +- * @param geoY +- * @param worldZ +- * @return the next lower Z value ++ * Returns block of geodata on given coordinates. ++ * @param geoX : Geodata X ++ * @param geoY : Geodata Y ++ * @return {@link ABlock} : Block of geodata. + */ +- public int getNextLowerZ(int geoX, int geoY, int worldZ) ++ private final ABlock getBlock(int geoX, int geoY) + { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) ++ final int x = geoX / GeoStructure.BLOCK_CELLS_X; ++ final int y = geoY / GeoStructure.BLOCK_CELLS_Y; ++ ++ // if x or y is out of array return null ++ if ((x > -1) && (y > -1) && (x < GeoStructure.GEO_BLOCKS_X) && (y < GeoStructure.GEO_BLOCKS_Y)) + { +- return worldZ; ++ return _blocks[x][y]; + } +- return region.getNextLowerZ(geoX, geoY, worldZ); ++ return null; + } + + /** +- * @param geoX +- * @param geoY +- * @param worldZ +- * @return the next higher Z value ++ * Check if geo coordinates has geo. ++ * @param geoX : Geodata X ++ * @param geoY : Geodata Y ++ * @return boolean : True, if given geo coordinates have geodata + */ +- public int getNextHigherZ(int geoX, int geoY, int worldZ) ++ public boolean hasGeoPos(int geoX, int geoY) + { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) ++ final ABlock block = getBlock(geoX, geoY); ++ if (block == null) // null block check + { +- return worldZ; ++ // TODO: Find when this can be null. (Bad geodata? Check World getRegion method.) ++ // LOGGER.warning("Could not find geodata block at " + getWorldX(geoX) + ", " + getWorldY(geoY) + "."); ++ return false; + } +- return region.getNextHigherZ(geoX, geoY, worldZ); ++ return block.hasGeoPos(); + } + + /** +- * Gets the Z height. +- * @param x the world x +- * @param y the world y +- * @param z the world z +- * @return the nearest Z height ++ * 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 int getHeight(int x, int y, int z) ++ public short getHeightNearest(int geoX, int geoY, int worldZ) + { +- return getNearestZ(getGeoX(x), getGeoY(y), z); ++ final ABlock block = getBlock(geoX, geoY); ++ return block != null ? block.getHeightNearest(geoX, geoY, worldZ) : (short) worldZ; + } + + /** +- * Gets the next lower Z height. +- * @param x the world x +- * @param y the world y +- * @param z the world z +- * @return the nearest Z height ++ * 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 int getLowerHeight(int x, int y, int z) ++ public byte getNsweNearest(int geoX, int geoY, int worldZ) + { +- return getNextLowerZ(getGeoX(x), getGeoY(y), z); ++ final ABlock block = getBlock(geoX, geoY); ++ return block != null ? block.getNsweNearest(geoX, geoY, worldZ) : (byte) 0xFF; + } + + /** +- * Gets the next higher Z height. +- * @param x the world x +- * @param y the world y +- * @param z the world z +- * @return the nearest Z height ++ * Check if world coordinates has geo. ++ * @param worldX : World X ++ * @param worldY : World Y ++ * @return boolean : True, if given world coordinates have geodata + */ +- public int getHigherHeight(int x, int y, int z) ++ public boolean hasGeo(int worldX, int worldY) + { +- return getNextHigherZ(getGeoX(x), getGeoY(y), z); ++ return hasGeoPos(getGeoX(worldX), getGeoY(worldY)); + } + + /** +- * @param worldX +- * @return the geo X ++ * 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 int getGeoX(int worldX) ++ public short getHeight(int worldX, int worldY, int worldZ) + { +- return (worldX - WORLD_MIN_X) / 16; ++ return getHeightNearest(getGeoX(worldX), getGeoY(worldY), worldZ); + } + +- /** +- * @param worldY +- * @return the geo Y +- */ +- public int getGeoY(int worldY) +- { +- return (worldY - WORLD_MIN_Y) / 16; +- } ++ // PATHFINDING + + /** +- * @param worldZ +- * @return the geo Z ++ * Check line of sight from {@link WorldObject} to {@link WorldObject}. ++ * @param origin : The origin object. ++ * @param target : The target object. ++ * @return {@code boolean} : True if origin can see target + */ +- public int getGeoZ(int worldZ) ++ public boolean canSeeTarget(WorldObject origin, WorldObject target) + { +- return (worldZ - WORLD_MIN_Z) / 16; +- } +- +- /** +- * @param geoX +- * @return the world X +- */ +- public int getWorldX(int geoX) +- { +- return (geoX * 16) + WORLD_MIN_X + 8; +- } +- +- /** +- * @param geoY +- * @return the world Y +- */ +- public int getWorldY(int geoY) +- { +- return (geoY * 16) + WORLD_MIN_Y + 8; +- } +- +- /** +- * @param geoZ +- * @return the world Z +- */ +- public int getWorldZ(int geoZ) +- { +- return (geoZ * 16) + WORLD_MIN_Z + 8; +- } +- +- /** +- * 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) +- { +- if (target.isDoor()) ++ if (target.isDoor() || target.isArtefact() || (target.isCreature() && ((Creature) target).isFlying())) + { +- // Can always see doors. + return true; + } +- return 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(), cha.getInstanceId(), worldPosition.getX(), worldPosition.getY(), worldPosition.getZ()); +- } +- +- /** +- * 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 instanceId +- * @return +- */ +- public boolean canSeeTarget(int x, int y, int z, int instanceId, int tx, int ty, int tz, int tInstanceId) +- { +- return (instanceId != tInstanceId) ? false : canSeeTarget(x, y, z, instanceId, tx, ty, tz); +- } +- +- /** +- * 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 +- * @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 instanceId, int tx, int ty, int tz) +- { +- if (DoorData.getInstance().checkIfDoorsBetween(x, y, z, tx, ty, tz, instanceId, true)) ++ ++ // get origin and target world coordinates ++ final int ox = origin.getX(); ++ final int oy = origin.getY(); ++ final int oz = origin.getZ(); ++ final int tx = target.getX(); ++ final int ty = target.getY(); ++ final int tz = target.getZ(); ++ ++ if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, origin.getInstanceId(), true)) + { + return false; + } +- return canSeeTarget(x, y, z, tx, ty, tz); +- } +- +- /** +- * @param prevX +- * @param prevY +- * @param prevGeoZ +- * @param curX +- * @param curY +- * @param nswe +- * @return the LOS Z value +- */ +- 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))) ++ if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, origin.getInstanceId())) + { +- throw new RuntimeException("Multiple directions!"); ++ return false; + } + +- if (checkNearestNsweAntiCornerCut(prevX, prevY, prevGeoZ, nswe)) ++ // get origin and check existing geo coordinates ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) + { +- return getNearestZ(curX, curY, prevGeoZ); ++ return true; + } + +- return getNextHigherZ(curX, curY, prevGeoZ); ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coordinates ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) ++ { ++ return true; ++ } ++ ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // origin and target coordinates are same ++ if ((gox == gtx) && (goy == gty)) ++ { ++ return goz == gtz; ++ } ++ ++ // get origin and target height, real height = collision height * 2 ++ double oheight = 0; ++ if (origin.isCreature()) ++ { ++ oheight = ((Creature) origin).getTemplate().getCollisionHeight() * 2; ++ } ++ ++ double theight = 0; ++ if (target.isCreature()) ++ { ++ theight = ((Creature) target).getTemplate().getCollisionHeight() * 2; ++ } ++ ++ // perform geodata check ++ return checkSee(gox, goy, goz, oheight, gtx, gty, gtz, theight, origin.getInstanceId()); + } + + /** +- * Can see target. Does not check doors between. +- * @param xValue the x coordinate +- * @param yValue the y coordinate +- * @param zValue the z coordinate +- * @param txValue the target's x coordinate +- * @param tyValue the target's y coordinate +- * @param tzValue the target's z coordinate +- * @return {@code true} if there is line of sight between the given coordinate sets, {@code false} otherwise ++ * Check line of sight from {@link WorldObject} to {@link Location}. ++ * @param origin : The origin object. ++ * @param position : The target position. ++ * @return {@code boolean} : True if object can see position + */ +- public boolean canSeeTarget(int xValue, int yValue, int zValue, int txValue, int tyValue, int tzValue) ++ public boolean canSeeTarget(WorldObject origin, Location position) + { +- int x = xValue; +- int y = yValue; +- int tx = txValue; +- int ty = tyValue; ++ // get origin and target world coordinates ++ final int ox = origin.getX(); ++ final int oy = origin.getY(); ++ final int oz = origin.getZ(); ++ final int tx = position.getX(); ++ final int ty = position.getY(); ++ final int tz = position.getZ(); + +- int geoX = getGeoX(x); +- int geoY = getGeoY(y); +- int tGeoX = getGeoX(tx); +- int tGeoY = getGeoY(ty); ++ if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, origin.getInstanceId(), true)) ++ { ++ return false; ++ } ++ if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, origin.getInstanceId())) ++ { ++ return false; ++ } + +- int z = getNearestZ(geoX, geoY, zValue); +- int tz = getNearestZ(tGeoX, tGeoY, tzValue); ++ // get origin and check existing geo coordinates ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) ++ { ++ return true; ++ } + +- // Fastpath. +- if ((geoX == tGeoX) && (geoY == tGeoY)) ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coordinates ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) + { +- if (hasGeoPos(tGeoX, tGeoY)) +- { +- return z == tz; +- } + return true; + } + +- if (tz > z) ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // origin and target coordinates are same ++ if ((gox == gtx) && (goy == gty)) + { +- int tmp = tx; +- tx = x; +- x = tmp; +- +- tmp = ty; +- ty = y; +- y = tmp; +- +- tmp = tz; +- tz = z; +- z = tmp; +- +- tmp = tGeoX; +- tGeoX = geoX; +- geoX = tmp; +- +- tmp = tGeoY; +- tGeoY = geoY; +- geoY = tmp; ++ return goz == gtz; + } + +- final LinePointIterator3D pointIter = new LinePointIterator3D(geoX, geoY, z, tGeoX, tGeoY, tz); +- // 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()) ++ // get origin and target height, real height = collision height * 2 ++ double oheight = 0; ++ if (origin.isCreature()) + { +- final int curX = pointIter.x(); +- final int curY = pointIter.y(); ++ oheight = ((Creature) origin).getTemplate().getCollisionHeight(); ++ } ++ ++ // perform geodata check ++ return checkSee(gox, goy, goz, oheight, gtx, gty, gtz, 0, origin.getInstanceId()); ++ } ++ ++ /** ++ * Simple check for origin to target visibility. ++ * @param goxValue : origin X geodata coordinate ++ * @param goyValue : origin Y geodata coordinate ++ * @param gozValue : origin Z geodata coordinate ++ * @param oheight : origin height (if instance of {@link Character}) ++ * @param gtxValue : target X geodata coordinate ++ * @param gtyValue : target Y geodata coordinate ++ * @param gtzValue : target Z geodata coordinate ++ * @param theight : target height (if instance of {@link Character}) ++ * @param instanceId ++ * @return {@code boolean} : True, when target can be seen. ++ */ ++ private final boolean checkSee(int goxValue, int goyValue, int gozValue, double oheight, int gtxValue, int gtyValue, int gtzValue, double theight, int instanceId) ++ { ++ int goz = gozValue; ++ int gtz = gtzValue; ++ int gox = goxValue; ++ int goy = goyValue; ++ int gtx = gtxValue; ++ int gty = gtyValue; ++ ++ // get line of sight Z coordinates ++ double losoz = goz + ((oheight * Config.PART_OF_CHARACTER_HEIGHT) / 100); ++ double lostz = gtz + ((theight * Config.PART_OF_CHARACTER_HEIGHT) / 100); ++ ++ // get X delta and signum ++ final int dx = Math.abs(gtx - gox); ++ final int sx = gox < gtx ? 1 : -1; ++ final byte dirox = sx > 0 ? GeoStructure.CELL_FLAG_E : GeoStructure.CELL_FLAG_W; ++ final byte dirtx = sx > 0 ? GeoStructure.CELL_FLAG_W : GeoStructure.CELL_FLAG_E; ++ ++ // get Y delta and signum ++ final int dy = Math.abs(gty - goy); ++ final int sy = goy < gty ? 1 : -1; ++ final byte diroy = sy > 0 ? GeoStructure.CELL_FLAG_S : GeoStructure.CELL_FLAG_N; ++ final byte dirty = sy > 0 ? GeoStructure.CELL_FLAG_N : GeoStructure.CELL_FLAG_S; ++ ++ // get Z delta ++ final int dm = Math.max(dx, dy); ++ final double dz = (lostz - losoz) / dm; ++ ++ // get direction flag for diagonal movement ++ final byte diroxy = getDirXY(dirox, diroy); ++ final byte dirtxy = getDirXY(dirtx, dirty); ++ ++ // delta, determines axis to move on (+..X axis, -..Y axis) ++ int d = dx - dy; ++ ++ // NSWE direction of movement ++ byte diro; ++ byte dirt; ++ ++ // initialize node values ++ int nox = gox; ++ int noy = goy; ++ int ntx = gtx; ++ int nty = gty; ++ byte nsweo = getNsweNearest(gox, goy, goz); ++ byte nswet = getNsweNearest(gtx, gty, gtz); ++ ++ // loop ++ ABlock block; ++ int index; ++ for (int i = 0; i < ((dm + 1) / 2); i++) ++ { ++ // reset direction flag ++ diro = 0; ++ dirt = 0; + +- if ((curX == prevX) && (curY == prevY)) ++ // calculate next point coordinates ++ final int e2 = 2 * d; ++ if ((e2 > -dy) && (e2 < dx)) + { +- continue; ++ // calculate next point XY coordinates ++ d -= dy; ++ d += dx; ++ nox += sx; ++ ntx -= sx; ++ noy += sy; ++ nty -= sy; ++ diro |= diroxy; ++ dirt |= dirtxy; + } ++ else if (e2 > -dy) ++ { ++ // calculate next point X coordinate ++ d -= dy; ++ nox += sx; ++ ntx -= sx; ++ diro |= dirox; ++ dirt |= dirtx; ++ } ++ else if (e2 < dx) ++ { ++ // calculate next point Y coordinate ++ d += dx; ++ noy += sy; ++ nty -= sy; ++ diro |= diroy; ++ dirt |= dirty; ++ } + +- final int beeCurZ = pointIter.z(); +- int curGeoZ = prevGeoZ; +- +- // Check if the position has geodata. +- if (hasGeoPos(curX, curY)) + { +- final int beeCurGeoZ = getNearestZ(curX, curY, beeCurZ); +- final int nswe = GeoUtils.computeNswe(prevX, prevY, curX, curY); // .computeDirection(prevX, prevY, curX, curY); +- curGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, curX, curY, nswe); +- int maxHeight; +- if (ptIndex < ELEVATED_SEE_OVER_DISTANCE) ++ // get block of the next cell ++ block = getBlock(nox, noy); ++ ++ // get index of particular layer, based on movement conditions ++ if ((nsweo & diro) == 0) + { +- maxHeight = z + MAX_SEE_OVER_HEIGHT; ++ index = block.getIndexAbove(nox, noy, goz - GeoStructure.CELL_IGNORE_HEIGHT); + } + else + { +- maxHeight = beeCurZ + MAX_SEE_OVER_HEIGHT; ++ index = block.getIndexBelow(nox, noy, goz + GeoStructure.CELL_IGNORE_HEIGHT); + } + +- boolean canSeeThrough = false; +- if ((curGeoZ <= maxHeight) && (curGeoZ <= beeCurGeoZ)) ++ // layer does not exist, return ++ if (index == -1) + { +- if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) +- { +- 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 +- { +- canSeeThrough = true; +- } ++ return false; + } + +- if (!canSeeThrough) ++ // get layer and next line of sight Z coordinate ++ goz = block.getHeight(index); ++ losoz += dz; ++ ++ // perform line of sight check, return when fails ++ if ((goz - losoz) > Config.MAX_OBSTACLE_HEIGHT) + { + return false; + } ++ ++ // get layer nswe ++ nsweo = block.getNswe(index); + } ++ { ++ // get block of the next cell ++ block = getBlock(ntx, nty); ++ ++ // get index of particular layer, based on movement conditions ++ if ((nswet & dirt) == 0) ++ { ++ index = block.getIndexAbove(ntx, nty, gtz - GeoStructure.CELL_IGNORE_HEIGHT); ++ } ++ else ++ { ++ index = block.getIndexBelow(ntx, nty, gtz + GeoStructure.CELL_IGNORE_HEIGHT); ++ } ++ ++ // layer does not exist, return ++ if (index == -1) ++ { ++ return false; ++ } ++ ++ // get layer and next line of sight Z coordinate ++ gtz = block.getHeight(index); ++ lostz -= dz; ++ ++ // perform line of sight check, return when fails ++ if ((gtz - lostz) > Config.MAX_OBSTACLE_HEIGHT) ++ { ++ return false; ++ } ++ ++ // get layer nswe ++ nswet = block.getNswe(index); ++ } + +- prevX = curX; +- prevY = curY; +- prevGeoZ = curGeoZ; +- ++ptIndex; ++ // update coords ++ gox = nox; ++ goy = noy; ++ gtx = ntx; ++ gty = nty; + } + +- return true; ++ // when iteration is completed, compare final Z coordinates ++ return Math.abs(goz - gtz) < (GeoStructure.CELL_HEIGHT * 4); + } + + /** +- * Move check. +- * @param x the x coordinate +- * @param y the y coordinate +- * @param zValue the z coordinate +- * @param tx the target's x coordinate +- * @param ty the target's y coordinate +- * @param tzValue the target's z coordinate +- * @param instanceId the instance id +- * @return the last Location (x,y,z) where player can walk - just before wall ++ * Check movement from coordinates to 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 {code boolean} : True if target coordinates are reachable from origin coordinates + */ +- public Location canMoveToTargetLoc(int x, int y, int zValue, int tx, int ty, int tzValue, int instanceId) ++ public boolean canMoveToTarget(int ox, int oy, int oz, int tx, int ty, int tz, int instanceId) + { +- final int geoX = getGeoX(x); +- final int geoY = getGeoY(y); +- final int z = getNearestZ(geoX, geoY, zValue); +- final int tGeoX = getGeoX(tx); +- final int tGeoY = getGeoY(ty); +- final int tz = getNearestZ(tGeoX, tGeoY, tzValue); ++ // get origin and check existing geo coordinates ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) ++ { ++ return true; ++ } + +- if (DoorData.getInstance().checkIfDoorsBetween(x, y, z, tx, ty, tz, instanceId, false)) ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coordinates ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) + { +- return new Location(x, y, getHeight(x, y, z)); ++ return true; + } +- if (FenceData.getInstance().checkIfFenceBetween(x, y, z, tx, ty, tz, instanceId)) ++ ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // target coordinates reached ++ if ((gox == gtx) && (goy == gty) && (goz == gtz)) + { +- return new Location(x, y, getHeight(x, y, z)); ++ return true; + } + +- 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 = z; ++ // perform geodata check ++ final GeoLocation loc = checkMove(gox, goy, goz, gtx, gty, gtz, instanceId); ++ return (loc.getGeoX() == gtx) && (loc.getGeoY() == gty); ++ } ++ ++ /** ++ * Check movement from origin to target. Returns last available point in the checked path. ++ * @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 {@link Location} : Last point where object can walk (just before wall) ++ */ ++ public Location canMoveToTargetLoc(int ox, int oy, int oz, int tx, int ty, int tz, int instanceId) ++ { ++ // Mobius: Double check for doors before normal checkMove to avoid exploiting key movement. ++ if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, instanceId, false)) ++ { ++ return new Location(ox, oy, oz); ++ } ++ if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, instanceId)) ++ { ++ return new Location(ox, oy, oz); ++ } + +- while (pointIter.next()) ++ // get origin and check existing geo coordinates ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) + { +- final int curX = pointIter.x(); +- final int curY = pointIter.y(); +- final int curZ = getNearestZ(curX, curY, prevZ); +- +- if (hasGeoPos(prevX, prevY)) +- { +- final int nswe = GeoUtils.computeNswe(prevX, prevY, curX, curY); +- if (!checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, nswe)) +- { +- // Can't move, return previous location. +- return new Location(getWorldX(prevX), getWorldY(prevY), prevZ); +- } +- } +- +- prevX = curX; +- prevY = curY; +- prevZ = curZ; ++ return new Location(tx, ty, tz); + } + +- if (hasGeoPos(prevX, prevY) && (prevZ != tz)) ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coordinates ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) + { +- // Different floors, return start location. +- return new Location(x, y, z); ++ return new Location(tx, ty, tz); + } + +- return new Location(tx, ty, tz); ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // target coordinates reached ++ if ((gox == gtx) && (goy == gty) && (goz == gtz)) ++ { ++ return new Location(tx, ty, tz); ++ } ++ ++ // perform geodata check ++ return checkMove(gox, goy, goz, gtx, gty, gtz, instanceId); + } + + /** +- * 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 fromZvalue 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 toZvalue the Z coordinate to end checking at +- * @param instanceId the instance +- * @return {@code true} if the character at start coordinates can move to end coordinates, {@code false} otherwise ++ * With this method you can check if a position is visible or can be reached by beeline movement.
++ * 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 gox : origin X geodata coordinate ++ * @param goy : origin Y geodata coordinate ++ * @param goz : origin Z geodata coordinate ++ * @param gtx : target X geodata coordinate ++ * @param gty : target Y geodata coordinate ++ * @param gtz : target Z geodata coordinate ++ * @param instanceId ++ * @return {@link GeoLocation} : The last allowed point of movement. + */ +- public boolean canMoveToTarget(int fromX, int fromY, int fromZvalue, int toX, int toY, int toZvalue, int instanceId) ++ protected final GeoLocation checkMove(int gox, int goy, int goz, int gtx, int gty, int gtz, int instanceId) + { +- final int geoX = getGeoX(fromX); +- final int geoY = getGeoY(fromY); +- final int fromZ = getNearestZ(geoX, geoY, fromZvalue); +- final int tGeoX = getGeoX(toX); +- final int tGeoY = getGeoY(toY); +- final int toZ = getNearestZ(tGeoX, tGeoY, toZvalue); +- +- if (DoorData.getInstance().checkIfDoorsBetween(fromX, fromY, fromZ, toX, toY, toZ, instanceId, false)) ++ if (DoorData.getInstance().checkIfDoorsBetween(gox, goy, goz, gtx, gty, gtz, instanceId, false)) + { +- return false; ++ return new GeoLocation(gox, goy, goz); + } +- if (FenceData.getInstance().checkIfFenceBetween(fromX, fromY, fromZ, toX, toY, toZ, instanceId)) ++ if (FenceData.getInstance().checkIfFenceBetween(gox, goy, goz, gtx, gty, gtz, instanceId)) + { +- return false; ++ return new GeoLocation(gox, goy, goz); + } + +- 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 = fromZ; ++ // get X delta, signum and direction flag ++ final int dx = Math.abs(gtx - gox); ++ final int sx = gox < gtx ? 1 : -1; ++ final byte dirX = sx > 0 ? GeoStructure.CELL_FLAG_E : GeoStructure.CELL_FLAG_W; + +- while (pointIter.next()) ++ // get Y delta, signum and direction flag ++ final int dy = Math.abs(gty - goy); ++ final int sy = goy < gty ? 1 : -1; ++ final byte dirY = sy > 0 ? GeoStructure.CELL_FLAG_S : GeoStructure.CELL_FLAG_N; ++ ++ // get direction flag for diagonal movement ++ final byte dirXY = getDirXY(dirX, dirY); ++ ++ // delta, determines axis to move on (+..X axis, -..Y axis) ++ int d = dx - dy; ++ ++ // NSWE direction of movement ++ byte direction; ++ ++ // load pointer coordinates ++ int gpx = gox; ++ int gpy = goy; ++ int gpz = goz; ++ ++ // load next pointer ++ int nx = gpx; ++ int ny = gpy; ++ ++ // loop ++ int count = 0; ++ while (count++ < Config.MAX_ITERATIONS) + { +- final int curX = pointIter.x(); +- final int curY = pointIter.y(); +- final int curZ = getNearestZ(curX, curY, prevZ); ++ direction = 0; + +- if (hasGeoPos(prevX, prevY)) ++ // calculate next point coordinates ++ final int e2 = 2 * d; ++ if ((e2 > -dy) && (e2 < dx)) + { +- final int nswe = GeoUtils.computeNswe(prevX, prevY, curX, curY); +- if (!checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, nswe)) ++ d -= dy; ++ d += dx; ++ nx += sx; ++ ny += sy; ++ direction |= dirXY; ++ } ++ else if (e2 > -dy) ++ { ++ d -= dy; ++ nx += sx; ++ direction |= dirX; ++ } ++ else if (e2 < dx) ++ { ++ d += dx; ++ ny += sy; ++ direction |= dirY; ++ } ++ ++ // obstacle found, return ++ if ((getNsweNearest(gpx, gpy, gpz) & direction) == 0) ++ { ++ return new GeoLocation(gpx, gpy, gpz); ++ } ++ ++ // update pointer coordinates ++ gpx = nx; ++ gpy = ny; ++ gpz = getHeightNearest(nx, ny, gpz); ++ ++ // target coordinates reached ++ if ((gpx == gtx) && (gpy == gty)) ++ { ++ if (gpz == gtz) + { +- return false; ++ // path found, Z coordinates are okay, return target point ++ return new GeoLocation(gtx, gty, gtz); + } ++ ++ // path found, Z coordinates are not okay, return last good point ++ return new GeoLocation(gpx, gpy, gpz); + } ++ } ++ ++ return new GeoLocation(gox, goy, goz); ++ } ++ ++ /** ++ * Returns diagonal NSWE flag format of combined two NSWE flags. ++ * @param dirX : X direction NSWE flag ++ * @param dirY : Y direction NSWE flag ++ * @return byte : NSWE flag of combined direction ++ */ ++ private static byte getDirXY(byte dirX, byte dirY) ++ { ++ // check axis directions ++ if (dirY == GeoStructure.CELL_FLAG_N) ++ { ++ if (dirX == GeoStructure.CELL_FLAG_W) ++ { ++ return GeoStructure.CELL_FLAG_NW; ++ } + +- prevX = curX; +- prevY = curY; +- prevZ = curZ; ++ return GeoStructure.CELL_FLAG_NE; + } + +- if (hasGeoPos(prevX, prevY) && (prevZ != toZ)) ++ if (dirX == GeoStructure.CELL_FLAG_W) + { +- // Different floors. +- return false; ++ return GeoStructure.CELL_FLAG_SW; + } + +- return true; ++ return GeoStructure.CELL_FLAG_SE; + } + ++ /** ++ * 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 ++ */ ++ public List findPath(int ox, int oy, int oz, int tx, int ty, int tz, int instanceId) ++ { ++ return null; ++ } ++ ++ /** ++ * Returns the instance of the {@link GeoEngine}. ++ * @return {@link GeoEngine} : The instance. ++ */ + public static GeoEngine getInstance() + { + return SingletonHolder.INSTANCE; +@@ -742,6 +933,6 @@ + + private static class SingletonHolder + { +- protected static final GeoEngine INSTANCE = new GeoEngine(); ++ protected static final GeoEngine INSTANCE = Config.PATHFINDING ? new GeoEnginePathfinding() : new GeoEngine(); + } +-} ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/GeoEnginePathfinding.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/GeoEnginePathfinding.java (revision 8319) ++++ java/org/l2jmobius/gameserver/geoengine/GeoEnginePathfinding.java (working copy) +@@ -20,87 +20,83 @@ + import java.util.LinkedList; + import java.util.List; + import java.util.ListIterator; +-import java.util.logging.Level; +-import java.util.logging.Logger; + + import org.l2jmobius.Config; +-import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; +-import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +-import org.l2jmobius.gameserver.geoengine.pathfinding.CellNode; +-import org.l2jmobius.gameserver.geoengine.pathfinding.CellNodeBuffer; +-import org.l2jmobius.gameserver.geoengine.pathfinding.NodeLoc; +-import org.l2jmobius.gameserver.model.World; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoLocation; ++import org.l2jmobius.gameserver.geoengine.pathfinding.Node; ++import org.l2jmobius.gameserver.geoengine.pathfinding.NodeBuffer; ++import org.l2jmobius.gameserver.model.Location; + + /** +- * @author -Nemesiss- ++ * @author Hasha + */ +-public class GeoEnginePathfinding ++final class GeoEnginePathfinding extends GeoEngine + { +- private static final Logger LOGGER = Logger.getLogger(GeoEnginePathfinding.class.getName()); ++ // pre-allocated buffers ++ private final BufferHolder[] _buffers; + +- private BufferInfo[] _buffers; +- + protected GeoEnginePathfinding() + { +- try ++ super(); ++ ++ final String[] array = Config.PATHFIND_BUFFERS.split(";"); ++ _buffers = new BufferHolder[array.length]; ++ int count = 0; ++ for (int i = 0; i < array.length; i++) + { +- final String[] array = Config.PATHFIND_BUFFERS.split(";"); ++ final String buf = array[i]; ++ final String[] args = buf.split("x"); + +- _buffers = new BufferInfo[array.length]; +- +- String buf; +- String[] args; +- for (int i = 0; i < array.length; i++) ++ try + { +- buf = array[i]; +- args = buf.split("x"); +- if (args.length != 2) +- { +- throw new Exception("Invalid buffer definition: " + buf); +- } +- +- _buffers[i] = new BufferInfo(Integer.parseInt(args[0]), Integer.parseInt(args[1])); ++ final int size = Integer.parseInt(args[1]); ++ count += size; ++ _buffers[i] = new BufferHolder(Integer.parseInt(args[0]), size); + } ++ catch (Exception e) ++ { ++ LOGGER.warning("GeoEnginePathfinding: Can not load buffer setting: " + buf); ++ } + } +- catch (Exception e) +- { +- LOGGER.log(Level.WARNING, "CellPathFinding: Problem during buffer init: " + e.getMessage(), e); +- throw new Error("CellPathFinding: load aborted"); +- } ++ ++ LOGGER.info("GeoEnginePathfinding: Loaded " + count + " node buffers."); + } + +- public boolean pathNodesExist(short regionoffset) ++ @Override ++ public List findPath(int ox, int oy, int oz, int tx, int ty, int tz, int instanceId) + { +- return false; +- } +- +- public List findPath(int x, int y, int z, int tx, int ty, int tz, int instanceId) +- { +- final int gx = GeoEngine.getInstance().getGeoX(x); +- final int gy = GeoEngine.getInstance().getGeoY(y); +- if (!GeoEngine.getInstance().hasGeo(x, y)) ++ // get origin and check existing geo coords ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) + { + 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)) ++ ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coords ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) + { + 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)))); ++ ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // Prepare buffer for pathfinding calculations ++ final NodeBuffer buffer = getBuffer(64 + (2 * Math.max(Math.abs(gox - gtx), Math.abs(goy - gty)))); + if (buffer == null) + { + return null; + } + +- List path = null; ++ // find path ++ List path = null; + try + { +- final CellNode result = buffer.findPath(gx, gy, gz, gtx, gty, gtz); +- ++ final Node result = buffer.findPath(gox, goy, goz, gtx, gty, gtz); + if (result == null) + { + return null; +@@ -124,33 +120,45 @@ + return path; + } + +- int currentX, currentY, currentZ; +- ListIterator middlePoint; ++ // get path list iterator ++ final ListIterator point = path.listIterator(); + +- middlePoint = path.listIterator(); +- currentX = x; +- currentY = y; +- currentZ = z; ++ // get node A (origin) ++ int nodeAx = gox; ++ int nodeAy = goy; ++ short nodeAz = goz; + +- while (middlePoint.hasNext()) ++ // get node B ++ GeoLocation nodeB = (GeoLocation) point.next(); ++ ++ // iterate thought the path to optimize it ++ int count = 0; ++ while (point.hasNext() && (count++ < Config.MAX_ITERATIONS)) + { +- final AbstractNodeLoc locMiddle = middlePoint.next(); +- if (!middlePoint.hasNext()) +- { +- break; +- } ++ // get node C ++ final GeoLocation nodeC = (GeoLocation) path.get(point.nextIndex()); + +- final AbstractNodeLoc locEnd = path.get(middlePoint.nextIndex()); +- if (GeoEngine.getInstance().canMoveToTarget(currentX, currentY, currentZ, locEnd.getX(), locEnd.getY(), locEnd.getZ(), instanceId)) ++ // check movement from node A to node C ++ final GeoLocation loc = checkMove(nodeAx, nodeAy, nodeAz, nodeC.getGeoX(), nodeC.getGeoY(), nodeC.getZ(), instanceId); ++ if ((loc.getGeoX() == nodeC.getGeoX()) && (loc.getGeoY() == nodeC.getGeoY())) + { +- middlePoint.remove(); ++ // can move from node A to node C ++ ++ // remove node B ++ point.remove(); + } + else + { +- currentX = locMiddle.getX(); +- currentY = locMiddle.getY(); +- currentZ = locMiddle.getZ(); ++ // can not move from node A to node C ++ ++ // set node A (node B is part of path, update A coordinates) ++ nodeAx = nodeB.getGeoX(); ++ nodeAy = nodeB.getGeoY(); ++ nodeAz = (short) nodeB.getZ(); + } ++ ++ // set node B ++ nodeB = (GeoLocation) point.next(); + } + + return path; +@@ -157,144 +165,102 @@ + } + + /** +- * Convert geodata position to pathnode position +- * @param geo_pos +- * @return pathnode position ++ * Create list of node locations as result of calculated buffer node tree. ++ * @param node : the entry point ++ * @return List : list of node location + */ +- public short getNodePos(int geo_pos) ++ private static List constructPath(Node node) + { +- return (short) (geo_pos >> 3); // OK? +- } +- +- /** +- * Convert node position to pathnode block position +- * @param node_pos +- * @return pathnode block position (0...255) +- */ +- public short getNodeBlock(int node_pos) +- { +- return (short) (node_pos % 256); +- } +- +- public byte getRegionX(int node_pos) +- { +- return (byte) ((node_pos >> 8) + World.TILE_X_MIN); +- } +- +- public byte getRegionY(int node_pos) +- { +- return (byte) ((node_pos >> 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 node_x rx +- * @return +- */ +- public int calculateWorldX(short node_x) +- { +- return World.MAP_MIN_X + (node_x * 128) + 48; +- } +- +- /** +- * Convert pathnode y to World y position +- * @param node_y +- * @return +- */ +- public int calculateWorldY(short node_y) +- { +- return World.MAP_MIN_Y + (node_y * 128) + 48; +- } +- +- private List constructPath(AbstractNode nodeValue) +- { +- final LinkedList path = new LinkedList<>(); +- int previousDirectionX = Integer.MIN_VALUE; +- int previousDirectionY = Integer.MIN_VALUE; +- int directionX, directionY; ++ // create empty list ++ final LinkedList list = new LinkedList<>(); + +- AbstractNode node = nodeValue; +- while (node.getParent() != null) ++ // set direction X/Y ++ int dx = 0; ++ int dy = 0; ++ ++ // get target parent ++ Node target = node; ++ Node parent = target.getParent(); ++ ++ // while parent exists ++ int count = 0; ++ while ((parent != null) && (count++ < Config.MAX_ITERATIONS)) + { +- directionX = node.getLoc().getNodeX() - node.getParent().getLoc().getNodeX(); +- directionY = node.getLoc().getNodeY() - node.getParent().getLoc().getNodeY(); ++ // get parent <> target direction X/Y ++ final int nx = parent.getLoc().getGeoX() - target.getLoc().getGeoX(); ++ final int ny = parent.getLoc().getGeoY() - target.getLoc().getGeoY(); + +- // only add a new route point if moving direction changes +- if ((directionX != previousDirectionX) || (directionY != previousDirectionY)) ++ // direction has changed? ++ if ((dx != nx) || (dy != ny)) + { +- previousDirectionX = directionX; +- previousDirectionY = directionY; ++ // add node to the beginning of the list ++ list.addFirst(target.getLoc()); + +- path.addFirst(node.getLoc()); +- node.setLoc(null); ++ // update direction X/Y ++ dx = nx; ++ dy = ny; + } + +- node = node.getParent(); ++ // move to next node, set target and get its parent ++ target = parent; ++ parent = target.getParent(); + } + +- return path; ++ // return list ++ return list; + } + +- private CellNodeBuffer alloc(int size) ++ /** ++ * Provides optimize selection of the buffer. When all pre-initialized buffer are locked, creates new buffer. ++ * @param size : pre-calculated minimal required size ++ * @return NodeBuffer : buffer ++ */ ++ private final NodeBuffer getBuffer(int size) + { +- CellNodeBuffer current = null; +- for (BufferInfo i : _buffers) ++ NodeBuffer current = null; ++ for (BufferHolder holder : _buffers) + { +- if (i.mapSize >= size) ++ // Find proper size of buffer ++ if (holder._size < size) + { +- for (CellNodeBuffer buf : i.buffer) ++ continue; ++ } ++ ++ // Find unlocked NodeBuffer ++ for (NodeBuffer buffer : holder._buffer) ++ { ++ if (!buffer.isLocked()) + { +- if (buf.lock()) +- { +- current = buf; +- break; +- } ++ continue; + } +- if (current != null) +- { +- break; +- } + +- // not found, allocate temporary buffer +- current = new CellNodeBuffer(i.mapSize); +- current.lock(); +- if (i.buffer.size() < i.count) +- { +- i.buffer.add(current); +- break; +- } ++ return buffer; + } ++ ++ // NodeBuffer not found, allocate temporary buffer ++ current = new NodeBuffer(holder._size); ++ current.isLocked(); + } + + return current; + } + +- private static final class BufferInfo ++ /** ++ * NodeBuffer container with specified size and count of separate buffers. ++ */ ++ private static final class BufferHolder + { +- final int mapSize; +- final int count; +- ArrayList buffer; ++ final int _size; ++ List _buffer; + +- public BufferInfo(int size, int cnt) ++ public BufferHolder(int size, int count) + { +- mapSize = size; +- count = cnt; +- buffer = new ArrayList<>(count); ++ _size = size; ++ _buffer = new ArrayList<>(count); ++ for (int i = 0; i < count; i++) ++ { ++ _buffer.add(new NodeBuffer(size)); ++ } + } + } +- +- public static GeoEnginePathfinding getInstance() +- { +- return SingletonHolder.INSTANCE; +- } +- +- private static class SingletonHolder +- { +- protected static final GeoEnginePathfinding INSTANCE = new GeoEnginePathfinding(); +- } +-} ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java (working copy) +@@ -0,0 +1,197 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++import java.io.IOException; ++ ++/** ++ * @author Hasha ++ */ ++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 height of cell, which is closest to given coordinates.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @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 getHeightNearestOriginal(int geoX, int geoY, int worldZ); ++ ++ /** ++ * Returns the height of cell, which is first above 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, above given coordinates. ++ */ ++ public abstract short getHeightAbove(int geoX, int geoY, int worldZ); ++ ++ /** ++ * Returns the height of cell, which is first below 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, below given coordinates. ++ */ ++ public abstract short getHeightBelow(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 the NSWE flag byte of cell, which is closest to given coordinates.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @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 getNsweNearestOriginal(int geoX, int geoY, int worldZ); ++ ++ /** ++ * Returns the NSWE flag byte of cell, which is first above 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 getNsweAbove(int geoX, int geoY, int worldZ); ++ ++ /** ++ * Returns the NSWE flag byte of cell, which is first below 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 getNsweBelow(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 above given coordinates.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @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 getIndexAboveOriginal(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 index to data of the cell, which is first layer below given coordinates.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @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 getIndexBelowOriginal(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 height of cell given by cell index.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @param index : Index of the cell. ++ * @return short : Cell geodata Z coordinate, below given coordinates. ++ */ ++ public abstract short getHeightOriginal(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); ++ ++ /** ++ * Returns the NSWE flag byte of cell given by cell index.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @param index : Index of the cell. ++ * @return short : Cell geodata Z coordinate, below given coordinates. ++ */ ++ public abstract byte getNsweOriginal(int index); ++ ++ /** ++ * Sets the NSWE flag byte of cell given by cell index. ++ * @param index : Index of the cell. ++ * @param nswe : New NSWE flag byte. ++ */ ++ public abstract void setNswe(int index, byte nswe); ++ ++ /** ++ * Saves the block in L2D format to {@link BufferedOutputStream}. Used only for L2D geodata conversion. ++ * @param stream : The stream. ++ * @throws IOException : Can't save the block to steam. ++ */ ++ public abstract void saveBlock(BufferedOutputStream stream) throws IOException; ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java (working copy) +@@ -0,0 +1,252 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++import java.io.IOException; ++import java.nio.ByteBuffer; ++ ++/** ++ * @author Hasha ++ */ ++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. ++ * @param format : GeoFormat specifying format of loaded data. ++ */ ++ public BlockComplex(ByteBuffer bb, GeoFormat format) ++ { ++ // initialize buffer ++ _buffer = new byte[GeoStructure.BLOCK_CELLS * 3]; ++ ++ // load data ++ for (int i = 0; i < GeoStructure.BLOCK_CELLS; i++) ++ { ++ if (format != GeoFormat.L2D) ++ { ++ // 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); ++ } ++ else ++ { ++ // get nswe ++ final byte nswe = bb.get(); ++ _buffer[i * 3] = nswe; ++ ++ // get height ++ final short height = bb.getShort(); ++ _buffer[(i * 3) + 1] = (byte) (height & 0x00FF); ++ _buffer[(i * 3) + 2] = (byte) (height >> 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 short getHeightNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getHeightNearest(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeightAbove(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 short height = (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ ++ // check and return height ++ return height > worldZ ? height : Short.MIN_VALUE; ++ } ++ ++ @Override ++ public short getHeightBelow(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 short height = (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ ++ // check and return height ++ return height < worldZ ? height : Short.MAX_VALUE; ++ } ++ ++ @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 byte getNsweNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getNsweNearest(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public byte getNsweAbove(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 ? _buffer[index] : 0; ++ } ++ ++ @Override ++ public byte getNsweBelow(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 ? _buffer[index] : 0; ++ } ++ ++ @Override ++ public 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 getIndexAboveOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexAbove(geoX, geoY, worldZ); ++ } ++ ++ @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 int getIndexBelowOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexBelow(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeight(int index) ++ { ++ return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ } ++ ++ @Override ++ public short getHeightOriginal(int index) ++ { ++ return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ } ++ ++ @Override ++ public byte getNswe(int index) ++ { ++ return _buffer[index]; ++ } ++ ++ @Override ++ public byte getNsweOriginal(int index) ++ { ++ return _buffer[index]; ++ } ++ ++ @Override ++ public void setNswe(int index, byte nswe) ++ { ++ _buffer[index] = nswe; ++ } ++ ++ @Override ++ public void saveBlock(BufferedOutputStream stream) throws IOException ++ { ++ // write block type ++ stream.write(GeoStructure.TYPE_COMPLEX_L2D); ++ ++ // write block data ++ stream.write(_buffer, 0, GeoStructure.BLOCK_CELLS * 3); ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java (working copy) +@@ -0,0 +1,176 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++import java.io.IOException; ++import java.nio.ByteBuffer; ++ ++/** ++ * @author Hasha ++ */ ++public class BlockFlat extends ABlock ++{ ++ protected final short _height; ++ protected byte _nswe; ++ ++ /** ++ * Creates FlatBlock. ++ * @param bb : Input byte buffer. ++ * @param format : GeoFormat specifying format of loaded data. ++ */ ++ public BlockFlat(ByteBuffer bb, GeoFormat format) ++ { ++ _height = bb.getShort(); ++ _nswe = format != GeoFormat.L2D ? 0x0F : (byte) (0xFF); ++ if (format == GeoFormat.L2OFF) ++ { ++ bb.getShort(); ++ } ++ } ++ ++ @Override ++ public boolean hasGeoPos() ++ { ++ return true; ++ } ++ ++ @Override ++ public short getHeightNearest(int geoX, int geoY, int worldZ) ++ { ++ return _height; ++ } ++ ++ @Override ++ public short getHeightNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return _height; ++ } ++ ++ @Override ++ public short getHeightAbove(int geoX, int geoY, int worldZ) ++ { ++ // check and return height ++ return _height > worldZ ? _height : Short.MIN_VALUE; ++ } ++ ++ @Override ++ public short getHeightBelow(int geoX, int geoY, int worldZ) ++ { ++ // check and return height ++ return _height < worldZ ? _height : Short.MAX_VALUE; ++ } ++ ++ @Override ++ public byte getNsweNearest(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweAbove(int geoX, int geoY, int worldZ) ++ { ++ // check height and return nswe ++ return _height > worldZ ? _nswe : 0; ++ } ++ ++ @Override ++ public byte getNsweBelow(int geoX, int geoY, int worldZ) ++ { ++ // check height and return nswe ++ return _height < worldZ ? _nswe : 0; ++ } ++ ++ @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 getIndexAboveOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexAbove(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public int getIndexBelow(int geoX, int geoY, int worldZ) ++ { ++ // check height and return index ++ return _height < worldZ ? 0 : -1; ++ } ++ ++ @Override ++ public int getIndexBelowOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexBelow(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeight(int index) ++ { ++ return _height; ++ } ++ ++ @Override ++ public short getHeightOriginal(int index) ++ { ++ return _height; ++ } ++ ++ @Override ++ public byte getNswe(int index) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweOriginal(int index) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public void setNswe(int index, byte nswe) ++ { ++ _nswe = nswe; ++ } ++ ++ @Override ++ public void saveBlock(BufferedOutputStream stream) throws IOException ++ { ++ // write block type ++ stream.write(GeoStructure.TYPE_FLAT_L2D); ++ ++ // write height ++ stream.write((byte) (_height & 0x00FF)); ++ stream.write((byte) (_height >> 8)); ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java (working copy) +@@ -0,0 +1,465 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++import java.io.IOException; ++import java.nio.ByteBuffer; ++import java.nio.ByteOrder; ++import java.util.Arrays; ++ ++/** ++ * @author Hasha ++ */ ++public class BlockMultilayer extends ABlock ++{ ++ private static final int MAX_LAYERS = Byte.MAX_VALUE; ++ ++ private static ByteBuffer _temp; ++ ++ /** ++ * Initializes the temporarily buffer. ++ */ ++ public static void initialize() ++ { ++ // initialize temporarily buffer and sorting mechanism ++ _temp = ByteBuffer.allocate(GeoStructure.BLOCK_CELLS * MAX_LAYERS * 3); ++ _temp.order(ByteOrder.LITTLE_ENDIAN); ++ } ++ ++ /** ++ * Releases temporarily buffer. ++ */ ++ public static void release() ++ { ++ _temp = null; ++ } ++ ++ protected byte[] _buffer; ++ ++ /** ++ * Implicit constructor for children class. ++ */ ++ protected BlockMultilayer() ++ { ++ _buffer = null; ++ } ++ ++ /** ++ * Creates MultilayerBlock. ++ * @param bb : Input byte buffer. ++ * @param format : GeoFormat specifying format of loaded data. ++ */ ++ public BlockMultilayer(ByteBuffer bb, GeoFormat format) ++ { ++ // 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 = format != GeoFormat.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++) ++ { ++ if (format != GeoFormat.L2D) ++ { ++ // get data ++ final short data = bb.getShort(); ++ ++ // add nswe and height ++ _temp.put((byte) (data & 0x000F)); ++ _temp.putShort((short) ((short) (data & 0xFFF0) >> 1)); ++ } ++ else ++ { ++ // add nswe ++ _temp.put(bb.get()); ++ ++ // add height ++ _temp.putShort(bb.getShort()); ++ } ++ } ++ } ++ ++ // 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 short getHeightNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getHeightNearest(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeightAbove(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 height ++ if (height > worldZ) ++ { ++ return (short) height; ++ } ++ ++ // move index to next layer ++ index -= 3; ++ } ++ ++ // none layer found, return minimum value ++ return Short.MIN_VALUE; ++ } ++ ++ @Override ++ public short getHeightBelow(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 height ++ if (height < worldZ) ++ { ++ return (short) height; ++ } ++ ++ // move index to next layer ++ index += 3; ++ } ++ ++ // none layer found, return maximum value ++ return Short.MAX_VALUE; ++ } ++ ++ @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 byte getNsweNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getNsweNearest(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public byte getNsweAbove(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 nswe ++ if (height > worldZ) ++ { ++ return _buffer[index]; ++ } ++ ++ // move index to next layer ++ index -= 3; ++ } ++ ++ // none layer found, block movement ++ return 0; ++ } ++ ++ @Override ++ public byte getNsweBelow(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 nswe ++ if (height < worldZ) ++ { ++ return _buffer[index]; ++ } ++ ++ // move index to next layer ++ index += 3; ++ } ++ ++ // none layer found, block movement ++ return 0; ++ } ++ ++ @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 first layer data (first from bottom) ++ byte layers = _buffer[index++]; ++ ++ // loop though all cell layers, find closest layer ++ 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; ++ } ++ ++ // none layer found ++ return -1; ++ } ++ ++ @Override ++ public int getIndexAboveOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexAbove(geoX, geoY, worldZ); ++ } ++ ++ @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; ++ } ++ ++ // none layer found ++ return -1; ++ } ++ ++ @Override ++ public int getIndexBelowOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexBelow(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeight(int index) ++ { ++ // get height ++ return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ } ++ ++ @Override ++ public short getHeightOriginal(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]; ++ } ++ ++ @Override ++ public byte getNsweOriginal(int index) ++ { ++ // get nswe ++ return _buffer[index]; ++ } ++ ++ @Override ++ public void setNswe(int index, byte nswe) ++ { ++ // set nswe ++ _buffer[index] = nswe; ++ } ++ ++ @Override ++ public void saveBlock(BufferedOutputStream stream) throws IOException ++ { ++ // write block type ++ stream.write(GeoStructure.TYPE_MULTILAYER_L2D); ++ ++ // for each cell ++ int index = 0; ++ for (int i = 0; i < GeoStructure.BLOCK_CELLS; i++) ++ { ++ // write layers count ++ final byte layers = _buffer[index++]; ++ stream.write(layers); ++ ++ // write cell data ++ stream.write(_buffer, index, layers * 3); ++ ++ // move index to next cell ++ index += layers * 3; ++ } ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java (working copy) +@@ -0,0 +1,150 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++ ++/** ++ * @author Hasha ++ */ ++public class BlockNull extends ABlock ++{ ++ private final byte _nswe; ++ ++ public BlockNull() ++ { ++ _nswe = (byte) 0xFF; ++ } ++ ++ @Override ++ public boolean hasGeoPos() ++ { ++ return false; ++ } ++ ++ @Override ++ public short getHeightNearest(int geoX, int geoY, int worldZ) ++ { ++ return (short) worldZ; ++ } ++ ++ @Override ++ public short getHeightNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return (short) worldZ; ++ } ++ ++ @Override ++ public short getHeightAbove(int geoX, int geoY, int worldZ) ++ { ++ return (short) worldZ; ++ } ++ ++ @Override ++ public short getHeightBelow(int geoX, int geoY, int worldZ) ++ { ++ return (short) worldZ; ++ } ++ ++ @Override ++ public byte getNsweNearest(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweAbove(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweBelow(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) ++ { ++ return 0; ++ } ++ ++ @Override ++ public int getIndexAboveOriginal(int geoX, int geoY, int worldZ) ++ { ++ return 0; ++ } ++ ++ @Override ++ public int getIndexBelow(int geoX, int geoY, int worldZ) ++ { ++ return 0; ++ } ++ ++ @Override ++ public int getIndexBelowOriginal(int geoX, int geoY, int worldZ) ++ { ++ return 0; ++ } ++ ++ @Override ++ public short getHeight(int index) ++ { ++ return 0; ++ } ++ ++ @Override ++ public short getHeightOriginal(int index) ++ { ++ return 0; ++ } ++ ++ @Override ++ public byte getNswe(int index) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweOriginal(int index) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public void setNswe(int index, byte nswe) ++ { ++ } ++ ++ @Override ++ public void saveBlock(BufferedOutputStream stream) ++ { ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java (revision 8319) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java (nonexistent) +@@ -1,48 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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() +- { +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/ComplexBlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/ComplexBlock.java (revision 8319) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/ComplexBlock.java (nonexistent) +@@ -1,77 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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; +- +-/** +- * @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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/FlatBlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/FlatBlock.java (revision 8319) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/FlatBlock.java (nonexistent) +@@ -1,56 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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; +- +-/** +- * @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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/GeoFormat.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/GeoFormat.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/GeoFormat.java (working copy) +@@ -0,0 +1,39 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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 Hasha ++ */ ++public enum GeoFormat ++{ ++ L2J("%d_%d.l2j"), ++ L2OFF("%d_%d_conv.dat"), ++ L2D("%d_%d.l2d"); ++ ++ private final String _filename; ++ ++ private GeoFormat(String filename) ++ { ++ _filename = filename; ++ } ++ ++ public String getFilename() ++ { ++ return _filename; ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/GeoLocation.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/GeoLocation.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/GeoLocation.java (working copy) +@@ -0,0 +1,67 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.geoengine.GeoEngine; ++import org.l2jmobius.gameserver.model.Location; ++ ++/** ++ * @author Hasha ++ */ ++public class GeoLocation extends Location ++{ ++ private byte _nswe; ++ ++ public GeoLocation(int x, int y, int z) ++ { ++ super(x, y, GeoEngine.getInstance().getHeightNearest(x, y, z)); ++ _nswe = GeoEngine.getInstance().getNsweNearest(x, y, z); ++ } ++ ++ public void set(int x, int y, short z) ++ { ++ super.setXYZ(x, y, GeoEngine.getInstance().getHeightNearest(x, y, z)); ++ _nswe = GeoEngine.getInstance().getNsweNearest(x, y, z); ++ } ++ ++ public int getGeoX() ++ { ++ return _x; ++ } ++ ++ public int getGeoY() ++ { ++ return _y; ++ } ++ ++ @Override ++ public int getX() ++ { ++ return GeoEngine.getWorldX(_x); ++ } ++ ++ @Override ++ public int getY() ++ { ++ return GeoEngine.getWorldY(_y); ++ } ++ ++ public byte getNSWE() ++ { ++ return _nswe; ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java (working copy) +@@ -0,0 +1,70 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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; ++ ++/** ++ * @author Hasha ++ */ ++public class GeoStructure ++{ ++ // cells ++ public static final byte CELL_FLAG_E = 1 << 0; ++ public static final byte CELL_FLAG_W = 1 << 1; ++ public static final byte CELL_FLAG_S = 1 << 2; ++ public static final byte CELL_FLAG_N = 1 << 3; ++ public static final byte CELL_FLAG_SE = 1 << 4; ++ public static final byte CELL_FLAG_SW = 1 << 5; ++ public static final byte CELL_FLAG_NE = 1 << 6; ++ public static final byte CELL_FLAG_NW = (byte) (1 << 7); ++ ++ public static final int CELL_HEIGHT = 8; ++ public static final int CELL_IGNORE_HEIGHT = CELL_HEIGHT * 6; ++ ++ // blocks ++ public static final byte TYPE_FLAT_L2J_L2OFF = 0; ++ public static final byte TYPE_FLAT_L2D = (byte) 0xD0; ++ public static final byte TYPE_COMPLEX_L2J = 1; ++ public static final byte TYPE_COMPLEX_L2OFF = 0x40; ++ public static final byte TYPE_COMPLEX_L2D = (byte) 0xD1; ++ 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) ++ public static final byte TYPE_MULTILAYER_L2D = (byte) 0xD2; ++ ++ 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; ++ ++ // regions ++ 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; ++ ++ // global geodata ++ private static final int GEO_REGIONS_X = ((World.TILE_X_MAX - World.TILE_X_MIN) + 1); ++ private 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 +Index: java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java (revision 8319) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java (nonexistent) +@@ -1,42 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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); +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/IGeoObject.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/IGeoObject.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/IGeoObject.java (working copy) +@@ -0,0 +1,53 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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 Hasha ++ */ ++public interface IGeoObject ++{ ++ /** ++ * Returns geodata X coordinate of the {@link IGeoObject}. ++ * @return int : Geodata X coordinate. ++ */ ++ int getGeoX(); ++ ++ /** ++ * Returns geodata Y coordinate of the {@link IGeoObject}. ++ * @return int : Geodata Y coordinate. ++ */ ++ int getGeoY(); ++ ++ /** ++ * Returns geodata Z coordinate of the {@link IGeoObject}. ++ * @return int : Geodata Z coordinate. ++ */ ++ int getGeoZ(); ++ ++ /** ++ * Returns height of the {@link IGeoObject}. ++ * @return int : Height. ++ */ ++ int getHeight(); ++ ++ /** ++ * Returns {@link IGeoObject} data. ++ * @return byte[][] : {@link IGeoObject} data. ++ */ ++ byte[][] getObjectGeoData(); ++} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java (revision 8319) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java (nonexistent) +@@ -1,47 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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(); +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/MultilayerBlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/MultilayerBlock.java (revision 8319) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/MultilayerBlock.java (nonexistent) +@@ -1,183 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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; +- +-/** +- * @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); +- +- // 1 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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/NullRegion.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/NullRegion.java (revision 8319) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/NullRegion.java (nonexistent) +@@ -1,55 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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 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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/Region.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/Region.java (revision 8319) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/Region.java (nonexistent) +@@ -1,92 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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; +- +-/** +- * @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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java (revision 8319) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java (nonexistent) +@@ -1,87 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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 T _loc; +- private AbstractNode _parent; +- +- public AbstractNode(T loc) +- { +- _loc = loc; +- } +- +- public void setParent(AbstractNode p) +- { +- _parent = p; +- } +- +- public AbstractNode getParent() +- { +- return _parent; +- } +- +- public T getLoc() +- { +- return _loc; +- } +- +- public void setLoc(T l) +- { +- _loc = l; +- } +- +- @Override +- public int hashCode() +- { +- final int prime = 31; +- int result = 1; +- result = (prime * result) + ((_loc == null) ? 0 : _loc.hashCode()); +- return result; +- } +- +- @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 +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java (revision 8319) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java (nonexistent) +@@ -1,33 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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 int getNodeX(); +- +- public abstract int getNodeY(); +-} +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNode.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNode.java (revision 8319) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNode.java (nonexistent) +@@ -1,67 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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 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 +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNodeBuffer.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNodeBuffer.java (revision 8319) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNodeBuffer.java (nonexistent) +@@ -1,348 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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.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 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) +- { +- _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(); +- } +- +- 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 void getNeighbors() +- { +- if (!_current.getLoc().canGoAll()) +- { +- 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); +- } +- +- // SouthEast +- if ((nodeE != null) && (nodeS != null)) +- { +- if (nodeE.getLoc().canGoSouth() && nodeS.getLoc().canGoEast()) +- { +- addNode(x + 1, y + 1, z, true); +- } +- } +- +- // SouthWest +- if ((nodeS != null) && (nodeW != null)) +- { +- if (nodeW.getLoc().canGoSouth() && nodeS.getLoc().canGoWest()) +- { +- addNode(x - 1, y + 1, z, true); +- } +- } +- +- // NorthEast +- if ((nodeN != null) && (nodeE != null)) +- { +- if (nodeE.getLoc().canGoNorth() && nodeN.getLoc().canGoEast()) +- { +- addNode(x + 1, y - 1, z, true); +- } +- } +- +- // NorthWest +- if ((nodeN != null) && (nodeW != null)) +- { +- if (nodeW.getLoc().canGoNorth() && nodeN.getLoc().canGoWest()) +- { +- addNode(x - 1, y - 1, z, true); +- } +- } +- } +- +- private 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(); +- // reinit node if needed +- if (result.getLoc() != null) +- { +- result.getLoc().set(x, y, z); +- } +- else +- { +- result.setLoc(new NodeLoc(x, y, z)); +- } +- } +- +- return result; +- } +- +- private 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 boolean isHighWeight(int x, int y, int z) +- { +- final CellNode result = getNode(x, y, z); +- if (result == null) +- { +- return true; +- } +- +- if (!result.getLoc().canGoAll()) +- { +- return true; +- } +- if (Math.abs(result.getLoc().getZ() - z) > 16) +- { +- return true; +- } +- +- return false; +- } +- +- private 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 +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java (working copy) +@@ -0,0 +1,87 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.geodata.GeoLocation; ++ ++/** ++ * @author Hasha ++ */ ++public class Node ++{ ++ // node coords and nswe flag ++ private GeoLocation _loc; ++ ++ // node parent (for reverse path construction) ++ private Node _parent; ++ // node child (for moving over nodes during iteration) ++ private Node _child; ++ ++ // node G cost (movement cost = parent movement cost + current movement cost) ++ private double _cost = -1000; ++ ++ public void setLoc(int x, int y, int z) ++ { ++ _loc = new GeoLocation(x, y, z); ++ } ++ ++ public GeoLocation getLoc() ++ { ++ return _loc; ++ } ++ ++ public void setParent(Node parent) ++ { ++ _parent = parent; ++ } ++ ++ public Node getParent() ++ { ++ return _parent; ++ } ++ ++ public void setChild(Node child) ++ { ++ _child = child; ++ } ++ ++ public Node getChild() ++ { ++ return _child; ++ } ++ ++ public void setCost(double cost) ++ { ++ _cost = cost; ++ } ++ ++ public double getCost() ++ { ++ return _cost; ++ } ++ ++ public void free() ++ { ++ // reset node location ++ _loc = null; ++ ++ // reset node parent, child and cost ++ _parent = null; ++ _child = null; ++ _cost = -1000; ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java (working copy) +@@ -0,0 +1,306 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.concurrent.locks.ReentrantLock; ++ ++import org.l2jmobius.Config; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; ++ ++/** ++ * @author DS, Hasha; Credits to Diamond ++ */ ++public class NodeBuffer ++{ ++ private final ReentrantLock _lock = new ReentrantLock(); ++ private final int _size; ++ private final Node[][] _buffer; ++ ++ // center coordinates ++ private int _cx = 0; ++ private int _cy = 0; ++ ++ // target coordinates ++ private int _gtx = 0; ++ private int _gty = 0; ++ private short _gtz = 0; ++ ++ private Node _current = null; ++ ++ /** ++ * Constructor of NodeBuffer. ++ * @param size : one dimension size of buffer ++ */ ++ public NodeBuffer(int size) ++ { ++ // set size ++ _size = size; ++ ++ // initialize buffer ++ _buffer = new Node[size][size]; ++ for (int x = 0; x < size; x++) ++ { ++ for (int y = 0; y < size; y++) ++ { ++ _buffer[x][y] = 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 Node : first node of path ++ */ ++ public Node findPath(int gox, int goy, short goz, int gtx, int gty, short gtz) ++ { ++ // set coordinates (middle of the line (gox,goy) - (gtx,gty), will be in the center of the buffer) ++ _cx = gox + ((gtx - gox - _size) / 2); ++ _cy = goy + ((gty - goy - _size) / 2); ++ ++ _gtx = gtx; ++ _gty = gty; ++ _gtz = gtz; ++ ++ _current = getNode(gox, goy, goz); ++ _current.setCost(getCostH(gox, goy, goz)); ++ ++ int count = 0; ++ do ++ { ++ // reached target? ++ if ((_current.getLoc().getGeoX() == _gtx) && (_current.getLoc().getGeoY() == _gty) && (Math.abs(_current.getLoc().getZ() - _gtz) < 8)) ++ { ++ return _current; ++ } ++ ++ // expand current node ++ expand(); ++ ++ // move pointer ++ _current = _current.getChild(); ++ } ++ while ((_current != null) && (count++ < Config.MAX_ITERATIONS)); ++ ++ return null; ++ } ++ ++ public boolean isLocked() ++ { ++ return _lock.tryLock(); ++ } ++ ++ public void free() ++ { ++ _current = null; ++ ++ for (Node[] nodes : _buffer) ++ { ++ for (Node node : nodes) ++ { ++ if (node.getLoc() != null) ++ { ++ node.free(); ++ } ++ } ++ } ++ ++ _lock.unlock(); ++ } ++ ++ /** ++ * Check _current Node and add its neighbors to the buffer. ++ */ ++ private final void expand() ++ { ++ // can't move anywhere, don't expand ++ final byte nswe = _current.getLoc().getNSWE(); ++ if (nswe == 0) ++ { ++ return; ++ } ++ ++ // get geo coords of the node to be expanded ++ final int x = _current.getLoc().getGeoX(); ++ final int y = _current.getLoc().getGeoY(); ++ final short z = (short) _current.getLoc().getZ(); ++ ++ // can move north, expand ++ if ((nswe & GeoStructure.CELL_FLAG_N) != 0) ++ { ++ addNode(x, y - 1, z, Config.BASE_WEIGHT); ++ } ++ ++ // can move south, expand ++ if ((nswe & GeoStructure.CELL_FLAG_S) != 0) ++ { ++ addNode(x, y + 1, z, Config.BASE_WEIGHT); ++ } ++ ++ // can move west, expand ++ if ((nswe & GeoStructure.CELL_FLAG_W) != 0) ++ { ++ addNode(x - 1, y, z, Config.BASE_WEIGHT); ++ } ++ ++ // can move east, expand ++ if ((nswe & GeoStructure.CELL_FLAG_E) != 0) ++ { ++ addNode(x + 1, y, z, Config.BASE_WEIGHT); ++ } ++ ++ // can move north-west, expand ++ if ((nswe & GeoStructure.CELL_FLAG_NW) != 0) ++ { ++ addNode(x - 1, y - 1, z, Config.DIAGONAL_WEIGHT); ++ } ++ ++ // can move north-east, expand ++ if ((nswe & GeoStructure.CELL_FLAG_NE) != 0) ++ { ++ addNode(x + 1, y - 1, z, Config.DIAGONAL_WEIGHT); ++ } ++ ++ // can move south-west, expand ++ if ((nswe & GeoStructure.CELL_FLAG_SW) != 0) ++ { ++ addNode(x - 1, y + 1, z, Config.DIAGONAL_WEIGHT); ++ } ++ ++ // can move south-east, expand ++ if ((nswe & GeoStructure.CELL_FLAG_SE) != 0) ++ { ++ addNode(x + 1, y + 1, z, Config.DIAGONAL_WEIGHT); ++ } ++ } ++ ++ /** ++ * Returns node, if it exists in buffer. ++ * @param x : node X coord ++ * @param y : node Y coord ++ * @param z : node Z coord ++ * @return Node : node, if exits in buffer ++ */ ++ private final Node getNode(int x, int y, short z) ++ { ++ // check node X out of coordinates ++ final int ix = x - _cx; ++ if ((ix < 0) || (ix >= _size)) ++ { ++ return null; ++ } ++ ++ // check node Y out of coordinates ++ final int iy = y - _cy; ++ if ((iy < 0) || (iy >= _size)) ++ { ++ return null; ++ } ++ ++ // get node ++ final Node result = _buffer[ix][iy]; ++ ++ // check and update ++ if (result.getLoc() == null) ++ { ++ result.setLoc(x, y, z); ++ } ++ ++ // return node ++ return result; ++ } ++ ++ /** ++ * Add node given by coordinates to the buffer. ++ * @param x : geo X coord ++ * @param y : geo Y coord ++ * @param z : geo Z coord ++ * @param weight : weight of movement to new node ++ */ ++ private final void addNode(int x, int y, short z, int weight) ++ { ++ // get node to be expanded ++ final Node node = getNode(x, y, z); ++ if (node == null) ++ { ++ return; ++ } ++ ++ // Z distance between nearby cells is higher than cell size ++ if (node.getLoc().getZ() > (z + (2 * GeoStructure.CELL_HEIGHT))) ++ { ++ return; ++ } ++ ++ // node was already expanded, return ++ if (node.getCost() >= 0) ++ { ++ return; ++ } ++ ++ node.setParent(_current); ++ if (node.getLoc().getNSWE() != (byte) 0xFF) ++ { ++ node.setCost(getCostH(x, y, node.getLoc().getZ()) + (weight * Config.OBSTACLE_MULTIPLIER)); ++ } ++ else ++ { ++ node.setCost(getCostH(x, y, node.getLoc().getZ()) + weight); ++ } ++ ++ Node current = _current; ++ int count = 0; ++ while ((current.getChild() != null) && (count < (Config.MAX_ITERATIONS * 4))) ++ { ++ count++; ++ if (current.getChild().getCost() > node.getCost()) ++ { ++ node.setChild(current.getChild()); ++ break; ++ } ++ current = current.getChild(); ++ } ++ ++ if (count >= (Config.MAX_ITERATIONS * 4)) ++ { ++ System.err.println("Pathfinding: too long loop detected, cost:" + node.getCost()); ++ } ++ ++ current.setChild(node); ++ } ++ ++ /** ++ * @param x : node X coord ++ * @param y : node Y coord ++ * @param i : node Z coord ++ * @return double : node cost ++ */ ++ private final double getCostH(int x, int y, int i) ++ { ++ final int dX = x - _gtx; ++ final int dY = y - _gty; ++ final int dZ = (i - _gtz) / GeoStructure.CELL_HEIGHT; ++ ++ // return (Math.abs(dX) + Math.abs(dY) + Math.abs(dZ)) * Config.HEURISTIC_WEIGHT; // Manhattan distance ++ return Math.sqrt((dX * dX) + (dY * dY) + (dZ * dZ)) * Config.HEURISTIC_WEIGHT; // Direct distance ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeLoc.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeLoc.java (revision 8319) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeLoc.java (nonexistent) +@@ -1,183 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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.Cell; +- +-/** +- * @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 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 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; +- // return super.hashCode(); +- } +- +- @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; +- if (_x != other._x) +- { +- return false; +- } +- if (_y != other._y) +- { +- return false; +- } +- if (_goNorth != other._goNorth) +- { +- return false; +- } +- if (_goEast != other._goEast) +- { +- return false; +- } +- if (_goSouth != other._goSouth) +- { +- return false; +- } +- if (_goWest != other._goWest) +- { +- return false; +- } +- if (_geoHeight != other._geoHeight) +- { +- return false; +- } +- return true; +- } +-} +Index: java/org/l2jmobius/gameserver/model/actor/Creature.java +=================================================================== +--- java/org/l2jmobius/gameserver/model/actor/Creature.java (revision 8399) ++++ java/org/l2jmobius/gameserver/model/actor/Creature.java (working copy) +@@ -57,8 +57,6 @@ + import org.l2jmobius.gameserver.enums.Team; + import org.l2jmobius.gameserver.enums.TeleportWhereType; + import org.l2jmobius.gameserver.geoengine.GeoEngine; +-import org.l2jmobius.gameserver.geoengine.GeoEnginePathfinding; +-import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; + import org.l2jmobius.gameserver.instancemanager.IdManager; + import org.l2jmobius.gameserver.instancemanager.InstanceManager; + import org.l2jmobius.gameserver.instancemanager.MapRegionManager; +@@ -3362,7 +3360,7 @@ + + public boolean disregardingGeodata; + public int onGeodataPathIndex; +- public List geoPath; ++ public List geoPath; + public int geoPathAccurateTx; + public int geoPathAccurateTy; + public int geoPathGtx; +@@ -4386,7 +4384,7 @@ + if (((originalDistance - distance) > 30) && !isAfraid() && !isInVehicle) + { + // Path calculation -- overrides previous movement check +- m.geoPath = GeoEnginePathfinding.getInstance().findPath(curX, curY, curZ, originalX, originalY, originalZ, getInstanceId()); ++ m.geoPath = GeoEngine.getInstance().findPath(curX, curY, curZ, originalX, originalY, originalZ, getInstanceId()); + if ((m.geoPath == null) || (m.geoPath.size() < 2)) // No path found + { + if (isPlayer() && !_isFlying && !isInWater) +Index: java/org/l2jmobius/gameserver/model/actor/Summon.java +=================================================================== +--- java/org/l2jmobius/gameserver/model/actor/Summon.java (revision 8319) ++++ java/org/l2jmobius/gameserver/model/actor/Summon.java (working copy) +@@ -29,7 +29,6 @@ + import org.l2jmobius.gameserver.enums.ShotType; + import org.l2jmobius.gameserver.enums.Team; + import org.l2jmobius.gameserver.geoengine.GeoEngine; +-import org.l2jmobius.gameserver.geoengine.GeoEnginePathfinding; + import org.l2jmobius.gameserver.handler.IItemHandler; + import org.l2jmobius.gameserver.handler.ItemHandler; + import org.l2jmobius.gameserver.instancemanager.TerritoryWarManager; +@@ -646,7 +645,7 @@ + return false; + } + +- if ((this != target) && skill.isPhysical() && Config.PATHFINDING && (GeoEnginePathfinding.getInstance().findPath(getX(), getY(), getZ(), target.getX(), target.getY(), target.getZ(), getInstanceId()) == null)) ++ if ((this != target) && skill.isPhysical() && Config.PATHFINDING && (GeoEngine.getInstance().findPath(getX(), getY(), getZ(), target.getX(), target.getY(), target.getZ(), getInstanceId()) == null)) + { + sendPacket(SystemMessageId.CANNOT_SEE_TARGET); + return false; +Index: java/org/l2jmobius/gameserver/model/actor/instance/PlayerInstance.java +=================================================================== +--- java/org/l2jmobius/gameserver/model/actor/instance/PlayerInstance.java (revision 8352) ++++ java/org/l2jmobius/gameserver/model/actor/instance/PlayerInstance.java (working copy) +@@ -13406,7 +13406,7 @@ + { + if (Config.CORRECT_PLAYER_Z) + { +- final int nearestZ = GeoEngine.getInstance().getHigherHeight(getX(), getY(), getZ()); ++ final int nearestZ = GeoEngine.getInstance().getHeightNearest(getX(), getY(), getZ()); + if (getZ() < nearestZ) + { + teleToLocation(new Location(getX(), getY(), nearestZ)); +Index: java/org/l2jmobius/gameserver/util/GeoUtils.java +=================================================================== +--- java/org/l2jmobius/gameserver/util/GeoUtils.java (revision 8319) ++++ java/org/l2jmobius/gameserver/util/GeoUtils.java (working copy) +@@ -19,7 +19,7 @@ + import java.awt.Color; + + import org.l2jmobius.gameserver.geoengine.GeoEngine; +-import org.l2jmobius.gameserver.geoengine.geodata.Cell; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; + import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance; + import org.l2jmobius.gameserver.network.serverpackets.ExServerPrimitive; + +@@ -26,25 +26,25 @@ + /** + * @author HorridoJoho + */ +-public final class GeoUtils ++public class GeoUtils + { + public static void debug2DLine(PlayerInstance player, int x, int y, int tx, int ty, int z) + { +- final int gx = GeoEngine.getInstance().getGeoX(x); +- final int gy = GeoEngine.getInstance().getGeoY(y); ++ final int gx = GeoEngine.getGeoX(x); ++ final int gy = GeoEngine.getGeoY(y); + +- final int tgx = GeoEngine.getInstance().getGeoX(tx); +- final int tgy = GeoEngine.getInstance().getGeoY(ty); ++ final int tgx = GeoEngine.getGeoX(tx); ++ final int tgy = GeoEngine.getGeoY(ty); + + final ExServerPrimitive prim = new ExServerPrimitive("Debug2DLine", x, y, z); +- prim.addLine(Color.BLUE, GeoEngine.getInstance().getWorldX(gx), GeoEngine.getInstance().getWorldY(gy), z, GeoEngine.getInstance().getWorldX(tgx), GeoEngine.getInstance().getWorldY(tgy), z); ++ prim.addLine(Color.BLUE, GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), z, GeoEngine.getWorldX(tgx), GeoEngine.getWorldY(tgy), z); + + final LinePointIterator iter = new LinePointIterator(gx, gy, tgx, tgy); + + while (iter.next()) + { +- final int wx = GeoEngine.getInstance().getWorldX(iter.x()); +- final int wy = GeoEngine.getInstance().getWorldY(iter.y()); ++ final int wx = GeoEngine.getWorldX(iter.x()); ++ final int wy = GeoEngine.getWorldY(iter.y()); + + prim.addPoint(Color.RED, wx, wy, z); + } +@@ -53,21 +53,21 @@ + + public static void debug3DLine(PlayerInstance player, int x, int y, int z, int tx, int ty, int tz) + { +- final int gx = GeoEngine.getInstance().getGeoX(x); +- final int gy = GeoEngine.getInstance().getGeoY(y); ++ final int gx = GeoEngine.getGeoX(x); ++ final int gy = GeoEngine.getGeoY(y); + +- final int tgx = GeoEngine.getInstance().getGeoX(tx); +- final int tgy = GeoEngine.getInstance().getGeoY(ty); ++ final int tgx = GeoEngine.getGeoX(tx); ++ final int tgy = GeoEngine.getGeoY(ty); + + final ExServerPrimitive prim = new ExServerPrimitive("Debug3DLine", x, y, z); +- prim.addLine(Color.BLUE, GeoEngine.getInstance().getWorldX(gx), GeoEngine.getInstance().getWorldY(gy), z, GeoEngine.getInstance().getWorldX(tgx), GeoEngine.getInstance().getWorldY(tgy), tz); ++ prim.addLine(Color.BLUE, GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), z, GeoEngine.getWorldX(tgx), GeoEngine.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.getInstance().getWorldX(prevX); +- int wy = GeoEngine.getInstance().getWorldY(prevY); ++ int wx = GeoEngine.getWorldX(prevX); ++ int wy = GeoEngine.getWorldY(prevY); + int wz = iter.z(); + prim.addPoint(Color.RED, wx, wy, wz); + +@@ -78,8 +78,8 @@ + + if ((curX != prevX) || (curY != prevY)) + { +- wx = GeoEngine.getInstance().getWorldX(curX); +- wy = GeoEngine.getInstance().getWorldY(curY); ++ wx = GeoEngine.getWorldX(curX); ++ wy = GeoEngine.getWorldY(curY); + wz = iter.z(); + + prim.addPoint(Color.RED, wx, wy, wz); +@@ -93,7 +93,7 @@ + + private static Color getDirectionColor(int x, int y, int z, int nswe) + { +- if (GeoEngine.getInstance().checkNearestNswe(x, y, z, nswe)) ++ if ((GeoEngine.getInstance().getNsweNearest(x, y, z) & nswe) == nswe) + { + return Color.GREEN; + } +@@ -109,9 +109,8 @@ + int iPacket = 0; + + ExServerPrimitive exsp = null; +- final GeoEngine ge = GeoEngine.getInstance(); +- final int playerGx = ge.getGeoX(player.getX()); +- final int playerGy = ge.getGeoY(player.getY()); ++ final int playerGx = GeoEngine.getGeoX(player.getX()); ++ final int playerGy = GeoEngine.getGeoY(player.getY()); + for (int dx = -geoRadius; dx <= geoRadius; ++dx) + { + for (int dy = -geoRadius; dy <= geoRadius; ++dy) +@@ -135,12 +134,12 @@ + final int gx = playerGx + dx; + final int gy = playerGy + dy; + +- final int x = ge.getWorldX(gx); +- final int y = ge.getWorldY(gy); +- final int z = ge.getNearestZ(gx, gy, player.getZ()); ++ final int x = GeoEngine.getWorldX(gx); ++ final int y = GeoEngine.getWorldY(gy); ++ final int z = GeoEngine.getInstance().getHeightNearest(gx, gy, player.getZ()); + + // north arrow +- Color col = getDirectionColor(gx, gy, z, Cell.NSWE_NORTH); ++ Color col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_N); + 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); +@@ -147,7 +146,7 @@ + exsp.addLine(col, x - 4, y - 4, z, x + 4, y - 4, z); + + // east arrow +- col = getDirectionColor(gx, gy, z, Cell.NSWE_EAST); ++ col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_E); + 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); +@@ -154,13 +153,13 @@ + exsp.addLine(col, x + 4, y - 4, z, x + 4, y + 4, z); + + // south arrow +- col = getDirectionColor(gx, gy, z, Cell.NSWE_SOUTH); ++ col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_S); + 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, Cell.NSWE_WEST); ++ col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_W); + 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); +@@ -188,15 +187,15 @@ + { + if (y > lastY) + { +- return Cell.NSWE_SOUTH_EAST; ++ return GeoStructure.CELL_FLAG_SE; // Direction.SOUTH_EAST; + } + else if (y < lastY) + { +- return Cell.NSWE_NORTH_EAST; ++ return GeoStructure.CELL_FLAG_NE; // Direction.NORTH_EAST; + } + else + { +- return Cell.NSWE_EAST; ++ return GeoStructure.CELL_FLAG_E; // Direction.EAST; + } + } + else if (x < lastX) // west +@@ -203,26 +202,27 @@ + { + if (y > lastY) + { +- return Cell.NSWE_SOUTH_WEST; ++ return GeoStructure.CELL_FLAG_SW; // Direction.SOUTH_WEST; + } + else if (y < lastY) + { +- return Cell.NSWE_NORTH_WEST; ++ return GeoStructure.CELL_FLAG_NW; // Direction.NORTH_WEST; + } + else + { +- return Cell.NSWE_WEST; ++ return GeoStructure.CELL_FLAG_W; // Direction.WEST; + } + } +- else // unchanged x ++ else ++ // unchanged x + { + if (y > lastY) + { +- return Cell.NSWE_SOUTH; ++ return GeoStructure.CELL_FLAG_S; // Direction.SOUTH; + } + else if (y < lastY) + { +- return Cell.NSWE_NORTH; ++ return GeoStructure.CELL_FLAG_N; // Direction.NORTH; + } + else + { +Index: java/org/l2jmobius/tools/geodataconverter/GeoDataConverter.java +=================================================================== +--- java/org/l2jmobius/tools/geodataconverter/GeoDataConverter.java (nonexistent) ++++ java/org/l2jmobius/tools/geodataconverter/GeoDataConverter.java (working copy) +@@ -0,0 +1,386 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ */ ++package org.l2jmobius.tools.geodataconverter; ++ ++import java.io.BufferedOutputStream; ++import java.io.File; ++import java.io.FileOutputStream; ++import java.io.RandomAccessFile; ++import java.nio.ByteOrder; ++import java.nio.MappedByteBuffer; ++import java.nio.channels.FileChannel; ++import java.util.Scanner; ++ ++import org.l2jmobius.Config; ++import org.l2jmobius.commons.util.PropertiesParser; ++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.GeoFormat; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; ++import org.l2jmobius.gameserver.model.World; ++ ++/** ++ * @author Hasha ++ */ ++public class GeoDataConverter ++{ ++ private static GeoFormat _format; ++ private static ABlock[][] _blocks; ++ ++ public static void main(String[] args) ++ { ++ // initialize config ++ loadGeoengineConfigs(); ++ ++ // get geodata format ++ String type = ""; ++ try (Scanner scn = new Scanner(System.in)) ++ { ++ while (!(type.equalsIgnoreCase("J") || type.equalsIgnoreCase("O") || type.equalsIgnoreCase("E"))) ++ { ++ System.out.println("GeoDataConverter: Select source geodata type:"); ++ System.out.println(" J: L2J (e.g. 23_22.l2j)"); ++ System.out.println(" O: L2OFF (e.g. 23_22_conv.dat)"); ++ System.out.println(" E: Exit"); ++ System.out.print("Choice: "); ++ type = scn.next(); ++ } ++ } ++ if (type.equalsIgnoreCase("E")) ++ { ++ System.exit(0); ++ } ++ ++ _format = type.equalsIgnoreCase("J") ? GeoFormat.L2J : GeoFormat.L2OFF; ++ ++ // start conversion ++ System.out.println("GeoDataConverter: Converting all " + _format + " files."); ++ ++ // initialize geodata container ++ _blocks = new ABlock[GeoStructure.REGION_BLOCKS_X][GeoStructure.REGION_BLOCKS_Y]; ++ ++ // initialize multilayer temporarily buffer ++ BlockMultilayer.initialize(); ++ ++ // load geo files ++ int converted = 0; ++ for (int rx = World.TILE_X_MIN; rx <= World.TILE_X_MAX; rx++) ++ { ++ for (int ry = World.TILE_Y_MIN; ry <= World.TILE_Y_MAX; ry++) ++ { ++ final String input = String.format(_format.getFilename(), rx, ry); ++ final String filepath = Config.GEODATA_PATH; ++ final File f = new File(filepath + input); ++ if (f.exists() && !f.isDirectory()) ++ { ++ // load geodata ++ if (!loadGeoBlocks(input)) ++ { ++ System.out.println("GeoDataConverter: Unable to load " + input + " region file."); ++ continue; ++ } ++ ++ // recalculate nswe ++ if (!recalculateNswe()) ++ { ++ System.out.println("GeoDataConverter: Unable to convert " + input + " region file."); ++ continue; ++ } ++ ++ // save geodata ++ final String output = String.format(GeoFormat.L2D.getFilename(), rx, ry); ++ if (!saveGeoBlocks(output)) ++ { ++ System.out.println("GeoDataConverter: Unable to save " + output + " region file."); ++ continue; ++ } ++ ++ converted++; ++ System.out.println("GeoDataConverter: Created " + output + " region file."); ++ } ++ } ++ } ++ System.out.println("GeoDataConverter: Converted " + converted + " " + _format + " to L2D region file(s)."); ++ ++ // release multilayer block temporarily buffer ++ BlockMultilayer.release(); ++ } ++ ++ /** ++ * Loads geo blocks from buffer of the region file. ++ * @param filename : The name of the to load. ++ * @return boolean : True when successful. ++ */ ++ private static boolean loadGeoBlocks(String filename) ++ { ++ // region file is load-able, try to load it ++ try (RandomAccessFile raf = new RandomAccessFile(Config.GEODATA_PATH + filename, "r"); ++ FileChannel fc = raf.getChannel()) ++ { ++ final MappedByteBuffer buffer = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()).load(); ++ buffer.order(ByteOrder.LITTLE_ENDIAN); ++ ++ // load 18B header for L2off geodata (1st and 2nd byte...region X and Y) ++ if (_format == GeoFormat.L2OFF) ++ { ++ for (int i = 0; i < 18; i++) ++ { ++ buffer.get(); ++ } ++ } ++ ++ // 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 (_format == GeoFormat.L2J) ++ { ++ // get block type ++ final byte type = buffer.get(); ++ ++ // load block according to block type ++ switch (type) ++ { ++ case GeoStructure.TYPE_FLAT_L2J_L2OFF: ++ { ++ _blocks[ix][iy] = new BlockFlat(buffer, _format); ++ break; ++ } ++ case GeoStructure.TYPE_COMPLEX_L2J: ++ { ++ _blocks[ix][iy] = new BlockComplex(buffer, _format); ++ break; ++ } ++ case GeoStructure.TYPE_MULTILAYER_L2J: ++ { ++ _blocks[ix][iy] = new BlockMultilayer(buffer, _format); ++ break; ++ } ++ default: ++ { ++ throw new IllegalArgumentException("Unknown block type: " + type); ++ } ++ } ++ } ++ else ++ { ++ // get block type ++ final short type = buffer.getShort(); ++ ++ // load block according to block type ++ switch (type) ++ { ++ case GeoStructure.TYPE_FLAT_L2J_L2OFF: ++ { ++ _blocks[ix][iy] = new BlockFlat(buffer, _format); ++ break; ++ } ++ case GeoStructure.TYPE_COMPLEX_L2OFF: ++ { ++ _blocks[ix][iy] = new BlockComplex(buffer, _format); ++ break; ++ } ++ default: ++ { ++ _blocks[ix][iy] = new BlockMultilayer(buffer, _format); ++ break; ++ } ++ } ++ } ++ } ++ } ++ ++ if (buffer.remaining() > 0) ++ { ++ System.out.println("GeoDataConverter: Region file " + filename + " can be corrupted, remaining " + buffer.remaining() + " bytes to read."); ++ return false; ++ } ++ ++ return true; ++ } ++ catch (Exception e) ++ { ++ System.out.println("GeoDataConverter: Error while loading " + filename + " region file."); ++ return false; ++ } ++ } ++ ++ /** ++ * Recalculate diagonal flags for the region file. ++ * @return boolean : True when successful. ++ */ ++ private static boolean recalculateNswe() ++ { ++ try ++ { ++ for (int x = 0; x < GeoStructure.REGION_CELLS_X; x++) ++ { ++ for (int y = 0; y < GeoStructure.REGION_CELLS_Y; y++) ++ { ++ // get block ++ final ABlock block = _blocks[x / GeoStructure.BLOCK_CELLS_X][y / GeoStructure.BLOCK_CELLS_Y]; ++ ++ // skip flat blocks ++ if (block instanceof BlockFlat) ++ { ++ continue; ++ } ++ ++ // for complex and multilayer blocks go though all layers ++ short height = Short.MAX_VALUE; ++ int index; ++ while ((index = block.getIndexBelow(x, y, height)) != -1) ++ { ++ // get height and nswe ++ height = block.getHeight(index); ++ byte nswe = block.getNswe(index); ++ ++ // update nswe with diagonal flags ++ nswe = updateNsweBelow(x, y, height, nswe); ++ ++ // set nswe of the cell ++ block.setNswe(index, nswe); ++ } ++ } ++ } ++ ++ return true; ++ } ++ catch (Exception e) ++ { ++ return false; ++ } ++ } ++ ++ /** ++ * Updates the NSWE flag with diagonal flags. ++ * @param x : Geodata X coordinate. ++ * @param y : Geodata Y coordinate. ++ * @param z : Geodata Z coordinate. ++ * @param nsweValue : NSWE flag to be updated. ++ * @return byte : Updated NSWE flag. ++ */ ++ private static byte updateNsweBelow(int x, int y, short z, byte nsweValue) ++ { ++ byte nswe = nsweValue; ++ ++ // calculate virtual layer height ++ final short height = (short) (z + GeoStructure.CELL_IGNORE_HEIGHT); ++ ++ // get NSWE of neighbor cells below virtual layer (NPC/PC can fall down of clif, but can not climb it -> NSWE of cell below) ++ final byte nsweN = getNsweBelow(x, y - 1, height); ++ final byte nsweS = getNsweBelow(x, y + 1, height); ++ final byte nsweW = getNsweBelow(x - 1, y, height); ++ final byte nsweE = getNsweBelow(x + 1, y, height); ++ ++ // north-west ++ if ((((nswe & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) || (((nswe & GeoStructure.CELL_FLAG_W) != 0) && ((nsweW & GeoStructure.CELL_FLAG_N) != 0))) ++ { ++ nswe |= GeoStructure.CELL_FLAG_NW; ++ } ++ ++ // north-east ++ if ((((nswe & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) || (((nswe & GeoStructure.CELL_FLAG_E) != 0) && ((nsweE & GeoStructure.CELL_FLAG_N) != 0))) ++ { ++ nswe |= GeoStructure.CELL_FLAG_NE; ++ } ++ ++ // south-west ++ if ((((nswe & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) || (((nswe & GeoStructure.CELL_FLAG_W) != 0) && ((nsweW & GeoStructure.CELL_FLAG_S) != 0))) ++ { ++ nswe |= GeoStructure.CELL_FLAG_SW; ++ } ++ ++ // south-east ++ if ((((nswe & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) || (((nswe & GeoStructure.CELL_FLAG_E) != 0) && ((nsweE & GeoStructure.CELL_FLAG_S) != 0))) ++ { ++ nswe |= GeoStructure.CELL_FLAG_SE; ++ } ++ ++ return nswe; ++ } ++ ++ private static byte getNsweBelow(int geoX, int geoY, short worldZ) ++ { ++ // out of geo coordinates ++ if ((geoX < 0) || (geoX >= GeoStructure.REGION_CELLS_X)) ++ { ++ return 0; ++ } ++ ++ // out of geo coordinates ++ if ((geoY < 0) || (geoY >= GeoStructure.REGION_CELLS_Y)) ++ { ++ return 0; ++ } ++ ++ // get block ++ final ABlock block = _blocks[geoX / GeoStructure.BLOCK_CELLS_X][geoY / GeoStructure.BLOCK_CELLS_Y]; ++ ++ // get index, when valid, return nswe ++ final int index = block.getIndexBelow(geoX, geoY, worldZ); ++ return index == -1 ? 0 : block.getNswe(index); ++ } ++ ++ /** ++ * Save region file to file. ++ * @param filename : The name of file to save. ++ * @return boolean : True when successful. ++ */ ++ private static boolean saveGeoBlocks(String filename) ++ { ++ try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(Config.GEODATA_PATH + filename), GeoStructure.REGION_BLOCKS * GeoStructure.BLOCK_CELLS * 3)) ++ { ++ // loop over region blocks and save each block ++ for (int ix = 0; ix < GeoStructure.REGION_BLOCKS_X; ix++) ++ { ++ for (int iy = 0; iy < GeoStructure.REGION_BLOCKS_Y; iy++) ++ { ++ _blocks[ix][iy].saveBlock(bos); ++ } ++ } ++ ++ // flush data to file ++ bos.flush(); ++ ++ return true; ++ } ++ catch (Exception e) ++ { ++ return false; ++ } ++ } ++ ++ private static void loadGeoengineConfigs() ++ { ++ final PropertiesParser geoData = new PropertiesParser(Config.GEOENGINE_CONFIG_FILE); ++ Config.GEODATA_PATH = geoData.getString("GeoDataPath", "./data/geodata/"); ++ Config.COORD_SYNCHRONIZE = geoData.getInt("CoordSynchronize", -1); ++ Config.PART_OF_CHARACTER_HEIGHT = geoData.getInt("PartOfCharacterHeight", 75); ++ Config.MAX_OBSTACLE_HEIGHT = geoData.getInt("MaxObstacleHeight", 32); ++ Config.PATHFINDING = geoData.getBoolean("PathFinding", true); ++ Config.PATHFIND_BUFFERS = geoData.getString("PathFindBuffers", "100x6;128x6;192x6;256x4;320x4;384x4;500x2"); ++ Config.BASE_WEIGHT = geoData.getInt("BaseWeight", 10); ++ Config.DIAGONAL_WEIGHT = geoData.getInt("DiagonalWeight", 14); ++ Config.OBSTACLE_MULTIPLIER = geoData.getInt("ObstacleMultiplier", 10); ++ Config.HEURISTIC_WEIGHT = geoData.getInt("HeuristicWeight", 20); ++ Config.MAX_ITERATIONS = geoData.getInt("MaxIterations", 3500); ++ } ++} +\ No newline at end of file diff --git a/L2J_Mobius_CT_2.6_HighFive/dist/game/data/geodata/L2D_GeoEngine.patch b/L2J_Mobius_CT_2.6_HighFive/dist/game/data/geodata/L2D_GeoEngine.patch new file mode 100644 index 0000000000..b11c767ce4 --- /dev/null +++ b/L2J_Mobius_CT_2.6_HighFive/dist/game/data/geodata/L2D_GeoEngine.patch @@ -0,0 +1,6075 @@ +Index: dist/game/GeoDataConverter.bat +=================================================================== +--- dist/game/GeoDataConverter.bat (nonexistent) ++++ dist/game/GeoDataConverter.bat (working copy) +@@ -0,0 +1,6 @@ ++@echo off ++title L2D geodata converter ++ ++java -Xmx512m -cp ./../libs/* org.l2jmobius.tools.geodataconverter.GeoDataConverter ++ ++pause +Index: dist/game/GeoDataConverter.sh +=================================================================== +--- dist/game/GeoDataConverter.sh (nonexistent) ++++ dist/game/GeoDataConverter.sh (working copy) +@@ -0,0 +1,4 @@ ++#! /bin/sh ++ ++java -Xmx512m -cp ../libs/*: org.l2jmobius.tools.geodataconverter.GeoDataConverter > log/stdout.log 2>&1 ++ +Index: dist/game/config/GeoEngine.ini +=================================================================== +--- dist/game/config/GeoEngine.ini (revision 8319) ++++ dist/game/config/GeoEngine.ini (working copy) +@@ -1,6 +1,10 @@ + # ================================================================= + # Geodata + # ================================================================= ++# Because of real-time performance we are using geodata files only in ++# diagonal L2D format now (using filename e.g. 22_16.l2d). ++# L2D geodata can be obtained by conversion of existing L2J or L2OFF geodata. ++# Launch "GeoDataConverter.bat/sh" and follow instructions to start the conversion. + + # 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/ +@@ -13,6 +17,16 @@ + CoordSynchronize = 2 + + # ================================================================= ++# Path checking ++# ================================================================= ++ ++# 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 finding + # ================================================================= + +@@ -23,19 +37,23 @@ + # Pathfinding array buffers configuration, default: 100x6;128x6;192x6;256x4;320x4;384x4;500x2 + PathFindBuffers = 100x6;128x6;192x6;256x4;320x4;384x4;500x2 + +-# Weight for nodes without obstacles far from walls +-LowWeight = 0.5 ++# Base path weight, when moving from one node to another on axis direction, default: 10 ++BaseWeight = 10 + +-# Weight for nodes near walls +-MediumWeight = 2 ++# Path weight, when moving from one node to another on diagonal direction, default: BaseWeight * sqrt(2) = 14 ++DiagonalWeight = 14 + +-# Weight for nodes with obstacles +-HighWeight = 3 ++# When movement flags of target node is blocked to any direction, multiply movement weight by this multiplier. ++# This causes pathfinding algorithm to avoid path construction exactly near an obstacle, default: 10 ++ObstacleMultiplier = 10 + +-# Weight for diagonal movement. +-# Default: LowWeight * sqrt(2) +-DiagonalWeight = 0.707 ++# Weight of the heuristic algorithm, which is giving estimated cost from node to target, default: 20 ++# For proper function must be higher than BaseWeight and/or DiagonalWeight. ++HeuristicWeight = 20 + ++# Maximum number of generated nodes per one path-finding process, default 3500 ++MaxIterations = 3500 ++ + # ================================================================= + # Other + # ================================================================= +Index: dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java +=================================================================== +--- dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java (revision 8319) ++++ dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java (working copy) +@@ -55,9 +55,8 @@ + final int worldX = activeChar.getX(); + final int worldY = activeChar.getY(); + final int worldZ = activeChar.getZ(); +- final int geoX = GeoEngine.getInstance().getGeoX(worldX); +- final int geoY = GeoEngine.getInstance().getGeoY(worldY); +- ++ final int geoX = GeoEngine.getGeoX(worldX); ++ final int geoY = GeoEngine.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)); +@@ -73,9 +72,8 @@ + final int worldX = activeChar.getX(); + final int worldY = activeChar.getY(); + final int worldZ = activeChar.getZ(); +- final int geoX = GeoEngine.getInstance().getGeoX(worldX); +- final int geoY = GeoEngine.getInstance().getGeoY(worldY); +- ++ final int geoX = GeoEngine.getGeoX(worldX); ++ final int geoY = GeoEngine.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)); +Index: dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java +=================================================================== +--- dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java (revision 8319) ++++ dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java (working copy) +@@ -18,11 +18,11 @@ + + import java.util.List; + +-import org.l2jmobius.Config; +-import org.l2jmobius.gameserver.geoengine.GeoEnginePathfinding; +-import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; ++import org.l2jmobius.gameserver.geoengine.GeoEngine; + import org.l2jmobius.gameserver.handler.IAdminCommandHandler; ++import org.l2jmobius.gameserver.model.Location; + import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance; ++import org.l2jmobius.gameserver.network.SystemMessageId; + import org.l2jmobius.gameserver.util.BuilderUtil; + + public class AdminPathNode implements IAdminCommandHandler +@@ -37,29 +37,30 @@ + { + if (command.equals("admin_path_find")) + { +- if (!Config.PATHFINDING) +- { +- BuilderUtil.sendSysMessage(activeChar, "PathFinding is disabled."); +- return true; +- } + if (activeChar.getTarget() != null) + { +- final List path = GeoEnginePathfinding.getInstance().findPath(activeChar.getX(), activeChar.getY(), (short) activeChar.getZ(), activeChar.getTarget().getX(), activeChar.getTarget().getY(), (short) activeChar.getTarget().getZ(), activeChar.getInstanceId()); ++ 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()); + if (path == null) + { +- BuilderUtil.sendSysMessage(activeChar, "No Route!"); +- return true; ++ BuilderUtil.sendSysMessage(activeChar, "No route found or pathfinding disabled."); + } +- for (AbstractNodeLoc a : path) ++ else + { +- BuilderUtil.sendSysMessage(activeChar, "x:" + a.getX() + " y:" + a.getY() + " z:" + a.getZ()); ++ for (Location point : path) ++ { ++ BuilderUtil.sendSysMessage(activeChar, "x:" + point.getX() + " y:" + point.getY() + " z:" + point.getZ()); ++ } + } + } + else + { +- BuilderUtil.sendSysMessage(activeChar, "No Target!"); ++ activeChar.sendPacket(SystemMessageId.INVALID_TARGET); + } + } ++ else ++ { ++ return false; ++ } + return true; + } + +Index: java/org/l2jmobius/Config.java +=================================================================== +--- java/org/l2jmobius/Config.java (revision 8388) ++++ java/org/l2jmobius/Config.java (working copy) +@@ -32,7 +32,6 @@ + import java.net.UnknownHostException; + import java.nio.charset.StandardCharsets; + import java.nio.file.Files; +-import java.nio.file.Path; + import java.nio.file.Paths; + import java.util.ArrayList; + import java.util.Arrays; +@@ -1059,14 +1058,17 @@ + // -------------------------------------------------- + // GeoEngine + // -------------------------------------------------- +- public static Path GEODATA_PATH; ++ public static String GEODATA_PATH; ++ public static int COORD_SYNCHRONIZE; ++ public static int PART_OF_CHARACTER_HEIGHT; ++ public static int MAX_OBSTACLE_HEIGHT; + public static boolean PATHFINDING; + public static String PATHFIND_BUFFERS; +- public static float LOW_WEIGHT; +- public static float MEDIUM_WEIGHT; +- public static float HIGH_WEIGHT; +- public static float DIAGONAL_WEIGHT; +- public static int COORD_SYNCHRONIZE; ++ public static int BASE_WEIGHT; ++ public static int DIAGONAL_WEIGHT; ++ public static int HEURISTIC_WEIGHT; ++ public static int OBSTACLE_MULTIPLIER; ++ public static int MAX_ITERATIONS; + public static boolean CORRECT_PLAYER_Z; + + // -------------------------------------------------- +@@ -2595,14 +2597,17 @@ + + // Load GeoEngine config file (if exists) + final PropertiesParser GeoEngine = new PropertiesParser(GEOENGINE_CONFIG_FILE); +- GEODATA_PATH = Paths.get(GeoEngine.getString("GeoDataPath", "./data/geodata")); ++ GEODATA_PATH = GeoEngine.getString("GeoDataPath", "./data/geodata/"); ++ COORD_SYNCHRONIZE = GeoEngine.getInt("CoordSynchronize", -1); ++ PART_OF_CHARACTER_HEIGHT = GeoEngine.getInt("PartOfCharacterHeight", 75); ++ MAX_OBSTACLE_HEIGHT = GeoEngine.getInt("MaxObstacleHeight", 32); + PATHFINDING = GeoEngine.getBoolean("PathFinding", true); + PATHFIND_BUFFERS = GeoEngine.getString("PathFindBuffers", "100x6;128x6;192x6;256x4;320x4;384x4;500x2"); +- LOW_WEIGHT = GeoEngine.getFloat("LowWeight", 0.5f); +- MEDIUM_WEIGHT = GeoEngine.getFloat("MediumWeight", 2); +- HIGH_WEIGHT = GeoEngine.getFloat("HighWeight", 3); +- DIAGONAL_WEIGHT = GeoEngine.getFloat("DiagonalWeight", 0.707f); +- COORD_SYNCHRONIZE = GeoEngine.getInt("CoordSynchronize", -1); ++ BASE_WEIGHT = GeoEngine.getInt("BaseWeight", 10); ++ DIAGONAL_WEIGHT = GeoEngine.getInt("DiagonalWeight", 14); ++ OBSTACLE_MULTIPLIER = GeoEngine.getInt("ObstacleMultiplier", 10); ++ HEURISTIC_WEIGHT = GeoEngine.getInt("HeuristicWeight", 20); ++ MAX_ITERATIONS = GeoEngine.getInt("MaxIterations", 3500); + CORRECT_PLAYER_Z = GeoEngine.getBoolean("CorrectPlayerZ", false); + + // Load AllowedPlayerRaces config file (if exists) +Index: java/org/l2jmobius/gameserver/ai/SummonAI.java +=================================================================== +--- java/org/l2jmobius/gameserver/ai/SummonAI.java (revision 8319) ++++ java/org/l2jmobius/gameserver/ai/SummonAI.java (working copy) +@@ -26,7 +26,6 @@ + import org.l2jmobius.commons.concurrent.ThreadPool; + import org.l2jmobius.commons.util.Rnd; + import org.l2jmobius.gameserver.geoengine.GeoEngine; +-import org.l2jmobius.gameserver.geoengine.GeoEnginePathfinding; + import org.l2jmobius.gameserver.model.WorldObject; + import org.l2jmobius.gameserver.model.actor.Creature; + import org.l2jmobius.gameserver.model.actor.Summon; +@@ -51,7 +50,7 @@ + @Override + protected void onIntentionAttack(Creature target) + { +- if (Config.PATHFINDING && (GeoEnginePathfinding.getInstance().findPath(_actor.getX(), _actor.getY(), _actor.getZ(), target.getX(), target.getY(), target.getZ(), _actor.getInstanceId()) == null)) ++ if (Config.PATHFINDING && (GeoEngine.getInstance().findPath(_actor.getX(), _actor.getY(), _actor.getZ(), target.getX(), target.getY(), target.getZ(), _actor.getInstanceId()) == null)) + { + return; + } +Index: java/org/l2jmobius/gameserver/geoengine/GeoEngine.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/GeoEngine.java (revision 8352) ++++ java/org/l2jmobius/gameserver/geoengine/GeoEngine.java (working copy) +@@ -16,99 +16,89 @@ + */ + package org.l2jmobius.gameserver.geoengine; + +-import java.io.IOException; ++import java.io.File; + import java.io.RandomAccessFile; + import java.nio.ByteOrder; +-import java.nio.channels.FileChannel.MapMode; +-import java.nio.file.Files; +-import java.nio.file.Path; +-import java.util.concurrent.atomic.AtomicReferenceArray; +-import java.util.logging.Level; ++import java.nio.MappedByteBuffer; ++import java.nio.channels.FileChannel; ++import java.util.List; + 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.geoengine.geodata.Cell; +-import org.l2jmobius.gameserver.geoengine.geodata.IRegion; +-import org.l2jmobius.gameserver.geoengine.geodata.NullRegion; +-import org.l2jmobius.gameserver.geoengine.geodata.Region; ++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.GeoFormat; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoLocation; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; + import org.l2jmobius.gameserver.model.Location; + import org.l2jmobius.gameserver.model.World; + import org.l2jmobius.gameserver.model.WorldObject; +-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; ++import org.l2jmobius.gameserver.model.actor.Creature; ++import org.l2jmobius.gameserver.util.MathUtil; + + /** +- * @author -Nemesiss-, HorridoJoho ++ * @author Hasha + */ + public class GeoEngine + { +- private static final Logger LOGGER = Logger.getLogger(GeoEngine.class.getName()); ++ protected static final Logger LOGGER = Logger.getLogger(GeoEngine.class.getName()); + +- private static final int WORLD_MIN_X = -655360; +- private static final int WORLD_MIN_Y = -589824; +- private static final int WORLD_MIN_Z = -16384; ++ private final ABlock[][] _blocks; ++ private final BlockNull _nullBlock; + +- /** 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; +- +- /** The regions array */ +- private final AtomicReferenceArray _regions = new AtomicReferenceArray<>(GEO_REGIONS); +- +- 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; +- +- protected GeoEngine() ++ /** ++ * GeoEngine constructor. Loads all geodata files of chosen geodata format. ++ */ ++ public GeoEngine() + { + LOGGER.info("GeoEngine: Initializing..."); +- for (int i = 0; i < _regions.length(); i++) +- { +- _regions.set(i, NullRegion.INSTANCE); +- } + ++ // 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; +- try ++ long fileSize = 0; ++ for (int rx = World.TILE_X_MIN; rx <= World.TILE_X_MAX; rx++) + { +- for (int regionX = World.TILE_X_MIN; regionX <= World.TILE_X_MAX; regionX++) ++ for (int ry = World.TILE_Y_MIN; ry <= World.TILE_Y_MAX; ry++) + { +- for (int regionY = World.TILE_Y_MIN; regionY <= World.TILE_Y_MAX; regionY++) ++ final File f = new File(Config.GEODATA_PATH + String.format(GeoFormat.L2D.getFilename(), rx, ry)); ++ if (f.exists() && !f.isDirectory()) + { +- 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(rx, ry)) + { +- try (RandomAccessFile raf = new RandomAccessFile(geoFilePath.toFile(), "r")) +- { +- _regions.set((regionX * GEO_REGIONS_Y) + regionY, new Region(raf.getChannel().map(MapMode.READ_ONLY, 0, raf.length()).load().order(ByteOrder.LITTLE_ENDIAN))); +- loaded++; +- } ++ loaded++; ++ fileSize += f.length(); + } + } ++ else ++ { ++ // region file is not load-able, load null blocks ++ loadNullBlocks(rx, ry); ++ } + } + } +- catch (Exception e) ++ ++ LOGGER.info("GeoEngine: Loaded " + loaded + " geodata files."); ++ if (loaded > 0) + { +- LOGGER.log(Level.SEVERE, "GeoEngine: Failed to load geodata!", e); +- System.exit(1); ++ LOGGER.info("GeoEngine: Total geodata file size " + (fileSize / 1024 / 1024) + " MB."); + } + +- LOGGER.info("GeoEngine: Loaded " + loaded + " geodata files."); +- +- // Avoid wrong configs when no files are loaded. ++ // avoid wrong configs when no files are loaded + if (loaded == 0) + { + if (Config.PATHFINDING) +@@ -122,619 +112,820 @@ + LOGGER.info("GeoEngine: Forcing CoordSynchronize setting to -1."); + } + } ++ ++ // release multilayer block temporarily buffer ++ BlockMultilayer.release(); + } + + /** +- * @param geoX +- * @param geoY +- * @return the region ++ * 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 IRegion getRegion(int geoX, int geoY) ++ private final boolean loadGeoBlocks(int regionX, int regionY) + { +- final int region = ((geoX / IRegion.REGION_CELLS_X) * GEO_REGIONS_Y) + (geoY / IRegion.REGION_CELLS_Y); +- if ((region < 0) || (region >= _regions.length())) ++ final String filename = String.format(GeoFormat.L2D.getFilename(), regionX, regionY); ++ ++ // standard load ++ try (RandomAccessFile raf = new RandomAccessFile(Config.GEODATA_PATH + filename, "r"); ++ FileChannel fc = raf.getChannel()) + { +- return null; ++ // initialize file buffer ++ final MappedByteBuffer buffer = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()).load(); ++ buffer.order(ByteOrder.LITTLE_ENDIAN); ++ ++ // 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++) ++ { ++ // get block type ++ final byte type = buffer.get(); ++ ++ // load block according to block type ++ switch (type) ++ { ++ case GeoStructure.TYPE_FLAT_L2D: ++ { ++ _blocks[blockX + ix][blockY + iy] = new BlockFlat(buffer, GeoFormat.L2D); ++ break; ++ } ++ case GeoStructure.TYPE_COMPLEX_L2D: ++ { ++ _blocks[blockX + ix][blockY + iy] = new BlockComplex(buffer, GeoFormat.L2D); ++ break; ++ } ++ case GeoStructure.TYPE_MULTILAYER_L2D: ++ { ++ _blocks[blockX + ix][blockY + iy] = new BlockMultilayer(buffer, GeoFormat.L2D); ++ break; ++ } ++ default: ++ { ++ throw new IllegalArgumentException("Unknown block type: " + type); ++ } ++ } ++ } ++ } ++ ++ // check data consistency ++ if (buffer.remaining() > 0) ++ { ++ LOGGER.warning("GeoEngine: Region file " + filename + " can be corrupted, remaining " + buffer.remaining() + " bytes to read."); ++ } ++ ++ // loading was successful ++ return true; + } +- return _regions.get(region); +- } +- +- /** +- * @param filePath +- * @param regionX +- * @param regionY +- * @throws IOException +- */ +- 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")) ++ catch (Exception e) + { +- _regions.set(regionOffset, new Region(raf.getChannel().map(MapMode.READ_ONLY, 0, raf.length()).load().order(ByteOrder.LITTLE_ENDIAN))); ++ // an error occured while loading, load null blocks ++ LOGGER.warning("GeoEngine: Error while loading " + filename + " region file."); ++ LOGGER.warning(e.getMessage()); ++ ++ // replace whole region file with null blocks ++ loadNullBlocks(regionX, regionY); ++ ++ // loading was not successful ++ return false; + } + } + + /** +- * @param regionX +- * @param regionY ++ * 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. + */ +- public void unloadRegion(int regionX, int regionY) ++ private final void loadNullBlocks(int regionX, int regionY) + { +- _regions.set((regionX * GEO_REGIONS_Y) + regionY, NullRegion.INSTANCE); +- } +- +- /** +- * @param geoX +- * @param geoY +- * @return if geodata exist +- */ +- public boolean hasGeoPos(int geoX, int geoY) +- { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) ++ // 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++) + { +- return false; ++ for (int iy = 0; iy < GeoStructure.REGION_BLOCKS_Y; iy++) ++ { ++ _blocks[blockX + ix][blockY + iy] = _nullBlock; ++ } + } +- return region.hasGeo(); + } + ++ // GEODATA - GENERAL ++ + /** +- * Checks the specified position for available geodata. +- * @param x the world x +- * @param y the world y +- * @return {@code true} if there is geodata for the given coordinates, {@code false} otherwise ++ * Converts world X to geodata X. ++ * @param worldX ++ * @return int : Geo X + */ +- public boolean hasGeo(int x, int y) ++ public static int getGeoX(int worldX) + { +- return hasGeoPos(getGeoX(x), getGeoY(y)); ++ return (MathUtil.limit(worldX, World.MAP_MIN_X, World.MAP_MAX_X) - World.MAP_MIN_X) >> 4; + } + + /** +- * @param geoX +- * @param geoY +- * @param worldZ +- * @param nswe +- * @return the nearest nswe check ++ * Converts world Y to geodata Y. ++ * @param worldY ++ * @return int : Geo Y + */ +- public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) ++ public static int getGeoY(int worldY) + { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) +- { +- return true; +- } +- return region.checkNearestNswe(geoX, geoY, worldZ, nswe); ++ return (MathUtil.limit(worldY, World.MAP_MIN_Y, World.MAP_MAX_Y) - World.MAP_MIN_Y) >> 4; + } + + /** ++ * Converts geodata X to world X. + * @param geoX +- * @param geoY +- * @param worldZ +- * @param nswe +- * @return the nearest nswe anti-corner cut ++ * @return int : World X + */ +- public boolean checkNearestNsweAntiCornerCut(int geoX, int geoY, int worldZ, int nswe) ++ public static int getWorldX(int geoX) + { +- boolean can = true; +- if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) +- { +- can = checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_EAST) && checkNearestNswe(geoX + 1, geoY, worldZ, Cell.NSWE_NORTH); +- } +- if (can && ((nswe & Cell.NSWE_NORTH_WEST) == Cell.NSWE_NORTH_WEST)) +- { +- 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 = 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 = checkNearestNswe(geoX, geoY + 1, worldZ, Cell.NSWE_WEST) && checkNearestNswe(geoX - 1, geoY, worldZ, Cell.NSWE_SOUTH); +- } +- return can && checkNearestNswe(geoX, geoY, worldZ, nswe); ++ return (MathUtil.limit(geoX, 0, GeoStructure.GEO_CELLS_X) << 4) + World.MAP_MIN_X + 8; + } + + /** +- * @param geoX ++ * Converts geodata Y to world Y. + * @param geoY +- * @param worldZ +- * @return the nearest Z value ++ * @return int : World Y + */ +- public int getNearestZ(int geoX, int geoY, int worldZ) ++ public static int getWorldY(int geoY) + { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) +- { +- return worldZ; +- } +- return region.getNearestZ(geoX, geoY, worldZ); ++ return (MathUtil.limit(geoY, 0, GeoStructure.GEO_CELLS_Y) << 4) + World.MAP_MIN_Y + 8; + } + + /** +- * @param geoX +- * @param geoY +- * @param worldZ +- * @return the next lower Z value ++ * Returns block of geodata on given coordinates. ++ * @param geoX : Geodata X ++ * @param geoY : Geodata Y ++ * @return {@link ABlock} : Block of geodata. + */ +- public int getNextLowerZ(int geoX, int geoY, int worldZ) ++ private final ABlock getBlock(int geoX, int geoY) + { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) ++ final int x = geoX / GeoStructure.BLOCK_CELLS_X; ++ final int y = geoY / GeoStructure.BLOCK_CELLS_Y; ++ ++ // if x or y is out of array return null ++ if ((x > -1) && (y > -1) && (x < GeoStructure.GEO_BLOCKS_X) && (y < GeoStructure.GEO_BLOCKS_Y)) + { +- return worldZ; ++ return _blocks[x][y]; + } +- return region.getNextLowerZ(geoX, geoY, worldZ); ++ return null; + } + + /** +- * @param geoX +- * @param geoY +- * @param worldZ +- * @return the next higher Z value ++ * Check if geo coordinates has geo. ++ * @param geoX : Geodata X ++ * @param geoY : Geodata Y ++ * @return boolean : True, if given geo coordinates have geodata + */ +- public int getNextHigherZ(int geoX, int geoY, int worldZ) ++ public boolean hasGeoPos(int geoX, int geoY) + { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) ++ final ABlock block = getBlock(geoX, geoY); ++ if (block == null) // null block check + { +- return worldZ; ++ // TODO: Find when this can be null. (Bad geodata? Check World getRegion method.) ++ // LOGGER.warning("Could not find geodata block at " + getWorldX(geoX) + ", " + getWorldY(geoY) + "."); ++ return false; + } +- return region.getNextHigherZ(geoX, geoY, worldZ); ++ return block.hasGeoPos(); + } + + /** +- * Gets the Z height. +- * @param x the world x +- * @param y the world y +- * @param z the world z +- * @return the nearest Z height ++ * 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 int getHeight(int x, int y, int z) ++ public short getHeightNearest(int geoX, int geoY, int worldZ) + { +- return getNearestZ(getGeoX(x), getGeoY(y), z); ++ final ABlock block = getBlock(geoX, geoY); ++ return block != null ? block.getHeightNearest(geoX, geoY, worldZ) : (short) worldZ; + } + + /** +- * Gets the next lower Z height. +- * @param x the world x +- * @param y the world y +- * @param z the world z +- * @return the nearest Z height ++ * 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 int getLowerHeight(int x, int y, int z) ++ public byte getNsweNearest(int geoX, int geoY, int worldZ) + { +- return getNextLowerZ(getGeoX(x), getGeoY(y), z); ++ final ABlock block = getBlock(geoX, geoY); ++ return block != null ? block.getNsweNearest(geoX, geoY, worldZ) : (byte) 0xFF; + } + + /** +- * Gets the next higher Z height. +- * @param x the world x +- * @param y the world y +- * @param z the world z +- * @return the nearest Z height ++ * Check if world coordinates has geo. ++ * @param worldX : World X ++ * @param worldY : World Y ++ * @return boolean : True, if given world coordinates have geodata + */ +- public int getHigherHeight(int x, int y, int z) ++ public boolean hasGeo(int worldX, int worldY) + { +- return getNextHigherZ(getGeoX(x), getGeoY(y), z); ++ return hasGeoPos(getGeoX(worldX), getGeoY(worldY)); + } + + /** +- * @param worldX +- * @return the geo X ++ * 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 int getGeoX(int worldX) ++ public short getHeight(int worldX, int worldY, int worldZ) + { +- return (worldX - WORLD_MIN_X) / 16; ++ return getHeightNearest(getGeoX(worldX), getGeoY(worldY), worldZ); + } + +- /** +- * @param worldY +- * @return the geo Y +- */ +- public int getGeoY(int worldY) +- { +- return (worldY - WORLD_MIN_Y) / 16; +- } ++ // PATHFINDING + + /** +- * @param worldZ +- * @return the geo Z ++ * Check line of sight from {@link WorldObject} to {@link WorldObject}. ++ * @param origin : The origin object. ++ * @param target : The target object. ++ * @return {@code boolean} : True if origin can see target + */ +- public int getGeoZ(int worldZ) ++ public boolean canSeeTarget(WorldObject origin, WorldObject target) + { +- return (worldZ - WORLD_MIN_Z) / 16; +- } +- +- /** +- * @param geoX +- * @return the world X +- */ +- public int getWorldX(int geoX) +- { +- return (geoX * 16) + WORLD_MIN_X + 8; +- } +- +- /** +- * @param geoY +- * @return the world Y +- */ +- public int getWorldY(int geoY) +- { +- return (geoY * 16) + WORLD_MIN_Y + 8; +- } +- +- /** +- * @param geoZ +- * @return the world Z +- */ +- public int getWorldZ(int geoZ) +- { +- return (geoZ * 16) + WORLD_MIN_Z + 8; +- } +- +- /** +- * 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) +- { +- if (target.isDoor()) ++ if (target.isDoor() || target.isArtefact() || (target.isCreature() && ((Creature) target).isFlying())) + { +- // Can always see doors. + return true; + } +- return 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(), cha.getInstanceId(), worldPosition.getX(), worldPosition.getY(), worldPosition.getZ()); +- } +- +- /** +- * 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 instanceId +- * @return +- */ +- public boolean canSeeTarget(int x, int y, int z, int instanceId, int tx, int ty, int tz, int tInstanceId) +- { +- return (instanceId != tInstanceId) ? false : canSeeTarget(x, y, z, instanceId, tx, ty, tz); +- } +- +- /** +- * 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 +- * @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 instanceId, int tx, int ty, int tz) +- { +- if (DoorData.getInstance().checkIfDoorsBetween(x, y, z, tx, ty, tz, instanceId, true)) ++ ++ // get origin and target world coordinates ++ final int ox = origin.getX(); ++ final int oy = origin.getY(); ++ final int oz = origin.getZ(); ++ final int tx = target.getX(); ++ final int ty = target.getY(); ++ final int tz = target.getZ(); ++ ++ if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, origin.getInstanceId(), true)) + { + return false; + } +- return canSeeTarget(x, y, z, tx, ty, tz); +- } +- +- /** +- * @param prevX +- * @param prevY +- * @param prevGeoZ +- * @param curX +- * @param curY +- * @param nswe +- * @return the LOS Z value +- */ +- 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))) ++ if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, origin.getInstanceId())) + { +- throw new RuntimeException("Multiple directions!"); ++ return false; + } + +- if (checkNearestNsweAntiCornerCut(prevX, prevY, prevGeoZ, nswe)) ++ // get origin and check existing geo coordinates ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) + { +- return getNearestZ(curX, curY, prevGeoZ); ++ return true; + } + +- return getNextHigherZ(curX, curY, prevGeoZ); ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coordinates ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) ++ { ++ return true; ++ } ++ ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // origin and target coordinates are same ++ if ((gox == gtx) && (goy == gty)) ++ { ++ return goz == gtz; ++ } ++ ++ // get origin and target height, real height = collision height * 2 ++ double oheight = 0; ++ if (origin.isCreature()) ++ { ++ oheight = ((Creature) origin).getTemplate().getCollisionHeight() * 2; ++ } ++ ++ double theight = 0; ++ if (target.isCreature()) ++ { ++ theight = ((Creature) target).getTemplate().getCollisionHeight() * 2; ++ } ++ ++ // perform geodata check ++ return checkSee(gox, goy, goz, oheight, gtx, gty, gtz, theight, origin.getInstanceId()); + } + + /** +- * Can see target. Does not check doors between. +- * @param xValue the x coordinate +- * @param yValue the y coordinate +- * @param zValue the z coordinate +- * @param txValue the target's x coordinate +- * @param tyValue the target's y coordinate +- * @param tzValue the target's z coordinate +- * @return {@code true} if there is line of sight between the given coordinate sets, {@code false} otherwise ++ * Check line of sight from {@link WorldObject} to {@link Location}. ++ * @param origin : The origin object. ++ * @param position : The target position. ++ * @return {@code boolean} : True if object can see position + */ +- public boolean canSeeTarget(int xValue, int yValue, int zValue, int txValue, int tyValue, int tzValue) ++ public boolean canSeeTarget(WorldObject origin, Location position) + { +- int x = xValue; +- int y = yValue; +- int tx = txValue; +- int ty = tyValue; ++ // get origin and target world coordinates ++ final int ox = origin.getX(); ++ final int oy = origin.getY(); ++ final int oz = origin.getZ(); ++ final int tx = position.getX(); ++ final int ty = position.getY(); ++ final int tz = position.getZ(); + +- int geoX = getGeoX(x); +- int geoY = getGeoY(y); +- int tGeoX = getGeoX(tx); +- int tGeoY = getGeoY(ty); ++ if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, origin.getInstanceId(), true)) ++ { ++ return false; ++ } ++ if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, origin.getInstanceId())) ++ { ++ return false; ++ } + +- int z = getNearestZ(geoX, geoY, zValue); +- int tz = getNearestZ(tGeoX, tGeoY, tzValue); ++ // get origin and check existing geo coordinates ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) ++ { ++ return true; ++ } + +- // Fastpath. +- if ((geoX == tGeoX) && (geoY == tGeoY)) ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coordinates ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) + { +- if (hasGeoPos(tGeoX, tGeoY)) +- { +- return z == tz; +- } + return true; + } + +- if (tz > z) ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // origin and target coordinates are same ++ if ((gox == gtx) && (goy == gty)) + { +- int tmp = tx; +- tx = x; +- x = tmp; +- +- tmp = ty; +- ty = y; +- y = tmp; +- +- tmp = tz; +- tz = z; +- z = tmp; +- +- tmp = tGeoX; +- tGeoX = geoX; +- geoX = tmp; +- +- tmp = tGeoY; +- tGeoY = geoY; +- geoY = tmp; ++ return goz == gtz; + } + +- final LinePointIterator3D pointIter = new LinePointIterator3D(geoX, geoY, z, tGeoX, tGeoY, tz); +- // 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()) ++ // get origin and target height, real height = collision height * 2 ++ double oheight = 0; ++ if (origin.isCreature()) + { +- final int curX = pointIter.x(); +- final int curY = pointIter.y(); ++ oheight = ((Creature) origin).getTemplate().getCollisionHeight(); ++ } ++ ++ // perform geodata check ++ return checkSee(gox, goy, goz, oheight, gtx, gty, gtz, 0, origin.getInstanceId()); ++ } ++ ++ /** ++ * Simple check for origin to target visibility. ++ * @param goxValue : origin X geodata coordinate ++ * @param goyValue : origin Y geodata coordinate ++ * @param gozValue : origin Z geodata coordinate ++ * @param oheight : origin height (if instance of {@link Character}) ++ * @param gtxValue : target X geodata coordinate ++ * @param gtyValue : target Y geodata coordinate ++ * @param gtzValue : target Z geodata coordinate ++ * @param theight : target height (if instance of {@link Character}) ++ * @param instanceId ++ * @return {@code boolean} : True, when target can be seen. ++ */ ++ private final boolean checkSee(int goxValue, int goyValue, int gozValue, double oheight, int gtxValue, int gtyValue, int gtzValue, double theight, int instanceId) ++ { ++ int goz = gozValue; ++ int gtz = gtzValue; ++ int gox = goxValue; ++ int goy = goyValue; ++ int gtx = gtxValue; ++ int gty = gtyValue; ++ ++ // get line of sight Z coordinates ++ double losoz = goz + ((oheight * Config.PART_OF_CHARACTER_HEIGHT) / 100); ++ double lostz = gtz + ((theight * Config.PART_OF_CHARACTER_HEIGHT) / 100); ++ ++ // get X delta and signum ++ final int dx = Math.abs(gtx - gox); ++ final int sx = gox < gtx ? 1 : -1; ++ final byte dirox = sx > 0 ? GeoStructure.CELL_FLAG_E : GeoStructure.CELL_FLAG_W; ++ final byte dirtx = sx > 0 ? GeoStructure.CELL_FLAG_W : GeoStructure.CELL_FLAG_E; ++ ++ // get Y delta and signum ++ final int dy = Math.abs(gty - goy); ++ final int sy = goy < gty ? 1 : -1; ++ final byte diroy = sy > 0 ? GeoStructure.CELL_FLAG_S : GeoStructure.CELL_FLAG_N; ++ final byte dirty = sy > 0 ? GeoStructure.CELL_FLAG_N : GeoStructure.CELL_FLAG_S; ++ ++ // get Z delta ++ final int dm = Math.max(dx, dy); ++ final double dz = (lostz - losoz) / dm; ++ ++ // get direction flag for diagonal movement ++ final byte diroxy = getDirXY(dirox, diroy); ++ final byte dirtxy = getDirXY(dirtx, dirty); ++ ++ // delta, determines axis to move on (+..X axis, -..Y axis) ++ int d = dx - dy; ++ ++ // NSWE direction of movement ++ byte diro; ++ byte dirt; ++ ++ // initialize node values ++ int nox = gox; ++ int noy = goy; ++ int ntx = gtx; ++ int nty = gty; ++ byte nsweo = getNsweNearest(gox, goy, goz); ++ byte nswet = getNsweNearest(gtx, gty, gtz); ++ ++ // loop ++ ABlock block; ++ int index; ++ for (int i = 0; i < ((dm + 1) / 2); i++) ++ { ++ // reset direction flag ++ diro = 0; ++ dirt = 0; + +- if ((curX == prevX) && (curY == prevY)) ++ // calculate next point coordinates ++ final int e2 = 2 * d; ++ if ((e2 > -dy) && (e2 < dx)) + { +- continue; ++ // calculate next point XY coordinates ++ d -= dy; ++ d += dx; ++ nox += sx; ++ ntx -= sx; ++ noy += sy; ++ nty -= sy; ++ diro |= diroxy; ++ dirt |= dirtxy; + } ++ else if (e2 > -dy) ++ { ++ // calculate next point X coordinate ++ d -= dy; ++ nox += sx; ++ ntx -= sx; ++ diro |= dirox; ++ dirt |= dirtx; ++ } ++ else if (e2 < dx) ++ { ++ // calculate next point Y coordinate ++ d += dx; ++ noy += sy; ++ nty -= sy; ++ diro |= diroy; ++ dirt |= dirty; ++ } + +- final int beeCurZ = pointIter.z(); +- int curGeoZ = prevGeoZ; +- +- // Check if the position has geodata. +- if (hasGeoPos(curX, curY)) + { +- final int beeCurGeoZ = getNearestZ(curX, curY, beeCurZ); +- final int nswe = GeoUtils.computeNswe(prevX, prevY, curX, curY); // .computeDirection(prevX, prevY, curX, curY); +- curGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, curX, curY, nswe); +- int maxHeight; +- if (ptIndex < ELEVATED_SEE_OVER_DISTANCE) ++ // get block of the next cell ++ block = getBlock(nox, noy); ++ ++ // get index of particular layer, based on movement conditions ++ if ((nsweo & diro) == 0) + { +- maxHeight = z + MAX_SEE_OVER_HEIGHT; ++ index = block.getIndexAbove(nox, noy, goz - GeoStructure.CELL_IGNORE_HEIGHT); + } + else + { +- maxHeight = beeCurZ + MAX_SEE_OVER_HEIGHT; ++ index = block.getIndexBelow(nox, noy, goz + GeoStructure.CELL_IGNORE_HEIGHT); + } + +- boolean canSeeThrough = false; +- if ((curGeoZ <= maxHeight) && (curGeoZ <= beeCurGeoZ)) ++ // layer does not exist, return ++ if (index == -1) + { +- if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) +- { +- 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 +- { +- canSeeThrough = true; +- } ++ return false; + } + +- if (!canSeeThrough) ++ // get layer and next line of sight Z coordinate ++ goz = block.getHeight(index); ++ losoz += dz; ++ ++ // perform line of sight check, return when fails ++ if ((goz - losoz) > Config.MAX_OBSTACLE_HEIGHT) + { + return false; + } ++ ++ // get layer nswe ++ nsweo = block.getNswe(index); + } ++ { ++ // get block of the next cell ++ block = getBlock(ntx, nty); ++ ++ // get index of particular layer, based on movement conditions ++ if ((nswet & dirt) == 0) ++ { ++ index = block.getIndexAbove(ntx, nty, gtz - GeoStructure.CELL_IGNORE_HEIGHT); ++ } ++ else ++ { ++ index = block.getIndexBelow(ntx, nty, gtz + GeoStructure.CELL_IGNORE_HEIGHT); ++ } ++ ++ // layer does not exist, return ++ if (index == -1) ++ { ++ return false; ++ } ++ ++ // get layer and next line of sight Z coordinate ++ gtz = block.getHeight(index); ++ lostz -= dz; ++ ++ // perform line of sight check, return when fails ++ if ((gtz - lostz) > Config.MAX_OBSTACLE_HEIGHT) ++ { ++ return false; ++ } ++ ++ // get layer nswe ++ nswet = block.getNswe(index); ++ } + +- prevX = curX; +- prevY = curY; +- prevGeoZ = curGeoZ; +- ++ptIndex; ++ // update coords ++ gox = nox; ++ goy = noy; ++ gtx = ntx; ++ gty = nty; + } + +- return true; ++ // when iteration is completed, compare final Z coordinates ++ return Math.abs(goz - gtz) < (GeoStructure.CELL_HEIGHT * 4); + } + + /** +- * Move check. +- * @param x the x coordinate +- * @param y the y coordinate +- * @param zValue the z coordinate +- * @param tx the target's x coordinate +- * @param ty the target's y coordinate +- * @param tzValue the target's z coordinate +- * @param instanceId the instance id +- * @return the last Location (x,y,z) where player can walk - just before wall ++ * Check movement from coordinates to 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 {code boolean} : True if target coordinates are reachable from origin coordinates + */ +- public Location canMoveToTargetLoc(int x, int y, int zValue, int tx, int ty, int tzValue, int instanceId) ++ public boolean canMoveToTarget(int ox, int oy, int oz, int tx, int ty, int tz, int instanceId) + { +- final int geoX = getGeoX(x); +- final int geoY = getGeoY(y); +- final int z = getNearestZ(geoX, geoY, zValue); +- final int tGeoX = getGeoX(tx); +- final int tGeoY = getGeoY(ty); +- final int tz = getNearestZ(tGeoX, tGeoY, tzValue); ++ // get origin and check existing geo coordinates ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) ++ { ++ return true; ++ } + +- if (DoorData.getInstance().checkIfDoorsBetween(x, y, z, tx, ty, tz, instanceId, false)) ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coordinates ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) + { +- return new Location(x, y, getHeight(x, y, z)); ++ return true; + } +- if (FenceData.getInstance().checkIfFenceBetween(x, y, z, tx, ty, tz, instanceId)) ++ ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // target coordinates reached ++ if ((gox == gtx) && (goy == gty) && (goz == gtz)) + { +- return new Location(x, y, getHeight(x, y, z)); ++ return true; + } + +- 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 = z; ++ // perform geodata check ++ final GeoLocation loc = checkMove(gox, goy, goz, gtx, gty, gtz, instanceId); ++ return (loc.getGeoX() == gtx) && (loc.getGeoY() == gty); ++ } ++ ++ /** ++ * Check movement from origin to target. Returns last available point in the checked path. ++ * @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 {@link Location} : Last point where object can walk (just before wall) ++ */ ++ public Location canMoveToTargetLoc(int ox, int oy, int oz, int tx, int ty, int tz, int instanceId) ++ { ++ // Mobius: Double check for doors before normal checkMove to avoid exploiting key movement. ++ if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, instanceId, false)) ++ { ++ return new Location(ox, oy, oz); ++ } ++ if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, instanceId)) ++ { ++ return new Location(ox, oy, oz); ++ } + +- while (pointIter.next()) ++ // get origin and check existing geo coordinates ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) + { +- final int curX = pointIter.x(); +- final int curY = pointIter.y(); +- final int curZ = getNearestZ(curX, curY, prevZ); +- +- if (hasGeoPos(prevX, prevY)) +- { +- final int nswe = GeoUtils.computeNswe(prevX, prevY, curX, curY); +- if (!checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, nswe)) +- { +- // Can't move, return previous location. +- return new Location(getWorldX(prevX), getWorldY(prevY), prevZ); +- } +- } +- +- prevX = curX; +- prevY = curY; +- prevZ = curZ; ++ return new Location(tx, ty, tz); + } + +- if (hasGeoPos(prevX, prevY) && (prevZ != tz)) ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coordinates ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) + { +- // Different floors, return start location. +- return new Location(x, y, z); ++ return new Location(tx, ty, tz); + } + +- return new Location(tx, ty, tz); ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // target coordinates reached ++ if ((gox == gtx) && (goy == gty) && (goz == gtz)) ++ { ++ return new Location(tx, ty, tz); ++ } ++ ++ // perform geodata check ++ return checkMove(gox, goy, goz, gtx, gty, gtz, instanceId); + } + + /** +- * 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 fromZvalue 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 toZvalue the Z coordinate to end checking at +- * @param instanceId the instance +- * @return {@code true} if the character at start coordinates can move to end coordinates, {@code false} otherwise ++ * With this method you can check if a position is visible or can be reached by beeline movement.
++ * 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 gox : origin X geodata coordinate ++ * @param goy : origin Y geodata coordinate ++ * @param goz : origin Z geodata coordinate ++ * @param gtx : target X geodata coordinate ++ * @param gty : target Y geodata coordinate ++ * @param gtz : target Z geodata coordinate ++ * @param instanceId ++ * @return {@link GeoLocation} : The last allowed point of movement. + */ +- public boolean canMoveToTarget(int fromX, int fromY, int fromZvalue, int toX, int toY, int toZvalue, int instanceId) ++ protected final GeoLocation checkMove(int gox, int goy, int goz, int gtx, int gty, int gtz, int instanceId) + { +- final int geoX = getGeoX(fromX); +- final int geoY = getGeoY(fromY); +- final int fromZ = getNearestZ(geoX, geoY, fromZvalue); +- final int tGeoX = getGeoX(toX); +- final int tGeoY = getGeoY(toY); +- final int toZ = getNearestZ(tGeoX, tGeoY, toZvalue); +- +- if (DoorData.getInstance().checkIfDoorsBetween(fromX, fromY, fromZ, toX, toY, toZ, instanceId, false)) ++ if (DoorData.getInstance().checkIfDoorsBetween(gox, goy, goz, gtx, gty, gtz, instanceId, false)) + { +- return false; ++ return new GeoLocation(gox, goy, goz); + } +- if (FenceData.getInstance().checkIfFenceBetween(fromX, fromY, fromZ, toX, toY, toZ, instanceId)) ++ if (FenceData.getInstance().checkIfFenceBetween(gox, goy, goz, gtx, gty, gtz, instanceId)) + { +- return false; ++ return new GeoLocation(gox, goy, goz); + } + +- 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 = fromZ; ++ // get X delta, signum and direction flag ++ final int dx = Math.abs(gtx - gox); ++ final int sx = gox < gtx ? 1 : -1; ++ final byte dirX = sx > 0 ? GeoStructure.CELL_FLAG_E : GeoStructure.CELL_FLAG_W; + +- while (pointIter.next()) ++ // get Y delta, signum and direction flag ++ final int dy = Math.abs(gty - goy); ++ final int sy = goy < gty ? 1 : -1; ++ final byte dirY = sy > 0 ? GeoStructure.CELL_FLAG_S : GeoStructure.CELL_FLAG_N; ++ ++ // get direction flag for diagonal movement ++ final byte dirXY = getDirXY(dirX, dirY); ++ ++ // delta, determines axis to move on (+..X axis, -..Y axis) ++ int d = dx - dy; ++ ++ // NSWE direction of movement ++ byte direction; ++ ++ // load pointer coordinates ++ int gpx = gox; ++ int gpy = goy; ++ int gpz = goz; ++ ++ // load next pointer ++ int nx = gpx; ++ int ny = gpy; ++ ++ // loop ++ int count = 0; ++ while (count++ < Config.MAX_ITERATIONS) + { +- final int curX = pointIter.x(); +- final int curY = pointIter.y(); +- final int curZ = getNearestZ(curX, curY, prevZ); ++ direction = 0; + +- if (hasGeoPos(prevX, prevY)) ++ // calculate next point coordinates ++ final int e2 = 2 * d; ++ if ((e2 > -dy) && (e2 < dx)) + { +- final int nswe = GeoUtils.computeNswe(prevX, prevY, curX, curY); +- if (!checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, nswe)) ++ d -= dy; ++ d += dx; ++ nx += sx; ++ ny += sy; ++ direction |= dirXY; ++ } ++ else if (e2 > -dy) ++ { ++ d -= dy; ++ nx += sx; ++ direction |= dirX; ++ } ++ else if (e2 < dx) ++ { ++ d += dx; ++ ny += sy; ++ direction |= dirY; ++ } ++ ++ // obstacle found, return ++ if ((getNsweNearest(gpx, gpy, gpz) & direction) == 0) ++ { ++ return new GeoLocation(gpx, gpy, gpz); ++ } ++ ++ // update pointer coordinates ++ gpx = nx; ++ gpy = ny; ++ gpz = getHeightNearest(nx, ny, gpz); ++ ++ // target coordinates reached ++ if ((gpx == gtx) && (gpy == gty)) ++ { ++ if (gpz == gtz) + { +- return false; ++ // path found, Z coordinates are okay, return target point ++ return new GeoLocation(gtx, gty, gtz); + } ++ ++ // path found, Z coordinates are not okay, return last good point ++ return new GeoLocation(gpx, gpy, gpz); + } ++ } ++ ++ return new GeoLocation(gox, goy, goz); ++ } ++ ++ /** ++ * Returns diagonal NSWE flag format of combined two NSWE flags. ++ * @param dirX : X direction NSWE flag ++ * @param dirY : Y direction NSWE flag ++ * @return byte : NSWE flag of combined direction ++ */ ++ private static byte getDirXY(byte dirX, byte dirY) ++ { ++ // check axis directions ++ if (dirY == GeoStructure.CELL_FLAG_N) ++ { ++ if (dirX == GeoStructure.CELL_FLAG_W) ++ { ++ return GeoStructure.CELL_FLAG_NW; ++ } + +- prevX = curX; +- prevY = curY; +- prevZ = curZ; ++ return GeoStructure.CELL_FLAG_NE; + } + +- if (hasGeoPos(prevX, prevY) && (prevZ != toZ)) ++ if (dirX == GeoStructure.CELL_FLAG_W) + { +- // Different floors. +- return false; ++ return GeoStructure.CELL_FLAG_SW; + } + +- return true; ++ return GeoStructure.CELL_FLAG_SE; + } + ++ /** ++ * 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 ++ */ ++ public List findPath(int ox, int oy, int oz, int tx, int ty, int tz, int instanceId) ++ { ++ return null; ++ } ++ ++ /** ++ * Returns the instance of the {@link GeoEngine}. ++ * @return {@link GeoEngine} : The instance. ++ */ + public static GeoEngine getInstance() + { + return SingletonHolder.INSTANCE; +@@ -742,6 +933,6 @@ + + private static class SingletonHolder + { +- protected static final GeoEngine INSTANCE = new GeoEngine(); ++ protected static final GeoEngine INSTANCE = Config.PATHFINDING ? new GeoEnginePathfinding() : new GeoEngine(); + } +-} ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/GeoEnginePathfinding.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/GeoEnginePathfinding.java (revision 8319) ++++ java/org/l2jmobius/gameserver/geoengine/GeoEnginePathfinding.java (working copy) +@@ -20,87 +20,83 @@ + import java.util.LinkedList; + import java.util.List; + import java.util.ListIterator; +-import java.util.logging.Level; +-import java.util.logging.Logger; + + import org.l2jmobius.Config; +-import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; +-import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +-import org.l2jmobius.gameserver.geoengine.pathfinding.CellNode; +-import org.l2jmobius.gameserver.geoengine.pathfinding.CellNodeBuffer; +-import org.l2jmobius.gameserver.geoengine.pathfinding.NodeLoc; +-import org.l2jmobius.gameserver.model.World; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoLocation; ++import org.l2jmobius.gameserver.geoengine.pathfinding.Node; ++import org.l2jmobius.gameserver.geoengine.pathfinding.NodeBuffer; ++import org.l2jmobius.gameserver.model.Location; + + /** +- * @author -Nemesiss- ++ * @author Hasha + */ +-public class GeoEnginePathfinding ++final class GeoEnginePathfinding extends GeoEngine + { +- private static final Logger LOGGER = Logger.getLogger(GeoEnginePathfinding.class.getName()); ++ // pre-allocated buffers ++ private final BufferHolder[] _buffers; + +- private BufferInfo[] _buffers; +- + protected GeoEnginePathfinding() + { +- try ++ super(); ++ ++ final String[] array = Config.PATHFIND_BUFFERS.split(";"); ++ _buffers = new BufferHolder[array.length]; ++ int count = 0; ++ for (int i = 0; i < array.length; i++) + { +- final String[] array = Config.PATHFIND_BUFFERS.split(";"); ++ final String buf = array[i]; ++ final String[] args = buf.split("x"); + +- _buffers = new BufferInfo[array.length]; +- +- String buf; +- String[] args; +- for (int i = 0; i < array.length; i++) ++ try + { +- buf = array[i]; +- args = buf.split("x"); +- if (args.length != 2) +- { +- throw new Exception("Invalid buffer definition: " + buf); +- } +- +- _buffers[i] = new BufferInfo(Integer.parseInt(args[0]), Integer.parseInt(args[1])); ++ final int size = Integer.parseInt(args[1]); ++ count += size; ++ _buffers[i] = new BufferHolder(Integer.parseInt(args[0]), size); + } ++ catch (Exception e) ++ { ++ LOGGER.warning("GeoEnginePathfinding: Can not load buffer setting: " + buf); ++ } + } +- catch (Exception e) +- { +- LOGGER.log(Level.WARNING, "CellPathFinding: Problem during buffer init: " + e.getMessage(), e); +- throw new Error("CellPathFinding: load aborted"); +- } ++ ++ LOGGER.info("GeoEnginePathfinding: Loaded " + count + " node buffers."); + } + +- public boolean pathNodesExist(short regionoffset) ++ @Override ++ public List findPath(int ox, int oy, int oz, int tx, int ty, int tz, int instanceId) + { +- return false; +- } +- +- public List findPath(int x, int y, int z, int tx, int ty, int tz, int instanceId) +- { +- final int gx = GeoEngine.getInstance().getGeoX(x); +- final int gy = GeoEngine.getInstance().getGeoY(y); +- if (!GeoEngine.getInstance().hasGeo(x, y)) ++ // get origin and check existing geo coords ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) + { + 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)) ++ ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coords ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) + { + 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)))); ++ ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // Prepare buffer for pathfinding calculations ++ final NodeBuffer buffer = getBuffer(64 + (2 * Math.max(Math.abs(gox - gtx), Math.abs(goy - gty)))); + if (buffer == null) + { + return null; + } + +- List path = null; ++ // find path ++ List path = null; + try + { +- final CellNode result = buffer.findPath(gx, gy, gz, gtx, gty, gtz); +- ++ final Node result = buffer.findPath(gox, goy, goz, gtx, gty, gtz); + if (result == null) + { + return null; +@@ -124,33 +120,45 @@ + return path; + } + +- int currentX, currentY, currentZ; +- ListIterator middlePoint; ++ // get path list iterator ++ final ListIterator point = path.listIterator(); + +- middlePoint = path.listIterator(); +- currentX = x; +- currentY = y; +- currentZ = z; ++ // get node A (origin) ++ int nodeAx = gox; ++ int nodeAy = goy; ++ short nodeAz = goz; + +- while (middlePoint.hasNext()) ++ // get node B ++ GeoLocation nodeB = (GeoLocation) point.next(); ++ ++ // iterate thought the path to optimize it ++ int count = 0; ++ while (point.hasNext() && (count++ < Config.MAX_ITERATIONS)) + { +- final AbstractNodeLoc locMiddle = middlePoint.next(); +- if (!middlePoint.hasNext()) +- { +- break; +- } ++ // get node C ++ final GeoLocation nodeC = (GeoLocation) path.get(point.nextIndex()); + +- final AbstractNodeLoc locEnd = path.get(middlePoint.nextIndex()); +- if (GeoEngine.getInstance().canMoveToTarget(currentX, currentY, currentZ, locEnd.getX(), locEnd.getY(), locEnd.getZ(), instanceId)) ++ // check movement from node A to node C ++ final GeoLocation loc = checkMove(nodeAx, nodeAy, nodeAz, nodeC.getGeoX(), nodeC.getGeoY(), nodeC.getZ(), instanceId); ++ if ((loc.getGeoX() == nodeC.getGeoX()) && (loc.getGeoY() == nodeC.getGeoY())) + { +- middlePoint.remove(); ++ // can move from node A to node C ++ ++ // remove node B ++ point.remove(); + } + else + { +- currentX = locMiddle.getX(); +- currentY = locMiddle.getY(); +- currentZ = locMiddle.getZ(); ++ // can not move from node A to node C ++ ++ // set node A (node B is part of path, update A coordinates) ++ nodeAx = nodeB.getGeoX(); ++ nodeAy = nodeB.getGeoY(); ++ nodeAz = (short) nodeB.getZ(); + } ++ ++ // set node B ++ nodeB = (GeoLocation) point.next(); + } + + return path; +@@ -157,144 +165,102 @@ + } + + /** +- * Convert geodata position to pathnode position +- * @param geo_pos +- * @return pathnode position ++ * Create list of node locations as result of calculated buffer node tree. ++ * @param node : the entry point ++ * @return List : list of node location + */ +- public short getNodePos(int geo_pos) ++ private static List constructPath(Node node) + { +- return (short) (geo_pos >> 3); // OK? +- } +- +- /** +- * Convert node position to pathnode block position +- * @param node_pos +- * @return pathnode block position (0...255) +- */ +- public short getNodeBlock(int node_pos) +- { +- return (short) (node_pos % 256); +- } +- +- public byte getRegionX(int node_pos) +- { +- return (byte) ((node_pos >> 8) + World.TILE_X_MIN); +- } +- +- public byte getRegionY(int node_pos) +- { +- return (byte) ((node_pos >> 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 node_x rx +- * @return +- */ +- public int calculateWorldX(short node_x) +- { +- return World.MAP_MIN_X + (node_x * 128) + 48; +- } +- +- /** +- * Convert pathnode y to World y position +- * @param node_y +- * @return +- */ +- public int calculateWorldY(short node_y) +- { +- return World.MAP_MIN_Y + (node_y * 128) + 48; +- } +- +- private List constructPath(AbstractNode nodeValue) +- { +- final LinkedList path = new LinkedList<>(); +- int previousDirectionX = Integer.MIN_VALUE; +- int previousDirectionY = Integer.MIN_VALUE; +- int directionX, directionY; ++ // create empty list ++ final LinkedList list = new LinkedList<>(); + +- AbstractNode node = nodeValue; +- while (node.getParent() != null) ++ // set direction X/Y ++ int dx = 0; ++ int dy = 0; ++ ++ // get target parent ++ Node target = node; ++ Node parent = target.getParent(); ++ ++ // while parent exists ++ int count = 0; ++ while ((parent != null) && (count++ < Config.MAX_ITERATIONS)) + { +- directionX = node.getLoc().getNodeX() - node.getParent().getLoc().getNodeX(); +- directionY = node.getLoc().getNodeY() - node.getParent().getLoc().getNodeY(); ++ // get parent <> target direction X/Y ++ final int nx = parent.getLoc().getGeoX() - target.getLoc().getGeoX(); ++ final int ny = parent.getLoc().getGeoY() - target.getLoc().getGeoY(); + +- // only add a new route point if moving direction changes +- if ((directionX != previousDirectionX) || (directionY != previousDirectionY)) ++ // direction has changed? ++ if ((dx != nx) || (dy != ny)) + { +- previousDirectionX = directionX; +- previousDirectionY = directionY; ++ // add node to the beginning of the list ++ list.addFirst(target.getLoc()); + +- path.addFirst(node.getLoc()); +- node.setLoc(null); ++ // update direction X/Y ++ dx = nx; ++ dy = ny; + } + +- node = node.getParent(); ++ // move to next node, set target and get its parent ++ target = parent; ++ parent = target.getParent(); + } + +- return path; ++ // return list ++ return list; + } + +- private CellNodeBuffer alloc(int size) ++ /** ++ * Provides optimize selection of the buffer. When all pre-initialized buffer are locked, creates new buffer. ++ * @param size : pre-calculated minimal required size ++ * @return NodeBuffer : buffer ++ */ ++ private final NodeBuffer getBuffer(int size) + { +- CellNodeBuffer current = null; +- for (BufferInfo i : _buffers) ++ NodeBuffer current = null; ++ for (BufferHolder holder : _buffers) + { +- if (i.mapSize >= size) ++ // Find proper size of buffer ++ if (holder._size < size) + { +- for (CellNodeBuffer buf : i.buffer) ++ continue; ++ } ++ ++ // Find unlocked NodeBuffer ++ for (NodeBuffer buffer : holder._buffer) ++ { ++ if (!buffer.isLocked()) + { +- if (buf.lock()) +- { +- current = buf; +- break; +- } ++ continue; + } +- if (current != null) +- { +- break; +- } + +- // not found, allocate temporary buffer +- current = new CellNodeBuffer(i.mapSize); +- current.lock(); +- if (i.buffer.size() < i.count) +- { +- i.buffer.add(current); +- break; +- } ++ return buffer; + } ++ ++ // NodeBuffer not found, allocate temporary buffer ++ current = new NodeBuffer(holder._size); ++ current.isLocked(); + } + + return current; + } + +- private static final class BufferInfo ++ /** ++ * NodeBuffer container with specified size and count of separate buffers. ++ */ ++ private static final class BufferHolder + { +- final int mapSize; +- final int count; +- ArrayList buffer; ++ final int _size; ++ List _buffer; + +- public BufferInfo(int size, int cnt) ++ public BufferHolder(int size, int count) + { +- mapSize = size; +- count = cnt; +- buffer = new ArrayList<>(count); ++ _size = size; ++ _buffer = new ArrayList<>(count); ++ for (int i = 0; i < count; i++) ++ { ++ _buffer.add(new NodeBuffer(size)); ++ } + } + } +- +- public static GeoEnginePathfinding getInstance() +- { +- return SingletonHolder.INSTANCE; +- } +- +- private static class SingletonHolder +- { +- protected static final GeoEnginePathfinding INSTANCE = new GeoEnginePathfinding(); +- } +-} ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java (working copy) +@@ -0,0 +1,197 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++import java.io.IOException; ++ ++/** ++ * @author Hasha ++ */ ++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 height of cell, which is closest to given coordinates.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @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 getHeightNearestOriginal(int geoX, int geoY, int worldZ); ++ ++ /** ++ * Returns the height of cell, which is first above 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, above given coordinates. ++ */ ++ public abstract short getHeightAbove(int geoX, int geoY, int worldZ); ++ ++ /** ++ * Returns the height of cell, which is first below 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, below given coordinates. ++ */ ++ public abstract short getHeightBelow(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 the NSWE flag byte of cell, which is closest to given coordinates.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @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 getNsweNearestOriginal(int geoX, int geoY, int worldZ); ++ ++ /** ++ * Returns the NSWE flag byte of cell, which is first above 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 getNsweAbove(int geoX, int geoY, int worldZ); ++ ++ /** ++ * Returns the NSWE flag byte of cell, which is first below 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 getNsweBelow(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 above given coordinates.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @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 getIndexAboveOriginal(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 index to data of the cell, which is first layer below given coordinates.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @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 getIndexBelowOriginal(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 height of cell given by cell index.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @param index : Index of the cell. ++ * @return short : Cell geodata Z coordinate, below given coordinates. ++ */ ++ public abstract short getHeightOriginal(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); ++ ++ /** ++ * Returns the NSWE flag byte of cell given by cell index.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @param index : Index of the cell. ++ * @return short : Cell geodata Z coordinate, below given coordinates. ++ */ ++ public abstract byte getNsweOriginal(int index); ++ ++ /** ++ * Sets the NSWE flag byte of cell given by cell index. ++ * @param index : Index of the cell. ++ * @param nswe : New NSWE flag byte. ++ */ ++ public abstract void setNswe(int index, byte nswe); ++ ++ /** ++ * Saves the block in L2D format to {@link BufferedOutputStream}. Used only for L2D geodata conversion. ++ * @param stream : The stream. ++ * @throws IOException : Can't save the block to steam. ++ */ ++ public abstract void saveBlock(BufferedOutputStream stream) throws IOException; ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java (working copy) +@@ -0,0 +1,252 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++import java.io.IOException; ++import java.nio.ByteBuffer; ++ ++/** ++ * @author Hasha ++ */ ++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. ++ * @param format : GeoFormat specifying format of loaded data. ++ */ ++ public BlockComplex(ByteBuffer bb, GeoFormat format) ++ { ++ // initialize buffer ++ _buffer = new byte[GeoStructure.BLOCK_CELLS * 3]; ++ ++ // load data ++ for (int i = 0; i < GeoStructure.BLOCK_CELLS; i++) ++ { ++ if (format != GeoFormat.L2D) ++ { ++ // 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); ++ } ++ else ++ { ++ // get nswe ++ final byte nswe = bb.get(); ++ _buffer[i * 3] = nswe; ++ ++ // get height ++ final short height = bb.getShort(); ++ _buffer[(i * 3) + 1] = (byte) (height & 0x00FF); ++ _buffer[(i * 3) + 2] = (byte) (height >> 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 short getHeightNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getHeightNearest(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeightAbove(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 short height = (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ ++ // check and return height ++ return height > worldZ ? height : Short.MIN_VALUE; ++ } ++ ++ @Override ++ public short getHeightBelow(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 short height = (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ ++ // check and return height ++ return height < worldZ ? height : Short.MAX_VALUE; ++ } ++ ++ @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 byte getNsweNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getNsweNearest(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public byte getNsweAbove(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 ? _buffer[index] : 0; ++ } ++ ++ @Override ++ public byte getNsweBelow(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 ? _buffer[index] : 0; ++ } ++ ++ @Override ++ public 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 getIndexAboveOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexAbove(geoX, geoY, worldZ); ++ } ++ ++ @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 int getIndexBelowOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexBelow(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeight(int index) ++ { ++ return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ } ++ ++ @Override ++ public short getHeightOriginal(int index) ++ { ++ return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ } ++ ++ @Override ++ public byte getNswe(int index) ++ { ++ return _buffer[index]; ++ } ++ ++ @Override ++ public byte getNsweOriginal(int index) ++ { ++ return _buffer[index]; ++ } ++ ++ @Override ++ public void setNswe(int index, byte nswe) ++ { ++ _buffer[index] = nswe; ++ } ++ ++ @Override ++ public void saveBlock(BufferedOutputStream stream) throws IOException ++ { ++ // write block type ++ stream.write(GeoStructure.TYPE_COMPLEX_L2D); ++ ++ // write block data ++ stream.write(_buffer, 0, GeoStructure.BLOCK_CELLS * 3); ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java (working copy) +@@ -0,0 +1,176 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++import java.io.IOException; ++import java.nio.ByteBuffer; ++ ++/** ++ * @author Hasha ++ */ ++public class BlockFlat extends ABlock ++{ ++ protected final short _height; ++ protected byte _nswe; ++ ++ /** ++ * Creates FlatBlock. ++ * @param bb : Input byte buffer. ++ * @param format : GeoFormat specifying format of loaded data. ++ */ ++ public BlockFlat(ByteBuffer bb, GeoFormat format) ++ { ++ _height = bb.getShort(); ++ _nswe = format != GeoFormat.L2D ? 0x0F : (byte) (0xFF); ++ if (format == GeoFormat.L2OFF) ++ { ++ bb.getShort(); ++ } ++ } ++ ++ @Override ++ public boolean hasGeoPos() ++ { ++ return true; ++ } ++ ++ @Override ++ public short getHeightNearest(int geoX, int geoY, int worldZ) ++ { ++ return _height; ++ } ++ ++ @Override ++ public short getHeightNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return _height; ++ } ++ ++ @Override ++ public short getHeightAbove(int geoX, int geoY, int worldZ) ++ { ++ // check and return height ++ return _height > worldZ ? _height : Short.MIN_VALUE; ++ } ++ ++ @Override ++ public short getHeightBelow(int geoX, int geoY, int worldZ) ++ { ++ // check and return height ++ return _height < worldZ ? _height : Short.MAX_VALUE; ++ } ++ ++ @Override ++ public byte getNsweNearest(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweAbove(int geoX, int geoY, int worldZ) ++ { ++ // check height and return nswe ++ return _height > worldZ ? _nswe : 0; ++ } ++ ++ @Override ++ public byte getNsweBelow(int geoX, int geoY, int worldZ) ++ { ++ // check height and return nswe ++ return _height < worldZ ? _nswe : 0; ++ } ++ ++ @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 getIndexAboveOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexAbove(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public int getIndexBelow(int geoX, int geoY, int worldZ) ++ { ++ // check height and return index ++ return _height < worldZ ? 0 : -1; ++ } ++ ++ @Override ++ public int getIndexBelowOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexBelow(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeight(int index) ++ { ++ return _height; ++ } ++ ++ @Override ++ public short getHeightOriginal(int index) ++ { ++ return _height; ++ } ++ ++ @Override ++ public byte getNswe(int index) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweOriginal(int index) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public void setNswe(int index, byte nswe) ++ { ++ _nswe = nswe; ++ } ++ ++ @Override ++ public void saveBlock(BufferedOutputStream stream) throws IOException ++ { ++ // write block type ++ stream.write(GeoStructure.TYPE_FLAT_L2D); ++ ++ // write height ++ stream.write((byte) (_height & 0x00FF)); ++ stream.write((byte) (_height >> 8)); ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java (working copy) +@@ -0,0 +1,465 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++import java.io.IOException; ++import java.nio.ByteBuffer; ++import java.nio.ByteOrder; ++import java.util.Arrays; ++ ++/** ++ * @author Hasha ++ */ ++public class BlockMultilayer extends ABlock ++{ ++ private static final int MAX_LAYERS = Byte.MAX_VALUE; ++ ++ private static ByteBuffer _temp; ++ ++ /** ++ * Initializes the temporarily buffer. ++ */ ++ public static void initialize() ++ { ++ // initialize temporarily buffer and sorting mechanism ++ _temp = ByteBuffer.allocate(GeoStructure.BLOCK_CELLS * MAX_LAYERS * 3); ++ _temp.order(ByteOrder.LITTLE_ENDIAN); ++ } ++ ++ /** ++ * Releases temporarily buffer. ++ */ ++ public static void release() ++ { ++ _temp = null; ++ } ++ ++ protected byte[] _buffer; ++ ++ /** ++ * Implicit constructor for children class. ++ */ ++ protected BlockMultilayer() ++ { ++ _buffer = null; ++ } ++ ++ /** ++ * Creates MultilayerBlock. ++ * @param bb : Input byte buffer. ++ * @param format : GeoFormat specifying format of loaded data. ++ */ ++ public BlockMultilayer(ByteBuffer bb, GeoFormat format) ++ { ++ // 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 = format != GeoFormat.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++) ++ { ++ if (format != GeoFormat.L2D) ++ { ++ // get data ++ final short data = bb.getShort(); ++ ++ // add nswe and height ++ _temp.put((byte) (data & 0x000F)); ++ _temp.putShort((short) ((short) (data & 0xFFF0) >> 1)); ++ } ++ else ++ { ++ // add nswe ++ _temp.put(bb.get()); ++ ++ // add height ++ _temp.putShort(bb.getShort()); ++ } ++ } ++ } ++ ++ // 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 short getHeightNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getHeightNearest(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeightAbove(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 height ++ if (height > worldZ) ++ { ++ return (short) height; ++ } ++ ++ // move index to next layer ++ index -= 3; ++ } ++ ++ // none layer found, return minimum value ++ return Short.MIN_VALUE; ++ } ++ ++ @Override ++ public short getHeightBelow(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 height ++ if (height < worldZ) ++ { ++ return (short) height; ++ } ++ ++ // move index to next layer ++ index += 3; ++ } ++ ++ // none layer found, return maximum value ++ return Short.MAX_VALUE; ++ } ++ ++ @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 byte getNsweNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getNsweNearest(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public byte getNsweAbove(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 nswe ++ if (height > worldZ) ++ { ++ return _buffer[index]; ++ } ++ ++ // move index to next layer ++ index -= 3; ++ } ++ ++ // none layer found, block movement ++ return 0; ++ } ++ ++ @Override ++ public byte getNsweBelow(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 nswe ++ if (height < worldZ) ++ { ++ return _buffer[index]; ++ } ++ ++ // move index to next layer ++ index += 3; ++ } ++ ++ // none layer found, block movement ++ return 0; ++ } ++ ++ @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 first layer data (first from bottom) ++ byte layers = _buffer[index++]; ++ ++ // loop though all cell layers, find closest layer ++ 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; ++ } ++ ++ // none layer found ++ return -1; ++ } ++ ++ @Override ++ public int getIndexAboveOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexAbove(geoX, geoY, worldZ); ++ } ++ ++ @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; ++ } ++ ++ // none layer found ++ return -1; ++ } ++ ++ @Override ++ public int getIndexBelowOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexBelow(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeight(int index) ++ { ++ // get height ++ return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ } ++ ++ @Override ++ public short getHeightOriginal(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]; ++ } ++ ++ @Override ++ public byte getNsweOriginal(int index) ++ { ++ // get nswe ++ return _buffer[index]; ++ } ++ ++ @Override ++ public void setNswe(int index, byte nswe) ++ { ++ // set nswe ++ _buffer[index] = nswe; ++ } ++ ++ @Override ++ public void saveBlock(BufferedOutputStream stream) throws IOException ++ { ++ // write block type ++ stream.write(GeoStructure.TYPE_MULTILAYER_L2D); ++ ++ // for each cell ++ int index = 0; ++ for (int i = 0; i < GeoStructure.BLOCK_CELLS; i++) ++ { ++ // write layers count ++ final byte layers = _buffer[index++]; ++ stream.write(layers); ++ ++ // write cell data ++ stream.write(_buffer, index, layers * 3); ++ ++ // move index to next cell ++ index += layers * 3; ++ } ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java (working copy) +@@ -0,0 +1,150 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++ ++/** ++ * @author Hasha ++ */ ++public class BlockNull extends ABlock ++{ ++ private final byte _nswe; ++ ++ public BlockNull() ++ { ++ _nswe = (byte) 0xFF; ++ } ++ ++ @Override ++ public boolean hasGeoPos() ++ { ++ return false; ++ } ++ ++ @Override ++ public short getHeightNearest(int geoX, int geoY, int worldZ) ++ { ++ return (short) worldZ; ++ } ++ ++ @Override ++ public short getHeightNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return (short) worldZ; ++ } ++ ++ @Override ++ public short getHeightAbove(int geoX, int geoY, int worldZ) ++ { ++ return (short) worldZ; ++ } ++ ++ @Override ++ public short getHeightBelow(int geoX, int geoY, int worldZ) ++ { ++ return (short) worldZ; ++ } ++ ++ @Override ++ public byte getNsweNearest(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweAbove(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweBelow(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) ++ { ++ return 0; ++ } ++ ++ @Override ++ public int getIndexAboveOriginal(int geoX, int geoY, int worldZ) ++ { ++ return 0; ++ } ++ ++ @Override ++ public int getIndexBelow(int geoX, int geoY, int worldZ) ++ { ++ return 0; ++ } ++ ++ @Override ++ public int getIndexBelowOriginal(int geoX, int geoY, int worldZ) ++ { ++ return 0; ++ } ++ ++ @Override ++ public short getHeight(int index) ++ { ++ return 0; ++ } ++ ++ @Override ++ public short getHeightOriginal(int index) ++ { ++ return 0; ++ } ++ ++ @Override ++ public byte getNswe(int index) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweOriginal(int index) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public void setNswe(int index, byte nswe) ++ { ++ } ++ ++ @Override ++ public void saveBlock(BufferedOutputStream stream) ++ { ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java (revision 8319) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java (nonexistent) +@@ -1,48 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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() +- { +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/ComplexBlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/ComplexBlock.java (revision 8319) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/ComplexBlock.java (nonexistent) +@@ -1,77 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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; +- +-/** +- * @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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/FlatBlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/FlatBlock.java (revision 8319) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/FlatBlock.java (nonexistent) +@@ -1,56 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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; +- +-/** +- * @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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/GeoFormat.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/GeoFormat.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/GeoFormat.java (working copy) +@@ -0,0 +1,39 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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 Hasha ++ */ ++public enum GeoFormat ++{ ++ L2J("%d_%d.l2j"), ++ L2OFF("%d_%d_conv.dat"), ++ L2D("%d_%d.l2d"); ++ ++ private final String _filename; ++ ++ private GeoFormat(String filename) ++ { ++ _filename = filename; ++ } ++ ++ public String getFilename() ++ { ++ return _filename; ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/GeoLocation.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/GeoLocation.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/GeoLocation.java (working copy) +@@ -0,0 +1,67 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.geoengine.GeoEngine; ++import org.l2jmobius.gameserver.model.Location; ++ ++/** ++ * @author Hasha ++ */ ++public class GeoLocation extends Location ++{ ++ private byte _nswe; ++ ++ public GeoLocation(int x, int y, int z) ++ { ++ super(x, y, GeoEngine.getInstance().getHeightNearest(x, y, z)); ++ _nswe = GeoEngine.getInstance().getNsweNearest(x, y, z); ++ } ++ ++ public void set(int x, int y, short z) ++ { ++ super.setXYZ(x, y, GeoEngine.getInstance().getHeightNearest(x, y, z)); ++ _nswe = GeoEngine.getInstance().getNsweNearest(x, y, z); ++ } ++ ++ public int getGeoX() ++ { ++ return _x; ++ } ++ ++ public int getGeoY() ++ { ++ return _y; ++ } ++ ++ @Override ++ public int getX() ++ { ++ return GeoEngine.getWorldX(_x); ++ } ++ ++ @Override ++ public int getY() ++ { ++ return GeoEngine.getWorldY(_y); ++ } ++ ++ public byte getNSWE() ++ { ++ return _nswe; ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java (working copy) +@@ -0,0 +1,70 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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; ++ ++/** ++ * @author Hasha ++ */ ++public class GeoStructure ++{ ++ // cells ++ public static final byte CELL_FLAG_E = 1 << 0; ++ public static final byte CELL_FLAG_W = 1 << 1; ++ public static final byte CELL_FLAG_S = 1 << 2; ++ public static final byte CELL_FLAG_N = 1 << 3; ++ public static final byte CELL_FLAG_SE = 1 << 4; ++ public static final byte CELL_FLAG_SW = 1 << 5; ++ public static final byte CELL_FLAG_NE = 1 << 6; ++ public static final byte CELL_FLAG_NW = (byte) (1 << 7); ++ ++ public static final int CELL_HEIGHT = 8; ++ public static final int CELL_IGNORE_HEIGHT = CELL_HEIGHT * 6; ++ ++ // blocks ++ public static final byte TYPE_FLAT_L2J_L2OFF = 0; ++ public static final byte TYPE_FLAT_L2D = (byte) 0xD0; ++ public static final byte TYPE_COMPLEX_L2J = 1; ++ public static final byte TYPE_COMPLEX_L2OFF = 0x40; ++ public static final byte TYPE_COMPLEX_L2D = (byte) 0xD1; ++ 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) ++ public static final byte TYPE_MULTILAYER_L2D = (byte) 0xD2; ++ ++ 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; ++ ++ // regions ++ 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; ++ ++ // global geodata ++ private static final int GEO_REGIONS_X = ((World.TILE_X_MAX - World.TILE_X_MIN) + 1); ++ private 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 +Index: java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java (revision 8319) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java (nonexistent) +@@ -1,42 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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); +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/IGeoObject.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/IGeoObject.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/IGeoObject.java (working copy) +@@ -0,0 +1,53 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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 Hasha ++ */ ++public interface IGeoObject ++{ ++ /** ++ * Returns geodata X coordinate of the {@link IGeoObject}. ++ * @return int : Geodata X coordinate. ++ */ ++ int getGeoX(); ++ ++ /** ++ * Returns geodata Y coordinate of the {@link IGeoObject}. ++ * @return int : Geodata Y coordinate. ++ */ ++ int getGeoY(); ++ ++ /** ++ * Returns geodata Z coordinate of the {@link IGeoObject}. ++ * @return int : Geodata Z coordinate. ++ */ ++ int getGeoZ(); ++ ++ /** ++ * Returns height of the {@link IGeoObject}. ++ * @return int : Height. ++ */ ++ int getHeight(); ++ ++ /** ++ * Returns {@link IGeoObject} data. ++ * @return byte[][] : {@link IGeoObject} data. ++ */ ++ byte[][] getObjectGeoData(); ++} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java (revision 8319) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java (nonexistent) +@@ -1,47 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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(); +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/MultilayerBlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/MultilayerBlock.java (revision 8319) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/MultilayerBlock.java (nonexistent) +@@ -1,183 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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; +- +-/** +- * @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); +- +- // 1 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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/NullRegion.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/NullRegion.java (revision 8319) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/NullRegion.java (nonexistent) +@@ -1,55 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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 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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/Region.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/Region.java (revision 8319) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/Region.java (nonexistent) +@@ -1,92 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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; +- +-/** +- * @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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java (revision 8319) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java (nonexistent) +@@ -1,87 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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 T _loc; +- private AbstractNode _parent; +- +- public AbstractNode(T loc) +- { +- _loc = loc; +- } +- +- public void setParent(AbstractNode p) +- { +- _parent = p; +- } +- +- public AbstractNode getParent() +- { +- return _parent; +- } +- +- public T getLoc() +- { +- return _loc; +- } +- +- public void setLoc(T l) +- { +- _loc = l; +- } +- +- @Override +- public int hashCode() +- { +- final int prime = 31; +- int result = 1; +- result = (prime * result) + ((_loc == null) ? 0 : _loc.hashCode()); +- return result; +- } +- +- @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 +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java (revision 8319) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java (nonexistent) +@@ -1,33 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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 int getNodeX(); +- +- public abstract int getNodeY(); +-} +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNode.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNode.java (revision 8319) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNode.java (nonexistent) +@@ -1,67 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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 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 +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNodeBuffer.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNodeBuffer.java (revision 8319) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNodeBuffer.java (nonexistent) +@@ -1,348 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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.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 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) +- { +- _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(); +- } +- +- 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 void getNeighbors() +- { +- if (!_current.getLoc().canGoAll()) +- { +- 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); +- } +- +- // SouthEast +- if ((nodeE != null) && (nodeS != null)) +- { +- if (nodeE.getLoc().canGoSouth() && nodeS.getLoc().canGoEast()) +- { +- addNode(x + 1, y + 1, z, true); +- } +- } +- +- // SouthWest +- if ((nodeS != null) && (nodeW != null)) +- { +- if (nodeW.getLoc().canGoSouth() && nodeS.getLoc().canGoWest()) +- { +- addNode(x - 1, y + 1, z, true); +- } +- } +- +- // NorthEast +- if ((nodeN != null) && (nodeE != null)) +- { +- if (nodeE.getLoc().canGoNorth() && nodeN.getLoc().canGoEast()) +- { +- addNode(x + 1, y - 1, z, true); +- } +- } +- +- // NorthWest +- if ((nodeN != null) && (nodeW != null)) +- { +- if (nodeW.getLoc().canGoNorth() && nodeN.getLoc().canGoWest()) +- { +- addNode(x - 1, y - 1, z, true); +- } +- } +- } +- +- private 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(); +- // reinit node if needed +- if (result.getLoc() != null) +- { +- result.getLoc().set(x, y, z); +- } +- else +- { +- result.setLoc(new NodeLoc(x, y, z)); +- } +- } +- +- return result; +- } +- +- private 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 boolean isHighWeight(int x, int y, int z) +- { +- final CellNode result = getNode(x, y, z); +- if (result == null) +- { +- return true; +- } +- +- if (!result.getLoc().canGoAll()) +- { +- return true; +- } +- if (Math.abs(result.getLoc().getZ() - z) > 16) +- { +- return true; +- } +- +- return false; +- } +- +- private 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 +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java (working copy) +@@ -0,0 +1,87 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.geodata.GeoLocation; ++ ++/** ++ * @author Hasha ++ */ ++public class Node ++{ ++ // node coords and nswe flag ++ private GeoLocation _loc; ++ ++ // node parent (for reverse path construction) ++ private Node _parent; ++ // node child (for moving over nodes during iteration) ++ private Node _child; ++ ++ // node G cost (movement cost = parent movement cost + current movement cost) ++ private double _cost = -1000; ++ ++ public void setLoc(int x, int y, int z) ++ { ++ _loc = new GeoLocation(x, y, z); ++ } ++ ++ public GeoLocation getLoc() ++ { ++ return _loc; ++ } ++ ++ public void setParent(Node parent) ++ { ++ _parent = parent; ++ } ++ ++ public Node getParent() ++ { ++ return _parent; ++ } ++ ++ public void setChild(Node child) ++ { ++ _child = child; ++ } ++ ++ public Node getChild() ++ { ++ return _child; ++ } ++ ++ public void setCost(double cost) ++ { ++ _cost = cost; ++ } ++ ++ public double getCost() ++ { ++ return _cost; ++ } ++ ++ public void free() ++ { ++ // reset node location ++ _loc = null; ++ ++ // reset node parent, child and cost ++ _parent = null; ++ _child = null; ++ _cost = -1000; ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java (working copy) +@@ -0,0 +1,306 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.concurrent.locks.ReentrantLock; ++ ++import org.l2jmobius.Config; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; ++ ++/** ++ * @author DS, Hasha; Credits to Diamond ++ */ ++public class NodeBuffer ++{ ++ private final ReentrantLock _lock = new ReentrantLock(); ++ private final int _size; ++ private final Node[][] _buffer; ++ ++ // center coordinates ++ private int _cx = 0; ++ private int _cy = 0; ++ ++ // target coordinates ++ private int _gtx = 0; ++ private int _gty = 0; ++ private short _gtz = 0; ++ ++ private Node _current = null; ++ ++ /** ++ * Constructor of NodeBuffer. ++ * @param size : one dimension size of buffer ++ */ ++ public NodeBuffer(int size) ++ { ++ // set size ++ _size = size; ++ ++ // initialize buffer ++ _buffer = new Node[size][size]; ++ for (int x = 0; x < size; x++) ++ { ++ for (int y = 0; y < size; y++) ++ { ++ _buffer[x][y] = 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 Node : first node of path ++ */ ++ public Node findPath(int gox, int goy, short goz, int gtx, int gty, short gtz) ++ { ++ // set coordinates (middle of the line (gox,goy) - (gtx,gty), will be in the center of the buffer) ++ _cx = gox + ((gtx - gox - _size) / 2); ++ _cy = goy + ((gty - goy - _size) / 2); ++ ++ _gtx = gtx; ++ _gty = gty; ++ _gtz = gtz; ++ ++ _current = getNode(gox, goy, goz); ++ _current.setCost(getCostH(gox, goy, goz)); ++ ++ int count = 0; ++ do ++ { ++ // reached target? ++ if ((_current.getLoc().getGeoX() == _gtx) && (_current.getLoc().getGeoY() == _gty) && (Math.abs(_current.getLoc().getZ() - _gtz) < 8)) ++ { ++ return _current; ++ } ++ ++ // expand current node ++ expand(); ++ ++ // move pointer ++ _current = _current.getChild(); ++ } ++ while ((_current != null) && (count++ < Config.MAX_ITERATIONS)); ++ ++ return null; ++ } ++ ++ public boolean isLocked() ++ { ++ return _lock.tryLock(); ++ } ++ ++ public void free() ++ { ++ _current = null; ++ ++ for (Node[] nodes : _buffer) ++ { ++ for (Node node : nodes) ++ { ++ if (node.getLoc() != null) ++ { ++ node.free(); ++ } ++ } ++ } ++ ++ _lock.unlock(); ++ } ++ ++ /** ++ * Check _current Node and add its neighbors to the buffer. ++ */ ++ private final void expand() ++ { ++ // can't move anywhere, don't expand ++ final byte nswe = _current.getLoc().getNSWE(); ++ if (nswe == 0) ++ { ++ return; ++ } ++ ++ // get geo coords of the node to be expanded ++ final int x = _current.getLoc().getGeoX(); ++ final int y = _current.getLoc().getGeoY(); ++ final short z = (short) _current.getLoc().getZ(); ++ ++ // can move north, expand ++ if ((nswe & GeoStructure.CELL_FLAG_N) != 0) ++ { ++ addNode(x, y - 1, z, Config.BASE_WEIGHT); ++ } ++ ++ // can move south, expand ++ if ((nswe & GeoStructure.CELL_FLAG_S) != 0) ++ { ++ addNode(x, y + 1, z, Config.BASE_WEIGHT); ++ } ++ ++ // can move west, expand ++ if ((nswe & GeoStructure.CELL_FLAG_W) != 0) ++ { ++ addNode(x - 1, y, z, Config.BASE_WEIGHT); ++ } ++ ++ // can move east, expand ++ if ((nswe & GeoStructure.CELL_FLAG_E) != 0) ++ { ++ addNode(x + 1, y, z, Config.BASE_WEIGHT); ++ } ++ ++ // can move north-west, expand ++ if ((nswe & GeoStructure.CELL_FLAG_NW) != 0) ++ { ++ addNode(x - 1, y - 1, z, Config.DIAGONAL_WEIGHT); ++ } ++ ++ // can move north-east, expand ++ if ((nswe & GeoStructure.CELL_FLAG_NE) != 0) ++ { ++ addNode(x + 1, y - 1, z, Config.DIAGONAL_WEIGHT); ++ } ++ ++ // can move south-west, expand ++ if ((nswe & GeoStructure.CELL_FLAG_SW) != 0) ++ { ++ addNode(x - 1, y + 1, z, Config.DIAGONAL_WEIGHT); ++ } ++ ++ // can move south-east, expand ++ if ((nswe & GeoStructure.CELL_FLAG_SE) != 0) ++ { ++ addNode(x + 1, y + 1, z, Config.DIAGONAL_WEIGHT); ++ } ++ } ++ ++ /** ++ * Returns node, if it exists in buffer. ++ * @param x : node X coord ++ * @param y : node Y coord ++ * @param z : node Z coord ++ * @return Node : node, if exits in buffer ++ */ ++ private final Node getNode(int x, int y, short z) ++ { ++ // check node X out of coordinates ++ final int ix = x - _cx; ++ if ((ix < 0) || (ix >= _size)) ++ { ++ return null; ++ } ++ ++ // check node Y out of coordinates ++ final int iy = y - _cy; ++ if ((iy < 0) || (iy >= _size)) ++ { ++ return null; ++ } ++ ++ // get node ++ final Node result = _buffer[ix][iy]; ++ ++ // check and update ++ if (result.getLoc() == null) ++ { ++ result.setLoc(x, y, z); ++ } ++ ++ // return node ++ return result; ++ } ++ ++ /** ++ * Add node given by coordinates to the buffer. ++ * @param x : geo X coord ++ * @param y : geo Y coord ++ * @param z : geo Z coord ++ * @param weight : weight of movement to new node ++ */ ++ private final void addNode(int x, int y, short z, int weight) ++ { ++ // get node to be expanded ++ final Node node = getNode(x, y, z); ++ if (node == null) ++ { ++ return; ++ } ++ ++ // Z distance between nearby cells is higher than cell size ++ if (node.getLoc().getZ() > (z + (2 * GeoStructure.CELL_HEIGHT))) ++ { ++ return; ++ } ++ ++ // node was already expanded, return ++ if (node.getCost() >= 0) ++ { ++ return; ++ } ++ ++ node.setParent(_current); ++ if (node.getLoc().getNSWE() != (byte) 0xFF) ++ { ++ node.setCost(getCostH(x, y, node.getLoc().getZ()) + (weight * Config.OBSTACLE_MULTIPLIER)); ++ } ++ else ++ { ++ node.setCost(getCostH(x, y, node.getLoc().getZ()) + weight); ++ } ++ ++ Node current = _current; ++ int count = 0; ++ while ((current.getChild() != null) && (count < (Config.MAX_ITERATIONS * 4))) ++ { ++ count++; ++ if (current.getChild().getCost() > node.getCost()) ++ { ++ node.setChild(current.getChild()); ++ break; ++ } ++ current = current.getChild(); ++ } ++ ++ if (count >= (Config.MAX_ITERATIONS * 4)) ++ { ++ System.err.println("Pathfinding: too long loop detected, cost:" + node.getCost()); ++ } ++ ++ current.setChild(node); ++ } ++ ++ /** ++ * @param x : node X coord ++ * @param y : node Y coord ++ * @param i : node Z coord ++ * @return double : node cost ++ */ ++ private final double getCostH(int x, int y, int i) ++ { ++ final int dX = x - _gtx; ++ final int dY = y - _gty; ++ final int dZ = (i - _gtz) / GeoStructure.CELL_HEIGHT; ++ ++ // return (Math.abs(dX) + Math.abs(dY) + Math.abs(dZ)) * Config.HEURISTIC_WEIGHT; // Manhattan distance ++ return Math.sqrt((dX * dX) + (dY * dY) + (dZ * dZ)) * Config.HEURISTIC_WEIGHT; // Direct distance ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeLoc.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeLoc.java (revision 8319) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeLoc.java (nonexistent) +@@ -1,183 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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.Cell; +- +-/** +- * @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 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 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; +- // return super.hashCode(); +- } +- +- @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; +- if (_x != other._x) +- { +- return false; +- } +- if (_y != other._y) +- { +- return false; +- } +- if (_goNorth != other._goNorth) +- { +- return false; +- } +- if (_goEast != other._goEast) +- { +- return false; +- } +- if (_goSouth != other._goSouth) +- { +- return false; +- } +- if (_goWest != other._goWest) +- { +- return false; +- } +- if (_geoHeight != other._geoHeight) +- { +- return false; +- } +- return true; +- } +-} +Index: java/org/l2jmobius/gameserver/model/actor/Creature.java +=================================================================== +--- java/org/l2jmobius/gameserver/model/actor/Creature.java (revision 8399) ++++ java/org/l2jmobius/gameserver/model/actor/Creature.java (working copy) +@@ -57,8 +57,6 @@ + import org.l2jmobius.gameserver.enums.Team; + import org.l2jmobius.gameserver.enums.TeleportWhereType; + import org.l2jmobius.gameserver.geoengine.GeoEngine; +-import org.l2jmobius.gameserver.geoengine.GeoEnginePathfinding; +-import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; + import org.l2jmobius.gameserver.instancemanager.IdManager; + import org.l2jmobius.gameserver.instancemanager.InstanceManager; + import org.l2jmobius.gameserver.instancemanager.MapRegionManager; +@@ -3362,7 +3360,7 @@ + + public boolean disregardingGeodata; + public int onGeodataPathIndex; +- public List geoPath; ++ public List geoPath; + public int geoPathAccurateTx; + public int geoPathAccurateTy; + public int geoPathGtx; +@@ -4386,7 +4384,7 @@ + if (((originalDistance - distance) > 30) && !isAfraid() && !isInVehicle) + { + // Path calculation -- overrides previous movement check +- m.geoPath = GeoEnginePathfinding.getInstance().findPath(curX, curY, curZ, originalX, originalY, originalZ, getInstanceId()); ++ m.geoPath = GeoEngine.getInstance().findPath(curX, curY, curZ, originalX, originalY, originalZ, getInstanceId()); + if ((m.geoPath == null) || (m.geoPath.size() < 2)) // No path found + { + if (isPlayer() && !_isFlying && !isInWater) +Index: java/org/l2jmobius/gameserver/model/actor/Summon.java +=================================================================== +--- java/org/l2jmobius/gameserver/model/actor/Summon.java (revision 8319) ++++ java/org/l2jmobius/gameserver/model/actor/Summon.java (working copy) +@@ -29,7 +29,6 @@ + import org.l2jmobius.gameserver.enums.ShotType; + import org.l2jmobius.gameserver.enums.Team; + import org.l2jmobius.gameserver.geoengine.GeoEngine; +-import org.l2jmobius.gameserver.geoengine.GeoEnginePathfinding; + import org.l2jmobius.gameserver.handler.IItemHandler; + import org.l2jmobius.gameserver.handler.ItemHandler; + import org.l2jmobius.gameserver.instancemanager.TerritoryWarManager; +@@ -646,7 +645,7 @@ + return false; + } + +- if ((this != target) && skill.isPhysical() && Config.PATHFINDING && (GeoEnginePathfinding.getInstance().findPath(getX(), getY(), getZ(), target.getX(), target.getY(), target.getZ(), getInstanceId()) == null)) ++ if ((this != target) && skill.isPhysical() && Config.PATHFINDING && (GeoEngine.getInstance().findPath(getX(), getY(), getZ(), target.getX(), target.getY(), target.getZ(), getInstanceId()) == null)) + { + sendPacket(SystemMessageId.CANNOT_SEE_TARGET); + return false; +Index: java/org/l2jmobius/gameserver/model/actor/instance/PlayerInstance.java +=================================================================== +--- java/org/l2jmobius/gameserver/model/actor/instance/PlayerInstance.java (revision 8352) ++++ java/org/l2jmobius/gameserver/model/actor/instance/PlayerInstance.java (working copy) +@@ -13406,7 +13406,7 @@ + { + if (Config.CORRECT_PLAYER_Z) + { +- final int nearestZ = GeoEngine.getInstance().getHigherHeight(getX(), getY(), getZ()); ++ final int nearestZ = GeoEngine.getInstance().getHeightNearest(getX(), getY(), getZ()); + if (getZ() < nearestZ) + { + teleToLocation(new Location(getX(), getY(), nearestZ)); +Index: java/org/l2jmobius/gameserver/util/GeoUtils.java +=================================================================== +--- java/org/l2jmobius/gameserver/util/GeoUtils.java (revision 8319) ++++ java/org/l2jmobius/gameserver/util/GeoUtils.java (working copy) +@@ -19,7 +19,7 @@ + import java.awt.Color; + + import org.l2jmobius.gameserver.geoengine.GeoEngine; +-import org.l2jmobius.gameserver.geoengine.geodata.Cell; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; + import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance; + import org.l2jmobius.gameserver.network.serverpackets.ExServerPrimitive; + +@@ -26,25 +26,25 @@ + /** + * @author HorridoJoho + */ +-public final class GeoUtils ++public class GeoUtils + { + public static void debug2DLine(PlayerInstance player, int x, int y, int tx, int ty, int z) + { +- final int gx = GeoEngine.getInstance().getGeoX(x); +- final int gy = GeoEngine.getInstance().getGeoY(y); ++ final int gx = GeoEngine.getGeoX(x); ++ final int gy = GeoEngine.getGeoY(y); + +- final int tgx = GeoEngine.getInstance().getGeoX(tx); +- final int tgy = GeoEngine.getInstance().getGeoY(ty); ++ final int tgx = GeoEngine.getGeoX(tx); ++ final int tgy = GeoEngine.getGeoY(ty); + + final ExServerPrimitive prim = new ExServerPrimitive("Debug2DLine", x, y, z); +- prim.addLine(Color.BLUE, GeoEngine.getInstance().getWorldX(gx), GeoEngine.getInstance().getWorldY(gy), z, GeoEngine.getInstance().getWorldX(tgx), GeoEngine.getInstance().getWorldY(tgy), z); ++ prim.addLine(Color.BLUE, GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), z, GeoEngine.getWorldX(tgx), GeoEngine.getWorldY(tgy), z); + + final LinePointIterator iter = new LinePointIterator(gx, gy, tgx, tgy); + + while (iter.next()) + { +- final int wx = GeoEngine.getInstance().getWorldX(iter.x()); +- final int wy = GeoEngine.getInstance().getWorldY(iter.y()); ++ final int wx = GeoEngine.getWorldX(iter.x()); ++ final int wy = GeoEngine.getWorldY(iter.y()); + + prim.addPoint(Color.RED, wx, wy, z); + } +@@ -53,21 +53,21 @@ + + public static void debug3DLine(PlayerInstance player, int x, int y, int z, int tx, int ty, int tz) + { +- final int gx = GeoEngine.getInstance().getGeoX(x); +- final int gy = GeoEngine.getInstance().getGeoY(y); ++ final int gx = GeoEngine.getGeoX(x); ++ final int gy = GeoEngine.getGeoY(y); + +- final int tgx = GeoEngine.getInstance().getGeoX(tx); +- final int tgy = GeoEngine.getInstance().getGeoY(ty); ++ final int tgx = GeoEngine.getGeoX(tx); ++ final int tgy = GeoEngine.getGeoY(ty); + + final ExServerPrimitive prim = new ExServerPrimitive("Debug3DLine", x, y, z); +- prim.addLine(Color.BLUE, GeoEngine.getInstance().getWorldX(gx), GeoEngine.getInstance().getWorldY(gy), z, GeoEngine.getInstance().getWorldX(tgx), GeoEngine.getInstance().getWorldY(tgy), tz); ++ prim.addLine(Color.BLUE, GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), z, GeoEngine.getWorldX(tgx), GeoEngine.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.getInstance().getWorldX(prevX); +- int wy = GeoEngine.getInstance().getWorldY(prevY); ++ int wx = GeoEngine.getWorldX(prevX); ++ int wy = GeoEngine.getWorldY(prevY); + int wz = iter.z(); + prim.addPoint(Color.RED, wx, wy, wz); + +@@ -78,8 +78,8 @@ + + if ((curX != prevX) || (curY != prevY)) + { +- wx = GeoEngine.getInstance().getWorldX(curX); +- wy = GeoEngine.getInstance().getWorldY(curY); ++ wx = GeoEngine.getWorldX(curX); ++ wy = GeoEngine.getWorldY(curY); + wz = iter.z(); + + prim.addPoint(Color.RED, wx, wy, wz); +@@ -93,7 +93,7 @@ + + private static Color getDirectionColor(int x, int y, int z, int nswe) + { +- if (GeoEngine.getInstance().checkNearestNswe(x, y, z, nswe)) ++ if ((GeoEngine.getInstance().getNsweNearest(x, y, z) & nswe) == nswe) + { + return Color.GREEN; + } +@@ -109,9 +109,8 @@ + int iPacket = 0; + + ExServerPrimitive exsp = null; +- final GeoEngine ge = GeoEngine.getInstance(); +- final int playerGx = ge.getGeoX(player.getX()); +- final int playerGy = ge.getGeoY(player.getY()); ++ final int playerGx = GeoEngine.getGeoX(player.getX()); ++ final int playerGy = GeoEngine.getGeoY(player.getY()); + for (int dx = -geoRadius; dx <= geoRadius; ++dx) + { + for (int dy = -geoRadius; dy <= geoRadius; ++dy) +@@ -135,12 +134,12 @@ + final int gx = playerGx + dx; + final int gy = playerGy + dy; + +- final int x = ge.getWorldX(gx); +- final int y = ge.getWorldY(gy); +- final int z = ge.getNearestZ(gx, gy, player.getZ()); ++ final int x = GeoEngine.getWorldX(gx); ++ final int y = GeoEngine.getWorldY(gy); ++ final int z = GeoEngine.getInstance().getHeightNearest(gx, gy, player.getZ()); + + // north arrow +- Color col = getDirectionColor(gx, gy, z, Cell.NSWE_NORTH); ++ Color col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_N); + 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); +@@ -147,7 +146,7 @@ + exsp.addLine(col, x - 4, y - 4, z, x + 4, y - 4, z); + + // east arrow +- col = getDirectionColor(gx, gy, z, Cell.NSWE_EAST); ++ col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_E); + 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); +@@ -154,13 +153,13 @@ + exsp.addLine(col, x + 4, y - 4, z, x + 4, y + 4, z); + + // south arrow +- col = getDirectionColor(gx, gy, z, Cell.NSWE_SOUTH); ++ col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_S); + 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, Cell.NSWE_WEST); ++ col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_W); + 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); +@@ -188,15 +187,15 @@ + { + if (y > lastY) + { +- return Cell.NSWE_SOUTH_EAST; ++ return GeoStructure.CELL_FLAG_SE; // Direction.SOUTH_EAST; + } + else if (y < lastY) + { +- return Cell.NSWE_NORTH_EAST; ++ return GeoStructure.CELL_FLAG_NE; // Direction.NORTH_EAST; + } + else + { +- return Cell.NSWE_EAST; ++ return GeoStructure.CELL_FLAG_E; // Direction.EAST; + } + } + else if (x < lastX) // west +@@ -203,26 +202,27 @@ + { + if (y > lastY) + { +- return Cell.NSWE_SOUTH_WEST; ++ return GeoStructure.CELL_FLAG_SW; // Direction.SOUTH_WEST; + } + else if (y < lastY) + { +- return Cell.NSWE_NORTH_WEST; ++ return GeoStructure.CELL_FLAG_NW; // Direction.NORTH_WEST; + } + else + { +- return Cell.NSWE_WEST; ++ return GeoStructure.CELL_FLAG_W; // Direction.WEST; + } + } +- else // unchanged x ++ else ++ // unchanged x + { + if (y > lastY) + { +- return Cell.NSWE_SOUTH; ++ return GeoStructure.CELL_FLAG_S; // Direction.SOUTH; + } + else if (y < lastY) + { +- return Cell.NSWE_NORTH; ++ return GeoStructure.CELL_FLAG_N; // Direction.NORTH; + } + else + { +Index: java/org/l2jmobius/tools/geodataconverter/GeoDataConverter.java +=================================================================== +--- java/org/l2jmobius/tools/geodataconverter/GeoDataConverter.java (nonexistent) ++++ java/org/l2jmobius/tools/geodataconverter/GeoDataConverter.java (working copy) +@@ -0,0 +1,386 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ */ ++package org.l2jmobius.tools.geodataconverter; ++ ++import java.io.BufferedOutputStream; ++import java.io.File; ++import java.io.FileOutputStream; ++import java.io.RandomAccessFile; ++import java.nio.ByteOrder; ++import java.nio.MappedByteBuffer; ++import java.nio.channels.FileChannel; ++import java.util.Scanner; ++ ++import org.l2jmobius.Config; ++import org.l2jmobius.commons.util.PropertiesParser; ++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.GeoFormat; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; ++import org.l2jmobius.gameserver.model.World; ++ ++/** ++ * @author Hasha ++ */ ++public class GeoDataConverter ++{ ++ private static GeoFormat _format; ++ private static ABlock[][] _blocks; ++ ++ public static void main(String[] args) ++ { ++ // initialize config ++ loadGeoengineConfigs(); ++ ++ // get geodata format ++ String type = ""; ++ try (Scanner scn = new Scanner(System.in)) ++ { ++ while (!(type.equalsIgnoreCase("J") || type.equalsIgnoreCase("O") || type.equalsIgnoreCase("E"))) ++ { ++ System.out.println("GeoDataConverter: Select source geodata type:"); ++ System.out.println(" J: L2J (e.g. 23_22.l2j)"); ++ System.out.println(" O: L2OFF (e.g. 23_22_conv.dat)"); ++ System.out.println(" E: Exit"); ++ System.out.print("Choice: "); ++ type = scn.next(); ++ } ++ } ++ if (type.equalsIgnoreCase("E")) ++ { ++ System.exit(0); ++ } ++ ++ _format = type.equalsIgnoreCase("J") ? GeoFormat.L2J : GeoFormat.L2OFF; ++ ++ // start conversion ++ System.out.println("GeoDataConverter: Converting all " + _format + " files."); ++ ++ // initialize geodata container ++ _blocks = new ABlock[GeoStructure.REGION_BLOCKS_X][GeoStructure.REGION_BLOCKS_Y]; ++ ++ // initialize multilayer temporarily buffer ++ BlockMultilayer.initialize(); ++ ++ // load geo files ++ int converted = 0; ++ for (int rx = World.TILE_X_MIN; rx <= World.TILE_X_MAX; rx++) ++ { ++ for (int ry = World.TILE_Y_MIN; ry <= World.TILE_Y_MAX; ry++) ++ { ++ final String input = String.format(_format.getFilename(), rx, ry); ++ final String filepath = Config.GEODATA_PATH; ++ final File f = new File(filepath + input); ++ if (f.exists() && !f.isDirectory()) ++ { ++ // load geodata ++ if (!loadGeoBlocks(input)) ++ { ++ System.out.println("GeoDataConverter: Unable to load " + input + " region file."); ++ continue; ++ } ++ ++ // recalculate nswe ++ if (!recalculateNswe()) ++ { ++ System.out.println("GeoDataConverter: Unable to convert " + input + " region file."); ++ continue; ++ } ++ ++ // save geodata ++ final String output = String.format(GeoFormat.L2D.getFilename(), rx, ry); ++ if (!saveGeoBlocks(output)) ++ { ++ System.out.println("GeoDataConverter: Unable to save " + output + " region file."); ++ continue; ++ } ++ ++ converted++; ++ System.out.println("GeoDataConverter: Created " + output + " region file."); ++ } ++ } ++ } ++ System.out.println("GeoDataConverter: Converted " + converted + " " + _format + " to L2D region file(s)."); ++ ++ // release multilayer block temporarily buffer ++ BlockMultilayer.release(); ++ } ++ ++ /** ++ * Loads geo blocks from buffer of the region file. ++ * @param filename : The name of the to load. ++ * @return boolean : True when successful. ++ */ ++ private static boolean loadGeoBlocks(String filename) ++ { ++ // region file is load-able, try to load it ++ try (RandomAccessFile raf = new RandomAccessFile(Config.GEODATA_PATH + filename, "r"); ++ FileChannel fc = raf.getChannel()) ++ { ++ final MappedByteBuffer buffer = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()).load(); ++ buffer.order(ByteOrder.LITTLE_ENDIAN); ++ ++ // load 18B header for L2off geodata (1st and 2nd byte...region X and Y) ++ if (_format == GeoFormat.L2OFF) ++ { ++ for (int i = 0; i < 18; i++) ++ { ++ buffer.get(); ++ } ++ } ++ ++ // 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 (_format == GeoFormat.L2J) ++ { ++ // get block type ++ final byte type = buffer.get(); ++ ++ // load block according to block type ++ switch (type) ++ { ++ case GeoStructure.TYPE_FLAT_L2J_L2OFF: ++ { ++ _blocks[ix][iy] = new BlockFlat(buffer, _format); ++ break; ++ } ++ case GeoStructure.TYPE_COMPLEX_L2J: ++ { ++ _blocks[ix][iy] = new BlockComplex(buffer, _format); ++ break; ++ } ++ case GeoStructure.TYPE_MULTILAYER_L2J: ++ { ++ _blocks[ix][iy] = new BlockMultilayer(buffer, _format); ++ break; ++ } ++ default: ++ { ++ throw new IllegalArgumentException("Unknown block type: " + type); ++ } ++ } ++ } ++ else ++ { ++ // get block type ++ final short type = buffer.getShort(); ++ ++ // load block according to block type ++ switch (type) ++ { ++ case GeoStructure.TYPE_FLAT_L2J_L2OFF: ++ { ++ _blocks[ix][iy] = new BlockFlat(buffer, _format); ++ break; ++ } ++ case GeoStructure.TYPE_COMPLEX_L2OFF: ++ { ++ _blocks[ix][iy] = new BlockComplex(buffer, _format); ++ break; ++ } ++ default: ++ { ++ _blocks[ix][iy] = new BlockMultilayer(buffer, _format); ++ break; ++ } ++ } ++ } ++ } ++ } ++ ++ if (buffer.remaining() > 0) ++ { ++ System.out.println("GeoDataConverter: Region file " + filename + " can be corrupted, remaining " + buffer.remaining() + " bytes to read."); ++ return false; ++ } ++ ++ return true; ++ } ++ catch (Exception e) ++ { ++ System.out.println("GeoDataConverter: Error while loading " + filename + " region file."); ++ return false; ++ } ++ } ++ ++ /** ++ * Recalculate diagonal flags for the region file. ++ * @return boolean : True when successful. ++ */ ++ private static boolean recalculateNswe() ++ { ++ try ++ { ++ for (int x = 0; x < GeoStructure.REGION_CELLS_X; x++) ++ { ++ for (int y = 0; y < GeoStructure.REGION_CELLS_Y; y++) ++ { ++ // get block ++ final ABlock block = _blocks[x / GeoStructure.BLOCK_CELLS_X][y / GeoStructure.BLOCK_CELLS_Y]; ++ ++ // skip flat blocks ++ if (block instanceof BlockFlat) ++ { ++ continue; ++ } ++ ++ // for complex and multilayer blocks go though all layers ++ short height = Short.MAX_VALUE; ++ int index; ++ while ((index = block.getIndexBelow(x, y, height)) != -1) ++ { ++ // get height and nswe ++ height = block.getHeight(index); ++ byte nswe = block.getNswe(index); ++ ++ // update nswe with diagonal flags ++ nswe = updateNsweBelow(x, y, height, nswe); ++ ++ // set nswe of the cell ++ block.setNswe(index, nswe); ++ } ++ } ++ } ++ ++ return true; ++ } ++ catch (Exception e) ++ { ++ return false; ++ } ++ } ++ ++ /** ++ * Updates the NSWE flag with diagonal flags. ++ * @param x : Geodata X coordinate. ++ * @param y : Geodata Y coordinate. ++ * @param z : Geodata Z coordinate. ++ * @param nsweValue : NSWE flag to be updated. ++ * @return byte : Updated NSWE flag. ++ */ ++ private static byte updateNsweBelow(int x, int y, short z, byte nsweValue) ++ { ++ byte nswe = nsweValue; ++ ++ // calculate virtual layer height ++ final short height = (short) (z + GeoStructure.CELL_IGNORE_HEIGHT); ++ ++ // get NSWE of neighbor cells below virtual layer (NPC/PC can fall down of clif, but can not climb it -> NSWE of cell below) ++ final byte nsweN = getNsweBelow(x, y - 1, height); ++ final byte nsweS = getNsweBelow(x, y + 1, height); ++ final byte nsweW = getNsweBelow(x - 1, y, height); ++ final byte nsweE = getNsweBelow(x + 1, y, height); ++ ++ // north-west ++ if ((((nswe & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) || (((nswe & GeoStructure.CELL_FLAG_W) != 0) && ((nsweW & GeoStructure.CELL_FLAG_N) != 0))) ++ { ++ nswe |= GeoStructure.CELL_FLAG_NW; ++ } ++ ++ // north-east ++ if ((((nswe & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) || (((nswe & GeoStructure.CELL_FLAG_E) != 0) && ((nsweE & GeoStructure.CELL_FLAG_N) != 0))) ++ { ++ nswe |= GeoStructure.CELL_FLAG_NE; ++ } ++ ++ // south-west ++ if ((((nswe & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) || (((nswe & GeoStructure.CELL_FLAG_W) != 0) && ((nsweW & GeoStructure.CELL_FLAG_S) != 0))) ++ { ++ nswe |= GeoStructure.CELL_FLAG_SW; ++ } ++ ++ // south-east ++ if ((((nswe & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) || (((nswe & GeoStructure.CELL_FLAG_E) != 0) && ((nsweE & GeoStructure.CELL_FLAG_S) != 0))) ++ { ++ nswe |= GeoStructure.CELL_FLAG_SE; ++ } ++ ++ return nswe; ++ } ++ ++ private static byte getNsweBelow(int geoX, int geoY, short worldZ) ++ { ++ // out of geo coordinates ++ if ((geoX < 0) || (geoX >= GeoStructure.REGION_CELLS_X)) ++ { ++ return 0; ++ } ++ ++ // out of geo coordinates ++ if ((geoY < 0) || (geoY >= GeoStructure.REGION_CELLS_Y)) ++ { ++ return 0; ++ } ++ ++ // get block ++ final ABlock block = _blocks[geoX / GeoStructure.BLOCK_CELLS_X][geoY / GeoStructure.BLOCK_CELLS_Y]; ++ ++ // get index, when valid, return nswe ++ final int index = block.getIndexBelow(geoX, geoY, worldZ); ++ return index == -1 ? 0 : block.getNswe(index); ++ } ++ ++ /** ++ * Save region file to file. ++ * @param filename : The name of file to save. ++ * @return boolean : True when successful. ++ */ ++ private static boolean saveGeoBlocks(String filename) ++ { ++ try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(Config.GEODATA_PATH + filename), GeoStructure.REGION_BLOCKS * GeoStructure.BLOCK_CELLS * 3)) ++ { ++ // loop over region blocks and save each block ++ for (int ix = 0; ix < GeoStructure.REGION_BLOCKS_X; ix++) ++ { ++ for (int iy = 0; iy < GeoStructure.REGION_BLOCKS_Y; iy++) ++ { ++ _blocks[ix][iy].saveBlock(bos); ++ } ++ } ++ ++ // flush data to file ++ bos.flush(); ++ ++ return true; ++ } ++ catch (Exception e) ++ { ++ return false; ++ } ++ } ++ ++ private static void loadGeoengineConfigs() ++ { ++ final PropertiesParser geoData = new PropertiesParser(Config.GEOENGINE_CONFIG_FILE); ++ Config.GEODATA_PATH = geoData.getString("GeoDataPath", "./data/geodata/"); ++ Config.COORD_SYNCHRONIZE = geoData.getInt("CoordSynchronize", -1); ++ Config.PART_OF_CHARACTER_HEIGHT = geoData.getInt("PartOfCharacterHeight", 75); ++ Config.MAX_OBSTACLE_HEIGHT = geoData.getInt("MaxObstacleHeight", 32); ++ Config.PATHFINDING = geoData.getBoolean("PathFinding", true); ++ Config.PATHFIND_BUFFERS = geoData.getString("PathFindBuffers", "100x6;128x6;192x6;256x4;320x4;384x4;500x2"); ++ Config.BASE_WEIGHT = geoData.getInt("BaseWeight", 10); ++ Config.DIAGONAL_WEIGHT = geoData.getInt("DiagonalWeight", 14); ++ Config.OBSTACLE_MULTIPLIER = geoData.getInt("ObstacleMultiplier", 10); ++ Config.HEURISTIC_WEIGHT = geoData.getInt("HeuristicWeight", 20); ++ Config.MAX_ITERATIONS = geoData.getInt("MaxIterations", 3500); ++ } ++} +\ No newline at end of file diff --git a/L2J_Mobius_Classic_2.0_Saviors/dist/game/data/geodata/L2D_GeoEngine.patch b/L2J_Mobius_Classic_2.0_Saviors/dist/game/data/geodata/L2D_GeoEngine.patch new file mode 100644 index 0000000000..63e2faf0c9 --- /dev/null +++ b/L2J_Mobius_Classic_2.0_Saviors/dist/game/data/geodata/L2D_GeoEngine.patch @@ -0,0 +1,6052 @@ +Index: dist/game/GeoDataConverter.bat +=================================================================== +--- dist/game/GeoDataConverter.bat (nonexistent) ++++ dist/game/GeoDataConverter.bat (working copy) +@@ -0,0 +1,6 @@ ++@echo off ++title L2D geodata converter ++ ++java -Xmx512m -cp ./../libs/* org.l2jmobius.tools.geodataconverter.GeoDataConverter ++ ++pause +Index: dist/game/GeoDataConverter.sh +=================================================================== +--- dist/game/GeoDataConverter.sh (nonexistent) ++++ dist/game/GeoDataConverter.sh (working copy) +@@ -0,0 +1,4 @@ ++#! /bin/sh ++ ++java -Xmx512m -cp ../libs/*: org.l2jmobius.tools.geodataconverter.GeoDataConverter > log/stdout.log 2>&1 ++ +Index: dist/game/config/GeoEngine.ini +=================================================================== +--- dist/game/config/GeoEngine.ini (revision 8397) ++++ dist/game/config/GeoEngine.ini (working copy) +@@ -1,6 +1,10 @@ + # ================================================================= + # Geodata + # ================================================================= ++# Because of real-time performance we are using geodata files only in ++# diagonal L2D format now (using filename e.g. 22_16.l2d). ++# L2D geodata can be obtained by conversion of existing L2J or L2OFF geodata. ++# Launch "GeoDataConverter.bat/sh" and follow instructions to start the conversion. + + # 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/ +@@ -13,6 +17,16 @@ + CoordSynchronize = 2 + + # ================================================================= ++# Path checking ++# ================================================================= ++ ++# 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 finding + # ================================================================= + +@@ -23,19 +37,23 @@ + # Pathfinding array buffers configuration, default: 100x6;128x6;192x6;256x4;320x4;384x4;500x2 + PathFindBuffers = 100x6;128x6;192x6;256x4;320x4;384x4;500x2 + +-# Weight for nodes without obstacles far from walls +-LowWeight = 0.5 ++# Base path weight, when moving from one node to another on axis direction, default: 10 ++BaseWeight = 10 + +-# Weight for nodes near walls +-MediumWeight = 2 ++# Path weight, when moving from one node to another on diagonal direction, default: BaseWeight * sqrt(2) = 14 ++DiagonalWeight = 14 + +-# Weight for nodes with obstacles +-HighWeight = 3 ++# When movement flags of target node is blocked to any direction, multiply movement weight by this multiplier. ++# This causes pathfinding algorithm to avoid path construction exactly near an obstacle, default: 10 ++ObstacleMultiplier = 10 + +-# Weight for diagonal movement. +-# Default: LowWeight * sqrt(2) +-DiagonalWeight = 0.707 ++# Weight of the heuristic algorithm, which is giving estimated cost from node to target, default: 20 ++# For proper function must be higher than BaseWeight and/or DiagonalWeight. ++HeuristicWeight = 20 + ++# Maximum number of generated nodes per one path-finding process, default 3500 ++MaxIterations = 3500 ++ + # ================================================================= + # Other + # ================================================================= +Index: dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java +=================================================================== +--- dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java (revision 8397) ++++ dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java (working copy) +@@ -55,9 +55,8 @@ + final int worldX = activeChar.getX(); + final int worldY = activeChar.getY(); + final int worldZ = activeChar.getZ(); +- final int geoX = GeoEngine.getInstance().getGeoX(worldX); +- final int geoY = GeoEngine.getInstance().getGeoY(worldY); +- ++ final int geoX = GeoEngine.getGeoX(worldX); ++ final int geoY = GeoEngine.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)); +@@ -73,9 +72,8 @@ + final int worldX = activeChar.getX(); + final int worldY = activeChar.getY(); + final int worldZ = activeChar.getZ(); +- final int geoX = GeoEngine.getInstance().getGeoX(worldX); +- final int geoY = GeoEngine.getInstance().getGeoY(worldY); +- ++ final int geoX = GeoEngine.getGeoX(worldX); ++ final int geoY = GeoEngine.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)); +Index: dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java +=================================================================== +--- dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java (revision 8397) ++++ dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java (working copy) +@@ -18,11 +18,11 @@ + + import java.util.List; + +-import org.l2jmobius.Config; +-import org.l2jmobius.gameserver.geoengine.GeoEnginePathfinding; +-import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; ++import org.l2jmobius.gameserver.geoengine.GeoEngine; + import org.l2jmobius.gameserver.handler.IAdminCommandHandler; ++import org.l2jmobius.gameserver.model.Location; + import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance; ++import org.l2jmobius.gameserver.network.SystemMessageId; + import org.l2jmobius.gameserver.util.BuilderUtil; + + public class AdminPathNode implements IAdminCommandHandler +@@ -37,29 +37,30 @@ + { + if (command.equals("admin_path_find")) + { +- if (!Config.PATHFINDING) +- { +- BuilderUtil.sendSysMessage(activeChar, "PathFinding is disabled."); +- return true; +- } + if (activeChar.getTarget() != null) + { +- final List path = GeoEnginePathfinding.getInstance().findPath(activeChar.getX(), activeChar.getY(), (short) activeChar.getZ(), activeChar.getTarget().getX(), activeChar.getTarget().getY(), (short) activeChar.getTarget().getZ(), activeChar.getInstanceWorld()); ++ 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()); + if (path == null) + { +- BuilderUtil.sendSysMessage(activeChar, "No Route!"); +- return true; ++ BuilderUtil.sendSysMessage(activeChar, "No route found or pathfinding disabled."); + } +- for (AbstractNodeLoc a : path) ++ else + { +- BuilderUtil.sendSysMessage(activeChar, "x:" + a.getX() + " y:" + a.getY() + " z:" + a.getZ()); ++ for (Location point : path) ++ { ++ BuilderUtil.sendSysMessage(activeChar, "x:" + point.getX() + " y:" + point.getY() + " z:" + point.getZ()); ++ } + } + } + else + { +- BuilderUtil.sendSysMessage(activeChar, "No Target!"); ++ activeChar.sendPacket(SystemMessageId.INVALID_TARGET); + } + } ++ else ++ { ++ return false; ++ } + return true; + } + +Index: java/org/l2jmobius/Config.java +=================================================================== +--- java/org/l2jmobius/Config.java (revision 8397) ++++ java/org/l2jmobius/Config.java (working copy) +@@ -32,7 +32,6 @@ + import java.net.UnknownHostException; + import java.nio.charset.StandardCharsets; + import java.nio.file.Files; +-import java.nio.file.Path; + import java.nio.file.Paths; + import java.time.Duration; + import java.util.ArrayList; +@@ -927,14 +926,17 @@ + // -------------------------------------------------- + // GeoEngine + // -------------------------------------------------- +- public static Path GEODATA_PATH; ++ public static String GEODATA_PATH; ++ public static int COORD_SYNCHRONIZE; ++ public static int PART_OF_CHARACTER_HEIGHT; ++ public static int MAX_OBSTACLE_HEIGHT; + public static boolean PATHFINDING; + public static String PATHFIND_BUFFERS; +- public static float LOW_WEIGHT; +- public static float MEDIUM_WEIGHT; +- public static float HIGH_WEIGHT; +- public static float DIAGONAL_WEIGHT; +- public static int COORD_SYNCHRONIZE; ++ public static int BASE_WEIGHT; ++ public static int DIAGONAL_WEIGHT; ++ public static int HEURISTIC_WEIGHT; ++ public static int OBSTACLE_MULTIPLIER; ++ public static int MAX_ITERATIONS; + public static boolean CORRECT_PLAYER_Z; + + /** Attribute System */ +@@ -2468,14 +2470,17 @@ + + // Load GeoEngine config file (if exists) + final PropertiesParser GeoEngine = new PropertiesParser(GEOENGINE_CONFIG_FILE); +- GEODATA_PATH = Paths.get(GeoEngine.getString("GeoDataPath", "./data/geodata")); ++ GEODATA_PATH = GeoEngine.getString("GeoDataPath", "./data/geodata/"); ++ COORD_SYNCHRONIZE = GeoEngine.getInt("CoordSynchronize", -1); ++ PART_OF_CHARACTER_HEIGHT = GeoEngine.getInt("PartOfCharacterHeight", 75); ++ MAX_OBSTACLE_HEIGHT = GeoEngine.getInt("MaxObstacleHeight", 32); + PATHFINDING = GeoEngine.getBoolean("PathFinding", true); + PATHFIND_BUFFERS = GeoEngine.getString("PathFindBuffers", "100x6;128x6;192x6;256x4;320x4;384x4;500x2"); +- LOW_WEIGHT = GeoEngine.getFloat("LowWeight", 0.5f); +- MEDIUM_WEIGHT = GeoEngine.getFloat("MediumWeight", 2); +- HIGH_WEIGHT = GeoEngine.getFloat("HighWeight", 3); +- DIAGONAL_WEIGHT = GeoEngine.getFloat("DiagonalWeight", 0.707f); +- COORD_SYNCHRONIZE = GeoEngine.getInt("CoordSynchronize", -1); ++ BASE_WEIGHT = GeoEngine.getInt("BaseWeight", 10); ++ DIAGONAL_WEIGHT = GeoEngine.getInt("DiagonalWeight", 14); ++ OBSTACLE_MULTIPLIER = GeoEngine.getInt("ObstacleMultiplier", 10); ++ HEURISTIC_WEIGHT = GeoEngine.getInt("HeuristicWeight", 20); ++ MAX_ITERATIONS = GeoEngine.getInt("MaxIterations", 3500); + CORRECT_PLAYER_Z = GeoEngine.getBoolean("CorrectPlayerZ", false); + + // Load AllowedPlayerRaces config file (if exists) +Index: java/org/l2jmobius/gameserver/geoengine/GeoEngine.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/GeoEngine.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/GeoEngine.java (working copy) +@@ -16,101 +16,91 @@ + */ + package org.l2jmobius.gameserver.geoengine; + +-import java.io.IOException; ++import java.io.File; + import java.io.RandomAccessFile; + import java.nio.ByteOrder; +-import java.nio.channels.FileChannel.MapMode; +-import java.nio.file.Files; +-import java.nio.file.Path; +-import java.util.concurrent.atomic.AtomicReferenceArray; +-import java.util.logging.Level; ++import java.nio.MappedByteBuffer; ++import java.nio.channels.FileChannel; ++import java.util.List; + 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.geoengine.geodata.Cell; +-import org.l2jmobius.gameserver.geoengine.geodata.IRegion; +-import org.l2jmobius.gameserver.geoengine.geodata.NullRegion; +-import org.l2jmobius.gameserver.geoengine.geodata.Region; ++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.GeoFormat; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoLocation; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; + import org.l2jmobius.gameserver.instancemanager.WarpedSpaceManager; + 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; ++import org.l2jmobius.gameserver.util.MathUtil; + + /** +- * @author -Nemesiss-, HorridoJoho ++ * @author Hasha + */ + public class GeoEngine + { +- private static final Logger LOGGER = Logger.getLogger(GeoEngine.class.getName()); ++ protected static final Logger LOGGER = Logger.getLogger(GeoEngine.class.getName()); + +- private static final int WORLD_MIN_X = -655360; +- private static final int WORLD_MIN_Y = -589824; +- private static final int WORLD_MIN_Z = -16384; ++ private final ABlock[][] _blocks; ++ private final BlockNull _nullBlock; + +- /** 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; +- +- /** The regions array */ +- private final AtomicReferenceArray _regions = new AtomicReferenceArray<>(GEO_REGIONS); +- +- 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; +- +- protected GeoEngine() ++ /** ++ * GeoEngine constructor. Loads all geodata files of chosen geodata format. ++ */ ++ public GeoEngine() + { + LOGGER.info("GeoEngine: Initializing..."); +- for (int i = 0; i < _regions.length(); i++) +- { +- _regions.set(i, NullRegion.INSTANCE); +- } + ++ // 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; +- try ++ long fileSize = 0; ++ for (int rx = World.TILE_X_MIN; rx <= World.TILE_X_MAX; rx++) + { +- for (int regionX = World.TILE_X_MIN; regionX <= World.TILE_X_MAX; regionX++) ++ for (int ry = World.TILE_Y_MIN; ry <= World.TILE_Y_MAX; ry++) + { +- for (int regionY = World.TILE_Y_MIN; regionY <= World.TILE_Y_MAX; regionY++) ++ final File f = new File(Config.GEODATA_PATH + String.format(GeoFormat.L2D.getFilename(), rx, ry)); ++ if (f.exists() && !f.isDirectory()) + { +- 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(rx, ry)) + { +- try (RandomAccessFile raf = new RandomAccessFile(geoFilePath.toFile(), "r")) +- { +- _regions.set((regionX * GEO_REGIONS_Y) + regionY, new Region(raf.getChannel().map(MapMode.READ_ONLY, 0, raf.length()).load().order(ByteOrder.LITTLE_ENDIAN))); +- loaded++; +- } ++ loaded++; ++ fileSize += f.length(); + } + } ++ else ++ { ++ // region file is not load-able, load null blocks ++ loadNullBlocks(rx, ry); ++ } + } + } +- catch (Exception e) ++ ++ LOGGER.info("GeoEngine: Loaded " + loaded + " geodata files."); ++ if (loaded > 0) + { +- LOGGER.log(Level.SEVERE, "GeoEngine: Failed to load geodata!", e); +- System.exit(1); ++ LOGGER.info("GeoEngine: Total geodata file size " + (fileSize / 1024 / 1024) + " MB."); + } + +- LOGGER.info("GeoEngine: Loaded " + loaded + " geodata files."); +- +- // Avoid wrong configs when no files are loaded. ++ // avoid wrong configs when no files are loaded + if (loaded == 0) + { + if (Config.PATHFINDING) +@@ -124,627 +114,832 @@ + LOGGER.info("GeoEngine: Forcing CoordSynchronize setting to -1."); + } + } ++ ++ // release multilayer block temporarily buffer ++ BlockMultilayer.release(); + } + + /** +- * @param geoX +- * @param geoY +- * @return the region ++ * 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 IRegion getRegion(int geoX, int geoY) ++ private final boolean loadGeoBlocks(int regionX, int regionY) + { +- final int region = ((geoX / IRegion.REGION_CELLS_X) * GEO_REGIONS_Y) + (geoY / IRegion.REGION_CELLS_Y); +- if ((region < 0) || (region >= _regions.length())) ++ final String filename = String.format(GeoFormat.L2D.getFilename(), regionX, regionY); ++ ++ // standard load ++ try (RandomAccessFile raf = new RandomAccessFile(Config.GEODATA_PATH + filename, "r"); ++ FileChannel fc = raf.getChannel()) + { +- return null; ++ // initialize file buffer ++ final MappedByteBuffer buffer = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()).load(); ++ buffer.order(ByteOrder.LITTLE_ENDIAN); ++ ++ // 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++) ++ { ++ // get block type ++ final byte type = buffer.get(); ++ ++ // load block according to block type ++ switch (type) ++ { ++ case GeoStructure.TYPE_FLAT_L2D: ++ { ++ _blocks[blockX + ix][blockY + iy] = new BlockFlat(buffer, GeoFormat.L2D); ++ break; ++ } ++ case GeoStructure.TYPE_COMPLEX_L2D: ++ { ++ _blocks[blockX + ix][blockY + iy] = new BlockComplex(buffer, GeoFormat.L2D); ++ break; ++ } ++ case GeoStructure.TYPE_MULTILAYER_L2D: ++ { ++ _blocks[blockX + ix][blockY + iy] = new BlockMultilayer(buffer, GeoFormat.L2D); ++ break; ++ } ++ default: ++ { ++ throw new IllegalArgumentException("Unknown block type: " + type); ++ } ++ } ++ } ++ } ++ ++ // check data consistency ++ if (buffer.remaining() > 0) ++ { ++ LOGGER.warning("GeoEngine: Region file " + filename + " can be corrupted, remaining " + buffer.remaining() + " bytes to read."); ++ } ++ ++ // loading was successful ++ return true; + } +- return _regions.get(region); +- } +- +- /** +- * @param filePath +- * @param regionX +- * @param regionY +- * @throws IOException +- */ +- 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")) ++ catch (Exception e) + { +- _regions.set(regionOffset, new Region(raf.getChannel().map(MapMode.READ_ONLY, 0, raf.length()).load().order(ByteOrder.LITTLE_ENDIAN))); ++ // an error occured while loading, load null blocks ++ LOGGER.warning("GeoEngine: Error while loading " + filename + " region file."); ++ LOGGER.warning(e.getMessage()); ++ ++ // replace whole region file with null blocks ++ loadNullBlocks(regionX, regionY); ++ ++ // loading was not successful ++ return false; + } + } + + /** +- * @param regionX +- * @param regionY ++ * 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. + */ +- public void unloadRegion(int regionX, int regionY) ++ private final void loadNullBlocks(int regionX, int regionY) + { +- _regions.set((regionX * GEO_REGIONS_Y) + regionY, NullRegion.INSTANCE); +- } +- +- /** +- * @param geoX +- * @param geoY +- * @return if geodata exist +- */ +- public boolean hasGeoPos(int geoX, int geoY) +- { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) ++ // 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++) + { +- return false; ++ for (int iy = 0; iy < GeoStructure.REGION_BLOCKS_Y; iy++) ++ { ++ _blocks[blockX + ix][blockY + iy] = _nullBlock; ++ } + } +- return region.hasGeo(); + } + ++ // GEODATA - GENERAL ++ + /** +- * Checks the specified position for available geodata. +- * @param x the world x +- * @param y the world y +- * @return {@code true} if there is geodata for the given coordinates, {@code false} otherwise ++ * Converts world X to geodata X. ++ * @param worldX ++ * @return int : Geo X + */ +- public boolean hasGeo(int x, int y) ++ public static int getGeoX(int worldX) + { +- return hasGeoPos(getGeoX(x), getGeoY(y)); ++ return (MathUtil.limit(worldX, World.MAP_MIN_X, World.MAP_MAX_X) - World.MAP_MIN_X) >> 4; + } + + /** +- * @param geoX +- * @param geoY +- * @param worldZ +- * @param nswe +- * @return the nearest nswe check ++ * Converts world Y to geodata Y. ++ * @param worldY ++ * @return int : Geo Y + */ +- public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) ++ public static int getGeoY(int worldY) + { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) +- { +- return true; +- } +- return region.checkNearestNswe(geoX, geoY, worldZ, nswe); ++ return (MathUtil.limit(worldY, World.MAP_MIN_Y, World.MAP_MAX_Y) - World.MAP_MIN_Y) >> 4; + } + + /** ++ * Converts geodata X to world X. + * @param geoX +- * @param geoY +- * @param worldZ +- * @param nswe +- * @return the nearest nswe anti-corner cut ++ * @return int : World X + */ +- public boolean checkNearestNsweAntiCornerCut(int geoX, int geoY, int worldZ, int nswe) ++ public static int getWorldX(int geoX) + { +- boolean can = true; +- if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) +- { +- can = checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_EAST) && checkNearestNswe(geoX + 1, geoY, worldZ, Cell.NSWE_NORTH); +- } +- if (can && ((nswe & Cell.NSWE_NORTH_WEST) == Cell.NSWE_NORTH_WEST)) +- { +- 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 = 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 = checkNearestNswe(geoX, geoY + 1, worldZ, Cell.NSWE_WEST) && checkNearestNswe(geoX - 1, geoY, worldZ, Cell.NSWE_SOUTH); +- } +- return can && checkNearestNswe(geoX, geoY, worldZ, nswe); ++ return (MathUtil.limit(geoX, 0, GeoStructure.GEO_CELLS_X) << 4) + World.MAP_MIN_X + 8; + } + + /** +- * @param geoX ++ * Converts geodata Y to world Y. + * @param geoY +- * @param worldZ +- * @return the nearest Z value ++ * @return int : World Y + */ +- public int getNearestZ(int geoX, int geoY, int worldZ) ++ public static int getWorldY(int geoY) + { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) +- { +- return worldZ; +- } +- return region.getNearestZ(geoX, geoY, worldZ); ++ return (MathUtil.limit(geoY, 0, GeoStructure.GEO_CELLS_Y) << 4) + World.MAP_MIN_Y + 8; + } + + /** +- * @param geoX +- * @param geoY +- * @param worldZ +- * @return the next lower Z value ++ * Returns block of geodata on given coordinates. ++ * @param geoX : Geodata X ++ * @param geoY : Geodata Y ++ * @return {@link ABlock} : Block of geodata. + */ +- public int getNextLowerZ(int geoX, int geoY, int worldZ) ++ private final ABlock getBlock(int geoX, int geoY) + { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) ++ final int x = geoX / GeoStructure.BLOCK_CELLS_X; ++ final int y = geoY / GeoStructure.BLOCK_CELLS_Y; ++ ++ // if x or y is out of array return null ++ if ((x > -1) && (y > -1) && (x < GeoStructure.GEO_BLOCKS_X) && (y < GeoStructure.GEO_BLOCKS_Y)) + { +- return worldZ; ++ return _blocks[x][y]; + } +- return region.getNextLowerZ(geoX, geoY, worldZ); ++ return null; + } + + /** +- * @param geoX +- * @param geoY +- * @param worldZ +- * @return the next higher Z value ++ * Check if geo coordinates has geo. ++ * @param geoX : Geodata X ++ * @param geoY : Geodata Y ++ * @return boolean : True, if given geo coordinates have geodata + */ +- public int getNextHigherZ(int geoX, int geoY, int worldZ) ++ public boolean hasGeoPos(int geoX, int geoY) + { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) ++ final ABlock block = getBlock(geoX, geoY); ++ if (block == null) // null block check + { +- return worldZ; ++ // TODO: Find when this can be null. (Bad geodata? Check World getRegion method.) ++ // LOGGER.warning("Could not find geodata block at " + getWorldX(geoX) + ", " + getWorldY(geoY) + "."); ++ return false; + } +- return region.getNextHigherZ(geoX, geoY, worldZ); ++ return block.hasGeoPos(); + } + + /** +- * Gets the Z height. +- * @param x the world x +- * @param y the world y +- * @param z the world z +- * @return the nearest Z height ++ * 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 int getHeight(int x, int y, int z) ++ public short getHeightNearest(int geoX, int geoY, int worldZ) + { +- return getNearestZ(getGeoX(x), getGeoY(y), z); ++ final ABlock block = getBlock(geoX, geoY); ++ return block != null ? block.getHeightNearest(geoX, geoY, worldZ) : (short) worldZ; + } + + /** +- * Gets the next lower Z height. +- * @param x the world x +- * @param y the world y +- * @param z the world z +- * @return the nearest Z height ++ * 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 int getLowerHeight(int x, int y, int z) ++ public byte getNsweNearest(int geoX, int geoY, int worldZ) + { +- return getNextLowerZ(getGeoX(x), getGeoY(y), z); ++ final ABlock block = getBlock(geoX, geoY); ++ return block != null ? block.getNsweNearest(geoX, geoY, worldZ) : (byte) 0xFF; + } + + /** +- * Gets the next higher Z height. +- * @param x the world x +- * @param y the world y +- * @param z the world z +- * @return the nearest Z height ++ * Check if world coordinates has geo. ++ * @param worldX : World X ++ * @param worldY : World Y ++ * @return boolean : True, if given world coordinates have geodata + */ +- public int getHigherHeight(int x, int y, int z) ++ public boolean hasGeo(int worldX, int worldY) + { +- return getNextHigherZ(getGeoX(x), getGeoY(y), z); ++ return hasGeoPos(getGeoX(worldX), getGeoY(worldY)); + } + + /** +- * @param worldX +- * @return the geo X ++ * 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 int getGeoX(int worldX) ++ public short getHeight(int worldX, int worldY, int worldZ) + { +- return (worldX - WORLD_MIN_X) / 16; ++ return getHeightNearest(getGeoX(worldX), getGeoY(worldY), worldZ); + } + +- /** +- * @param worldY +- * @return the geo Y +- */ +- public int getGeoY(int worldY) +- { +- return (worldY - WORLD_MIN_Y) / 16; +- } ++ // PATHFINDING + + /** +- * @param worldZ +- * @return the geo Z ++ * Check line of sight from {@link WorldObject} to {@link WorldObject}. ++ * @param origin : The origin object. ++ * @param target : The target object. ++ * @return {@code boolean} : True if origin can see target + */ +- public int getGeoZ(int worldZ) ++ public boolean canSeeTarget(WorldObject origin, WorldObject target) + { +- return (worldZ - WORLD_MIN_Z) / 16; +- } +- +- /** +- * @param geoX +- * @return the world X +- */ +- public int getWorldX(int geoX) +- { +- return (geoX * 16) + WORLD_MIN_X + 8; +- } +- +- /** +- * @param geoY +- * @return the world Y +- */ +- public int getWorldY(int geoY) +- { +- return (geoY * 16) + WORLD_MIN_Y + 8; +- } +- +- /** +- * @param geoZ +- * @return the world Z +- */ +- public int getWorldZ(int geoZ) +- { +- return (geoZ * 16) + WORLD_MIN_Z + 8; +- } +- +- /** +- * 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) +- { +- if (target.isDoor()) ++ if (target.isDoor() || target.isArtefact() || (target.isCreature() && ((Creature) target).isFlying())) + { +- // Can always see doors. + return true; + } +- return 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(), cha.getInstanceWorld(), worldPosition.getX(), worldPosition.getY(), worldPosition.getZ()); +- } +- +- /** +- * Can see target. Checks doors between. +- * @param x the x coordinate +- * @param y the y coordinate +- * @param z the z coordinate +- * @param world +- * @param tx the target's x coordinate +- * @param ty the target's y coordinate +- * @param tz the target's z coordinate +- * @param tworld the target's instanceId +- * @return +- */ +- public boolean canSeeTarget(int x, int y, int z, Instance world, int tx, int ty, int tz, Instance tworld) +- { +- return (world != tworld) ? false : canSeeTarget(x, y, z, world, tx, ty, tz); +- } +- +- /** +- * 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 +- * @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, Instance instance, int tx, int ty, int tz) +- { +- if (DoorData.getInstance().checkIfDoorsBetween(x, y, z, tx, ty, tz, instance, true)) ++ ++ // get origin and target world coordinates ++ final int ox = origin.getX(); ++ final int oy = origin.getY(); ++ final int oz = origin.getZ(); ++ final int tx = target.getX(); ++ final int ty = target.getY(); ++ final int tz = target.getZ(); ++ ++ if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld(), true)) + { + return false; + } +- return canSeeTarget(x, y, z, tx, ty, tz); +- } +- +- /** +- * @param prevX +- * @param prevY +- * @param prevGeoZ +- * @param curX +- * @param curY +- * @param nswe +- * @return the LOS Z value +- */ +- 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))) ++ if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld())) + { +- throw new RuntimeException("Multiple directions!"); ++ return false; + } ++ if (WarpedSpaceManager.getInstance().checkForWarpedSpace(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld())) ++ { ++ return false; ++ } + +- if (checkNearestNsweAntiCornerCut(prevX, prevY, prevGeoZ, nswe)) ++ // get origin and check existing geo coordinates ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) + { +- return getNearestZ(curX, curY, prevGeoZ); ++ return true; + } + +- return getNextHigherZ(curX, curY, prevGeoZ); ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coordinates ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) ++ { ++ return true; ++ } ++ ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // origin and target coordinates are same ++ if ((gox == gtx) && (goy == gty)) ++ { ++ return goz == gtz; ++ } ++ ++ // get origin and target height, real height = collision height * 2 ++ double oheight = 0; ++ if (origin.isCreature()) ++ { ++ oheight = ((Creature) origin).getCollisionHeight() * 2; ++ } ++ ++ double theight = 0; ++ if (target.isCreature()) ++ { ++ theight = ((Creature) target).getCollisionHeight() * 2; ++ } ++ ++ // perform geodata check ++ return checkSee(gox, goy, goz, oheight, gtx, gty, gtz, theight, origin.getInstanceWorld()); + } + + /** +- * Can see target. Does not check doors between. +- * @param xValue the x coordinate +- * @param yValue the y coordinate +- * @param zValue the z coordinate +- * @param txValue the target's x coordinate +- * @param tyValue the target's y coordinate +- * @param tzValue the target's z coordinate +- * @return {@code true} if there is line of sight between the given coordinate sets, {@code false} otherwise ++ * Check line of sight from {@link WorldObject} to {@link Location}. ++ * @param origin : The origin object. ++ * @param position : The target position. ++ * @return {@code boolean} : True if object can see position + */ +- public boolean canSeeTarget(int xValue, int yValue, int zValue, int txValue, int tyValue, int tzValue) ++ public boolean canSeeTarget(WorldObject origin, Location position) + { +- int x = xValue; +- int y = yValue; +- int tx = txValue; +- int ty = tyValue; ++ // get origin and target world coordinates ++ final int ox = origin.getX(); ++ final int oy = origin.getY(); ++ final int oz = origin.getZ(); ++ final int tx = position.getX(); ++ final int ty = position.getY(); ++ final int tz = position.getZ(); + +- int geoX = getGeoX(x); +- int geoY = getGeoY(y); +- int tGeoX = getGeoX(tx); +- int tGeoY = getGeoY(ty); ++ if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld(), true)) ++ { ++ return false; ++ } ++ if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld())) ++ { ++ return false; ++ } ++ if (WarpedSpaceManager.getInstance().checkForWarpedSpace(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld())) ++ { ++ return false; ++ } + +- int z = getNearestZ(geoX, geoY, zValue); +- int tz = getNearestZ(tGeoX, tGeoY, tzValue); ++ // get origin and check existing geo coordinates ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) ++ { ++ return true; ++ } + +- // Fastpath. +- if ((geoX == tGeoX) && (geoY == tGeoY)) ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coordinates ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) + { +- if (hasGeoPos(tGeoX, tGeoY)) +- { +- return z == tz; +- } + return true; + } + +- if (tz > z) ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // origin and target coordinates are same ++ if ((gox == gtx) && (goy == gty)) + { +- int tmp = tx; +- tx = x; +- x = tmp; +- +- tmp = ty; +- ty = y; +- y = tmp; +- +- tmp = tz; +- tz = z; +- z = tmp; +- +- tmp = tGeoX; +- tGeoX = geoX; +- geoX = tmp; +- +- tmp = tGeoY; +- tGeoY = geoY; +- geoY = tmp; ++ return goz == gtz; + } + +- final LinePointIterator3D pointIter = new LinePointIterator3D(geoX, geoY, z, tGeoX, tGeoY, tz); +- // 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()) ++ // get origin and target height, real height = collision height * 2 ++ double oheight = 0; ++ if (origin.isCreature()) + { +- final int curX = pointIter.x(); +- final int curY = pointIter.y(); ++ oheight = ((Creature) origin).getTemplate().getCollisionHeight(); ++ } ++ ++ // perform geodata check ++ return checkSee(gox, goy, goz, oheight, gtx, gty, gtz, 0, origin.getInstanceWorld()); ++ } ++ ++ /** ++ * Simple check for origin to target visibility. ++ * @param goxValue : origin X geodata coordinate ++ * @param goyValue : origin Y geodata coordinate ++ * @param gozValue : origin Z geodata coordinate ++ * @param oheight : origin height (if instance of {@link Character}) ++ * @param gtxValue : target X geodata coordinate ++ * @param gtyValue : target Y geodata coordinate ++ * @param gtzValue : target Z geodata coordinate ++ * @param theight : target height (if instance of {@link Character}) ++ * @param instance ++ * @return {@code boolean} : True, when target can be seen. ++ */ ++ private final boolean checkSee(int goxValue, int goyValue, int gozValue, double oheight, int gtxValue, int gtyValue, int gtzValue, double theight, Instance instance) ++ { ++ int goz = gozValue; ++ int gtz = gtzValue; ++ int gox = goxValue; ++ int goy = goyValue; ++ int gtx = gtxValue; ++ int gty = gtyValue; ++ ++ // get line of sight Z coordinates ++ double losoz = goz + ((oheight * Config.PART_OF_CHARACTER_HEIGHT) / 100); ++ double lostz = gtz + ((theight * Config.PART_OF_CHARACTER_HEIGHT) / 100); ++ ++ // get X delta and signum ++ final int dx = Math.abs(gtx - gox); ++ final int sx = gox < gtx ? 1 : -1; ++ final byte dirox = sx > 0 ? GeoStructure.CELL_FLAG_E : GeoStructure.CELL_FLAG_W; ++ final byte dirtx = sx > 0 ? GeoStructure.CELL_FLAG_W : GeoStructure.CELL_FLAG_E; ++ ++ // get Y delta and signum ++ final int dy = Math.abs(gty - goy); ++ final int sy = goy < gty ? 1 : -1; ++ final byte diroy = sy > 0 ? GeoStructure.CELL_FLAG_S : GeoStructure.CELL_FLAG_N; ++ final byte dirty = sy > 0 ? GeoStructure.CELL_FLAG_N : GeoStructure.CELL_FLAG_S; ++ ++ // get Z delta ++ final int dm = Math.max(dx, dy); ++ final double dz = (lostz - losoz) / dm; ++ ++ // get direction flag for diagonal movement ++ final byte diroxy = getDirXY(dirox, diroy); ++ final byte dirtxy = getDirXY(dirtx, dirty); ++ ++ // delta, determines axis to move on (+..X axis, -..Y axis) ++ int d = dx - dy; ++ ++ // NSWE direction of movement ++ byte diro; ++ byte dirt; ++ ++ // initialize node values ++ int nox = gox; ++ int noy = goy; ++ int ntx = gtx; ++ int nty = gty; ++ byte nsweo = getNsweNearest(gox, goy, goz); ++ byte nswet = getNsweNearest(gtx, gty, gtz); ++ ++ // loop ++ ABlock block; ++ int index; ++ for (int i = 0; i < ((dm + 1) / 2); i++) ++ { ++ // reset direction flag ++ diro = 0; ++ dirt = 0; + +- if ((curX == prevX) && (curY == prevY)) ++ // calculate next point coordinates ++ final int e2 = 2 * d; ++ if ((e2 > -dy) && (e2 < dx)) + { +- continue; ++ // calculate next point XY coordinates ++ d -= dy; ++ d += dx; ++ nox += sx; ++ ntx -= sx; ++ noy += sy; ++ nty -= sy; ++ diro |= diroxy; ++ dirt |= dirtxy; + } ++ else if (e2 > -dy) ++ { ++ // calculate next point X coordinate ++ d -= dy; ++ nox += sx; ++ ntx -= sx; ++ diro |= dirox; ++ dirt |= dirtx; ++ } ++ else if (e2 < dx) ++ { ++ // calculate next point Y coordinate ++ d += dx; ++ noy += sy; ++ nty -= sy; ++ diro |= diroy; ++ dirt |= dirty; ++ } + +- final int beeCurZ = pointIter.z(); +- int curGeoZ = prevGeoZ; +- +- // Check if the position has geodata. +- if (hasGeoPos(curX, curY)) + { +- final int beeCurGeoZ = getNearestZ(curX, curY, beeCurZ); +- final int nswe = GeoUtils.computeNswe(prevX, prevY, curX, curY); // .computeDirection(prevX, prevY, curX, curY); +- curGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, curX, curY, nswe); +- int maxHeight; +- if (ptIndex < ELEVATED_SEE_OVER_DISTANCE) ++ // get block of the next cell ++ block = getBlock(nox, noy); ++ ++ // get index of particular layer, based on movement conditions ++ if ((nsweo & diro) == 0) + { +- maxHeight = z + MAX_SEE_OVER_HEIGHT; ++ index = block.getIndexAbove(nox, noy, goz - GeoStructure.CELL_IGNORE_HEIGHT); + } + else + { +- maxHeight = beeCurZ + MAX_SEE_OVER_HEIGHT; ++ index = block.getIndexBelow(nox, noy, goz + GeoStructure.CELL_IGNORE_HEIGHT); + } + +- boolean canSeeThrough = false; +- if ((curGeoZ <= maxHeight) && (curGeoZ <= beeCurGeoZ)) ++ // layer does not exist, return ++ if (index == -1) + { +- if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) +- { +- 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 +- { +- canSeeThrough = true; +- } ++ return false; + } + +- if (!canSeeThrough) ++ // get layer and next line of sight Z coordinate ++ goz = block.getHeight(index); ++ losoz += dz; ++ ++ // perform line of sight check, return when fails ++ if ((goz - losoz) > Config.MAX_OBSTACLE_HEIGHT) + { + return false; + } ++ ++ // get layer nswe ++ nsweo = block.getNswe(index); + } ++ { ++ // get block of the next cell ++ block = getBlock(ntx, nty); ++ ++ // get index of particular layer, based on movement conditions ++ if ((nswet & dirt) == 0) ++ { ++ index = block.getIndexAbove(ntx, nty, gtz - GeoStructure.CELL_IGNORE_HEIGHT); ++ } ++ else ++ { ++ index = block.getIndexBelow(ntx, nty, gtz + GeoStructure.CELL_IGNORE_HEIGHT); ++ } ++ ++ // layer does not exist, return ++ if (index == -1) ++ { ++ return false; ++ } ++ ++ // get layer and next line of sight Z coordinate ++ gtz = block.getHeight(index); ++ lostz -= dz; ++ ++ // perform line of sight check, return when fails ++ if ((gtz - lostz) > Config.MAX_OBSTACLE_HEIGHT) ++ { ++ return false; ++ } ++ ++ // get layer nswe ++ nswet = block.getNswe(index); ++ } + +- prevX = curX; +- prevY = curY; +- prevGeoZ = curGeoZ; +- ++ptIndex; ++ // update coords ++ gox = nox; ++ goy = noy; ++ gtx = ntx; ++ gty = nty; + } + +- return true; ++ // when iteration is completed, compare final Z coordinates ++ return Math.abs(goz - gtz) < (GeoStructure.CELL_HEIGHT * 4); + } + + /** +- * Move check. +- * @param x the x coordinate +- * @param y the y coordinate +- * @param zValue the z coordinate +- * @param tx the target's x coordinate +- * @param ty the target's y coordinate +- * @param tzValue the target's z coordinate +- * @param instance the instance +- * @return the last Location (x,y,z) where player can walk - just before wall ++ * Check movement from coordinates to 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 {code boolean} : True if target coordinates are reachable from origin coordinates + */ +- public Location canMoveToTargetLoc(int x, int y, int zValue, int tx, int ty, int tzValue, Instance instance) ++ public boolean canMoveToTarget(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + { +- final int geoX = getGeoX(x); +- final int geoY = getGeoY(y); +- final int z = getNearestZ(geoX, geoY, zValue); +- final int tGeoX = getGeoX(tx); +- final int tGeoY = getGeoY(ty); +- final int tz = getNearestZ(tGeoX, tGeoY, tzValue); ++ // get origin and check existing geo coordinates ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) ++ { ++ return true; ++ } + +- if (DoorData.getInstance().checkIfDoorsBetween(x, y, z, tx, ty, tz, instance, false)) ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coordinates ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) + { +- return new Location(x, y, getHeight(x, y, z)); ++ return true; + } +- if (FenceData.getInstance().checkIfFenceBetween(x, y, z, tx, ty, tz, instance)) ++ ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // target coordinates reached ++ if ((gox == gtx) && (goy == gty) && (goz == gtz)) + { +- return new Location(x, y, getHeight(x, y, z)); ++ return true; + } +- if (WarpedSpaceManager.getInstance().checkForWarpedSpace(x, y, z, tx, ty, tz, instance)) ++ ++ // perform geodata check ++ final GeoLocation loc = checkMove(gox, goy, goz, gtx, gty, gtz, instance); ++ return (loc.getGeoX() == gtx) && (loc.getGeoY() == gty); ++ } ++ ++ /** ++ * Check movement from origin to target. Returns last available point in the checked path. ++ * @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 {@link Location} : Last point where object can walk (just before wall) ++ */ ++ public Location canMoveToTargetLoc(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) ++ { ++ // Mobius: Double check for doors before normal checkMove to avoid exploiting key movement. ++ if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, instance, false)) + { +- return new Location(x, y, getHeight(x, y, z)); ++ return new Location(ox, oy, oz); + } ++ if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, instance)) ++ { ++ return new Location(ox, oy, oz); ++ } + +- 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 = z; ++ // get origin and check existing geo coordinates ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) ++ { ++ return new Location(tx, ty, tz); ++ } + +- while (pointIter.next()) ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coordinates ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) + { +- final int curX = pointIter.x(); +- final int curY = pointIter.y(); +- final int curZ = getNearestZ(curX, curY, prevZ); +- +- if (hasGeoPos(prevX, prevY)) +- { +- final int nswe = GeoUtils.computeNswe(prevX, prevY, curX, curY); +- if (!checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, nswe)) +- { +- // Can't move, return previous location. +- return new Location(getWorldX(prevX), getWorldY(prevY), prevZ); +- } +- } +- +- prevX = curX; +- prevY = curY; +- prevZ = curZ; ++ return new Location(tx, ty, tz); + } + +- if (hasGeoPos(prevX, prevY) && (prevZ != tz)) ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // target coordinates reached ++ if ((gox == gtx) && (goy == gty) && (goz == gtz)) + { +- // Different floors, return start location. +- return new Location(x, y, z); ++ return new Location(tx, ty, tz); + } + +- return new Location(tx, ty, tz); ++ // perform geodata check ++ return checkMove(gox, goy, goz, gtx, gty, gtz, instance); + } + + /** +- * 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 fromZvalue 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 toZvalue 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 ++ * With this method you can check if a position is visible or can be reached by beeline movement.
++ * 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 gox : origin X geodata coordinate ++ * @param goy : origin Y geodata coordinate ++ * @param goz : origin Z geodata coordinate ++ * @param gtx : target X geodata coordinate ++ * @param gty : target Y geodata coordinate ++ * @param gtz : target Z geodata coordinate ++ * @param instance ++ * @return {@link GeoLocation} : The last allowed point of movement. + */ +- public boolean canMoveToTarget(int fromX, int fromY, int fromZvalue, int toX, int toY, int toZvalue, Instance instance) ++ protected final GeoLocation checkMove(int gox, int goy, int goz, int gtx, int gty, int gtz, Instance instance) + { +- final int geoX = getGeoX(fromX); +- final int geoY = getGeoY(fromY); +- final int fromZ = getNearestZ(geoX, geoY, fromZvalue); +- final int tGeoX = getGeoX(toX); +- final int tGeoY = getGeoY(toY); +- final int toZ = getNearestZ(tGeoX, tGeoY, toZvalue); +- +- if (DoorData.getInstance().checkIfDoorsBetween(fromX, fromY, fromZ, toX, toY, toZ, instance, false)) ++ if (DoorData.getInstance().checkIfDoorsBetween(gox, goy, goz, gtx, gty, gtz, instance, false)) + { +- return false; ++ return new GeoLocation(gox, goy, goz); + } +- if (FenceData.getInstance().checkIfFenceBetween(fromX, fromY, fromZ, toX, toY, toZ, instance)) ++ if (FenceData.getInstance().checkIfFenceBetween(gox, goy, goz, gtx, gty, gtz, instance)) + { +- return false; ++ return new GeoLocation(gox, goy, goz); + } +- if (WarpedSpaceManager.getInstance().checkForWarpedSpace(fromX, fromY, fromZ, toX, toY, toZ, instance)) ++ if (WarpedSpaceManager.getInstance().checkForWarpedSpace(gox, goy, goz, gtx, gty, gtz, instance)) + { +- return false; ++ return new GeoLocation(gox, goy, goz); + } + +- 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 = fromZ; ++ // get X delta, signum and direction flag ++ final int dx = Math.abs(gtx - gox); ++ final int sx = gox < gtx ? 1 : -1; ++ final byte dirX = sx > 0 ? GeoStructure.CELL_FLAG_E : GeoStructure.CELL_FLAG_W; + +- while (pointIter.next()) ++ // get Y delta, signum and direction flag ++ final int dy = Math.abs(gty - goy); ++ final int sy = goy < gty ? 1 : -1; ++ final byte dirY = sy > 0 ? GeoStructure.CELL_FLAG_S : GeoStructure.CELL_FLAG_N; ++ ++ // get direction flag for diagonal movement ++ final byte dirXY = getDirXY(dirX, dirY); ++ ++ // delta, determines axis to move on (+..X axis, -..Y axis) ++ int d = dx - dy; ++ ++ // NSWE direction of movement ++ byte direction; ++ ++ // load pointer coordinates ++ int gpx = gox; ++ int gpy = goy; ++ int gpz = goz; ++ ++ // load next pointer ++ int nx = gpx; ++ int ny = gpy; ++ ++ // loop ++ int count = 0; ++ while (count++ < Config.MAX_ITERATIONS) + { +- final int curX = pointIter.x(); +- final int curY = pointIter.y(); +- final int curZ = getNearestZ(curX, curY, prevZ); ++ direction = 0; + +- if (hasGeoPos(prevX, prevY)) ++ // calculate next point coordinates ++ final int e2 = 2 * d; ++ if ((e2 > -dy) && (e2 < dx)) + { +- final int nswe = GeoUtils.computeNswe(prevX, prevY, curX, curY); +- if (!checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, nswe)) ++ d -= dy; ++ d += dx; ++ nx += sx; ++ ny += sy; ++ direction |= dirXY; ++ } ++ else if (e2 > -dy) ++ { ++ d -= dy; ++ nx += sx; ++ direction |= dirX; ++ } ++ else if (e2 < dx) ++ { ++ d += dx; ++ ny += sy; ++ direction |= dirY; ++ } ++ ++ // obstacle found, return ++ if ((getNsweNearest(gpx, gpy, gpz) & direction) == 0) ++ { ++ return new GeoLocation(gpx, gpy, gpz); ++ } ++ ++ // update pointer coordinates ++ gpx = nx; ++ gpy = ny; ++ gpz = getHeightNearest(nx, ny, gpz); ++ ++ // target coordinates reached ++ if ((gpx == gtx) && (gpy == gty)) ++ { ++ if (gpz == gtz) + { +- return false; ++ // path found, Z coordinates are okay, return target point ++ return new GeoLocation(gtx, gty, gtz); + } ++ ++ // path found, Z coordinates are not okay, return last good point ++ return new GeoLocation(gpx, gpy, gpz); + } ++ } ++ ++ return new GeoLocation(gox, goy, goz); ++ } ++ ++ /** ++ * Returns diagonal NSWE flag format of combined two NSWE flags. ++ * @param dirX : X direction NSWE flag ++ * @param dirY : Y direction NSWE flag ++ * @return byte : NSWE flag of combined direction ++ */ ++ private static byte getDirXY(byte dirX, byte dirY) ++ { ++ // check axis directions ++ if (dirY == GeoStructure.CELL_FLAG_N) ++ { ++ if (dirX == GeoStructure.CELL_FLAG_W) ++ { ++ return GeoStructure.CELL_FLAG_NW; ++ } + +- prevX = curX; +- prevY = curY; +- prevZ = curZ; ++ return GeoStructure.CELL_FLAG_NE; + } + +- if (hasGeoPos(prevX, prevY) && (prevZ != toZ)) ++ if (dirX == GeoStructure.CELL_FLAG_W) + { +- // Different floors. +- return false; ++ return GeoStructure.CELL_FLAG_SW; + } + +- return true; ++ return GeoStructure.CELL_FLAG_SE; + } + ++ /** ++ * 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 ++ */ ++ public List findPath(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) ++ { ++ return null; ++ } ++ ++ /** ++ * Returns the instance of the {@link GeoEngine}. ++ * @return {@link GeoEngine} : The instance. ++ */ + public static GeoEngine getInstance() + { + return SingletonHolder.INSTANCE; +@@ -752,6 +947,6 @@ + + private static class SingletonHolder + { +- protected static final GeoEngine INSTANCE = new GeoEngine(); ++ protected static final GeoEngine INSTANCE = Config.PATHFINDING ? new GeoEnginePathfinding() : new GeoEngine(); + } +-} ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/GeoEnginePathfinding.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/GeoEnginePathfinding.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/GeoEnginePathfinding.java (working copy) +@@ -20,88 +20,84 @@ + import java.util.LinkedList; + import java.util.List; + import java.util.ListIterator; +-import java.util.logging.Level; +-import java.util.logging.Logger; + + import org.l2jmobius.Config; +-import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; +-import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +-import org.l2jmobius.gameserver.geoengine.pathfinding.CellNode; +-import org.l2jmobius.gameserver.geoengine.pathfinding.CellNodeBuffer; +-import org.l2jmobius.gameserver.geoengine.pathfinding.NodeLoc; +-import org.l2jmobius.gameserver.model.World; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoLocation; ++import org.l2jmobius.gameserver.geoengine.pathfinding.Node; ++import org.l2jmobius.gameserver.geoengine.pathfinding.NodeBuffer; ++import org.l2jmobius.gameserver.model.Location; + import org.l2jmobius.gameserver.model.instancezone.Instance; + + /** +- * @author -Nemesiss- ++ * @author Hasha + */ +-public class GeoEnginePathfinding ++final class GeoEnginePathfinding extends GeoEngine + { +- private static final Logger LOGGER = Logger.getLogger(GeoEnginePathfinding.class.getName()); ++ // pre-allocated buffers ++ private final BufferHolder[] _buffers; + +- private BufferInfo[] _buffers; +- + protected GeoEnginePathfinding() + { +- try ++ super(); ++ ++ final String[] array = Config.PATHFIND_BUFFERS.split(";"); ++ _buffers = new BufferHolder[array.length]; ++ int count = 0; ++ for (int i = 0; i < array.length; i++) + { +- final String[] array = Config.PATHFIND_BUFFERS.split(";"); ++ final String buf = array[i]; ++ final String[] args = buf.split("x"); + +- _buffers = new BufferInfo[array.length]; +- +- String buf; +- String[] args; +- for (int i = 0; i < array.length; i++) ++ try + { +- buf = array[i]; +- args = buf.split("x"); +- if (args.length != 2) +- { +- throw new Exception("Invalid buffer definition: " + buf); +- } +- +- _buffers[i] = new BufferInfo(Integer.parseInt(args[0]), Integer.parseInt(args[1])); ++ final int size = Integer.parseInt(args[1]); ++ count += size; ++ _buffers[i] = new BufferHolder(Integer.parseInt(args[0]), size); + } ++ catch (Exception e) ++ { ++ LOGGER.warning("GeoEnginePathfinding: Can not load buffer setting: " + buf); ++ } + } +- catch (Exception e) +- { +- LOGGER.log(Level.WARNING, "CellPathFinding: Problem during buffer init: " + e.getMessage(), e); +- throw new Error("CellPathFinding: load aborted"); +- } ++ ++ LOGGER.info("GeoEnginePathfinding: Loaded " + count + " node buffers."); + } + +- public boolean pathNodesExist(short regionoffset) ++ @Override ++ public List findPath(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + { +- return false; +- } +- +- public List findPath(int x, int y, int z, int tx, int ty, int tz, Instance instance) +- { +- final int gx = GeoEngine.getInstance().getGeoX(x); +- final int gy = GeoEngine.getInstance().getGeoY(y); +- if (!GeoEngine.getInstance().hasGeo(x, y)) ++ // get origin and check existing geo coords ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) + { + 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)) ++ ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coords ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) + { + 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)))); ++ ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // Prepare buffer for pathfinding calculations ++ final NodeBuffer buffer = getBuffer(64 + (2 * Math.max(Math.abs(gox - gtx), Math.abs(goy - gty)))); + if (buffer == null) + { + return null; + } + +- List path = null; ++ // find path ++ List path = null; + try + { +- final CellNode result = buffer.findPath(gx, gy, gz, gtx, gty, gtz); +- ++ final Node result = buffer.findPath(gox, goy, goz, gtx, gty, gtz); + if (result == null) + { + return null; +@@ -125,33 +121,45 @@ + return path; + } + +- int currentX, currentY, currentZ; +- ListIterator middlePoint; ++ // get path list iterator ++ final ListIterator point = path.listIterator(); + +- middlePoint = path.listIterator(); +- currentX = x; +- currentY = y; +- currentZ = z; ++ // get node A (origin) ++ int nodeAx = gox; ++ int nodeAy = goy; ++ short nodeAz = goz; + +- while (middlePoint.hasNext()) ++ // get node B ++ GeoLocation nodeB = (GeoLocation) point.next(); ++ ++ // iterate thought the path to optimize it ++ int count = 0; ++ while (point.hasNext() && (count++ < Config.MAX_ITERATIONS)) + { +- final AbstractNodeLoc locMiddle = middlePoint.next(); +- if (!middlePoint.hasNext()) +- { +- break; +- } ++ // get node C ++ final GeoLocation nodeC = (GeoLocation) path.get(point.nextIndex()); + +- final AbstractNodeLoc locEnd = path.get(middlePoint.nextIndex()); +- if (GeoEngine.getInstance().canMoveToTarget(currentX, currentY, currentZ, locEnd.getX(), locEnd.getY(), locEnd.getZ(), instance)) ++ // check movement from node A to node C ++ final GeoLocation loc = checkMove(nodeAx, nodeAy, nodeAz, nodeC.getGeoX(), nodeC.getGeoY(), nodeC.getZ(), instance); ++ if ((loc.getGeoX() == nodeC.getGeoX()) && (loc.getGeoY() == nodeC.getGeoY())) + { +- middlePoint.remove(); ++ // can move from node A to node C ++ ++ // remove node B ++ point.remove(); + } + else + { +- currentX = locMiddle.getX(); +- currentY = locMiddle.getY(); +- currentZ = locMiddle.getZ(); ++ // can not move from node A to node C ++ ++ // set node A (node B is part of path, update A coordinates) ++ nodeAx = nodeB.getGeoX(); ++ nodeAy = nodeB.getGeoY(); ++ nodeAz = (short) nodeB.getZ(); + } ++ ++ // set node B ++ nodeB = (GeoLocation) point.next(); + } + + return path; +@@ -158,144 +166,102 @@ + } + + /** +- * Convert geodata position to pathnode position +- * @param geo_pos +- * @return pathnode position ++ * Create list of node locations as result of calculated buffer node tree. ++ * @param node : the entry point ++ * @return List : list of node location + */ +- public short getNodePos(int geo_pos) ++ private static List constructPath(Node node) + { +- return (short) (geo_pos >> 3); // OK? +- } +- +- /** +- * Convert node position to pathnode block position +- * @param node_pos +- * @return pathnode block position (0...255) +- */ +- public short getNodeBlock(int node_pos) +- { +- return (short) (node_pos % 256); +- } +- +- public byte getRegionX(int node_pos) +- { +- return (byte) ((node_pos >> 8) + World.TILE_X_MIN); +- } +- +- public byte getRegionY(int node_pos) +- { +- return (byte) ((node_pos >> 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 node_x rx +- * @return +- */ +- public int calculateWorldX(short node_x) +- { +- return World.MAP_MIN_X + (node_x * 128) + 48; +- } +- +- /** +- * Convert pathnode y to World y position +- * @param node_y +- * @return +- */ +- public int calculateWorldY(short node_y) +- { +- return World.MAP_MIN_Y + (node_y * 128) + 48; +- } +- +- private List constructPath(AbstractNode nodeValue) +- { +- final LinkedList path = new LinkedList<>(); +- int previousDirectionX = Integer.MIN_VALUE; +- int previousDirectionY = Integer.MIN_VALUE; +- int directionX, directionY; ++ // create empty list ++ final LinkedList list = new LinkedList<>(); + +- AbstractNode node = nodeValue; +- while (node.getParent() != null) ++ // set direction X/Y ++ int dx = 0; ++ int dy = 0; ++ ++ // get target parent ++ Node target = node; ++ Node parent = target.getParent(); ++ ++ // while parent exists ++ int count = 0; ++ while ((parent != null) && (count++ < Config.MAX_ITERATIONS)) + { +- directionX = node.getLoc().getNodeX() - node.getParent().getLoc().getNodeX(); +- directionY = node.getLoc().getNodeY() - node.getParent().getLoc().getNodeY(); ++ // get parent <> target direction X/Y ++ final int nx = parent.getLoc().getGeoX() - target.getLoc().getGeoX(); ++ final int ny = parent.getLoc().getGeoY() - target.getLoc().getGeoY(); + +- // only add a new route point if moving direction changes +- if ((directionX != previousDirectionX) || (directionY != previousDirectionY)) ++ // direction has changed? ++ if ((dx != nx) || (dy != ny)) + { +- previousDirectionX = directionX; +- previousDirectionY = directionY; ++ // add node to the beginning of the list ++ list.addFirst(target.getLoc()); + +- path.addFirst(node.getLoc()); +- node.setLoc(null); ++ // update direction X/Y ++ dx = nx; ++ dy = ny; + } + +- node = node.getParent(); ++ // move to next node, set target and get its parent ++ target = parent; ++ parent = target.getParent(); + } + +- return path; ++ // return list ++ return list; + } + +- private CellNodeBuffer alloc(int size) ++ /** ++ * Provides optimize selection of the buffer. When all pre-initialized buffer are locked, creates new buffer. ++ * @param size : pre-calculated minimal required size ++ * @return NodeBuffer : buffer ++ */ ++ private final NodeBuffer getBuffer(int size) + { +- CellNodeBuffer current = null; +- for (BufferInfo i : _buffers) ++ NodeBuffer current = null; ++ for (BufferHolder holder : _buffers) + { +- if (i.mapSize >= size) ++ // Find proper size of buffer ++ if (holder._size < size) + { +- for (CellNodeBuffer buf : i.buffer) ++ continue; ++ } ++ ++ // Find unlocked NodeBuffer ++ for (NodeBuffer buffer : holder._buffer) ++ { ++ if (!buffer.isLocked()) + { +- if (buf.lock()) +- { +- current = buf; +- break; +- } ++ continue; + } +- if (current != null) +- { +- break; +- } + +- // not found, allocate temporary buffer +- current = new CellNodeBuffer(i.mapSize); +- current.lock(); +- if (i.buffer.size() < i.count) +- { +- i.buffer.add(current); +- break; +- } ++ return buffer; + } ++ ++ // NodeBuffer not found, allocate temporary buffer ++ current = new NodeBuffer(holder._size); ++ current.isLocked(); + } + + return current; + } + +- private static final class BufferInfo ++ /** ++ * NodeBuffer container with specified size and count of separate buffers. ++ */ ++ private static final class BufferHolder + { +- final int mapSize; +- final int count; +- ArrayList buffer; ++ final int _size; ++ List _buffer; + +- public BufferInfo(int size, int cnt) ++ public BufferHolder(int size, int count) + { +- mapSize = size; +- count = cnt; +- buffer = new ArrayList<>(count); ++ _size = size; ++ _buffer = new ArrayList<>(count); ++ for (int i = 0; i < count; i++) ++ { ++ _buffer.add(new NodeBuffer(size)); ++ } + } + } +- +- public static GeoEnginePathfinding getInstance() +- { +- return SingletonHolder.INSTANCE; +- } +- +- private static class SingletonHolder +- { +- protected static final GeoEnginePathfinding INSTANCE = new GeoEnginePathfinding(); +- } +-} ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java (working copy) +@@ -0,0 +1,197 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++import java.io.IOException; ++ ++/** ++ * @author Hasha ++ */ ++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 height of cell, which is closest to given coordinates.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @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 getHeightNearestOriginal(int geoX, int geoY, int worldZ); ++ ++ /** ++ * Returns the height of cell, which is first above 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, above given coordinates. ++ */ ++ public abstract short getHeightAbove(int geoX, int geoY, int worldZ); ++ ++ /** ++ * Returns the height of cell, which is first below 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, below given coordinates. ++ */ ++ public abstract short getHeightBelow(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 the NSWE flag byte of cell, which is closest to given coordinates.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @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 getNsweNearestOriginal(int geoX, int geoY, int worldZ); ++ ++ /** ++ * Returns the NSWE flag byte of cell, which is first above 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 getNsweAbove(int geoX, int geoY, int worldZ); ++ ++ /** ++ * Returns the NSWE flag byte of cell, which is first below 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 getNsweBelow(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 above given coordinates.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @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 getIndexAboveOriginal(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 index to data of the cell, which is first layer below given coordinates.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @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 getIndexBelowOriginal(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 height of cell given by cell index.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @param index : Index of the cell. ++ * @return short : Cell geodata Z coordinate, below given coordinates. ++ */ ++ public abstract short getHeightOriginal(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); ++ ++ /** ++ * Returns the NSWE flag byte of cell given by cell index.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @param index : Index of the cell. ++ * @return short : Cell geodata Z coordinate, below given coordinates. ++ */ ++ public abstract byte getNsweOriginal(int index); ++ ++ /** ++ * Sets the NSWE flag byte of cell given by cell index. ++ * @param index : Index of the cell. ++ * @param nswe : New NSWE flag byte. ++ */ ++ public abstract void setNswe(int index, byte nswe); ++ ++ /** ++ * Saves the block in L2D format to {@link BufferedOutputStream}. Used only for L2D geodata conversion. ++ * @param stream : The stream. ++ * @throws IOException : Can't save the block to steam. ++ */ ++ public abstract void saveBlock(BufferedOutputStream stream) throws IOException; ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java (working copy) +@@ -0,0 +1,252 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++import java.io.IOException; ++import java.nio.ByteBuffer; ++ ++/** ++ * @author Hasha ++ */ ++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. ++ * @param format : GeoFormat specifying format of loaded data. ++ */ ++ public BlockComplex(ByteBuffer bb, GeoFormat format) ++ { ++ // initialize buffer ++ _buffer = new byte[GeoStructure.BLOCK_CELLS * 3]; ++ ++ // load data ++ for (int i = 0; i < GeoStructure.BLOCK_CELLS; i++) ++ { ++ if (format != GeoFormat.L2D) ++ { ++ // 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); ++ } ++ else ++ { ++ // get nswe ++ final byte nswe = bb.get(); ++ _buffer[i * 3] = nswe; ++ ++ // get height ++ final short height = bb.getShort(); ++ _buffer[(i * 3) + 1] = (byte) (height & 0x00FF); ++ _buffer[(i * 3) + 2] = (byte) (height >> 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 short getHeightNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getHeightNearest(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeightAbove(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 short height = (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ ++ // check and return height ++ return height > worldZ ? height : Short.MIN_VALUE; ++ } ++ ++ @Override ++ public short getHeightBelow(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 short height = (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ ++ // check and return height ++ return height < worldZ ? height : Short.MAX_VALUE; ++ } ++ ++ @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 byte getNsweNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getNsweNearest(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public byte getNsweAbove(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 ? _buffer[index] : 0; ++ } ++ ++ @Override ++ public byte getNsweBelow(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 ? _buffer[index] : 0; ++ } ++ ++ @Override ++ public 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 getIndexAboveOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexAbove(geoX, geoY, worldZ); ++ } ++ ++ @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 int getIndexBelowOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexBelow(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeight(int index) ++ { ++ return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ } ++ ++ @Override ++ public short getHeightOriginal(int index) ++ { ++ return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ } ++ ++ @Override ++ public byte getNswe(int index) ++ { ++ return _buffer[index]; ++ } ++ ++ @Override ++ public byte getNsweOriginal(int index) ++ { ++ return _buffer[index]; ++ } ++ ++ @Override ++ public void setNswe(int index, byte nswe) ++ { ++ _buffer[index] = nswe; ++ } ++ ++ @Override ++ public void saveBlock(BufferedOutputStream stream) throws IOException ++ { ++ // write block type ++ stream.write(GeoStructure.TYPE_COMPLEX_L2D); ++ ++ // write block data ++ stream.write(_buffer, 0, GeoStructure.BLOCK_CELLS * 3); ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java (working copy) +@@ -0,0 +1,176 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++import java.io.IOException; ++import java.nio.ByteBuffer; ++ ++/** ++ * @author Hasha ++ */ ++public class BlockFlat extends ABlock ++{ ++ protected final short _height; ++ protected byte _nswe; ++ ++ /** ++ * Creates FlatBlock. ++ * @param bb : Input byte buffer. ++ * @param format : GeoFormat specifying format of loaded data. ++ */ ++ public BlockFlat(ByteBuffer bb, GeoFormat format) ++ { ++ _height = bb.getShort(); ++ _nswe = format != GeoFormat.L2D ? 0x0F : (byte) (0xFF); ++ if (format == GeoFormat.L2OFF) ++ { ++ bb.getShort(); ++ } ++ } ++ ++ @Override ++ public boolean hasGeoPos() ++ { ++ return true; ++ } ++ ++ @Override ++ public short getHeightNearest(int geoX, int geoY, int worldZ) ++ { ++ return _height; ++ } ++ ++ @Override ++ public short getHeightNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return _height; ++ } ++ ++ @Override ++ public short getHeightAbove(int geoX, int geoY, int worldZ) ++ { ++ // check and return height ++ return _height > worldZ ? _height : Short.MIN_VALUE; ++ } ++ ++ @Override ++ public short getHeightBelow(int geoX, int geoY, int worldZ) ++ { ++ // check and return height ++ return _height < worldZ ? _height : Short.MAX_VALUE; ++ } ++ ++ @Override ++ public byte getNsweNearest(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweAbove(int geoX, int geoY, int worldZ) ++ { ++ // check height and return nswe ++ return _height > worldZ ? _nswe : 0; ++ } ++ ++ @Override ++ public byte getNsweBelow(int geoX, int geoY, int worldZ) ++ { ++ // check height and return nswe ++ return _height < worldZ ? _nswe : 0; ++ } ++ ++ @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 getIndexAboveOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexAbove(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public int getIndexBelow(int geoX, int geoY, int worldZ) ++ { ++ // check height and return index ++ return _height < worldZ ? 0 : -1; ++ } ++ ++ @Override ++ public int getIndexBelowOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexBelow(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeight(int index) ++ { ++ return _height; ++ } ++ ++ @Override ++ public short getHeightOriginal(int index) ++ { ++ return _height; ++ } ++ ++ @Override ++ public byte getNswe(int index) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweOriginal(int index) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public void setNswe(int index, byte nswe) ++ { ++ _nswe = nswe; ++ } ++ ++ @Override ++ public void saveBlock(BufferedOutputStream stream) throws IOException ++ { ++ // write block type ++ stream.write(GeoStructure.TYPE_FLAT_L2D); ++ ++ // write height ++ stream.write((byte) (_height & 0x00FF)); ++ stream.write((byte) (_height >> 8)); ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java (working copy) +@@ -0,0 +1,465 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++import java.io.IOException; ++import java.nio.ByteBuffer; ++import java.nio.ByteOrder; ++import java.util.Arrays; ++ ++/** ++ * @author Hasha ++ */ ++public class BlockMultilayer extends ABlock ++{ ++ private static final int MAX_LAYERS = Byte.MAX_VALUE; ++ ++ private static ByteBuffer _temp; ++ ++ /** ++ * Initializes the temporarily buffer. ++ */ ++ public static void initialize() ++ { ++ // initialize temporarily buffer and sorting mechanism ++ _temp = ByteBuffer.allocate(GeoStructure.BLOCK_CELLS * MAX_LAYERS * 3); ++ _temp.order(ByteOrder.LITTLE_ENDIAN); ++ } ++ ++ /** ++ * Releases temporarily buffer. ++ */ ++ public static void release() ++ { ++ _temp = null; ++ } ++ ++ protected byte[] _buffer; ++ ++ /** ++ * Implicit constructor for children class. ++ */ ++ protected BlockMultilayer() ++ { ++ _buffer = null; ++ } ++ ++ /** ++ * Creates MultilayerBlock. ++ * @param bb : Input byte buffer. ++ * @param format : GeoFormat specifying format of loaded data. ++ */ ++ public BlockMultilayer(ByteBuffer bb, GeoFormat format) ++ { ++ // 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 = format != GeoFormat.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++) ++ { ++ if (format != GeoFormat.L2D) ++ { ++ // get data ++ final short data = bb.getShort(); ++ ++ // add nswe and height ++ _temp.put((byte) (data & 0x000F)); ++ _temp.putShort((short) ((short) (data & 0xFFF0) >> 1)); ++ } ++ else ++ { ++ // add nswe ++ _temp.put(bb.get()); ++ ++ // add height ++ _temp.putShort(bb.getShort()); ++ } ++ } ++ } ++ ++ // 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 short getHeightNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getHeightNearest(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeightAbove(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 height ++ if (height > worldZ) ++ { ++ return (short) height; ++ } ++ ++ // move index to next layer ++ index -= 3; ++ } ++ ++ // none layer found, return minimum value ++ return Short.MIN_VALUE; ++ } ++ ++ @Override ++ public short getHeightBelow(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 height ++ if (height < worldZ) ++ { ++ return (short) height; ++ } ++ ++ // move index to next layer ++ index += 3; ++ } ++ ++ // none layer found, return maximum value ++ return Short.MAX_VALUE; ++ } ++ ++ @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 byte getNsweNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getNsweNearest(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public byte getNsweAbove(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 nswe ++ if (height > worldZ) ++ { ++ return _buffer[index]; ++ } ++ ++ // move index to next layer ++ index -= 3; ++ } ++ ++ // none layer found, block movement ++ return 0; ++ } ++ ++ @Override ++ public byte getNsweBelow(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 nswe ++ if (height < worldZ) ++ { ++ return _buffer[index]; ++ } ++ ++ // move index to next layer ++ index += 3; ++ } ++ ++ // none layer found, block movement ++ return 0; ++ } ++ ++ @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 first layer data (first from bottom) ++ byte layers = _buffer[index++]; ++ ++ // loop though all cell layers, find closest layer ++ 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; ++ } ++ ++ // none layer found ++ return -1; ++ } ++ ++ @Override ++ public int getIndexAboveOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexAbove(geoX, geoY, worldZ); ++ } ++ ++ @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; ++ } ++ ++ // none layer found ++ return -1; ++ } ++ ++ @Override ++ public int getIndexBelowOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexBelow(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeight(int index) ++ { ++ // get height ++ return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ } ++ ++ @Override ++ public short getHeightOriginal(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]; ++ } ++ ++ @Override ++ public byte getNsweOriginal(int index) ++ { ++ // get nswe ++ return _buffer[index]; ++ } ++ ++ @Override ++ public void setNswe(int index, byte nswe) ++ { ++ // set nswe ++ _buffer[index] = nswe; ++ } ++ ++ @Override ++ public void saveBlock(BufferedOutputStream stream) throws IOException ++ { ++ // write block type ++ stream.write(GeoStructure.TYPE_MULTILAYER_L2D); ++ ++ // for each cell ++ int index = 0; ++ for (int i = 0; i < GeoStructure.BLOCK_CELLS; i++) ++ { ++ // write layers count ++ final byte layers = _buffer[index++]; ++ stream.write(layers); ++ ++ // write cell data ++ stream.write(_buffer, index, layers * 3); ++ ++ // move index to next cell ++ index += layers * 3; ++ } ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java (working copy) +@@ -0,0 +1,150 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++ ++/** ++ * @author Hasha ++ */ ++public class BlockNull extends ABlock ++{ ++ private final byte _nswe; ++ ++ public BlockNull() ++ { ++ _nswe = (byte) 0xFF; ++ } ++ ++ @Override ++ public boolean hasGeoPos() ++ { ++ return false; ++ } ++ ++ @Override ++ public short getHeightNearest(int geoX, int geoY, int worldZ) ++ { ++ return (short) worldZ; ++ } ++ ++ @Override ++ public short getHeightNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return (short) worldZ; ++ } ++ ++ @Override ++ public short getHeightAbove(int geoX, int geoY, int worldZ) ++ { ++ return (short) worldZ; ++ } ++ ++ @Override ++ public short getHeightBelow(int geoX, int geoY, int worldZ) ++ { ++ return (short) worldZ; ++ } ++ ++ @Override ++ public byte getNsweNearest(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweAbove(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweBelow(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) ++ { ++ return 0; ++ } ++ ++ @Override ++ public int getIndexAboveOriginal(int geoX, int geoY, int worldZ) ++ { ++ return 0; ++ } ++ ++ @Override ++ public int getIndexBelow(int geoX, int geoY, int worldZ) ++ { ++ return 0; ++ } ++ ++ @Override ++ public int getIndexBelowOriginal(int geoX, int geoY, int worldZ) ++ { ++ return 0; ++ } ++ ++ @Override ++ public short getHeight(int index) ++ { ++ return 0; ++ } ++ ++ @Override ++ public short getHeightOriginal(int index) ++ { ++ return 0; ++ } ++ ++ @Override ++ public byte getNswe(int index) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweOriginal(int index) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public void setNswe(int index, byte nswe) ++ { ++ } ++ ++ @Override ++ public void saveBlock(BufferedOutputStream stream) ++ { ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java (nonexistent) +@@ -1,48 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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() +- { +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/ComplexBlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/ComplexBlock.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/ComplexBlock.java (nonexistent) +@@ -1,77 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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; +- +-/** +- * @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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/FlatBlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/FlatBlock.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/FlatBlock.java (nonexistent) +@@ -1,56 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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; +- +-/** +- * @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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/GeoFormat.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/GeoFormat.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/GeoFormat.java (working copy) +@@ -0,0 +1,39 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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 Hasha ++ */ ++public enum GeoFormat ++{ ++ L2J("%d_%d.l2j"), ++ L2OFF("%d_%d_conv.dat"), ++ L2D("%d_%d.l2d"); ++ ++ private final String _filename; ++ ++ private GeoFormat(String filename) ++ { ++ _filename = filename; ++ } ++ ++ public String getFilename() ++ { ++ return _filename; ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/GeoLocation.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/GeoLocation.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/GeoLocation.java (working copy) +@@ -0,0 +1,67 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.geoengine.GeoEngine; ++import org.l2jmobius.gameserver.model.Location; ++ ++/** ++ * @author Hasha ++ */ ++public class GeoLocation extends Location ++{ ++ private byte _nswe; ++ ++ public GeoLocation(int x, int y, int z) ++ { ++ super(x, y, GeoEngine.getInstance().getHeightNearest(x, y, z)); ++ _nswe = GeoEngine.getInstance().getNsweNearest(x, y, z); ++ } ++ ++ public void set(int x, int y, short z) ++ { ++ super.setXYZ(x, y, GeoEngine.getInstance().getHeightNearest(x, y, z)); ++ _nswe = GeoEngine.getInstance().getNsweNearest(x, y, z); ++ } ++ ++ public int getGeoX() ++ { ++ return _x; ++ } ++ ++ public int getGeoY() ++ { ++ return _y; ++ } ++ ++ @Override ++ public int getX() ++ { ++ return GeoEngine.getWorldX(_x); ++ } ++ ++ @Override ++ public int getY() ++ { ++ return GeoEngine.getWorldY(_y); ++ } ++ ++ public byte getNSWE() ++ { ++ return _nswe; ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java (working copy) +@@ -0,0 +1,70 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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; ++ ++/** ++ * @author Hasha ++ */ ++public class GeoStructure ++{ ++ // cells ++ public static final byte CELL_FLAG_E = 1 << 0; ++ public static final byte CELL_FLAG_W = 1 << 1; ++ public static final byte CELL_FLAG_S = 1 << 2; ++ public static final byte CELL_FLAG_N = 1 << 3; ++ public static final byte CELL_FLAG_SE = 1 << 4; ++ public static final byte CELL_FLAG_SW = 1 << 5; ++ public static final byte CELL_FLAG_NE = 1 << 6; ++ public static final byte CELL_FLAG_NW = (byte) (1 << 7); ++ ++ public static final int CELL_HEIGHT = 8; ++ public static final int CELL_IGNORE_HEIGHT = CELL_HEIGHT * 6; ++ ++ // blocks ++ public static final byte TYPE_FLAT_L2J_L2OFF = 0; ++ public static final byte TYPE_FLAT_L2D = (byte) 0xD0; ++ public static final byte TYPE_COMPLEX_L2J = 1; ++ public static final byte TYPE_COMPLEX_L2OFF = 0x40; ++ public static final byte TYPE_COMPLEX_L2D = (byte) 0xD1; ++ 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) ++ public static final byte TYPE_MULTILAYER_L2D = (byte) 0xD2; ++ ++ 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; ++ ++ // regions ++ 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; ++ ++ // global geodata ++ private static final int GEO_REGIONS_X = ((World.TILE_X_MAX - World.TILE_X_MIN) + 1); ++ private 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 +Index: java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java (nonexistent) +@@ -1,42 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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); +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/IGeoObject.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/IGeoObject.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/IGeoObject.java (working copy) +@@ -0,0 +1,53 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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 Hasha ++ */ ++public interface IGeoObject ++{ ++ /** ++ * Returns geodata X coordinate of the {@link IGeoObject}. ++ * @return int : Geodata X coordinate. ++ */ ++ int getGeoX(); ++ ++ /** ++ * Returns geodata Y coordinate of the {@link IGeoObject}. ++ * @return int : Geodata Y coordinate. ++ */ ++ int getGeoY(); ++ ++ /** ++ * Returns geodata Z coordinate of the {@link IGeoObject}. ++ * @return int : Geodata Z coordinate. ++ */ ++ int getGeoZ(); ++ ++ /** ++ * Returns height of the {@link IGeoObject}. ++ * @return int : Height. ++ */ ++ int getHeight(); ++ ++ /** ++ * Returns {@link IGeoObject} data. ++ * @return byte[][] : {@link IGeoObject} data. ++ */ ++ byte[][] getObjectGeoData(); ++} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java (nonexistent) +@@ -1,47 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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(); +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/MultilayerBlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/MultilayerBlock.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/MultilayerBlock.java (nonexistent) +@@ -1,183 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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; +- +-/** +- * @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); +- +- // 1 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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/NullRegion.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/NullRegion.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/NullRegion.java (nonexistent) +@@ -1,55 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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 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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/Region.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/Region.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/Region.java (nonexistent) +@@ -1,92 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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; +- +-/** +- * @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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java (nonexistent) +@@ -1,87 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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 T _loc; +- private AbstractNode _parent; +- +- public AbstractNode(T loc) +- { +- _loc = loc; +- } +- +- public void setParent(AbstractNode p) +- { +- _parent = p; +- } +- +- public AbstractNode getParent() +- { +- return _parent; +- } +- +- public T getLoc() +- { +- return _loc; +- } +- +- public void setLoc(T l) +- { +- _loc = l; +- } +- +- @Override +- public int hashCode() +- { +- final int prime = 31; +- int result = 1; +- result = (prime * result) + ((_loc == null) ? 0 : _loc.hashCode()); +- return result; +- } +- +- @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 +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java (nonexistent) +@@ -1,33 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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 int getNodeX(); +- +- public abstract int getNodeY(); +-} +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNode.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNode.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNode.java (nonexistent) +@@ -1,67 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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 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 +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNodeBuffer.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNodeBuffer.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNodeBuffer.java (nonexistent) +@@ -1,348 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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.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 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) +- { +- _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(); +- } +- +- 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 void getNeighbors() +- { +- if (!_current.getLoc().canGoAll()) +- { +- 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); +- } +- +- // SouthEast +- if ((nodeE != null) && (nodeS != null)) +- { +- if (nodeE.getLoc().canGoSouth() && nodeS.getLoc().canGoEast()) +- { +- addNode(x + 1, y + 1, z, true); +- } +- } +- +- // SouthWest +- if ((nodeS != null) && (nodeW != null)) +- { +- if (nodeW.getLoc().canGoSouth() && nodeS.getLoc().canGoWest()) +- { +- addNode(x - 1, y + 1, z, true); +- } +- } +- +- // NorthEast +- if ((nodeN != null) && (nodeE != null)) +- { +- if (nodeE.getLoc().canGoNorth() && nodeN.getLoc().canGoEast()) +- { +- addNode(x + 1, y - 1, z, true); +- } +- } +- +- // NorthWest +- if ((nodeN != null) && (nodeW != null)) +- { +- if (nodeW.getLoc().canGoNorth() && nodeN.getLoc().canGoWest()) +- { +- addNode(x - 1, y - 1, z, true); +- } +- } +- } +- +- private 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(); +- // reinit node if needed +- if (result.getLoc() != null) +- { +- result.getLoc().set(x, y, z); +- } +- else +- { +- result.setLoc(new NodeLoc(x, y, z)); +- } +- } +- +- return result; +- } +- +- private 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 boolean isHighWeight(int x, int y, int z) +- { +- final CellNode result = getNode(x, y, z); +- if (result == null) +- { +- return true; +- } +- +- if (!result.getLoc().canGoAll()) +- { +- return true; +- } +- if (Math.abs(result.getLoc().getZ() - z) > 16) +- { +- return true; +- } +- +- return false; +- } +- +- private 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 +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java (working copy) +@@ -0,0 +1,87 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.geodata.GeoLocation; ++ ++/** ++ * @author Hasha ++ */ ++public class Node ++{ ++ // node coords and nswe flag ++ private GeoLocation _loc; ++ ++ // node parent (for reverse path construction) ++ private Node _parent; ++ // node child (for moving over nodes during iteration) ++ private Node _child; ++ ++ // node G cost (movement cost = parent movement cost + current movement cost) ++ private double _cost = -1000; ++ ++ public void setLoc(int x, int y, int z) ++ { ++ _loc = new GeoLocation(x, y, z); ++ } ++ ++ public GeoLocation getLoc() ++ { ++ return _loc; ++ } ++ ++ public void setParent(Node parent) ++ { ++ _parent = parent; ++ } ++ ++ public Node getParent() ++ { ++ return _parent; ++ } ++ ++ public void setChild(Node child) ++ { ++ _child = child; ++ } ++ ++ public Node getChild() ++ { ++ return _child; ++ } ++ ++ public void setCost(double cost) ++ { ++ _cost = cost; ++ } ++ ++ public double getCost() ++ { ++ return _cost; ++ } ++ ++ public void free() ++ { ++ // reset node location ++ _loc = null; ++ ++ // reset node parent, child and cost ++ _parent = null; ++ _child = null; ++ _cost = -1000; ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java (working copy) +@@ -0,0 +1,306 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.concurrent.locks.ReentrantLock; ++ ++import org.l2jmobius.Config; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; ++ ++/** ++ * @author DS, Hasha; Credits to Diamond ++ */ ++public class NodeBuffer ++{ ++ private final ReentrantLock _lock = new ReentrantLock(); ++ private final int _size; ++ private final Node[][] _buffer; ++ ++ // center coordinates ++ private int _cx = 0; ++ private int _cy = 0; ++ ++ // target coordinates ++ private int _gtx = 0; ++ private int _gty = 0; ++ private short _gtz = 0; ++ ++ private Node _current = null; ++ ++ /** ++ * Constructor of NodeBuffer. ++ * @param size : one dimension size of buffer ++ */ ++ public NodeBuffer(int size) ++ { ++ // set size ++ _size = size; ++ ++ // initialize buffer ++ _buffer = new Node[size][size]; ++ for (int x = 0; x < size; x++) ++ { ++ for (int y = 0; y < size; y++) ++ { ++ _buffer[x][y] = 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 Node : first node of path ++ */ ++ public Node findPath(int gox, int goy, short goz, int gtx, int gty, short gtz) ++ { ++ // set coordinates (middle of the line (gox,goy) - (gtx,gty), will be in the center of the buffer) ++ _cx = gox + ((gtx - gox - _size) / 2); ++ _cy = goy + ((gty - goy - _size) / 2); ++ ++ _gtx = gtx; ++ _gty = gty; ++ _gtz = gtz; ++ ++ _current = getNode(gox, goy, goz); ++ _current.setCost(getCostH(gox, goy, goz)); ++ ++ int count = 0; ++ do ++ { ++ // reached target? ++ if ((_current.getLoc().getGeoX() == _gtx) && (_current.getLoc().getGeoY() == _gty) && (Math.abs(_current.getLoc().getZ() - _gtz) < 8)) ++ { ++ return _current; ++ } ++ ++ // expand current node ++ expand(); ++ ++ // move pointer ++ _current = _current.getChild(); ++ } ++ while ((_current != null) && (count++ < Config.MAX_ITERATIONS)); ++ ++ return null; ++ } ++ ++ public boolean isLocked() ++ { ++ return _lock.tryLock(); ++ } ++ ++ public void free() ++ { ++ _current = null; ++ ++ for (Node[] nodes : _buffer) ++ { ++ for (Node node : nodes) ++ { ++ if (node.getLoc() != null) ++ { ++ node.free(); ++ } ++ } ++ } ++ ++ _lock.unlock(); ++ } ++ ++ /** ++ * Check _current Node and add its neighbors to the buffer. ++ */ ++ private final void expand() ++ { ++ // can't move anywhere, don't expand ++ final byte nswe = _current.getLoc().getNSWE(); ++ if (nswe == 0) ++ { ++ return; ++ } ++ ++ // get geo coords of the node to be expanded ++ final int x = _current.getLoc().getGeoX(); ++ final int y = _current.getLoc().getGeoY(); ++ final short z = (short) _current.getLoc().getZ(); ++ ++ // can move north, expand ++ if ((nswe & GeoStructure.CELL_FLAG_N) != 0) ++ { ++ addNode(x, y - 1, z, Config.BASE_WEIGHT); ++ } ++ ++ // can move south, expand ++ if ((nswe & GeoStructure.CELL_FLAG_S) != 0) ++ { ++ addNode(x, y + 1, z, Config.BASE_WEIGHT); ++ } ++ ++ // can move west, expand ++ if ((nswe & GeoStructure.CELL_FLAG_W) != 0) ++ { ++ addNode(x - 1, y, z, Config.BASE_WEIGHT); ++ } ++ ++ // can move east, expand ++ if ((nswe & GeoStructure.CELL_FLAG_E) != 0) ++ { ++ addNode(x + 1, y, z, Config.BASE_WEIGHT); ++ } ++ ++ // can move north-west, expand ++ if ((nswe & GeoStructure.CELL_FLAG_NW) != 0) ++ { ++ addNode(x - 1, y - 1, z, Config.DIAGONAL_WEIGHT); ++ } ++ ++ // can move north-east, expand ++ if ((nswe & GeoStructure.CELL_FLAG_NE) != 0) ++ { ++ addNode(x + 1, y - 1, z, Config.DIAGONAL_WEIGHT); ++ } ++ ++ // can move south-west, expand ++ if ((nswe & GeoStructure.CELL_FLAG_SW) != 0) ++ { ++ addNode(x - 1, y + 1, z, Config.DIAGONAL_WEIGHT); ++ } ++ ++ // can move south-east, expand ++ if ((nswe & GeoStructure.CELL_FLAG_SE) != 0) ++ { ++ addNode(x + 1, y + 1, z, Config.DIAGONAL_WEIGHT); ++ } ++ } ++ ++ /** ++ * Returns node, if it exists in buffer. ++ * @param x : node X coord ++ * @param y : node Y coord ++ * @param z : node Z coord ++ * @return Node : node, if exits in buffer ++ */ ++ private final Node getNode(int x, int y, short z) ++ { ++ // check node X out of coordinates ++ final int ix = x - _cx; ++ if ((ix < 0) || (ix >= _size)) ++ { ++ return null; ++ } ++ ++ // check node Y out of coordinates ++ final int iy = y - _cy; ++ if ((iy < 0) || (iy >= _size)) ++ { ++ return null; ++ } ++ ++ // get node ++ final Node result = _buffer[ix][iy]; ++ ++ // check and update ++ if (result.getLoc() == null) ++ { ++ result.setLoc(x, y, z); ++ } ++ ++ // return node ++ return result; ++ } ++ ++ /** ++ * Add node given by coordinates to the buffer. ++ * @param x : geo X coord ++ * @param y : geo Y coord ++ * @param z : geo Z coord ++ * @param weight : weight of movement to new node ++ */ ++ private final void addNode(int x, int y, short z, int weight) ++ { ++ // get node to be expanded ++ final Node node = getNode(x, y, z); ++ if (node == null) ++ { ++ return; ++ } ++ ++ // Z distance between nearby cells is higher than cell size ++ if (node.getLoc().getZ() > (z + (2 * GeoStructure.CELL_HEIGHT))) ++ { ++ return; ++ } ++ ++ // node was already expanded, return ++ if (node.getCost() >= 0) ++ { ++ return; ++ } ++ ++ node.setParent(_current); ++ if (node.getLoc().getNSWE() != (byte) 0xFF) ++ { ++ node.setCost(getCostH(x, y, node.getLoc().getZ()) + (weight * Config.OBSTACLE_MULTIPLIER)); ++ } ++ else ++ { ++ node.setCost(getCostH(x, y, node.getLoc().getZ()) + weight); ++ } ++ ++ Node current = _current; ++ int count = 0; ++ while ((current.getChild() != null) && (count < (Config.MAX_ITERATIONS * 4))) ++ { ++ count++; ++ if (current.getChild().getCost() > node.getCost()) ++ { ++ node.setChild(current.getChild()); ++ break; ++ } ++ current = current.getChild(); ++ } ++ ++ if (count >= (Config.MAX_ITERATIONS * 4)) ++ { ++ System.err.println("Pathfinding: too long loop detected, cost:" + node.getCost()); ++ } ++ ++ current.setChild(node); ++ } ++ ++ /** ++ * @param x : node X coord ++ * @param y : node Y coord ++ * @param i : node Z coord ++ * @return double : node cost ++ */ ++ private final double getCostH(int x, int y, int i) ++ { ++ final int dX = x - _gtx; ++ final int dY = y - _gty; ++ final int dZ = (i - _gtz) / GeoStructure.CELL_HEIGHT; ++ ++ // return (Math.abs(dX) + Math.abs(dY) + Math.abs(dZ)) * Config.HEURISTIC_WEIGHT; // Manhattan distance ++ return Math.sqrt((dX * dX) + (dY * dY) + (dZ * dZ)) * Config.HEURISTIC_WEIGHT; // Direct distance ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeLoc.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeLoc.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeLoc.java (nonexistent) +@@ -1,183 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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.Cell; +- +-/** +- * @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 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 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; +- // return super.hashCode(); +- } +- +- @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; +- if (_x != other._x) +- { +- return false; +- } +- if (_y != other._y) +- { +- return false; +- } +- if (_goNorth != other._goNorth) +- { +- return false; +- } +- if (_goEast != other._goEast) +- { +- return false; +- } +- if (_goSouth != other._goSouth) +- { +- return false; +- } +- if (_goWest != other._goWest) +- { +- return false; +- } +- if (_geoHeight != other._geoHeight) +- { +- return false; +- } +- return true; +- } +-} +Index: java/org/l2jmobius/gameserver/model/actor/Creature.java +=================================================================== +--- java/org/l2jmobius/gameserver/model/actor/Creature.java (revision 8399) ++++ java/org/l2jmobius/gameserver/model/actor/Creature.java (working copy) +@@ -66,8 +66,6 @@ + import org.l2jmobius.gameserver.enums.TeleportWhereType; + import org.l2jmobius.gameserver.enums.UserInfoType; + import org.l2jmobius.gameserver.geoengine.GeoEngine; +-import org.l2jmobius.gameserver.geoengine.GeoEnginePathfinding; +-import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; + import org.l2jmobius.gameserver.instancemanager.IdManager; + import org.l2jmobius.gameserver.instancemanager.MapRegionManager; + import org.l2jmobius.gameserver.instancemanager.QuestManager; +@@ -2577,7 +2575,7 @@ + + public boolean disregardingGeodata; + public int onGeodataPathIndex; +- public List geoPath; ++ public List geoPath; + public int geoPathAccurateTx; + public int geoPathAccurateTy; + public int geoPathGtx; +@@ -3466,7 +3464,7 @@ + if (((originalDistance - distance) > 30) && !isControlBlocked() && !isInVehicle) + { + // Path calculation -- overrides previous movement check +- m.geoPath = GeoEnginePathfinding.getInstance().findPath(curX, curY, curZ, originalX, originalY, originalZ, getInstanceWorld()); ++ m.geoPath = GeoEngine.getInstance().findPath(curX, curY, curZ, originalX, originalY, originalZ, getInstanceWorld()); + if ((m.geoPath == null) || (m.geoPath.size() < 2)) // No path found + { + if (isPlayer() && !_isFlying && !isInWater) +Index: java/org/l2jmobius/gameserver/model/actor/instance/PlayerInstance.java +=================================================================== +--- java/org/l2jmobius/gameserver/model/actor/instance/PlayerInstance.java (revision 8397) ++++ java/org/l2jmobius/gameserver/model/actor/instance/PlayerInstance.java (working copy) +@@ -12704,7 +12704,7 @@ + { + if (Config.CORRECT_PLAYER_Z) + { +- final int nearestZ = GeoEngine.getInstance().getHigherHeight(getX(), getY(), getZ()); ++ final int nearestZ = GeoEngine.getInstance().getHeightNearest(getX(), getY(), getZ()); + if (getZ() < nearestZ) + { + teleToLocation(new Location(getX(), getY(), nearestZ)); +Index: java/org/l2jmobius/gameserver/util/GeoUtils.java +=================================================================== +--- java/org/l2jmobius/gameserver/util/GeoUtils.java (revision 8397) ++++ java/org/l2jmobius/gameserver/util/GeoUtils.java (working copy) +@@ -19,7 +19,7 @@ + import java.awt.Color; + + import org.l2jmobius.gameserver.geoengine.GeoEngine; +-import org.l2jmobius.gameserver.geoengine.geodata.Cell; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; + import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance; + import org.l2jmobius.gameserver.network.serverpackets.ExServerPrimitive; + +@@ -26,25 +26,25 @@ + /** + * @author HorridoJoho + */ +-public final class GeoUtils ++public class GeoUtils + { + public static void debug2DLine(PlayerInstance player, int x, int y, int tx, int ty, int z) + { +- final int gx = GeoEngine.getInstance().getGeoX(x); +- final int gy = GeoEngine.getInstance().getGeoY(y); ++ final int gx = GeoEngine.getGeoX(x); ++ final int gy = GeoEngine.getGeoY(y); + +- final int tgx = GeoEngine.getInstance().getGeoX(tx); +- final int tgy = GeoEngine.getInstance().getGeoY(ty); ++ final int tgx = GeoEngine.getGeoX(tx); ++ final int tgy = GeoEngine.getGeoY(ty); + + final ExServerPrimitive prim = new ExServerPrimitive("Debug2DLine", x, y, z); +- prim.addLine(Color.BLUE, GeoEngine.getInstance().getWorldX(gx), GeoEngine.getInstance().getWorldY(gy), z, GeoEngine.getInstance().getWorldX(tgx), GeoEngine.getInstance().getWorldY(tgy), z); ++ prim.addLine(Color.BLUE, GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), z, GeoEngine.getWorldX(tgx), GeoEngine.getWorldY(tgy), z); + + final LinePointIterator iter = new LinePointIterator(gx, gy, tgx, tgy); + + while (iter.next()) + { +- final int wx = GeoEngine.getInstance().getWorldX(iter.x()); +- final int wy = GeoEngine.getInstance().getWorldY(iter.y()); ++ final int wx = GeoEngine.getWorldX(iter.x()); ++ final int wy = GeoEngine.getWorldY(iter.y()); + + prim.addPoint(Color.RED, wx, wy, z); + } +@@ -53,21 +53,21 @@ + + public static void debug3DLine(PlayerInstance player, int x, int y, int z, int tx, int ty, int tz) + { +- final int gx = GeoEngine.getInstance().getGeoX(x); +- final int gy = GeoEngine.getInstance().getGeoY(y); ++ final int gx = GeoEngine.getGeoX(x); ++ final int gy = GeoEngine.getGeoY(y); + +- final int tgx = GeoEngine.getInstance().getGeoX(tx); +- final int tgy = GeoEngine.getInstance().getGeoY(ty); ++ final int tgx = GeoEngine.getGeoX(tx); ++ final int tgy = GeoEngine.getGeoY(ty); + + final ExServerPrimitive prim = new ExServerPrimitive("Debug3DLine", x, y, z); +- prim.addLine(Color.BLUE, GeoEngine.getInstance().getWorldX(gx), GeoEngine.getInstance().getWorldY(gy), z, GeoEngine.getInstance().getWorldX(tgx), GeoEngine.getInstance().getWorldY(tgy), tz); ++ prim.addLine(Color.BLUE, GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), z, GeoEngine.getWorldX(tgx), GeoEngine.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.getInstance().getWorldX(prevX); +- int wy = GeoEngine.getInstance().getWorldY(prevY); ++ int wx = GeoEngine.getWorldX(prevX); ++ int wy = GeoEngine.getWorldY(prevY); + int wz = iter.z(); + prim.addPoint(Color.RED, wx, wy, wz); + +@@ -78,8 +78,8 @@ + + if ((curX != prevX) || (curY != prevY)) + { +- wx = GeoEngine.getInstance().getWorldX(curX); +- wy = GeoEngine.getInstance().getWorldY(curY); ++ wx = GeoEngine.getWorldX(curX); ++ wy = GeoEngine.getWorldY(curY); + wz = iter.z(); + + prim.addPoint(Color.RED, wx, wy, wz); +@@ -93,7 +93,7 @@ + + private static Color getDirectionColor(int x, int y, int z, int nswe) + { +- if (GeoEngine.getInstance().checkNearestNswe(x, y, z, nswe)) ++ if ((GeoEngine.getInstance().getNsweNearest(x, y, z) & nswe) == nswe) + { + return Color.GREEN; + } +@@ -109,9 +109,8 @@ + int iPacket = 0; + + ExServerPrimitive exsp = null; +- final GeoEngine ge = GeoEngine.getInstance(); +- final int playerGx = ge.getGeoX(player.getX()); +- final int playerGy = ge.getGeoY(player.getY()); ++ final int playerGx = GeoEngine.getGeoX(player.getX()); ++ final int playerGy = GeoEngine.getGeoY(player.getY()); + for (int dx = -geoRadius; dx <= geoRadius; ++dx) + { + for (int dy = -geoRadius; dy <= geoRadius; ++dy) +@@ -135,12 +134,12 @@ + final int gx = playerGx + dx; + final int gy = playerGy + dy; + +- final int x = ge.getWorldX(gx); +- final int y = ge.getWorldY(gy); +- final int z = ge.getNearestZ(gx, gy, player.getZ()); ++ final int x = GeoEngine.getWorldX(gx); ++ final int y = GeoEngine.getWorldY(gy); ++ final int z = GeoEngine.getInstance().getHeightNearest(gx, gy, player.getZ()); + + // north arrow +- Color col = getDirectionColor(gx, gy, z, Cell.NSWE_NORTH); ++ Color col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_N); + 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); +@@ -147,7 +146,7 @@ + exsp.addLine(col, x - 4, y - 4, z, x + 4, y - 4, z); + + // east arrow +- col = getDirectionColor(gx, gy, z, Cell.NSWE_EAST); ++ col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_E); + 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); +@@ -154,13 +153,13 @@ + exsp.addLine(col, x + 4, y - 4, z, x + 4, y + 4, z); + + // south arrow +- col = getDirectionColor(gx, gy, z, Cell.NSWE_SOUTH); ++ col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_S); + 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, Cell.NSWE_WEST); ++ col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_W); + 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); +@@ -188,15 +187,15 @@ + { + if (y > lastY) + { +- return Cell.NSWE_SOUTH_EAST; ++ return GeoStructure.CELL_FLAG_SE; // Direction.SOUTH_EAST; + } + else if (y < lastY) + { +- return Cell.NSWE_NORTH_EAST; ++ return GeoStructure.CELL_FLAG_NE; // Direction.NORTH_EAST; + } + else + { +- return Cell.NSWE_EAST; ++ return GeoStructure.CELL_FLAG_E; // Direction.EAST; + } + } + else if (x < lastX) // west +@@ -203,26 +202,27 @@ + { + if (y > lastY) + { +- return Cell.NSWE_SOUTH_WEST; ++ return GeoStructure.CELL_FLAG_SW; // Direction.SOUTH_WEST; + } + else if (y < lastY) + { +- return Cell.NSWE_NORTH_WEST; ++ return GeoStructure.CELL_FLAG_NW; // Direction.NORTH_WEST; + } + else + { +- return Cell.NSWE_WEST; ++ return GeoStructure.CELL_FLAG_W; // Direction.WEST; + } + } +- else // unchanged x ++ else ++ // unchanged x + { + if (y > lastY) + { +- return Cell.NSWE_SOUTH; ++ return GeoStructure.CELL_FLAG_S; // Direction.SOUTH; + } + else if (y < lastY) + { +- return Cell.NSWE_NORTH; ++ return GeoStructure.CELL_FLAG_N; // Direction.NORTH; + } + else + { +Index: java/org/l2jmobius/tools/geodataconverter/GeoDataConverter.java +=================================================================== +--- java/org/l2jmobius/tools/geodataconverter/GeoDataConverter.java (nonexistent) ++++ java/org/l2jmobius/tools/geodataconverter/GeoDataConverter.java (working copy) +@@ -0,0 +1,386 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ */ ++package org.l2jmobius.tools.geodataconverter; ++ ++import java.io.BufferedOutputStream; ++import java.io.File; ++import java.io.FileOutputStream; ++import java.io.RandomAccessFile; ++import java.nio.ByteOrder; ++import java.nio.MappedByteBuffer; ++import java.nio.channels.FileChannel; ++import java.util.Scanner; ++ ++import org.l2jmobius.Config; ++import org.l2jmobius.commons.util.PropertiesParser; ++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.GeoFormat; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; ++import org.l2jmobius.gameserver.model.World; ++ ++/** ++ * @author Hasha ++ */ ++public class GeoDataConverter ++{ ++ private static GeoFormat _format; ++ private static ABlock[][] _blocks; ++ ++ public static void main(String[] args) ++ { ++ // initialize config ++ loadGeoengineConfigs(); ++ ++ // get geodata format ++ String type = ""; ++ try (Scanner scn = new Scanner(System.in)) ++ { ++ while (!(type.equalsIgnoreCase("J") || type.equalsIgnoreCase("O") || type.equalsIgnoreCase("E"))) ++ { ++ System.out.println("GeoDataConverter: Select source geodata type:"); ++ System.out.println(" J: L2J (e.g. 23_22.l2j)"); ++ System.out.println(" O: L2OFF (e.g. 23_22_conv.dat)"); ++ System.out.println(" E: Exit"); ++ System.out.print("Choice: "); ++ type = scn.next(); ++ } ++ } ++ if (type.equalsIgnoreCase("E")) ++ { ++ System.exit(0); ++ } ++ ++ _format = type.equalsIgnoreCase("J") ? GeoFormat.L2J : GeoFormat.L2OFF; ++ ++ // start conversion ++ System.out.println("GeoDataConverter: Converting all " + _format + " files."); ++ ++ // initialize geodata container ++ _blocks = new ABlock[GeoStructure.REGION_BLOCKS_X][GeoStructure.REGION_BLOCKS_Y]; ++ ++ // initialize multilayer temporarily buffer ++ BlockMultilayer.initialize(); ++ ++ // load geo files ++ int converted = 0; ++ for (int rx = World.TILE_X_MIN; rx <= World.TILE_X_MAX; rx++) ++ { ++ for (int ry = World.TILE_Y_MIN; ry <= World.TILE_Y_MAX; ry++) ++ { ++ final String input = String.format(_format.getFilename(), rx, ry); ++ final String filepath = Config.GEODATA_PATH; ++ final File f = new File(filepath + input); ++ if (f.exists() && !f.isDirectory()) ++ { ++ // load geodata ++ if (!loadGeoBlocks(input)) ++ { ++ System.out.println("GeoDataConverter: Unable to load " + input + " region file."); ++ continue; ++ } ++ ++ // recalculate nswe ++ if (!recalculateNswe()) ++ { ++ System.out.println("GeoDataConverter: Unable to convert " + input + " region file."); ++ continue; ++ } ++ ++ // save geodata ++ final String output = String.format(GeoFormat.L2D.getFilename(), rx, ry); ++ if (!saveGeoBlocks(output)) ++ { ++ System.out.println("GeoDataConverter: Unable to save " + output + " region file."); ++ continue; ++ } ++ ++ converted++; ++ System.out.println("GeoDataConverter: Created " + output + " region file."); ++ } ++ } ++ } ++ System.out.println("GeoDataConverter: Converted " + converted + " " + _format + " to L2D region file(s)."); ++ ++ // release multilayer block temporarily buffer ++ BlockMultilayer.release(); ++ } ++ ++ /** ++ * Loads geo blocks from buffer of the region file. ++ * @param filename : The name of the to load. ++ * @return boolean : True when successful. ++ */ ++ private static boolean loadGeoBlocks(String filename) ++ { ++ // region file is load-able, try to load it ++ try (RandomAccessFile raf = new RandomAccessFile(Config.GEODATA_PATH + filename, "r"); ++ FileChannel fc = raf.getChannel()) ++ { ++ final MappedByteBuffer buffer = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()).load(); ++ buffer.order(ByteOrder.LITTLE_ENDIAN); ++ ++ // load 18B header for L2off geodata (1st and 2nd byte...region X and Y) ++ if (_format == GeoFormat.L2OFF) ++ { ++ for (int i = 0; i < 18; i++) ++ { ++ buffer.get(); ++ } ++ } ++ ++ // 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 (_format == GeoFormat.L2J) ++ { ++ // get block type ++ final byte type = buffer.get(); ++ ++ // load block according to block type ++ switch (type) ++ { ++ case GeoStructure.TYPE_FLAT_L2J_L2OFF: ++ { ++ _blocks[ix][iy] = new BlockFlat(buffer, _format); ++ break; ++ } ++ case GeoStructure.TYPE_COMPLEX_L2J: ++ { ++ _blocks[ix][iy] = new BlockComplex(buffer, _format); ++ break; ++ } ++ case GeoStructure.TYPE_MULTILAYER_L2J: ++ { ++ _blocks[ix][iy] = new BlockMultilayer(buffer, _format); ++ break; ++ } ++ default: ++ { ++ throw new IllegalArgumentException("Unknown block type: " + type); ++ } ++ } ++ } ++ else ++ { ++ // get block type ++ final short type = buffer.getShort(); ++ ++ // load block according to block type ++ switch (type) ++ { ++ case GeoStructure.TYPE_FLAT_L2J_L2OFF: ++ { ++ _blocks[ix][iy] = new BlockFlat(buffer, _format); ++ break; ++ } ++ case GeoStructure.TYPE_COMPLEX_L2OFF: ++ { ++ _blocks[ix][iy] = new BlockComplex(buffer, _format); ++ break; ++ } ++ default: ++ { ++ _blocks[ix][iy] = new BlockMultilayer(buffer, _format); ++ break; ++ } ++ } ++ } ++ } ++ } ++ ++ if (buffer.remaining() > 0) ++ { ++ System.out.println("GeoDataConverter: Region file " + filename + " can be corrupted, remaining " + buffer.remaining() + " bytes to read."); ++ return false; ++ } ++ ++ return true; ++ } ++ catch (Exception e) ++ { ++ System.out.println("GeoDataConverter: Error while loading " + filename + " region file."); ++ return false; ++ } ++ } ++ ++ /** ++ * Recalculate diagonal flags for the region file. ++ * @return boolean : True when successful. ++ */ ++ private static boolean recalculateNswe() ++ { ++ try ++ { ++ for (int x = 0; x < GeoStructure.REGION_CELLS_X; x++) ++ { ++ for (int y = 0; y < GeoStructure.REGION_CELLS_Y; y++) ++ { ++ // get block ++ final ABlock block = _blocks[x / GeoStructure.BLOCK_CELLS_X][y / GeoStructure.BLOCK_CELLS_Y]; ++ ++ // skip flat blocks ++ if (block instanceof BlockFlat) ++ { ++ continue; ++ } ++ ++ // for complex and multilayer blocks go though all layers ++ short height = Short.MAX_VALUE; ++ int index; ++ while ((index = block.getIndexBelow(x, y, height)) != -1) ++ { ++ // get height and nswe ++ height = block.getHeight(index); ++ byte nswe = block.getNswe(index); ++ ++ // update nswe with diagonal flags ++ nswe = updateNsweBelow(x, y, height, nswe); ++ ++ // set nswe of the cell ++ block.setNswe(index, nswe); ++ } ++ } ++ } ++ ++ return true; ++ } ++ catch (Exception e) ++ { ++ return false; ++ } ++ } ++ ++ /** ++ * Updates the NSWE flag with diagonal flags. ++ * @param x : Geodata X coordinate. ++ * @param y : Geodata Y coordinate. ++ * @param z : Geodata Z coordinate. ++ * @param nsweValue : NSWE flag to be updated. ++ * @return byte : Updated NSWE flag. ++ */ ++ private static byte updateNsweBelow(int x, int y, short z, byte nsweValue) ++ { ++ byte nswe = nsweValue; ++ ++ // calculate virtual layer height ++ final short height = (short) (z + GeoStructure.CELL_IGNORE_HEIGHT); ++ ++ // get NSWE of neighbor cells below virtual layer (NPC/PC can fall down of clif, but can not climb it -> NSWE of cell below) ++ final byte nsweN = getNsweBelow(x, y - 1, height); ++ final byte nsweS = getNsweBelow(x, y + 1, height); ++ final byte nsweW = getNsweBelow(x - 1, y, height); ++ final byte nsweE = getNsweBelow(x + 1, y, height); ++ ++ // north-west ++ if ((((nswe & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) || (((nswe & GeoStructure.CELL_FLAG_W) != 0) && ((nsweW & GeoStructure.CELL_FLAG_N) != 0))) ++ { ++ nswe |= GeoStructure.CELL_FLAG_NW; ++ } ++ ++ // north-east ++ if ((((nswe & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) || (((nswe & GeoStructure.CELL_FLAG_E) != 0) && ((nsweE & GeoStructure.CELL_FLAG_N) != 0))) ++ { ++ nswe |= GeoStructure.CELL_FLAG_NE; ++ } ++ ++ // south-west ++ if ((((nswe & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) || (((nswe & GeoStructure.CELL_FLAG_W) != 0) && ((nsweW & GeoStructure.CELL_FLAG_S) != 0))) ++ { ++ nswe |= GeoStructure.CELL_FLAG_SW; ++ } ++ ++ // south-east ++ if ((((nswe & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) || (((nswe & GeoStructure.CELL_FLAG_E) != 0) && ((nsweE & GeoStructure.CELL_FLAG_S) != 0))) ++ { ++ nswe |= GeoStructure.CELL_FLAG_SE; ++ } ++ ++ return nswe; ++ } ++ ++ private static byte getNsweBelow(int geoX, int geoY, short worldZ) ++ { ++ // out of geo coordinates ++ if ((geoX < 0) || (geoX >= GeoStructure.REGION_CELLS_X)) ++ { ++ return 0; ++ } ++ ++ // out of geo coordinates ++ if ((geoY < 0) || (geoY >= GeoStructure.REGION_CELLS_Y)) ++ { ++ return 0; ++ } ++ ++ // get block ++ final ABlock block = _blocks[geoX / GeoStructure.BLOCK_CELLS_X][geoY / GeoStructure.BLOCK_CELLS_Y]; ++ ++ // get index, when valid, return nswe ++ final int index = block.getIndexBelow(geoX, geoY, worldZ); ++ return index == -1 ? 0 : block.getNswe(index); ++ } ++ ++ /** ++ * Save region file to file. ++ * @param filename : The name of file to save. ++ * @return boolean : True when successful. ++ */ ++ private static boolean saveGeoBlocks(String filename) ++ { ++ try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(Config.GEODATA_PATH + filename), GeoStructure.REGION_BLOCKS * GeoStructure.BLOCK_CELLS * 3)) ++ { ++ // loop over region blocks and save each block ++ for (int ix = 0; ix < GeoStructure.REGION_BLOCKS_X; ix++) ++ { ++ for (int iy = 0; iy < GeoStructure.REGION_BLOCKS_Y; iy++) ++ { ++ _blocks[ix][iy].saveBlock(bos); ++ } ++ } ++ ++ // flush data to file ++ bos.flush(); ++ ++ return true; ++ } ++ catch (Exception e) ++ { ++ return false; ++ } ++ } ++ ++ private static void loadGeoengineConfigs() ++ { ++ final PropertiesParser geoData = new PropertiesParser(Config.GEOENGINE_CONFIG_FILE); ++ Config.GEODATA_PATH = geoData.getString("GeoDataPath", "./data/geodata/"); ++ Config.COORD_SYNCHRONIZE = geoData.getInt("CoordSynchronize", -1); ++ Config.PART_OF_CHARACTER_HEIGHT = geoData.getInt("PartOfCharacterHeight", 75); ++ Config.MAX_OBSTACLE_HEIGHT = geoData.getInt("MaxObstacleHeight", 32); ++ Config.PATHFINDING = geoData.getBoolean("PathFinding", true); ++ Config.PATHFIND_BUFFERS = geoData.getString("PathFindBuffers", "100x6;128x6;192x6;256x4;320x4;384x4;500x2"); ++ Config.BASE_WEIGHT = geoData.getInt("BaseWeight", 10); ++ Config.DIAGONAL_WEIGHT = geoData.getInt("DiagonalWeight", 14); ++ Config.OBSTACLE_MULTIPLIER = geoData.getInt("ObstacleMultiplier", 10); ++ Config.HEURISTIC_WEIGHT = geoData.getInt("HeuristicWeight", 20); ++ Config.MAX_ITERATIONS = geoData.getInt("MaxIterations", 3500); ++ } ++} +\ No newline at end of file diff --git a/L2J_Mobius_Classic_2.1_Zaken/dist/game/data/geodata/L2D_GeoEngine.patch b/L2J_Mobius_Classic_2.1_Zaken/dist/game/data/geodata/L2D_GeoEngine.patch new file mode 100644 index 0000000000..63e2faf0c9 --- /dev/null +++ b/L2J_Mobius_Classic_2.1_Zaken/dist/game/data/geodata/L2D_GeoEngine.patch @@ -0,0 +1,6052 @@ +Index: dist/game/GeoDataConverter.bat +=================================================================== +--- dist/game/GeoDataConverter.bat (nonexistent) ++++ dist/game/GeoDataConverter.bat (working copy) +@@ -0,0 +1,6 @@ ++@echo off ++title L2D geodata converter ++ ++java -Xmx512m -cp ./../libs/* org.l2jmobius.tools.geodataconverter.GeoDataConverter ++ ++pause +Index: dist/game/GeoDataConverter.sh +=================================================================== +--- dist/game/GeoDataConverter.sh (nonexistent) ++++ dist/game/GeoDataConverter.sh (working copy) +@@ -0,0 +1,4 @@ ++#! /bin/sh ++ ++java -Xmx512m -cp ../libs/*: org.l2jmobius.tools.geodataconverter.GeoDataConverter > log/stdout.log 2>&1 ++ +Index: dist/game/config/GeoEngine.ini +=================================================================== +--- dist/game/config/GeoEngine.ini (revision 8397) ++++ dist/game/config/GeoEngine.ini (working copy) +@@ -1,6 +1,10 @@ + # ================================================================= + # Geodata + # ================================================================= ++# Because of real-time performance we are using geodata files only in ++# diagonal L2D format now (using filename e.g. 22_16.l2d). ++# L2D geodata can be obtained by conversion of existing L2J or L2OFF geodata. ++# Launch "GeoDataConverter.bat/sh" and follow instructions to start the conversion. + + # 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/ +@@ -13,6 +17,16 @@ + CoordSynchronize = 2 + + # ================================================================= ++# Path checking ++# ================================================================= ++ ++# 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 finding + # ================================================================= + +@@ -23,19 +37,23 @@ + # Pathfinding array buffers configuration, default: 100x6;128x6;192x6;256x4;320x4;384x4;500x2 + PathFindBuffers = 100x6;128x6;192x6;256x4;320x4;384x4;500x2 + +-# Weight for nodes without obstacles far from walls +-LowWeight = 0.5 ++# Base path weight, when moving from one node to another on axis direction, default: 10 ++BaseWeight = 10 + +-# Weight for nodes near walls +-MediumWeight = 2 ++# Path weight, when moving from one node to another on diagonal direction, default: BaseWeight * sqrt(2) = 14 ++DiagonalWeight = 14 + +-# Weight for nodes with obstacles +-HighWeight = 3 ++# When movement flags of target node is blocked to any direction, multiply movement weight by this multiplier. ++# This causes pathfinding algorithm to avoid path construction exactly near an obstacle, default: 10 ++ObstacleMultiplier = 10 + +-# Weight for diagonal movement. +-# Default: LowWeight * sqrt(2) +-DiagonalWeight = 0.707 ++# Weight of the heuristic algorithm, which is giving estimated cost from node to target, default: 20 ++# For proper function must be higher than BaseWeight and/or DiagonalWeight. ++HeuristicWeight = 20 + ++# Maximum number of generated nodes per one path-finding process, default 3500 ++MaxIterations = 3500 ++ + # ================================================================= + # Other + # ================================================================= +Index: dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java +=================================================================== +--- dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java (revision 8397) ++++ dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java (working copy) +@@ -55,9 +55,8 @@ + final int worldX = activeChar.getX(); + final int worldY = activeChar.getY(); + final int worldZ = activeChar.getZ(); +- final int geoX = GeoEngine.getInstance().getGeoX(worldX); +- final int geoY = GeoEngine.getInstance().getGeoY(worldY); +- ++ final int geoX = GeoEngine.getGeoX(worldX); ++ final int geoY = GeoEngine.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)); +@@ -73,9 +72,8 @@ + final int worldX = activeChar.getX(); + final int worldY = activeChar.getY(); + final int worldZ = activeChar.getZ(); +- final int geoX = GeoEngine.getInstance().getGeoX(worldX); +- final int geoY = GeoEngine.getInstance().getGeoY(worldY); +- ++ final int geoX = GeoEngine.getGeoX(worldX); ++ final int geoY = GeoEngine.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)); +Index: dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java +=================================================================== +--- dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java (revision 8397) ++++ dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java (working copy) +@@ -18,11 +18,11 @@ + + import java.util.List; + +-import org.l2jmobius.Config; +-import org.l2jmobius.gameserver.geoengine.GeoEnginePathfinding; +-import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; ++import org.l2jmobius.gameserver.geoengine.GeoEngine; + import org.l2jmobius.gameserver.handler.IAdminCommandHandler; ++import org.l2jmobius.gameserver.model.Location; + import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance; ++import org.l2jmobius.gameserver.network.SystemMessageId; + import org.l2jmobius.gameserver.util.BuilderUtil; + + public class AdminPathNode implements IAdminCommandHandler +@@ -37,29 +37,30 @@ + { + if (command.equals("admin_path_find")) + { +- if (!Config.PATHFINDING) +- { +- BuilderUtil.sendSysMessage(activeChar, "PathFinding is disabled."); +- return true; +- } + if (activeChar.getTarget() != null) + { +- final List path = GeoEnginePathfinding.getInstance().findPath(activeChar.getX(), activeChar.getY(), (short) activeChar.getZ(), activeChar.getTarget().getX(), activeChar.getTarget().getY(), (short) activeChar.getTarget().getZ(), activeChar.getInstanceWorld()); ++ 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()); + if (path == null) + { +- BuilderUtil.sendSysMessage(activeChar, "No Route!"); +- return true; ++ BuilderUtil.sendSysMessage(activeChar, "No route found or pathfinding disabled."); + } +- for (AbstractNodeLoc a : path) ++ else + { +- BuilderUtil.sendSysMessage(activeChar, "x:" + a.getX() + " y:" + a.getY() + " z:" + a.getZ()); ++ for (Location point : path) ++ { ++ BuilderUtil.sendSysMessage(activeChar, "x:" + point.getX() + " y:" + point.getY() + " z:" + point.getZ()); ++ } + } + } + else + { +- BuilderUtil.sendSysMessage(activeChar, "No Target!"); ++ activeChar.sendPacket(SystemMessageId.INVALID_TARGET); + } + } ++ else ++ { ++ return false; ++ } + return true; + } + +Index: java/org/l2jmobius/Config.java +=================================================================== +--- java/org/l2jmobius/Config.java (revision 8397) ++++ java/org/l2jmobius/Config.java (working copy) +@@ -32,7 +32,6 @@ + import java.net.UnknownHostException; + import java.nio.charset.StandardCharsets; + import java.nio.file.Files; +-import java.nio.file.Path; + import java.nio.file.Paths; + import java.time.Duration; + import java.util.ArrayList; +@@ -927,14 +926,17 @@ + // -------------------------------------------------- + // GeoEngine + // -------------------------------------------------- +- public static Path GEODATA_PATH; ++ public static String GEODATA_PATH; ++ public static int COORD_SYNCHRONIZE; ++ public static int PART_OF_CHARACTER_HEIGHT; ++ public static int MAX_OBSTACLE_HEIGHT; + public static boolean PATHFINDING; + public static String PATHFIND_BUFFERS; +- public static float LOW_WEIGHT; +- public static float MEDIUM_WEIGHT; +- public static float HIGH_WEIGHT; +- public static float DIAGONAL_WEIGHT; +- public static int COORD_SYNCHRONIZE; ++ public static int BASE_WEIGHT; ++ public static int DIAGONAL_WEIGHT; ++ public static int HEURISTIC_WEIGHT; ++ public static int OBSTACLE_MULTIPLIER; ++ public static int MAX_ITERATIONS; + public static boolean CORRECT_PLAYER_Z; + + /** Attribute System */ +@@ -2468,14 +2470,17 @@ + + // Load GeoEngine config file (if exists) + final PropertiesParser GeoEngine = new PropertiesParser(GEOENGINE_CONFIG_FILE); +- GEODATA_PATH = Paths.get(GeoEngine.getString("GeoDataPath", "./data/geodata")); ++ GEODATA_PATH = GeoEngine.getString("GeoDataPath", "./data/geodata/"); ++ COORD_SYNCHRONIZE = GeoEngine.getInt("CoordSynchronize", -1); ++ PART_OF_CHARACTER_HEIGHT = GeoEngine.getInt("PartOfCharacterHeight", 75); ++ MAX_OBSTACLE_HEIGHT = GeoEngine.getInt("MaxObstacleHeight", 32); + PATHFINDING = GeoEngine.getBoolean("PathFinding", true); + PATHFIND_BUFFERS = GeoEngine.getString("PathFindBuffers", "100x6;128x6;192x6;256x4;320x4;384x4;500x2"); +- LOW_WEIGHT = GeoEngine.getFloat("LowWeight", 0.5f); +- MEDIUM_WEIGHT = GeoEngine.getFloat("MediumWeight", 2); +- HIGH_WEIGHT = GeoEngine.getFloat("HighWeight", 3); +- DIAGONAL_WEIGHT = GeoEngine.getFloat("DiagonalWeight", 0.707f); +- COORD_SYNCHRONIZE = GeoEngine.getInt("CoordSynchronize", -1); ++ BASE_WEIGHT = GeoEngine.getInt("BaseWeight", 10); ++ DIAGONAL_WEIGHT = GeoEngine.getInt("DiagonalWeight", 14); ++ OBSTACLE_MULTIPLIER = GeoEngine.getInt("ObstacleMultiplier", 10); ++ HEURISTIC_WEIGHT = GeoEngine.getInt("HeuristicWeight", 20); ++ MAX_ITERATIONS = GeoEngine.getInt("MaxIterations", 3500); + CORRECT_PLAYER_Z = GeoEngine.getBoolean("CorrectPlayerZ", false); + + // Load AllowedPlayerRaces config file (if exists) +Index: java/org/l2jmobius/gameserver/geoengine/GeoEngine.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/GeoEngine.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/GeoEngine.java (working copy) +@@ -16,101 +16,91 @@ + */ + package org.l2jmobius.gameserver.geoengine; + +-import java.io.IOException; ++import java.io.File; + import java.io.RandomAccessFile; + import java.nio.ByteOrder; +-import java.nio.channels.FileChannel.MapMode; +-import java.nio.file.Files; +-import java.nio.file.Path; +-import java.util.concurrent.atomic.AtomicReferenceArray; +-import java.util.logging.Level; ++import java.nio.MappedByteBuffer; ++import java.nio.channels.FileChannel; ++import java.util.List; + 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.geoengine.geodata.Cell; +-import org.l2jmobius.gameserver.geoengine.geodata.IRegion; +-import org.l2jmobius.gameserver.geoengine.geodata.NullRegion; +-import org.l2jmobius.gameserver.geoengine.geodata.Region; ++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.GeoFormat; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoLocation; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; + import org.l2jmobius.gameserver.instancemanager.WarpedSpaceManager; + 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; ++import org.l2jmobius.gameserver.util.MathUtil; + + /** +- * @author -Nemesiss-, HorridoJoho ++ * @author Hasha + */ + public class GeoEngine + { +- private static final Logger LOGGER = Logger.getLogger(GeoEngine.class.getName()); ++ protected static final Logger LOGGER = Logger.getLogger(GeoEngine.class.getName()); + +- private static final int WORLD_MIN_X = -655360; +- private static final int WORLD_MIN_Y = -589824; +- private static final int WORLD_MIN_Z = -16384; ++ private final ABlock[][] _blocks; ++ private final BlockNull _nullBlock; + +- /** 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; +- +- /** The regions array */ +- private final AtomicReferenceArray _regions = new AtomicReferenceArray<>(GEO_REGIONS); +- +- 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; +- +- protected GeoEngine() ++ /** ++ * GeoEngine constructor. Loads all geodata files of chosen geodata format. ++ */ ++ public GeoEngine() + { + LOGGER.info("GeoEngine: Initializing..."); +- for (int i = 0; i < _regions.length(); i++) +- { +- _regions.set(i, NullRegion.INSTANCE); +- } + ++ // 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; +- try ++ long fileSize = 0; ++ for (int rx = World.TILE_X_MIN; rx <= World.TILE_X_MAX; rx++) + { +- for (int regionX = World.TILE_X_MIN; regionX <= World.TILE_X_MAX; regionX++) ++ for (int ry = World.TILE_Y_MIN; ry <= World.TILE_Y_MAX; ry++) + { +- for (int regionY = World.TILE_Y_MIN; regionY <= World.TILE_Y_MAX; regionY++) ++ final File f = new File(Config.GEODATA_PATH + String.format(GeoFormat.L2D.getFilename(), rx, ry)); ++ if (f.exists() && !f.isDirectory()) + { +- 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(rx, ry)) + { +- try (RandomAccessFile raf = new RandomAccessFile(geoFilePath.toFile(), "r")) +- { +- _regions.set((regionX * GEO_REGIONS_Y) + regionY, new Region(raf.getChannel().map(MapMode.READ_ONLY, 0, raf.length()).load().order(ByteOrder.LITTLE_ENDIAN))); +- loaded++; +- } ++ loaded++; ++ fileSize += f.length(); + } + } ++ else ++ { ++ // region file is not load-able, load null blocks ++ loadNullBlocks(rx, ry); ++ } + } + } +- catch (Exception e) ++ ++ LOGGER.info("GeoEngine: Loaded " + loaded + " geodata files."); ++ if (loaded > 0) + { +- LOGGER.log(Level.SEVERE, "GeoEngine: Failed to load geodata!", e); +- System.exit(1); ++ LOGGER.info("GeoEngine: Total geodata file size " + (fileSize / 1024 / 1024) + " MB."); + } + +- LOGGER.info("GeoEngine: Loaded " + loaded + " geodata files."); +- +- // Avoid wrong configs when no files are loaded. ++ // avoid wrong configs when no files are loaded + if (loaded == 0) + { + if (Config.PATHFINDING) +@@ -124,627 +114,832 @@ + LOGGER.info("GeoEngine: Forcing CoordSynchronize setting to -1."); + } + } ++ ++ // release multilayer block temporarily buffer ++ BlockMultilayer.release(); + } + + /** +- * @param geoX +- * @param geoY +- * @return the region ++ * 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 IRegion getRegion(int geoX, int geoY) ++ private final boolean loadGeoBlocks(int regionX, int regionY) + { +- final int region = ((geoX / IRegion.REGION_CELLS_X) * GEO_REGIONS_Y) + (geoY / IRegion.REGION_CELLS_Y); +- if ((region < 0) || (region >= _regions.length())) ++ final String filename = String.format(GeoFormat.L2D.getFilename(), regionX, regionY); ++ ++ // standard load ++ try (RandomAccessFile raf = new RandomAccessFile(Config.GEODATA_PATH + filename, "r"); ++ FileChannel fc = raf.getChannel()) + { +- return null; ++ // initialize file buffer ++ final MappedByteBuffer buffer = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()).load(); ++ buffer.order(ByteOrder.LITTLE_ENDIAN); ++ ++ // 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++) ++ { ++ // get block type ++ final byte type = buffer.get(); ++ ++ // load block according to block type ++ switch (type) ++ { ++ case GeoStructure.TYPE_FLAT_L2D: ++ { ++ _blocks[blockX + ix][blockY + iy] = new BlockFlat(buffer, GeoFormat.L2D); ++ break; ++ } ++ case GeoStructure.TYPE_COMPLEX_L2D: ++ { ++ _blocks[blockX + ix][blockY + iy] = new BlockComplex(buffer, GeoFormat.L2D); ++ break; ++ } ++ case GeoStructure.TYPE_MULTILAYER_L2D: ++ { ++ _blocks[blockX + ix][blockY + iy] = new BlockMultilayer(buffer, GeoFormat.L2D); ++ break; ++ } ++ default: ++ { ++ throw new IllegalArgumentException("Unknown block type: " + type); ++ } ++ } ++ } ++ } ++ ++ // check data consistency ++ if (buffer.remaining() > 0) ++ { ++ LOGGER.warning("GeoEngine: Region file " + filename + " can be corrupted, remaining " + buffer.remaining() + " bytes to read."); ++ } ++ ++ // loading was successful ++ return true; + } +- return _regions.get(region); +- } +- +- /** +- * @param filePath +- * @param regionX +- * @param regionY +- * @throws IOException +- */ +- 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")) ++ catch (Exception e) + { +- _regions.set(regionOffset, new Region(raf.getChannel().map(MapMode.READ_ONLY, 0, raf.length()).load().order(ByteOrder.LITTLE_ENDIAN))); ++ // an error occured while loading, load null blocks ++ LOGGER.warning("GeoEngine: Error while loading " + filename + " region file."); ++ LOGGER.warning(e.getMessage()); ++ ++ // replace whole region file with null blocks ++ loadNullBlocks(regionX, regionY); ++ ++ // loading was not successful ++ return false; + } + } + + /** +- * @param regionX +- * @param regionY ++ * 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. + */ +- public void unloadRegion(int regionX, int regionY) ++ private final void loadNullBlocks(int regionX, int regionY) + { +- _regions.set((regionX * GEO_REGIONS_Y) + regionY, NullRegion.INSTANCE); +- } +- +- /** +- * @param geoX +- * @param geoY +- * @return if geodata exist +- */ +- public boolean hasGeoPos(int geoX, int geoY) +- { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) ++ // 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++) + { +- return false; ++ for (int iy = 0; iy < GeoStructure.REGION_BLOCKS_Y; iy++) ++ { ++ _blocks[blockX + ix][blockY + iy] = _nullBlock; ++ } + } +- return region.hasGeo(); + } + ++ // GEODATA - GENERAL ++ + /** +- * Checks the specified position for available geodata. +- * @param x the world x +- * @param y the world y +- * @return {@code true} if there is geodata for the given coordinates, {@code false} otherwise ++ * Converts world X to geodata X. ++ * @param worldX ++ * @return int : Geo X + */ +- public boolean hasGeo(int x, int y) ++ public static int getGeoX(int worldX) + { +- return hasGeoPos(getGeoX(x), getGeoY(y)); ++ return (MathUtil.limit(worldX, World.MAP_MIN_X, World.MAP_MAX_X) - World.MAP_MIN_X) >> 4; + } + + /** +- * @param geoX +- * @param geoY +- * @param worldZ +- * @param nswe +- * @return the nearest nswe check ++ * Converts world Y to geodata Y. ++ * @param worldY ++ * @return int : Geo Y + */ +- public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) ++ public static int getGeoY(int worldY) + { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) +- { +- return true; +- } +- return region.checkNearestNswe(geoX, geoY, worldZ, nswe); ++ return (MathUtil.limit(worldY, World.MAP_MIN_Y, World.MAP_MAX_Y) - World.MAP_MIN_Y) >> 4; + } + + /** ++ * Converts geodata X to world X. + * @param geoX +- * @param geoY +- * @param worldZ +- * @param nswe +- * @return the nearest nswe anti-corner cut ++ * @return int : World X + */ +- public boolean checkNearestNsweAntiCornerCut(int geoX, int geoY, int worldZ, int nswe) ++ public static int getWorldX(int geoX) + { +- boolean can = true; +- if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) +- { +- can = checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_EAST) && checkNearestNswe(geoX + 1, geoY, worldZ, Cell.NSWE_NORTH); +- } +- if (can && ((nswe & Cell.NSWE_NORTH_WEST) == Cell.NSWE_NORTH_WEST)) +- { +- 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 = 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 = checkNearestNswe(geoX, geoY + 1, worldZ, Cell.NSWE_WEST) && checkNearestNswe(geoX - 1, geoY, worldZ, Cell.NSWE_SOUTH); +- } +- return can && checkNearestNswe(geoX, geoY, worldZ, nswe); ++ return (MathUtil.limit(geoX, 0, GeoStructure.GEO_CELLS_X) << 4) + World.MAP_MIN_X + 8; + } + + /** +- * @param geoX ++ * Converts geodata Y to world Y. + * @param geoY +- * @param worldZ +- * @return the nearest Z value ++ * @return int : World Y + */ +- public int getNearestZ(int geoX, int geoY, int worldZ) ++ public static int getWorldY(int geoY) + { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) +- { +- return worldZ; +- } +- return region.getNearestZ(geoX, geoY, worldZ); ++ return (MathUtil.limit(geoY, 0, GeoStructure.GEO_CELLS_Y) << 4) + World.MAP_MIN_Y + 8; + } + + /** +- * @param geoX +- * @param geoY +- * @param worldZ +- * @return the next lower Z value ++ * Returns block of geodata on given coordinates. ++ * @param geoX : Geodata X ++ * @param geoY : Geodata Y ++ * @return {@link ABlock} : Block of geodata. + */ +- public int getNextLowerZ(int geoX, int geoY, int worldZ) ++ private final ABlock getBlock(int geoX, int geoY) + { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) ++ final int x = geoX / GeoStructure.BLOCK_CELLS_X; ++ final int y = geoY / GeoStructure.BLOCK_CELLS_Y; ++ ++ // if x or y is out of array return null ++ if ((x > -1) && (y > -1) && (x < GeoStructure.GEO_BLOCKS_X) && (y < GeoStructure.GEO_BLOCKS_Y)) + { +- return worldZ; ++ return _blocks[x][y]; + } +- return region.getNextLowerZ(geoX, geoY, worldZ); ++ return null; + } + + /** +- * @param geoX +- * @param geoY +- * @param worldZ +- * @return the next higher Z value ++ * Check if geo coordinates has geo. ++ * @param geoX : Geodata X ++ * @param geoY : Geodata Y ++ * @return boolean : True, if given geo coordinates have geodata + */ +- public int getNextHigherZ(int geoX, int geoY, int worldZ) ++ public boolean hasGeoPos(int geoX, int geoY) + { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) ++ final ABlock block = getBlock(geoX, geoY); ++ if (block == null) // null block check + { +- return worldZ; ++ // TODO: Find when this can be null. (Bad geodata? Check World getRegion method.) ++ // LOGGER.warning("Could not find geodata block at " + getWorldX(geoX) + ", " + getWorldY(geoY) + "."); ++ return false; + } +- return region.getNextHigherZ(geoX, geoY, worldZ); ++ return block.hasGeoPos(); + } + + /** +- * Gets the Z height. +- * @param x the world x +- * @param y the world y +- * @param z the world z +- * @return the nearest Z height ++ * 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 int getHeight(int x, int y, int z) ++ public short getHeightNearest(int geoX, int geoY, int worldZ) + { +- return getNearestZ(getGeoX(x), getGeoY(y), z); ++ final ABlock block = getBlock(geoX, geoY); ++ return block != null ? block.getHeightNearest(geoX, geoY, worldZ) : (short) worldZ; + } + + /** +- * Gets the next lower Z height. +- * @param x the world x +- * @param y the world y +- * @param z the world z +- * @return the nearest Z height ++ * 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 int getLowerHeight(int x, int y, int z) ++ public byte getNsweNearest(int geoX, int geoY, int worldZ) + { +- return getNextLowerZ(getGeoX(x), getGeoY(y), z); ++ final ABlock block = getBlock(geoX, geoY); ++ return block != null ? block.getNsweNearest(geoX, geoY, worldZ) : (byte) 0xFF; + } + + /** +- * Gets the next higher Z height. +- * @param x the world x +- * @param y the world y +- * @param z the world z +- * @return the nearest Z height ++ * Check if world coordinates has geo. ++ * @param worldX : World X ++ * @param worldY : World Y ++ * @return boolean : True, if given world coordinates have geodata + */ +- public int getHigherHeight(int x, int y, int z) ++ public boolean hasGeo(int worldX, int worldY) + { +- return getNextHigherZ(getGeoX(x), getGeoY(y), z); ++ return hasGeoPos(getGeoX(worldX), getGeoY(worldY)); + } + + /** +- * @param worldX +- * @return the geo X ++ * 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 int getGeoX(int worldX) ++ public short getHeight(int worldX, int worldY, int worldZ) + { +- return (worldX - WORLD_MIN_X) / 16; ++ return getHeightNearest(getGeoX(worldX), getGeoY(worldY), worldZ); + } + +- /** +- * @param worldY +- * @return the geo Y +- */ +- public int getGeoY(int worldY) +- { +- return (worldY - WORLD_MIN_Y) / 16; +- } ++ // PATHFINDING + + /** +- * @param worldZ +- * @return the geo Z ++ * Check line of sight from {@link WorldObject} to {@link WorldObject}. ++ * @param origin : The origin object. ++ * @param target : The target object. ++ * @return {@code boolean} : True if origin can see target + */ +- public int getGeoZ(int worldZ) ++ public boolean canSeeTarget(WorldObject origin, WorldObject target) + { +- return (worldZ - WORLD_MIN_Z) / 16; +- } +- +- /** +- * @param geoX +- * @return the world X +- */ +- public int getWorldX(int geoX) +- { +- return (geoX * 16) + WORLD_MIN_X + 8; +- } +- +- /** +- * @param geoY +- * @return the world Y +- */ +- public int getWorldY(int geoY) +- { +- return (geoY * 16) + WORLD_MIN_Y + 8; +- } +- +- /** +- * @param geoZ +- * @return the world Z +- */ +- public int getWorldZ(int geoZ) +- { +- return (geoZ * 16) + WORLD_MIN_Z + 8; +- } +- +- /** +- * 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) +- { +- if (target.isDoor()) ++ if (target.isDoor() || target.isArtefact() || (target.isCreature() && ((Creature) target).isFlying())) + { +- // Can always see doors. + return true; + } +- return 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(), cha.getInstanceWorld(), worldPosition.getX(), worldPosition.getY(), worldPosition.getZ()); +- } +- +- /** +- * Can see target. Checks doors between. +- * @param x the x coordinate +- * @param y the y coordinate +- * @param z the z coordinate +- * @param world +- * @param tx the target's x coordinate +- * @param ty the target's y coordinate +- * @param tz the target's z coordinate +- * @param tworld the target's instanceId +- * @return +- */ +- public boolean canSeeTarget(int x, int y, int z, Instance world, int tx, int ty, int tz, Instance tworld) +- { +- return (world != tworld) ? false : canSeeTarget(x, y, z, world, tx, ty, tz); +- } +- +- /** +- * 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 +- * @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, Instance instance, int tx, int ty, int tz) +- { +- if (DoorData.getInstance().checkIfDoorsBetween(x, y, z, tx, ty, tz, instance, true)) ++ ++ // get origin and target world coordinates ++ final int ox = origin.getX(); ++ final int oy = origin.getY(); ++ final int oz = origin.getZ(); ++ final int tx = target.getX(); ++ final int ty = target.getY(); ++ final int tz = target.getZ(); ++ ++ if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld(), true)) + { + return false; + } +- return canSeeTarget(x, y, z, tx, ty, tz); +- } +- +- /** +- * @param prevX +- * @param prevY +- * @param prevGeoZ +- * @param curX +- * @param curY +- * @param nswe +- * @return the LOS Z value +- */ +- 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))) ++ if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld())) + { +- throw new RuntimeException("Multiple directions!"); ++ return false; + } ++ if (WarpedSpaceManager.getInstance().checkForWarpedSpace(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld())) ++ { ++ return false; ++ } + +- if (checkNearestNsweAntiCornerCut(prevX, prevY, prevGeoZ, nswe)) ++ // get origin and check existing geo coordinates ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) + { +- return getNearestZ(curX, curY, prevGeoZ); ++ return true; + } + +- return getNextHigherZ(curX, curY, prevGeoZ); ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coordinates ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) ++ { ++ return true; ++ } ++ ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // origin and target coordinates are same ++ if ((gox == gtx) && (goy == gty)) ++ { ++ return goz == gtz; ++ } ++ ++ // get origin and target height, real height = collision height * 2 ++ double oheight = 0; ++ if (origin.isCreature()) ++ { ++ oheight = ((Creature) origin).getCollisionHeight() * 2; ++ } ++ ++ double theight = 0; ++ if (target.isCreature()) ++ { ++ theight = ((Creature) target).getCollisionHeight() * 2; ++ } ++ ++ // perform geodata check ++ return checkSee(gox, goy, goz, oheight, gtx, gty, gtz, theight, origin.getInstanceWorld()); + } + + /** +- * Can see target. Does not check doors between. +- * @param xValue the x coordinate +- * @param yValue the y coordinate +- * @param zValue the z coordinate +- * @param txValue the target's x coordinate +- * @param tyValue the target's y coordinate +- * @param tzValue the target's z coordinate +- * @return {@code true} if there is line of sight between the given coordinate sets, {@code false} otherwise ++ * Check line of sight from {@link WorldObject} to {@link Location}. ++ * @param origin : The origin object. ++ * @param position : The target position. ++ * @return {@code boolean} : True if object can see position + */ +- public boolean canSeeTarget(int xValue, int yValue, int zValue, int txValue, int tyValue, int tzValue) ++ public boolean canSeeTarget(WorldObject origin, Location position) + { +- int x = xValue; +- int y = yValue; +- int tx = txValue; +- int ty = tyValue; ++ // get origin and target world coordinates ++ final int ox = origin.getX(); ++ final int oy = origin.getY(); ++ final int oz = origin.getZ(); ++ final int tx = position.getX(); ++ final int ty = position.getY(); ++ final int tz = position.getZ(); + +- int geoX = getGeoX(x); +- int geoY = getGeoY(y); +- int tGeoX = getGeoX(tx); +- int tGeoY = getGeoY(ty); ++ if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld(), true)) ++ { ++ return false; ++ } ++ if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld())) ++ { ++ return false; ++ } ++ if (WarpedSpaceManager.getInstance().checkForWarpedSpace(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld())) ++ { ++ return false; ++ } + +- int z = getNearestZ(geoX, geoY, zValue); +- int tz = getNearestZ(tGeoX, tGeoY, tzValue); ++ // get origin and check existing geo coordinates ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) ++ { ++ return true; ++ } + +- // Fastpath. +- if ((geoX == tGeoX) && (geoY == tGeoY)) ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coordinates ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) + { +- if (hasGeoPos(tGeoX, tGeoY)) +- { +- return z == tz; +- } + return true; + } + +- if (tz > z) ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // origin and target coordinates are same ++ if ((gox == gtx) && (goy == gty)) + { +- int tmp = tx; +- tx = x; +- x = tmp; +- +- tmp = ty; +- ty = y; +- y = tmp; +- +- tmp = tz; +- tz = z; +- z = tmp; +- +- tmp = tGeoX; +- tGeoX = geoX; +- geoX = tmp; +- +- tmp = tGeoY; +- tGeoY = geoY; +- geoY = tmp; ++ return goz == gtz; + } + +- final LinePointIterator3D pointIter = new LinePointIterator3D(geoX, geoY, z, tGeoX, tGeoY, tz); +- // 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()) ++ // get origin and target height, real height = collision height * 2 ++ double oheight = 0; ++ if (origin.isCreature()) + { +- final int curX = pointIter.x(); +- final int curY = pointIter.y(); ++ oheight = ((Creature) origin).getTemplate().getCollisionHeight(); ++ } ++ ++ // perform geodata check ++ return checkSee(gox, goy, goz, oheight, gtx, gty, gtz, 0, origin.getInstanceWorld()); ++ } ++ ++ /** ++ * Simple check for origin to target visibility. ++ * @param goxValue : origin X geodata coordinate ++ * @param goyValue : origin Y geodata coordinate ++ * @param gozValue : origin Z geodata coordinate ++ * @param oheight : origin height (if instance of {@link Character}) ++ * @param gtxValue : target X geodata coordinate ++ * @param gtyValue : target Y geodata coordinate ++ * @param gtzValue : target Z geodata coordinate ++ * @param theight : target height (if instance of {@link Character}) ++ * @param instance ++ * @return {@code boolean} : True, when target can be seen. ++ */ ++ private final boolean checkSee(int goxValue, int goyValue, int gozValue, double oheight, int gtxValue, int gtyValue, int gtzValue, double theight, Instance instance) ++ { ++ int goz = gozValue; ++ int gtz = gtzValue; ++ int gox = goxValue; ++ int goy = goyValue; ++ int gtx = gtxValue; ++ int gty = gtyValue; ++ ++ // get line of sight Z coordinates ++ double losoz = goz + ((oheight * Config.PART_OF_CHARACTER_HEIGHT) / 100); ++ double lostz = gtz + ((theight * Config.PART_OF_CHARACTER_HEIGHT) / 100); ++ ++ // get X delta and signum ++ final int dx = Math.abs(gtx - gox); ++ final int sx = gox < gtx ? 1 : -1; ++ final byte dirox = sx > 0 ? GeoStructure.CELL_FLAG_E : GeoStructure.CELL_FLAG_W; ++ final byte dirtx = sx > 0 ? GeoStructure.CELL_FLAG_W : GeoStructure.CELL_FLAG_E; ++ ++ // get Y delta and signum ++ final int dy = Math.abs(gty - goy); ++ final int sy = goy < gty ? 1 : -1; ++ final byte diroy = sy > 0 ? GeoStructure.CELL_FLAG_S : GeoStructure.CELL_FLAG_N; ++ final byte dirty = sy > 0 ? GeoStructure.CELL_FLAG_N : GeoStructure.CELL_FLAG_S; ++ ++ // get Z delta ++ final int dm = Math.max(dx, dy); ++ final double dz = (lostz - losoz) / dm; ++ ++ // get direction flag for diagonal movement ++ final byte diroxy = getDirXY(dirox, diroy); ++ final byte dirtxy = getDirXY(dirtx, dirty); ++ ++ // delta, determines axis to move on (+..X axis, -..Y axis) ++ int d = dx - dy; ++ ++ // NSWE direction of movement ++ byte diro; ++ byte dirt; ++ ++ // initialize node values ++ int nox = gox; ++ int noy = goy; ++ int ntx = gtx; ++ int nty = gty; ++ byte nsweo = getNsweNearest(gox, goy, goz); ++ byte nswet = getNsweNearest(gtx, gty, gtz); ++ ++ // loop ++ ABlock block; ++ int index; ++ for (int i = 0; i < ((dm + 1) / 2); i++) ++ { ++ // reset direction flag ++ diro = 0; ++ dirt = 0; + +- if ((curX == prevX) && (curY == prevY)) ++ // calculate next point coordinates ++ final int e2 = 2 * d; ++ if ((e2 > -dy) && (e2 < dx)) + { +- continue; ++ // calculate next point XY coordinates ++ d -= dy; ++ d += dx; ++ nox += sx; ++ ntx -= sx; ++ noy += sy; ++ nty -= sy; ++ diro |= diroxy; ++ dirt |= dirtxy; + } ++ else if (e2 > -dy) ++ { ++ // calculate next point X coordinate ++ d -= dy; ++ nox += sx; ++ ntx -= sx; ++ diro |= dirox; ++ dirt |= dirtx; ++ } ++ else if (e2 < dx) ++ { ++ // calculate next point Y coordinate ++ d += dx; ++ noy += sy; ++ nty -= sy; ++ diro |= diroy; ++ dirt |= dirty; ++ } + +- final int beeCurZ = pointIter.z(); +- int curGeoZ = prevGeoZ; +- +- // Check if the position has geodata. +- if (hasGeoPos(curX, curY)) + { +- final int beeCurGeoZ = getNearestZ(curX, curY, beeCurZ); +- final int nswe = GeoUtils.computeNswe(prevX, prevY, curX, curY); // .computeDirection(prevX, prevY, curX, curY); +- curGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, curX, curY, nswe); +- int maxHeight; +- if (ptIndex < ELEVATED_SEE_OVER_DISTANCE) ++ // get block of the next cell ++ block = getBlock(nox, noy); ++ ++ // get index of particular layer, based on movement conditions ++ if ((nsweo & diro) == 0) + { +- maxHeight = z + MAX_SEE_OVER_HEIGHT; ++ index = block.getIndexAbove(nox, noy, goz - GeoStructure.CELL_IGNORE_HEIGHT); + } + else + { +- maxHeight = beeCurZ + MAX_SEE_OVER_HEIGHT; ++ index = block.getIndexBelow(nox, noy, goz + GeoStructure.CELL_IGNORE_HEIGHT); + } + +- boolean canSeeThrough = false; +- if ((curGeoZ <= maxHeight) && (curGeoZ <= beeCurGeoZ)) ++ // layer does not exist, return ++ if (index == -1) + { +- if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) +- { +- 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 +- { +- canSeeThrough = true; +- } ++ return false; + } + +- if (!canSeeThrough) ++ // get layer and next line of sight Z coordinate ++ goz = block.getHeight(index); ++ losoz += dz; ++ ++ // perform line of sight check, return when fails ++ if ((goz - losoz) > Config.MAX_OBSTACLE_HEIGHT) + { + return false; + } ++ ++ // get layer nswe ++ nsweo = block.getNswe(index); + } ++ { ++ // get block of the next cell ++ block = getBlock(ntx, nty); ++ ++ // get index of particular layer, based on movement conditions ++ if ((nswet & dirt) == 0) ++ { ++ index = block.getIndexAbove(ntx, nty, gtz - GeoStructure.CELL_IGNORE_HEIGHT); ++ } ++ else ++ { ++ index = block.getIndexBelow(ntx, nty, gtz + GeoStructure.CELL_IGNORE_HEIGHT); ++ } ++ ++ // layer does not exist, return ++ if (index == -1) ++ { ++ return false; ++ } ++ ++ // get layer and next line of sight Z coordinate ++ gtz = block.getHeight(index); ++ lostz -= dz; ++ ++ // perform line of sight check, return when fails ++ if ((gtz - lostz) > Config.MAX_OBSTACLE_HEIGHT) ++ { ++ return false; ++ } ++ ++ // get layer nswe ++ nswet = block.getNswe(index); ++ } + +- prevX = curX; +- prevY = curY; +- prevGeoZ = curGeoZ; +- ++ptIndex; ++ // update coords ++ gox = nox; ++ goy = noy; ++ gtx = ntx; ++ gty = nty; + } + +- return true; ++ // when iteration is completed, compare final Z coordinates ++ return Math.abs(goz - gtz) < (GeoStructure.CELL_HEIGHT * 4); + } + + /** +- * Move check. +- * @param x the x coordinate +- * @param y the y coordinate +- * @param zValue the z coordinate +- * @param tx the target's x coordinate +- * @param ty the target's y coordinate +- * @param tzValue the target's z coordinate +- * @param instance the instance +- * @return the last Location (x,y,z) where player can walk - just before wall ++ * Check movement from coordinates to 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 {code boolean} : True if target coordinates are reachable from origin coordinates + */ +- public Location canMoveToTargetLoc(int x, int y, int zValue, int tx, int ty, int tzValue, Instance instance) ++ public boolean canMoveToTarget(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + { +- final int geoX = getGeoX(x); +- final int geoY = getGeoY(y); +- final int z = getNearestZ(geoX, geoY, zValue); +- final int tGeoX = getGeoX(tx); +- final int tGeoY = getGeoY(ty); +- final int tz = getNearestZ(tGeoX, tGeoY, tzValue); ++ // get origin and check existing geo coordinates ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) ++ { ++ return true; ++ } + +- if (DoorData.getInstance().checkIfDoorsBetween(x, y, z, tx, ty, tz, instance, false)) ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coordinates ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) + { +- return new Location(x, y, getHeight(x, y, z)); ++ return true; + } +- if (FenceData.getInstance().checkIfFenceBetween(x, y, z, tx, ty, tz, instance)) ++ ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // target coordinates reached ++ if ((gox == gtx) && (goy == gty) && (goz == gtz)) + { +- return new Location(x, y, getHeight(x, y, z)); ++ return true; + } +- if (WarpedSpaceManager.getInstance().checkForWarpedSpace(x, y, z, tx, ty, tz, instance)) ++ ++ // perform geodata check ++ final GeoLocation loc = checkMove(gox, goy, goz, gtx, gty, gtz, instance); ++ return (loc.getGeoX() == gtx) && (loc.getGeoY() == gty); ++ } ++ ++ /** ++ * Check movement from origin to target. Returns last available point in the checked path. ++ * @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 {@link Location} : Last point where object can walk (just before wall) ++ */ ++ public Location canMoveToTargetLoc(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) ++ { ++ // Mobius: Double check for doors before normal checkMove to avoid exploiting key movement. ++ if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, instance, false)) + { +- return new Location(x, y, getHeight(x, y, z)); ++ return new Location(ox, oy, oz); + } ++ if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, instance)) ++ { ++ return new Location(ox, oy, oz); ++ } + +- 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 = z; ++ // get origin and check existing geo coordinates ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) ++ { ++ return new Location(tx, ty, tz); ++ } + +- while (pointIter.next()) ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coordinates ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) + { +- final int curX = pointIter.x(); +- final int curY = pointIter.y(); +- final int curZ = getNearestZ(curX, curY, prevZ); +- +- if (hasGeoPos(prevX, prevY)) +- { +- final int nswe = GeoUtils.computeNswe(prevX, prevY, curX, curY); +- if (!checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, nswe)) +- { +- // Can't move, return previous location. +- return new Location(getWorldX(prevX), getWorldY(prevY), prevZ); +- } +- } +- +- prevX = curX; +- prevY = curY; +- prevZ = curZ; ++ return new Location(tx, ty, tz); + } + +- if (hasGeoPos(prevX, prevY) && (prevZ != tz)) ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // target coordinates reached ++ if ((gox == gtx) && (goy == gty) && (goz == gtz)) + { +- // Different floors, return start location. +- return new Location(x, y, z); ++ return new Location(tx, ty, tz); + } + +- return new Location(tx, ty, tz); ++ // perform geodata check ++ return checkMove(gox, goy, goz, gtx, gty, gtz, instance); + } + + /** +- * 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 fromZvalue 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 toZvalue 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 ++ * With this method you can check if a position is visible or can be reached by beeline movement.
++ * 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 gox : origin X geodata coordinate ++ * @param goy : origin Y geodata coordinate ++ * @param goz : origin Z geodata coordinate ++ * @param gtx : target X geodata coordinate ++ * @param gty : target Y geodata coordinate ++ * @param gtz : target Z geodata coordinate ++ * @param instance ++ * @return {@link GeoLocation} : The last allowed point of movement. + */ +- public boolean canMoveToTarget(int fromX, int fromY, int fromZvalue, int toX, int toY, int toZvalue, Instance instance) ++ protected final GeoLocation checkMove(int gox, int goy, int goz, int gtx, int gty, int gtz, Instance instance) + { +- final int geoX = getGeoX(fromX); +- final int geoY = getGeoY(fromY); +- final int fromZ = getNearestZ(geoX, geoY, fromZvalue); +- final int tGeoX = getGeoX(toX); +- final int tGeoY = getGeoY(toY); +- final int toZ = getNearestZ(tGeoX, tGeoY, toZvalue); +- +- if (DoorData.getInstance().checkIfDoorsBetween(fromX, fromY, fromZ, toX, toY, toZ, instance, false)) ++ if (DoorData.getInstance().checkIfDoorsBetween(gox, goy, goz, gtx, gty, gtz, instance, false)) + { +- return false; ++ return new GeoLocation(gox, goy, goz); + } +- if (FenceData.getInstance().checkIfFenceBetween(fromX, fromY, fromZ, toX, toY, toZ, instance)) ++ if (FenceData.getInstance().checkIfFenceBetween(gox, goy, goz, gtx, gty, gtz, instance)) + { +- return false; ++ return new GeoLocation(gox, goy, goz); + } +- if (WarpedSpaceManager.getInstance().checkForWarpedSpace(fromX, fromY, fromZ, toX, toY, toZ, instance)) ++ if (WarpedSpaceManager.getInstance().checkForWarpedSpace(gox, goy, goz, gtx, gty, gtz, instance)) + { +- return false; ++ return new GeoLocation(gox, goy, goz); + } + +- 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 = fromZ; ++ // get X delta, signum and direction flag ++ final int dx = Math.abs(gtx - gox); ++ final int sx = gox < gtx ? 1 : -1; ++ final byte dirX = sx > 0 ? GeoStructure.CELL_FLAG_E : GeoStructure.CELL_FLAG_W; + +- while (pointIter.next()) ++ // get Y delta, signum and direction flag ++ final int dy = Math.abs(gty - goy); ++ final int sy = goy < gty ? 1 : -1; ++ final byte dirY = sy > 0 ? GeoStructure.CELL_FLAG_S : GeoStructure.CELL_FLAG_N; ++ ++ // get direction flag for diagonal movement ++ final byte dirXY = getDirXY(dirX, dirY); ++ ++ // delta, determines axis to move on (+..X axis, -..Y axis) ++ int d = dx - dy; ++ ++ // NSWE direction of movement ++ byte direction; ++ ++ // load pointer coordinates ++ int gpx = gox; ++ int gpy = goy; ++ int gpz = goz; ++ ++ // load next pointer ++ int nx = gpx; ++ int ny = gpy; ++ ++ // loop ++ int count = 0; ++ while (count++ < Config.MAX_ITERATIONS) + { +- final int curX = pointIter.x(); +- final int curY = pointIter.y(); +- final int curZ = getNearestZ(curX, curY, prevZ); ++ direction = 0; + +- if (hasGeoPos(prevX, prevY)) ++ // calculate next point coordinates ++ final int e2 = 2 * d; ++ if ((e2 > -dy) && (e2 < dx)) + { +- final int nswe = GeoUtils.computeNswe(prevX, prevY, curX, curY); +- if (!checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, nswe)) ++ d -= dy; ++ d += dx; ++ nx += sx; ++ ny += sy; ++ direction |= dirXY; ++ } ++ else if (e2 > -dy) ++ { ++ d -= dy; ++ nx += sx; ++ direction |= dirX; ++ } ++ else if (e2 < dx) ++ { ++ d += dx; ++ ny += sy; ++ direction |= dirY; ++ } ++ ++ // obstacle found, return ++ if ((getNsweNearest(gpx, gpy, gpz) & direction) == 0) ++ { ++ return new GeoLocation(gpx, gpy, gpz); ++ } ++ ++ // update pointer coordinates ++ gpx = nx; ++ gpy = ny; ++ gpz = getHeightNearest(nx, ny, gpz); ++ ++ // target coordinates reached ++ if ((gpx == gtx) && (gpy == gty)) ++ { ++ if (gpz == gtz) + { +- return false; ++ // path found, Z coordinates are okay, return target point ++ return new GeoLocation(gtx, gty, gtz); + } ++ ++ // path found, Z coordinates are not okay, return last good point ++ return new GeoLocation(gpx, gpy, gpz); + } ++ } ++ ++ return new GeoLocation(gox, goy, goz); ++ } ++ ++ /** ++ * Returns diagonal NSWE flag format of combined two NSWE flags. ++ * @param dirX : X direction NSWE flag ++ * @param dirY : Y direction NSWE flag ++ * @return byte : NSWE flag of combined direction ++ */ ++ private static byte getDirXY(byte dirX, byte dirY) ++ { ++ // check axis directions ++ if (dirY == GeoStructure.CELL_FLAG_N) ++ { ++ if (dirX == GeoStructure.CELL_FLAG_W) ++ { ++ return GeoStructure.CELL_FLAG_NW; ++ } + +- prevX = curX; +- prevY = curY; +- prevZ = curZ; ++ return GeoStructure.CELL_FLAG_NE; + } + +- if (hasGeoPos(prevX, prevY) && (prevZ != toZ)) ++ if (dirX == GeoStructure.CELL_FLAG_W) + { +- // Different floors. +- return false; ++ return GeoStructure.CELL_FLAG_SW; + } + +- return true; ++ return GeoStructure.CELL_FLAG_SE; + } + ++ /** ++ * 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 ++ */ ++ public List findPath(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) ++ { ++ return null; ++ } ++ ++ /** ++ * Returns the instance of the {@link GeoEngine}. ++ * @return {@link GeoEngine} : The instance. ++ */ + public static GeoEngine getInstance() + { + return SingletonHolder.INSTANCE; +@@ -752,6 +947,6 @@ + + private static class SingletonHolder + { +- protected static final GeoEngine INSTANCE = new GeoEngine(); ++ protected static final GeoEngine INSTANCE = Config.PATHFINDING ? new GeoEnginePathfinding() : new GeoEngine(); + } +-} ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/GeoEnginePathfinding.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/GeoEnginePathfinding.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/GeoEnginePathfinding.java (working copy) +@@ -20,88 +20,84 @@ + import java.util.LinkedList; + import java.util.List; + import java.util.ListIterator; +-import java.util.logging.Level; +-import java.util.logging.Logger; + + import org.l2jmobius.Config; +-import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; +-import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +-import org.l2jmobius.gameserver.geoengine.pathfinding.CellNode; +-import org.l2jmobius.gameserver.geoengine.pathfinding.CellNodeBuffer; +-import org.l2jmobius.gameserver.geoengine.pathfinding.NodeLoc; +-import org.l2jmobius.gameserver.model.World; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoLocation; ++import org.l2jmobius.gameserver.geoengine.pathfinding.Node; ++import org.l2jmobius.gameserver.geoengine.pathfinding.NodeBuffer; ++import org.l2jmobius.gameserver.model.Location; + import org.l2jmobius.gameserver.model.instancezone.Instance; + + /** +- * @author -Nemesiss- ++ * @author Hasha + */ +-public class GeoEnginePathfinding ++final class GeoEnginePathfinding extends GeoEngine + { +- private static final Logger LOGGER = Logger.getLogger(GeoEnginePathfinding.class.getName()); ++ // pre-allocated buffers ++ private final BufferHolder[] _buffers; + +- private BufferInfo[] _buffers; +- + protected GeoEnginePathfinding() + { +- try ++ super(); ++ ++ final String[] array = Config.PATHFIND_BUFFERS.split(";"); ++ _buffers = new BufferHolder[array.length]; ++ int count = 0; ++ for (int i = 0; i < array.length; i++) + { +- final String[] array = Config.PATHFIND_BUFFERS.split(";"); ++ final String buf = array[i]; ++ final String[] args = buf.split("x"); + +- _buffers = new BufferInfo[array.length]; +- +- String buf; +- String[] args; +- for (int i = 0; i < array.length; i++) ++ try + { +- buf = array[i]; +- args = buf.split("x"); +- if (args.length != 2) +- { +- throw new Exception("Invalid buffer definition: " + buf); +- } +- +- _buffers[i] = new BufferInfo(Integer.parseInt(args[0]), Integer.parseInt(args[1])); ++ final int size = Integer.parseInt(args[1]); ++ count += size; ++ _buffers[i] = new BufferHolder(Integer.parseInt(args[0]), size); + } ++ catch (Exception e) ++ { ++ LOGGER.warning("GeoEnginePathfinding: Can not load buffer setting: " + buf); ++ } + } +- catch (Exception e) +- { +- LOGGER.log(Level.WARNING, "CellPathFinding: Problem during buffer init: " + e.getMessage(), e); +- throw new Error("CellPathFinding: load aborted"); +- } ++ ++ LOGGER.info("GeoEnginePathfinding: Loaded " + count + " node buffers."); + } + +- public boolean pathNodesExist(short regionoffset) ++ @Override ++ public List findPath(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + { +- return false; +- } +- +- public List findPath(int x, int y, int z, int tx, int ty, int tz, Instance instance) +- { +- final int gx = GeoEngine.getInstance().getGeoX(x); +- final int gy = GeoEngine.getInstance().getGeoY(y); +- if (!GeoEngine.getInstance().hasGeo(x, y)) ++ // get origin and check existing geo coords ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) + { + 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)) ++ ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coords ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) + { + 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)))); ++ ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // Prepare buffer for pathfinding calculations ++ final NodeBuffer buffer = getBuffer(64 + (2 * Math.max(Math.abs(gox - gtx), Math.abs(goy - gty)))); + if (buffer == null) + { + return null; + } + +- List path = null; ++ // find path ++ List path = null; + try + { +- final CellNode result = buffer.findPath(gx, gy, gz, gtx, gty, gtz); +- ++ final Node result = buffer.findPath(gox, goy, goz, gtx, gty, gtz); + if (result == null) + { + return null; +@@ -125,33 +121,45 @@ + return path; + } + +- int currentX, currentY, currentZ; +- ListIterator middlePoint; ++ // get path list iterator ++ final ListIterator point = path.listIterator(); + +- middlePoint = path.listIterator(); +- currentX = x; +- currentY = y; +- currentZ = z; ++ // get node A (origin) ++ int nodeAx = gox; ++ int nodeAy = goy; ++ short nodeAz = goz; + +- while (middlePoint.hasNext()) ++ // get node B ++ GeoLocation nodeB = (GeoLocation) point.next(); ++ ++ // iterate thought the path to optimize it ++ int count = 0; ++ while (point.hasNext() && (count++ < Config.MAX_ITERATIONS)) + { +- final AbstractNodeLoc locMiddle = middlePoint.next(); +- if (!middlePoint.hasNext()) +- { +- break; +- } ++ // get node C ++ final GeoLocation nodeC = (GeoLocation) path.get(point.nextIndex()); + +- final AbstractNodeLoc locEnd = path.get(middlePoint.nextIndex()); +- if (GeoEngine.getInstance().canMoveToTarget(currentX, currentY, currentZ, locEnd.getX(), locEnd.getY(), locEnd.getZ(), instance)) ++ // check movement from node A to node C ++ final GeoLocation loc = checkMove(nodeAx, nodeAy, nodeAz, nodeC.getGeoX(), nodeC.getGeoY(), nodeC.getZ(), instance); ++ if ((loc.getGeoX() == nodeC.getGeoX()) && (loc.getGeoY() == nodeC.getGeoY())) + { +- middlePoint.remove(); ++ // can move from node A to node C ++ ++ // remove node B ++ point.remove(); + } + else + { +- currentX = locMiddle.getX(); +- currentY = locMiddle.getY(); +- currentZ = locMiddle.getZ(); ++ // can not move from node A to node C ++ ++ // set node A (node B is part of path, update A coordinates) ++ nodeAx = nodeB.getGeoX(); ++ nodeAy = nodeB.getGeoY(); ++ nodeAz = (short) nodeB.getZ(); + } ++ ++ // set node B ++ nodeB = (GeoLocation) point.next(); + } + + return path; +@@ -158,144 +166,102 @@ + } + + /** +- * Convert geodata position to pathnode position +- * @param geo_pos +- * @return pathnode position ++ * Create list of node locations as result of calculated buffer node tree. ++ * @param node : the entry point ++ * @return List : list of node location + */ +- public short getNodePos(int geo_pos) ++ private static List constructPath(Node node) + { +- return (short) (geo_pos >> 3); // OK? +- } +- +- /** +- * Convert node position to pathnode block position +- * @param node_pos +- * @return pathnode block position (0...255) +- */ +- public short getNodeBlock(int node_pos) +- { +- return (short) (node_pos % 256); +- } +- +- public byte getRegionX(int node_pos) +- { +- return (byte) ((node_pos >> 8) + World.TILE_X_MIN); +- } +- +- public byte getRegionY(int node_pos) +- { +- return (byte) ((node_pos >> 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 node_x rx +- * @return +- */ +- public int calculateWorldX(short node_x) +- { +- return World.MAP_MIN_X + (node_x * 128) + 48; +- } +- +- /** +- * Convert pathnode y to World y position +- * @param node_y +- * @return +- */ +- public int calculateWorldY(short node_y) +- { +- return World.MAP_MIN_Y + (node_y * 128) + 48; +- } +- +- private List constructPath(AbstractNode nodeValue) +- { +- final LinkedList path = new LinkedList<>(); +- int previousDirectionX = Integer.MIN_VALUE; +- int previousDirectionY = Integer.MIN_VALUE; +- int directionX, directionY; ++ // create empty list ++ final LinkedList list = new LinkedList<>(); + +- AbstractNode node = nodeValue; +- while (node.getParent() != null) ++ // set direction X/Y ++ int dx = 0; ++ int dy = 0; ++ ++ // get target parent ++ Node target = node; ++ Node parent = target.getParent(); ++ ++ // while parent exists ++ int count = 0; ++ while ((parent != null) && (count++ < Config.MAX_ITERATIONS)) + { +- directionX = node.getLoc().getNodeX() - node.getParent().getLoc().getNodeX(); +- directionY = node.getLoc().getNodeY() - node.getParent().getLoc().getNodeY(); ++ // get parent <> target direction X/Y ++ final int nx = parent.getLoc().getGeoX() - target.getLoc().getGeoX(); ++ final int ny = parent.getLoc().getGeoY() - target.getLoc().getGeoY(); + +- // only add a new route point if moving direction changes +- if ((directionX != previousDirectionX) || (directionY != previousDirectionY)) ++ // direction has changed? ++ if ((dx != nx) || (dy != ny)) + { +- previousDirectionX = directionX; +- previousDirectionY = directionY; ++ // add node to the beginning of the list ++ list.addFirst(target.getLoc()); + +- path.addFirst(node.getLoc()); +- node.setLoc(null); ++ // update direction X/Y ++ dx = nx; ++ dy = ny; + } + +- node = node.getParent(); ++ // move to next node, set target and get its parent ++ target = parent; ++ parent = target.getParent(); + } + +- return path; ++ // return list ++ return list; + } + +- private CellNodeBuffer alloc(int size) ++ /** ++ * Provides optimize selection of the buffer. When all pre-initialized buffer are locked, creates new buffer. ++ * @param size : pre-calculated minimal required size ++ * @return NodeBuffer : buffer ++ */ ++ private final NodeBuffer getBuffer(int size) + { +- CellNodeBuffer current = null; +- for (BufferInfo i : _buffers) ++ NodeBuffer current = null; ++ for (BufferHolder holder : _buffers) + { +- if (i.mapSize >= size) ++ // Find proper size of buffer ++ if (holder._size < size) + { +- for (CellNodeBuffer buf : i.buffer) ++ continue; ++ } ++ ++ // Find unlocked NodeBuffer ++ for (NodeBuffer buffer : holder._buffer) ++ { ++ if (!buffer.isLocked()) + { +- if (buf.lock()) +- { +- current = buf; +- break; +- } ++ continue; + } +- if (current != null) +- { +- break; +- } + +- // not found, allocate temporary buffer +- current = new CellNodeBuffer(i.mapSize); +- current.lock(); +- if (i.buffer.size() < i.count) +- { +- i.buffer.add(current); +- break; +- } ++ return buffer; + } ++ ++ // NodeBuffer not found, allocate temporary buffer ++ current = new NodeBuffer(holder._size); ++ current.isLocked(); + } + + return current; + } + +- private static final class BufferInfo ++ /** ++ * NodeBuffer container with specified size and count of separate buffers. ++ */ ++ private static final class BufferHolder + { +- final int mapSize; +- final int count; +- ArrayList buffer; ++ final int _size; ++ List _buffer; + +- public BufferInfo(int size, int cnt) ++ public BufferHolder(int size, int count) + { +- mapSize = size; +- count = cnt; +- buffer = new ArrayList<>(count); ++ _size = size; ++ _buffer = new ArrayList<>(count); ++ for (int i = 0; i < count; i++) ++ { ++ _buffer.add(new NodeBuffer(size)); ++ } + } + } +- +- public static GeoEnginePathfinding getInstance() +- { +- return SingletonHolder.INSTANCE; +- } +- +- private static class SingletonHolder +- { +- protected static final GeoEnginePathfinding INSTANCE = new GeoEnginePathfinding(); +- } +-} ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java (working copy) +@@ -0,0 +1,197 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++import java.io.IOException; ++ ++/** ++ * @author Hasha ++ */ ++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 height of cell, which is closest to given coordinates.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @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 getHeightNearestOriginal(int geoX, int geoY, int worldZ); ++ ++ /** ++ * Returns the height of cell, which is first above 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, above given coordinates. ++ */ ++ public abstract short getHeightAbove(int geoX, int geoY, int worldZ); ++ ++ /** ++ * Returns the height of cell, which is first below 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, below given coordinates. ++ */ ++ public abstract short getHeightBelow(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 the NSWE flag byte of cell, which is closest to given coordinates.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @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 getNsweNearestOriginal(int geoX, int geoY, int worldZ); ++ ++ /** ++ * Returns the NSWE flag byte of cell, which is first above 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 getNsweAbove(int geoX, int geoY, int worldZ); ++ ++ /** ++ * Returns the NSWE flag byte of cell, which is first below 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 getNsweBelow(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 above given coordinates.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @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 getIndexAboveOriginal(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 index to data of the cell, which is first layer below given coordinates.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @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 getIndexBelowOriginal(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 height of cell given by cell index.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @param index : Index of the cell. ++ * @return short : Cell geodata Z coordinate, below given coordinates. ++ */ ++ public abstract short getHeightOriginal(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); ++ ++ /** ++ * Returns the NSWE flag byte of cell given by cell index.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @param index : Index of the cell. ++ * @return short : Cell geodata Z coordinate, below given coordinates. ++ */ ++ public abstract byte getNsweOriginal(int index); ++ ++ /** ++ * Sets the NSWE flag byte of cell given by cell index. ++ * @param index : Index of the cell. ++ * @param nswe : New NSWE flag byte. ++ */ ++ public abstract void setNswe(int index, byte nswe); ++ ++ /** ++ * Saves the block in L2D format to {@link BufferedOutputStream}. Used only for L2D geodata conversion. ++ * @param stream : The stream. ++ * @throws IOException : Can't save the block to steam. ++ */ ++ public abstract void saveBlock(BufferedOutputStream stream) throws IOException; ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java (working copy) +@@ -0,0 +1,252 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++import java.io.IOException; ++import java.nio.ByteBuffer; ++ ++/** ++ * @author Hasha ++ */ ++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. ++ * @param format : GeoFormat specifying format of loaded data. ++ */ ++ public BlockComplex(ByteBuffer bb, GeoFormat format) ++ { ++ // initialize buffer ++ _buffer = new byte[GeoStructure.BLOCK_CELLS * 3]; ++ ++ // load data ++ for (int i = 0; i < GeoStructure.BLOCK_CELLS; i++) ++ { ++ if (format != GeoFormat.L2D) ++ { ++ // 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); ++ } ++ else ++ { ++ // get nswe ++ final byte nswe = bb.get(); ++ _buffer[i * 3] = nswe; ++ ++ // get height ++ final short height = bb.getShort(); ++ _buffer[(i * 3) + 1] = (byte) (height & 0x00FF); ++ _buffer[(i * 3) + 2] = (byte) (height >> 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 short getHeightNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getHeightNearest(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeightAbove(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 short height = (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ ++ // check and return height ++ return height > worldZ ? height : Short.MIN_VALUE; ++ } ++ ++ @Override ++ public short getHeightBelow(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 short height = (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ ++ // check and return height ++ return height < worldZ ? height : Short.MAX_VALUE; ++ } ++ ++ @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 byte getNsweNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getNsweNearest(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public byte getNsweAbove(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 ? _buffer[index] : 0; ++ } ++ ++ @Override ++ public byte getNsweBelow(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 ? _buffer[index] : 0; ++ } ++ ++ @Override ++ public 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 getIndexAboveOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexAbove(geoX, geoY, worldZ); ++ } ++ ++ @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 int getIndexBelowOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexBelow(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeight(int index) ++ { ++ return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ } ++ ++ @Override ++ public short getHeightOriginal(int index) ++ { ++ return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ } ++ ++ @Override ++ public byte getNswe(int index) ++ { ++ return _buffer[index]; ++ } ++ ++ @Override ++ public byte getNsweOriginal(int index) ++ { ++ return _buffer[index]; ++ } ++ ++ @Override ++ public void setNswe(int index, byte nswe) ++ { ++ _buffer[index] = nswe; ++ } ++ ++ @Override ++ public void saveBlock(BufferedOutputStream stream) throws IOException ++ { ++ // write block type ++ stream.write(GeoStructure.TYPE_COMPLEX_L2D); ++ ++ // write block data ++ stream.write(_buffer, 0, GeoStructure.BLOCK_CELLS * 3); ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java (working copy) +@@ -0,0 +1,176 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++import java.io.IOException; ++import java.nio.ByteBuffer; ++ ++/** ++ * @author Hasha ++ */ ++public class BlockFlat extends ABlock ++{ ++ protected final short _height; ++ protected byte _nswe; ++ ++ /** ++ * Creates FlatBlock. ++ * @param bb : Input byte buffer. ++ * @param format : GeoFormat specifying format of loaded data. ++ */ ++ public BlockFlat(ByteBuffer bb, GeoFormat format) ++ { ++ _height = bb.getShort(); ++ _nswe = format != GeoFormat.L2D ? 0x0F : (byte) (0xFF); ++ if (format == GeoFormat.L2OFF) ++ { ++ bb.getShort(); ++ } ++ } ++ ++ @Override ++ public boolean hasGeoPos() ++ { ++ return true; ++ } ++ ++ @Override ++ public short getHeightNearest(int geoX, int geoY, int worldZ) ++ { ++ return _height; ++ } ++ ++ @Override ++ public short getHeightNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return _height; ++ } ++ ++ @Override ++ public short getHeightAbove(int geoX, int geoY, int worldZ) ++ { ++ // check and return height ++ return _height > worldZ ? _height : Short.MIN_VALUE; ++ } ++ ++ @Override ++ public short getHeightBelow(int geoX, int geoY, int worldZ) ++ { ++ // check and return height ++ return _height < worldZ ? _height : Short.MAX_VALUE; ++ } ++ ++ @Override ++ public byte getNsweNearest(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweAbove(int geoX, int geoY, int worldZ) ++ { ++ // check height and return nswe ++ return _height > worldZ ? _nswe : 0; ++ } ++ ++ @Override ++ public byte getNsweBelow(int geoX, int geoY, int worldZ) ++ { ++ // check height and return nswe ++ return _height < worldZ ? _nswe : 0; ++ } ++ ++ @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 getIndexAboveOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexAbove(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public int getIndexBelow(int geoX, int geoY, int worldZ) ++ { ++ // check height and return index ++ return _height < worldZ ? 0 : -1; ++ } ++ ++ @Override ++ public int getIndexBelowOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexBelow(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeight(int index) ++ { ++ return _height; ++ } ++ ++ @Override ++ public short getHeightOriginal(int index) ++ { ++ return _height; ++ } ++ ++ @Override ++ public byte getNswe(int index) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweOriginal(int index) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public void setNswe(int index, byte nswe) ++ { ++ _nswe = nswe; ++ } ++ ++ @Override ++ public void saveBlock(BufferedOutputStream stream) throws IOException ++ { ++ // write block type ++ stream.write(GeoStructure.TYPE_FLAT_L2D); ++ ++ // write height ++ stream.write((byte) (_height & 0x00FF)); ++ stream.write((byte) (_height >> 8)); ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java (working copy) +@@ -0,0 +1,465 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++import java.io.IOException; ++import java.nio.ByteBuffer; ++import java.nio.ByteOrder; ++import java.util.Arrays; ++ ++/** ++ * @author Hasha ++ */ ++public class BlockMultilayer extends ABlock ++{ ++ private static final int MAX_LAYERS = Byte.MAX_VALUE; ++ ++ private static ByteBuffer _temp; ++ ++ /** ++ * Initializes the temporarily buffer. ++ */ ++ public static void initialize() ++ { ++ // initialize temporarily buffer and sorting mechanism ++ _temp = ByteBuffer.allocate(GeoStructure.BLOCK_CELLS * MAX_LAYERS * 3); ++ _temp.order(ByteOrder.LITTLE_ENDIAN); ++ } ++ ++ /** ++ * Releases temporarily buffer. ++ */ ++ public static void release() ++ { ++ _temp = null; ++ } ++ ++ protected byte[] _buffer; ++ ++ /** ++ * Implicit constructor for children class. ++ */ ++ protected BlockMultilayer() ++ { ++ _buffer = null; ++ } ++ ++ /** ++ * Creates MultilayerBlock. ++ * @param bb : Input byte buffer. ++ * @param format : GeoFormat specifying format of loaded data. ++ */ ++ public BlockMultilayer(ByteBuffer bb, GeoFormat format) ++ { ++ // 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 = format != GeoFormat.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++) ++ { ++ if (format != GeoFormat.L2D) ++ { ++ // get data ++ final short data = bb.getShort(); ++ ++ // add nswe and height ++ _temp.put((byte) (data & 0x000F)); ++ _temp.putShort((short) ((short) (data & 0xFFF0) >> 1)); ++ } ++ else ++ { ++ // add nswe ++ _temp.put(bb.get()); ++ ++ // add height ++ _temp.putShort(bb.getShort()); ++ } ++ } ++ } ++ ++ // 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 short getHeightNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getHeightNearest(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeightAbove(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 height ++ if (height > worldZ) ++ { ++ return (short) height; ++ } ++ ++ // move index to next layer ++ index -= 3; ++ } ++ ++ // none layer found, return minimum value ++ return Short.MIN_VALUE; ++ } ++ ++ @Override ++ public short getHeightBelow(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 height ++ if (height < worldZ) ++ { ++ return (short) height; ++ } ++ ++ // move index to next layer ++ index += 3; ++ } ++ ++ // none layer found, return maximum value ++ return Short.MAX_VALUE; ++ } ++ ++ @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 byte getNsweNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getNsweNearest(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public byte getNsweAbove(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 nswe ++ if (height > worldZ) ++ { ++ return _buffer[index]; ++ } ++ ++ // move index to next layer ++ index -= 3; ++ } ++ ++ // none layer found, block movement ++ return 0; ++ } ++ ++ @Override ++ public byte getNsweBelow(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 nswe ++ if (height < worldZ) ++ { ++ return _buffer[index]; ++ } ++ ++ // move index to next layer ++ index += 3; ++ } ++ ++ // none layer found, block movement ++ return 0; ++ } ++ ++ @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 first layer data (first from bottom) ++ byte layers = _buffer[index++]; ++ ++ // loop though all cell layers, find closest layer ++ 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; ++ } ++ ++ // none layer found ++ return -1; ++ } ++ ++ @Override ++ public int getIndexAboveOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexAbove(geoX, geoY, worldZ); ++ } ++ ++ @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; ++ } ++ ++ // none layer found ++ return -1; ++ } ++ ++ @Override ++ public int getIndexBelowOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexBelow(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeight(int index) ++ { ++ // get height ++ return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ } ++ ++ @Override ++ public short getHeightOriginal(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]; ++ } ++ ++ @Override ++ public byte getNsweOriginal(int index) ++ { ++ // get nswe ++ return _buffer[index]; ++ } ++ ++ @Override ++ public void setNswe(int index, byte nswe) ++ { ++ // set nswe ++ _buffer[index] = nswe; ++ } ++ ++ @Override ++ public void saveBlock(BufferedOutputStream stream) throws IOException ++ { ++ // write block type ++ stream.write(GeoStructure.TYPE_MULTILAYER_L2D); ++ ++ // for each cell ++ int index = 0; ++ for (int i = 0; i < GeoStructure.BLOCK_CELLS; i++) ++ { ++ // write layers count ++ final byte layers = _buffer[index++]; ++ stream.write(layers); ++ ++ // write cell data ++ stream.write(_buffer, index, layers * 3); ++ ++ // move index to next cell ++ index += layers * 3; ++ } ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java (working copy) +@@ -0,0 +1,150 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++ ++/** ++ * @author Hasha ++ */ ++public class BlockNull extends ABlock ++{ ++ private final byte _nswe; ++ ++ public BlockNull() ++ { ++ _nswe = (byte) 0xFF; ++ } ++ ++ @Override ++ public boolean hasGeoPos() ++ { ++ return false; ++ } ++ ++ @Override ++ public short getHeightNearest(int geoX, int geoY, int worldZ) ++ { ++ return (short) worldZ; ++ } ++ ++ @Override ++ public short getHeightNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return (short) worldZ; ++ } ++ ++ @Override ++ public short getHeightAbove(int geoX, int geoY, int worldZ) ++ { ++ return (short) worldZ; ++ } ++ ++ @Override ++ public short getHeightBelow(int geoX, int geoY, int worldZ) ++ { ++ return (short) worldZ; ++ } ++ ++ @Override ++ public byte getNsweNearest(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweAbove(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweBelow(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) ++ { ++ return 0; ++ } ++ ++ @Override ++ public int getIndexAboveOriginal(int geoX, int geoY, int worldZ) ++ { ++ return 0; ++ } ++ ++ @Override ++ public int getIndexBelow(int geoX, int geoY, int worldZ) ++ { ++ return 0; ++ } ++ ++ @Override ++ public int getIndexBelowOriginal(int geoX, int geoY, int worldZ) ++ { ++ return 0; ++ } ++ ++ @Override ++ public short getHeight(int index) ++ { ++ return 0; ++ } ++ ++ @Override ++ public short getHeightOriginal(int index) ++ { ++ return 0; ++ } ++ ++ @Override ++ public byte getNswe(int index) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweOriginal(int index) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public void setNswe(int index, byte nswe) ++ { ++ } ++ ++ @Override ++ public void saveBlock(BufferedOutputStream stream) ++ { ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java (nonexistent) +@@ -1,48 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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() +- { +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/ComplexBlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/ComplexBlock.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/ComplexBlock.java (nonexistent) +@@ -1,77 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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; +- +-/** +- * @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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/FlatBlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/FlatBlock.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/FlatBlock.java (nonexistent) +@@ -1,56 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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; +- +-/** +- * @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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/GeoFormat.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/GeoFormat.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/GeoFormat.java (working copy) +@@ -0,0 +1,39 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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 Hasha ++ */ ++public enum GeoFormat ++{ ++ L2J("%d_%d.l2j"), ++ L2OFF("%d_%d_conv.dat"), ++ L2D("%d_%d.l2d"); ++ ++ private final String _filename; ++ ++ private GeoFormat(String filename) ++ { ++ _filename = filename; ++ } ++ ++ public String getFilename() ++ { ++ return _filename; ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/GeoLocation.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/GeoLocation.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/GeoLocation.java (working copy) +@@ -0,0 +1,67 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.geoengine.GeoEngine; ++import org.l2jmobius.gameserver.model.Location; ++ ++/** ++ * @author Hasha ++ */ ++public class GeoLocation extends Location ++{ ++ private byte _nswe; ++ ++ public GeoLocation(int x, int y, int z) ++ { ++ super(x, y, GeoEngine.getInstance().getHeightNearest(x, y, z)); ++ _nswe = GeoEngine.getInstance().getNsweNearest(x, y, z); ++ } ++ ++ public void set(int x, int y, short z) ++ { ++ super.setXYZ(x, y, GeoEngine.getInstance().getHeightNearest(x, y, z)); ++ _nswe = GeoEngine.getInstance().getNsweNearest(x, y, z); ++ } ++ ++ public int getGeoX() ++ { ++ return _x; ++ } ++ ++ public int getGeoY() ++ { ++ return _y; ++ } ++ ++ @Override ++ public int getX() ++ { ++ return GeoEngine.getWorldX(_x); ++ } ++ ++ @Override ++ public int getY() ++ { ++ return GeoEngine.getWorldY(_y); ++ } ++ ++ public byte getNSWE() ++ { ++ return _nswe; ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java (working copy) +@@ -0,0 +1,70 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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; ++ ++/** ++ * @author Hasha ++ */ ++public class GeoStructure ++{ ++ // cells ++ public static final byte CELL_FLAG_E = 1 << 0; ++ public static final byte CELL_FLAG_W = 1 << 1; ++ public static final byte CELL_FLAG_S = 1 << 2; ++ public static final byte CELL_FLAG_N = 1 << 3; ++ public static final byte CELL_FLAG_SE = 1 << 4; ++ public static final byte CELL_FLAG_SW = 1 << 5; ++ public static final byte CELL_FLAG_NE = 1 << 6; ++ public static final byte CELL_FLAG_NW = (byte) (1 << 7); ++ ++ public static final int CELL_HEIGHT = 8; ++ public static final int CELL_IGNORE_HEIGHT = CELL_HEIGHT * 6; ++ ++ // blocks ++ public static final byte TYPE_FLAT_L2J_L2OFF = 0; ++ public static final byte TYPE_FLAT_L2D = (byte) 0xD0; ++ public static final byte TYPE_COMPLEX_L2J = 1; ++ public static final byte TYPE_COMPLEX_L2OFF = 0x40; ++ public static final byte TYPE_COMPLEX_L2D = (byte) 0xD1; ++ 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) ++ public static final byte TYPE_MULTILAYER_L2D = (byte) 0xD2; ++ ++ 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; ++ ++ // regions ++ 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; ++ ++ // global geodata ++ private static final int GEO_REGIONS_X = ((World.TILE_X_MAX - World.TILE_X_MIN) + 1); ++ private 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 +Index: java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java (nonexistent) +@@ -1,42 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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); +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/IGeoObject.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/IGeoObject.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/IGeoObject.java (working copy) +@@ -0,0 +1,53 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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 Hasha ++ */ ++public interface IGeoObject ++{ ++ /** ++ * Returns geodata X coordinate of the {@link IGeoObject}. ++ * @return int : Geodata X coordinate. ++ */ ++ int getGeoX(); ++ ++ /** ++ * Returns geodata Y coordinate of the {@link IGeoObject}. ++ * @return int : Geodata Y coordinate. ++ */ ++ int getGeoY(); ++ ++ /** ++ * Returns geodata Z coordinate of the {@link IGeoObject}. ++ * @return int : Geodata Z coordinate. ++ */ ++ int getGeoZ(); ++ ++ /** ++ * Returns height of the {@link IGeoObject}. ++ * @return int : Height. ++ */ ++ int getHeight(); ++ ++ /** ++ * Returns {@link IGeoObject} data. ++ * @return byte[][] : {@link IGeoObject} data. ++ */ ++ byte[][] getObjectGeoData(); ++} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java (nonexistent) +@@ -1,47 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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(); +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/MultilayerBlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/MultilayerBlock.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/MultilayerBlock.java (nonexistent) +@@ -1,183 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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; +- +-/** +- * @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); +- +- // 1 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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/NullRegion.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/NullRegion.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/NullRegion.java (nonexistent) +@@ -1,55 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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 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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/Region.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/Region.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/Region.java (nonexistent) +@@ -1,92 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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; +- +-/** +- * @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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java (nonexistent) +@@ -1,87 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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 T _loc; +- private AbstractNode _parent; +- +- public AbstractNode(T loc) +- { +- _loc = loc; +- } +- +- public void setParent(AbstractNode p) +- { +- _parent = p; +- } +- +- public AbstractNode getParent() +- { +- return _parent; +- } +- +- public T getLoc() +- { +- return _loc; +- } +- +- public void setLoc(T l) +- { +- _loc = l; +- } +- +- @Override +- public int hashCode() +- { +- final int prime = 31; +- int result = 1; +- result = (prime * result) + ((_loc == null) ? 0 : _loc.hashCode()); +- return result; +- } +- +- @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 +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java (nonexistent) +@@ -1,33 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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 int getNodeX(); +- +- public abstract int getNodeY(); +-} +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNode.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNode.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNode.java (nonexistent) +@@ -1,67 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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 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 +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNodeBuffer.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNodeBuffer.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNodeBuffer.java (nonexistent) +@@ -1,348 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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.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 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) +- { +- _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(); +- } +- +- 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 void getNeighbors() +- { +- if (!_current.getLoc().canGoAll()) +- { +- 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); +- } +- +- // SouthEast +- if ((nodeE != null) && (nodeS != null)) +- { +- if (nodeE.getLoc().canGoSouth() && nodeS.getLoc().canGoEast()) +- { +- addNode(x + 1, y + 1, z, true); +- } +- } +- +- // SouthWest +- if ((nodeS != null) && (nodeW != null)) +- { +- if (nodeW.getLoc().canGoSouth() && nodeS.getLoc().canGoWest()) +- { +- addNode(x - 1, y + 1, z, true); +- } +- } +- +- // NorthEast +- if ((nodeN != null) && (nodeE != null)) +- { +- if (nodeE.getLoc().canGoNorth() && nodeN.getLoc().canGoEast()) +- { +- addNode(x + 1, y - 1, z, true); +- } +- } +- +- // NorthWest +- if ((nodeN != null) && (nodeW != null)) +- { +- if (nodeW.getLoc().canGoNorth() && nodeN.getLoc().canGoWest()) +- { +- addNode(x - 1, y - 1, z, true); +- } +- } +- } +- +- private 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(); +- // reinit node if needed +- if (result.getLoc() != null) +- { +- result.getLoc().set(x, y, z); +- } +- else +- { +- result.setLoc(new NodeLoc(x, y, z)); +- } +- } +- +- return result; +- } +- +- private 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 boolean isHighWeight(int x, int y, int z) +- { +- final CellNode result = getNode(x, y, z); +- if (result == null) +- { +- return true; +- } +- +- if (!result.getLoc().canGoAll()) +- { +- return true; +- } +- if (Math.abs(result.getLoc().getZ() - z) > 16) +- { +- return true; +- } +- +- return false; +- } +- +- private 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 +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java (working copy) +@@ -0,0 +1,87 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.geodata.GeoLocation; ++ ++/** ++ * @author Hasha ++ */ ++public class Node ++{ ++ // node coords and nswe flag ++ private GeoLocation _loc; ++ ++ // node parent (for reverse path construction) ++ private Node _parent; ++ // node child (for moving over nodes during iteration) ++ private Node _child; ++ ++ // node G cost (movement cost = parent movement cost + current movement cost) ++ private double _cost = -1000; ++ ++ public void setLoc(int x, int y, int z) ++ { ++ _loc = new GeoLocation(x, y, z); ++ } ++ ++ public GeoLocation getLoc() ++ { ++ return _loc; ++ } ++ ++ public void setParent(Node parent) ++ { ++ _parent = parent; ++ } ++ ++ public Node getParent() ++ { ++ return _parent; ++ } ++ ++ public void setChild(Node child) ++ { ++ _child = child; ++ } ++ ++ public Node getChild() ++ { ++ return _child; ++ } ++ ++ public void setCost(double cost) ++ { ++ _cost = cost; ++ } ++ ++ public double getCost() ++ { ++ return _cost; ++ } ++ ++ public void free() ++ { ++ // reset node location ++ _loc = null; ++ ++ // reset node parent, child and cost ++ _parent = null; ++ _child = null; ++ _cost = -1000; ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java (working copy) +@@ -0,0 +1,306 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.concurrent.locks.ReentrantLock; ++ ++import org.l2jmobius.Config; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; ++ ++/** ++ * @author DS, Hasha; Credits to Diamond ++ */ ++public class NodeBuffer ++{ ++ private final ReentrantLock _lock = new ReentrantLock(); ++ private final int _size; ++ private final Node[][] _buffer; ++ ++ // center coordinates ++ private int _cx = 0; ++ private int _cy = 0; ++ ++ // target coordinates ++ private int _gtx = 0; ++ private int _gty = 0; ++ private short _gtz = 0; ++ ++ private Node _current = null; ++ ++ /** ++ * Constructor of NodeBuffer. ++ * @param size : one dimension size of buffer ++ */ ++ public NodeBuffer(int size) ++ { ++ // set size ++ _size = size; ++ ++ // initialize buffer ++ _buffer = new Node[size][size]; ++ for (int x = 0; x < size; x++) ++ { ++ for (int y = 0; y < size; y++) ++ { ++ _buffer[x][y] = 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 Node : first node of path ++ */ ++ public Node findPath(int gox, int goy, short goz, int gtx, int gty, short gtz) ++ { ++ // set coordinates (middle of the line (gox,goy) - (gtx,gty), will be in the center of the buffer) ++ _cx = gox + ((gtx - gox - _size) / 2); ++ _cy = goy + ((gty - goy - _size) / 2); ++ ++ _gtx = gtx; ++ _gty = gty; ++ _gtz = gtz; ++ ++ _current = getNode(gox, goy, goz); ++ _current.setCost(getCostH(gox, goy, goz)); ++ ++ int count = 0; ++ do ++ { ++ // reached target? ++ if ((_current.getLoc().getGeoX() == _gtx) && (_current.getLoc().getGeoY() == _gty) && (Math.abs(_current.getLoc().getZ() - _gtz) < 8)) ++ { ++ return _current; ++ } ++ ++ // expand current node ++ expand(); ++ ++ // move pointer ++ _current = _current.getChild(); ++ } ++ while ((_current != null) && (count++ < Config.MAX_ITERATIONS)); ++ ++ return null; ++ } ++ ++ public boolean isLocked() ++ { ++ return _lock.tryLock(); ++ } ++ ++ public void free() ++ { ++ _current = null; ++ ++ for (Node[] nodes : _buffer) ++ { ++ for (Node node : nodes) ++ { ++ if (node.getLoc() != null) ++ { ++ node.free(); ++ } ++ } ++ } ++ ++ _lock.unlock(); ++ } ++ ++ /** ++ * Check _current Node and add its neighbors to the buffer. ++ */ ++ private final void expand() ++ { ++ // can't move anywhere, don't expand ++ final byte nswe = _current.getLoc().getNSWE(); ++ if (nswe == 0) ++ { ++ return; ++ } ++ ++ // get geo coords of the node to be expanded ++ final int x = _current.getLoc().getGeoX(); ++ final int y = _current.getLoc().getGeoY(); ++ final short z = (short) _current.getLoc().getZ(); ++ ++ // can move north, expand ++ if ((nswe & GeoStructure.CELL_FLAG_N) != 0) ++ { ++ addNode(x, y - 1, z, Config.BASE_WEIGHT); ++ } ++ ++ // can move south, expand ++ if ((nswe & GeoStructure.CELL_FLAG_S) != 0) ++ { ++ addNode(x, y + 1, z, Config.BASE_WEIGHT); ++ } ++ ++ // can move west, expand ++ if ((nswe & GeoStructure.CELL_FLAG_W) != 0) ++ { ++ addNode(x - 1, y, z, Config.BASE_WEIGHT); ++ } ++ ++ // can move east, expand ++ if ((nswe & GeoStructure.CELL_FLAG_E) != 0) ++ { ++ addNode(x + 1, y, z, Config.BASE_WEIGHT); ++ } ++ ++ // can move north-west, expand ++ if ((nswe & GeoStructure.CELL_FLAG_NW) != 0) ++ { ++ addNode(x - 1, y - 1, z, Config.DIAGONAL_WEIGHT); ++ } ++ ++ // can move north-east, expand ++ if ((nswe & GeoStructure.CELL_FLAG_NE) != 0) ++ { ++ addNode(x + 1, y - 1, z, Config.DIAGONAL_WEIGHT); ++ } ++ ++ // can move south-west, expand ++ if ((nswe & GeoStructure.CELL_FLAG_SW) != 0) ++ { ++ addNode(x - 1, y + 1, z, Config.DIAGONAL_WEIGHT); ++ } ++ ++ // can move south-east, expand ++ if ((nswe & GeoStructure.CELL_FLAG_SE) != 0) ++ { ++ addNode(x + 1, y + 1, z, Config.DIAGONAL_WEIGHT); ++ } ++ } ++ ++ /** ++ * Returns node, if it exists in buffer. ++ * @param x : node X coord ++ * @param y : node Y coord ++ * @param z : node Z coord ++ * @return Node : node, if exits in buffer ++ */ ++ private final Node getNode(int x, int y, short z) ++ { ++ // check node X out of coordinates ++ final int ix = x - _cx; ++ if ((ix < 0) || (ix >= _size)) ++ { ++ return null; ++ } ++ ++ // check node Y out of coordinates ++ final int iy = y - _cy; ++ if ((iy < 0) || (iy >= _size)) ++ { ++ return null; ++ } ++ ++ // get node ++ final Node result = _buffer[ix][iy]; ++ ++ // check and update ++ if (result.getLoc() == null) ++ { ++ result.setLoc(x, y, z); ++ } ++ ++ // return node ++ return result; ++ } ++ ++ /** ++ * Add node given by coordinates to the buffer. ++ * @param x : geo X coord ++ * @param y : geo Y coord ++ * @param z : geo Z coord ++ * @param weight : weight of movement to new node ++ */ ++ private final void addNode(int x, int y, short z, int weight) ++ { ++ // get node to be expanded ++ final Node node = getNode(x, y, z); ++ if (node == null) ++ { ++ return; ++ } ++ ++ // Z distance between nearby cells is higher than cell size ++ if (node.getLoc().getZ() > (z + (2 * GeoStructure.CELL_HEIGHT))) ++ { ++ return; ++ } ++ ++ // node was already expanded, return ++ if (node.getCost() >= 0) ++ { ++ return; ++ } ++ ++ node.setParent(_current); ++ if (node.getLoc().getNSWE() != (byte) 0xFF) ++ { ++ node.setCost(getCostH(x, y, node.getLoc().getZ()) + (weight * Config.OBSTACLE_MULTIPLIER)); ++ } ++ else ++ { ++ node.setCost(getCostH(x, y, node.getLoc().getZ()) + weight); ++ } ++ ++ Node current = _current; ++ int count = 0; ++ while ((current.getChild() != null) && (count < (Config.MAX_ITERATIONS * 4))) ++ { ++ count++; ++ if (current.getChild().getCost() > node.getCost()) ++ { ++ node.setChild(current.getChild()); ++ break; ++ } ++ current = current.getChild(); ++ } ++ ++ if (count >= (Config.MAX_ITERATIONS * 4)) ++ { ++ System.err.println("Pathfinding: too long loop detected, cost:" + node.getCost()); ++ } ++ ++ current.setChild(node); ++ } ++ ++ /** ++ * @param x : node X coord ++ * @param y : node Y coord ++ * @param i : node Z coord ++ * @return double : node cost ++ */ ++ private final double getCostH(int x, int y, int i) ++ { ++ final int dX = x - _gtx; ++ final int dY = y - _gty; ++ final int dZ = (i - _gtz) / GeoStructure.CELL_HEIGHT; ++ ++ // return (Math.abs(dX) + Math.abs(dY) + Math.abs(dZ)) * Config.HEURISTIC_WEIGHT; // Manhattan distance ++ return Math.sqrt((dX * dX) + (dY * dY) + (dZ * dZ)) * Config.HEURISTIC_WEIGHT; // Direct distance ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeLoc.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeLoc.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeLoc.java (nonexistent) +@@ -1,183 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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.Cell; +- +-/** +- * @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 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 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; +- // return super.hashCode(); +- } +- +- @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; +- if (_x != other._x) +- { +- return false; +- } +- if (_y != other._y) +- { +- return false; +- } +- if (_goNorth != other._goNorth) +- { +- return false; +- } +- if (_goEast != other._goEast) +- { +- return false; +- } +- if (_goSouth != other._goSouth) +- { +- return false; +- } +- if (_goWest != other._goWest) +- { +- return false; +- } +- if (_geoHeight != other._geoHeight) +- { +- return false; +- } +- return true; +- } +-} +Index: java/org/l2jmobius/gameserver/model/actor/Creature.java +=================================================================== +--- java/org/l2jmobius/gameserver/model/actor/Creature.java (revision 8399) ++++ java/org/l2jmobius/gameserver/model/actor/Creature.java (working copy) +@@ -66,8 +66,6 @@ + import org.l2jmobius.gameserver.enums.TeleportWhereType; + import org.l2jmobius.gameserver.enums.UserInfoType; + import org.l2jmobius.gameserver.geoengine.GeoEngine; +-import org.l2jmobius.gameserver.geoengine.GeoEnginePathfinding; +-import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; + import org.l2jmobius.gameserver.instancemanager.IdManager; + import org.l2jmobius.gameserver.instancemanager.MapRegionManager; + import org.l2jmobius.gameserver.instancemanager.QuestManager; +@@ -2577,7 +2575,7 @@ + + public boolean disregardingGeodata; + public int onGeodataPathIndex; +- public List geoPath; ++ public List geoPath; + public int geoPathAccurateTx; + public int geoPathAccurateTy; + public int geoPathGtx; +@@ -3466,7 +3464,7 @@ + if (((originalDistance - distance) > 30) && !isControlBlocked() && !isInVehicle) + { + // Path calculation -- overrides previous movement check +- m.geoPath = GeoEnginePathfinding.getInstance().findPath(curX, curY, curZ, originalX, originalY, originalZ, getInstanceWorld()); ++ m.geoPath = GeoEngine.getInstance().findPath(curX, curY, curZ, originalX, originalY, originalZ, getInstanceWorld()); + if ((m.geoPath == null) || (m.geoPath.size() < 2)) // No path found + { + if (isPlayer() && !_isFlying && !isInWater) +Index: java/org/l2jmobius/gameserver/model/actor/instance/PlayerInstance.java +=================================================================== +--- java/org/l2jmobius/gameserver/model/actor/instance/PlayerInstance.java (revision 8397) ++++ java/org/l2jmobius/gameserver/model/actor/instance/PlayerInstance.java (working copy) +@@ -12704,7 +12704,7 @@ + { + if (Config.CORRECT_PLAYER_Z) + { +- final int nearestZ = GeoEngine.getInstance().getHigherHeight(getX(), getY(), getZ()); ++ final int nearestZ = GeoEngine.getInstance().getHeightNearest(getX(), getY(), getZ()); + if (getZ() < nearestZ) + { + teleToLocation(new Location(getX(), getY(), nearestZ)); +Index: java/org/l2jmobius/gameserver/util/GeoUtils.java +=================================================================== +--- java/org/l2jmobius/gameserver/util/GeoUtils.java (revision 8397) ++++ java/org/l2jmobius/gameserver/util/GeoUtils.java (working copy) +@@ -19,7 +19,7 @@ + import java.awt.Color; + + import org.l2jmobius.gameserver.geoengine.GeoEngine; +-import org.l2jmobius.gameserver.geoengine.geodata.Cell; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; + import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance; + import org.l2jmobius.gameserver.network.serverpackets.ExServerPrimitive; + +@@ -26,25 +26,25 @@ + /** + * @author HorridoJoho + */ +-public final class GeoUtils ++public class GeoUtils + { + public static void debug2DLine(PlayerInstance player, int x, int y, int tx, int ty, int z) + { +- final int gx = GeoEngine.getInstance().getGeoX(x); +- final int gy = GeoEngine.getInstance().getGeoY(y); ++ final int gx = GeoEngine.getGeoX(x); ++ final int gy = GeoEngine.getGeoY(y); + +- final int tgx = GeoEngine.getInstance().getGeoX(tx); +- final int tgy = GeoEngine.getInstance().getGeoY(ty); ++ final int tgx = GeoEngine.getGeoX(tx); ++ final int tgy = GeoEngine.getGeoY(ty); + + final ExServerPrimitive prim = new ExServerPrimitive("Debug2DLine", x, y, z); +- prim.addLine(Color.BLUE, GeoEngine.getInstance().getWorldX(gx), GeoEngine.getInstance().getWorldY(gy), z, GeoEngine.getInstance().getWorldX(tgx), GeoEngine.getInstance().getWorldY(tgy), z); ++ prim.addLine(Color.BLUE, GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), z, GeoEngine.getWorldX(tgx), GeoEngine.getWorldY(tgy), z); + + final LinePointIterator iter = new LinePointIterator(gx, gy, tgx, tgy); + + while (iter.next()) + { +- final int wx = GeoEngine.getInstance().getWorldX(iter.x()); +- final int wy = GeoEngine.getInstance().getWorldY(iter.y()); ++ final int wx = GeoEngine.getWorldX(iter.x()); ++ final int wy = GeoEngine.getWorldY(iter.y()); + + prim.addPoint(Color.RED, wx, wy, z); + } +@@ -53,21 +53,21 @@ + + public static void debug3DLine(PlayerInstance player, int x, int y, int z, int tx, int ty, int tz) + { +- final int gx = GeoEngine.getInstance().getGeoX(x); +- final int gy = GeoEngine.getInstance().getGeoY(y); ++ final int gx = GeoEngine.getGeoX(x); ++ final int gy = GeoEngine.getGeoY(y); + +- final int tgx = GeoEngine.getInstance().getGeoX(tx); +- final int tgy = GeoEngine.getInstance().getGeoY(ty); ++ final int tgx = GeoEngine.getGeoX(tx); ++ final int tgy = GeoEngine.getGeoY(ty); + + final ExServerPrimitive prim = new ExServerPrimitive("Debug3DLine", x, y, z); +- prim.addLine(Color.BLUE, GeoEngine.getInstance().getWorldX(gx), GeoEngine.getInstance().getWorldY(gy), z, GeoEngine.getInstance().getWorldX(tgx), GeoEngine.getInstance().getWorldY(tgy), tz); ++ prim.addLine(Color.BLUE, GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), z, GeoEngine.getWorldX(tgx), GeoEngine.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.getInstance().getWorldX(prevX); +- int wy = GeoEngine.getInstance().getWorldY(prevY); ++ int wx = GeoEngine.getWorldX(prevX); ++ int wy = GeoEngine.getWorldY(prevY); + int wz = iter.z(); + prim.addPoint(Color.RED, wx, wy, wz); + +@@ -78,8 +78,8 @@ + + if ((curX != prevX) || (curY != prevY)) + { +- wx = GeoEngine.getInstance().getWorldX(curX); +- wy = GeoEngine.getInstance().getWorldY(curY); ++ wx = GeoEngine.getWorldX(curX); ++ wy = GeoEngine.getWorldY(curY); + wz = iter.z(); + + prim.addPoint(Color.RED, wx, wy, wz); +@@ -93,7 +93,7 @@ + + private static Color getDirectionColor(int x, int y, int z, int nswe) + { +- if (GeoEngine.getInstance().checkNearestNswe(x, y, z, nswe)) ++ if ((GeoEngine.getInstance().getNsweNearest(x, y, z) & nswe) == nswe) + { + return Color.GREEN; + } +@@ -109,9 +109,8 @@ + int iPacket = 0; + + ExServerPrimitive exsp = null; +- final GeoEngine ge = GeoEngine.getInstance(); +- final int playerGx = ge.getGeoX(player.getX()); +- final int playerGy = ge.getGeoY(player.getY()); ++ final int playerGx = GeoEngine.getGeoX(player.getX()); ++ final int playerGy = GeoEngine.getGeoY(player.getY()); + for (int dx = -geoRadius; dx <= geoRadius; ++dx) + { + for (int dy = -geoRadius; dy <= geoRadius; ++dy) +@@ -135,12 +134,12 @@ + final int gx = playerGx + dx; + final int gy = playerGy + dy; + +- final int x = ge.getWorldX(gx); +- final int y = ge.getWorldY(gy); +- final int z = ge.getNearestZ(gx, gy, player.getZ()); ++ final int x = GeoEngine.getWorldX(gx); ++ final int y = GeoEngine.getWorldY(gy); ++ final int z = GeoEngine.getInstance().getHeightNearest(gx, gy, player.getZ()); + + // north arrow +- Color col = getDirectionColor(gx, gy, z, Cell.NSWE_NORTH); ++ Color col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_N); + 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); +@@ -147,7 +146,7 @@ + exsp.addLine(col, x - 4, y - 4, z, x + 4, y - 4, z); + + // east arrow +- col = getDirectionColor(gx, gy, z, Cell.NSWE_EAST); ++ col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_E); + 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); +@@ -154,13 +153,13 @@ + exsp.addLine(col, x + 4, y - 4, z, x + 4, y + 4, z); + + // south arrow +- col = getDirectionColor(gx, gy, z, Cell.NSWE_SOUTH); ++ col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_S); + 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, Cell.NSWE_WEST); ++ col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_W); + 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); +@@ -188,15 +187,15 @@ + { + if (y > lastY) + { +- return Cell.NSWE_SOUTH_EAST; ++ return GeoStructure.CELL_FLAG_SE; // Direction.SOUTH_EAST; + } + else if (y < lastY) + { +- return Cell.NSWE_NORTH_EAST; ++ return GeoStructure.CELL_FLAG_NE; // Direction.NORTH_EAST; + } + else + { +- return Cell.NSWE_EAST; ++ return GeoStructure.CELL_FLAG_E; // Direction.EAST; + } + } + else if (x < lastX) // west +@@ -203,26 +202,27 @@ + { + if (y > lastY) + { +- return Cell.NSWE_SOUTH_WEST; ++ return GeoStructure.CELL_FLAG_SW; // Direction.SOUTH_WEST; + } + else if (y < lastY) + { +- return Cell.NSWE_NORTH_WEST; ++ return GeoStructure.CELL_FLAG_NW; // Direction.NORTH_WEST; + } + else + { +- return Cell.NSWE_WEST; ++ return GeoStructure.CELL_FLAG_W; // Direction.WEST; + } + } +- else // unchanged x ++ else ++ // unchanged x + { + if (y > lastY) + { +- return Cell.NSWE_SOUTH; ++ return GeoStructure.CELL_FLAG_S; // Direction.SOUTH; + } + else if (y < lastY) + { +- return Cell.NSWE_NORTH; ++ return GeoStructure.CELL_FLAG_N; // Direction.NORTH; + } + else + { +Index: java/org/l2jmobius/tools/geodataconverter/GeoDataConverter.java +=================================================================== +--- java/org/l2jmobius/tools/geodataconverter/GeoDataConverter.java (nonexistent) ++++ java/org/l2jmobius/tools/geodataconverter/GeoDataConverter.java (working copy) +@@ -0,0 +1,386 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ */ ++package org.l2jmobius.tools.geodataconverter; ++ ++import java.io.BufferedOutputStream; ++import java.io.File; ++import java.io.FileOutputStream; ++import java.io.RandomAccessFile; ++import java.nio.ByteOrder; ++import java.nio.MappedByteBuffer; ++import java.nio.channels.FileChannel; ++import java.util.Scanner; ++ ++import org.l2jmobius.Config; ++import org.l2jmobius.commons.util.PropertiesParser; ++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.GeoFormat; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; ++import org.l2jmobius.gameserver.model.World; ++ ++/** ++ * @author Hasha ++ */ ++public class GeoDataConverter ++{ ++ private static GeoFormat _format; ++ private static ABlock[][] _blocks; ++ ++ public static void main(String[] args) ++ { ++ // initialize config ++ loadGeoengineConfigs(); ++ ++ // get geodata format ++ String type = ""; ++ try (Scanner scn = new Scanner(System.in)) ++ { ++ while (!(type.equalsIgnoreCase("J") || type.equalsIgnoreCase("O") || type.equalsIgnoreCase("E"))) ++ { ++ System.out.println("GeoDataConverter: Select source geodata type:"); ++ System.out.println(" J: L2J (e.g. 23_22.l2j)"); ++ System.out.println(" O: L2OFF (e.g. 23_22_conv.dat)"); ++ System.out.println(" E: Exit"); ++ System.out.print("Choice: "); ++ type = scn.next(); ++ } ++ } ++ if (type.equalsIgnoreCase("E")) ++ { ++ System.exit(0); ++ } ++ ++ _format = type.equalsIgnoreCase("J") ? GeoFormat.L2J : GeoFormat.L2OFF; ++ ++ // start conversion ++ System.out.println("GeoDataConverter: Converting all " + _format + " files."); ++ ++ // initialize geodata container ++ _blocks = new ABlock[GeoStructure.REGION_BLOCKS_X][GeoStructure.REGION_BLOCKS_Y]; ++ ++ // initialize multilayer temporarily buffer ++ BlockMultilayer.initialize(); ++ ++ // load geo files ++ int converted = 0; ++ for (int rx = World.TILE_X_MIN; rx <= World.TILE_X_MAX; rx++) ++ { ++ for (int ry = World.TILE_Y_MIN; ry <= World.TILE_Y_MAX; ry++) ++ { ++ final String input = String.format(_format.getFilename(), rx, ry); ++ final String filepath = Config.GEODATA_PATH; ++ final File f = new File(filepath + input); ++ if (f.exists() && !f.isDirectory()) ++ { ++ // load geodata ++ if (!loadGeoBlocks(input)) ++ { ++ System.out.println("GeoDataConverter: Unable to load " + input + " region file."); ++ continue; ++ } ++ ++ // recalculate nswe ++ if (!recalculateNswe()) ++ { ++ System.out.println("GeoDataConverter: Unable to convert " + input + " region file."); ++ continue; ++ } ++ ++ // save geodata ++ final String output = String.format(GeoFormat.L2D.getFilename(), rx, ry); ++ if (!saveGeoBlocks(output)) ++ { ++ System.out.println("GeoDataConverter: Unable to save " + output + " region file."); ++ continue; ++ } ++ ++ converted++; ++ System.out.println("GeoDataConverter: Created " + output + " region file."); ++ } ++ } ++ } ++ System.out.println("GeoDataConverter: Converted " + converted + " " + _format + " to L2D region file(s)."); ++ ++ // release multilayer block temporarily buffer ++ BlockMultilayer.release(); ++ } ++ ++ /** ++ * Loads geo blocks from buffer of the region file. ++ * @param filename : The name of the to load. ++ * @return boolean : True when successful. ++ */ ++ private static boolean loadGeoBlocks(String filename) ++ { ++ // region file is load-able, try to load it ++ try (RandomAccessFile raf = new RandomAccessFile(Config.GEODATA_PATH + filename, "r"); ++ FileChannel fc = raf.getChannel()) ++ { ++ final MappedByteBuffer buffer = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()).load(); ++ buffer.order(ByteOrder.LITTLE_ENDIAN); ++ ++ // load 18B header for L2off geodata (1st and 2nd byte...region X and Y) ++ if (_format == GeoFormat.L2OFF) ++ { ++ for (int i = 0; i < 18; i++) ++ { ++ buffer.get(); ++ } ++ } ++ ++ // 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 (_format == GeoFormat.L2J) ++ { ++ // get block type ++ final byte type = buffer.get(); ++ ++ // load block according to block type ++ switch (type) ++ { ++ case GeoStructure.TYPE_FLAT_L2J_L2OFF: ++ { ++ _blocks[ix][iy] = new BlockFlat(buffer, _format); ++ break; ++ } ++ case GeoStructure.TYPE_COMPLEX_L2J: ++ { ++ _blocks[ix][iy] = new BlockComplex(buffer, _format); ++ break; ++ } ++ case GeoStructure.TYPE_MULTILAYER_L2J: ++ { ++ _blocks[ix][iy] = new BlockMultilayer(buffer, _format); ++ break; ++ } ++ default: ++ { ++ throw new IllegalArgumentException("Unknown block type: " + type); ++ } ++ } ++ } ++ else ++ { ++ // get block type ++ final short type = buffer.getShort(); ++ ++ // load block according to block type ++ switch (type) ++ { ++ case GeoStructure.TYPE_FLAT_L2J_L2OFF: ++ { ++ _blocks[ix][iy] = new BlockFlat(buffer, _format); ++ break; ++ } ++ case GeoStructure.TYPE_COMPLEX_L2OFF: ++ { ++ _blocks[ix][iy] = new BlockComplex(buffer, _format); ++ break; ++ } ++ default: ++ { ++ _blocks[ix][iy] = new BlockMultilayer(buffer, _format); ++ break; ++ } ++ } ++ } ++ } ++ } ++ ++ if (buffer.remaining() > 0) ++ { ++ System.out.println("GeoDataConverter: Region file " + filename + " can be corrupted, remaining " + buffer.remaining() + " bytes to read."); ++ return false; ++ } ++ ++ return true; ++ } ++ catch (Exception e) ++ { ++ System.out.println("GeoDataConverter: Error while loading " + filename + " region file."); ++ return false; ++ } ++ } ++ ++ /** ++ * Recalculate diagonal flags for the region file. ++ * @return boolean : True when successful. ++ */ ++ private static boolean recalculateNswe() ++ { ++ try ++ { ++ for (int x = 0; x < GeoStructure.REGION_CELLS_X; x++) ++ { ++ for (int y = 0; y < GeoStructure.REGION_CELLS_Y; y++) ++ { ++ // get block ++ final ABlock block = _blocks[x / GeoStructure.BLOCK_CELLS_X][y / GeoStructure.BLOCK_CELLS_Y]; ++ ++ // skip flat blocks ++ if (block instanceof BlockFlat) ++ { ++ continue; ++ } ++ ++ // for complex and multilayer blocks go though all layers ++ short height = Short.MAX_VALUE; ++ int index; ++ while ((index = block.getIndexBelow(x, y, height)) != -1) ++ { ++ // get height and nswe ++ height = block.getHeight(index); ++ byte nswe = block.getNswe(index); ++ ++ // update nswe with diagonal flags ++ nswe = updateNsweBelow(x, y, height, nswe); ++ ++ // set nswe of the cell ++ block.setNswe(index, nswe); ++ } ++ } ++ } ++ ++ return true; ++ } ++ catch (Exception e) ++ { ++ return false; ++ } ++ } ++ ++ /** ++ * Updates the NSWE flag with diagonal flags. ++ * @param x : Geodata X coordinate. ++ * @param y : Geodata Y coordinate. ++ * @param z : Geodata Z coordinate. ++ * @param nsweValue : NSWE flag to be updated. ++ * @return byte : Updated NSWE flag. ++ */ ++ private static byte updateNsweBelow(int x, int y, short z, byte nsweValue) ++ { ++ byte nswe = nsweValue; ++ ++ // calculate virtual layer height ++ final short height = (short) (z + GeoStructure.CELL_IGNORE_HEIGHT); ++ ++ // get NSWE of neighbor cells below virtual layer (NPC/PC can fall down of clif, but can not climb it -> NSWE of cell below) ++ final byte nsweN = getNsweBelow(x, y - 1, height); ++ final byte nsweS = getNsweBelow(x, y + 1, height); ++ final byte nsweW = getNsweBelow(x - 1, y, height); ++ final byte nsweE = getNsweBelow(x + 1, y, height); ++ ++ // north-west ++ if ((((nswe & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) || (((nswe & GeoStructure.CELL_FLAG_W) != 0) && ((nsweW & GeoStructure.CELL_FLAG_N) != 0))) ++ { ++ nswe |= GeoStructure.CELL_FLAG_NW; ++ } ++ ++ // north-east ++ if ((((nswe & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) || (((nswe & GeoStructure.CELL_FLAG_E) != 0) && ((nsweE & GeoStructure.CELL_FLAG_N) != 0))) ++ { ++ nswe |= GeoStructure.CELL_FLAG_NE; ++ } ++ ++ // south-west ++ if ((((nswe & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) || (((nswe & GeoStructure.CELL_FLAG_W) != 0) && ((nsweW & GeoStructure.CELL_FLAG_S) != 0))) ++ { ++ nswe |= GeoStructure.CELL_FLAG_SW; ++ } ++ ++ // south-east ++ if ((((nswe & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) || (((nswe & GeoStructure.CELL_FLAG_E) != 0) && ((nsweE & GeoStructure.CELL_FLAG_S) != 0))) ++ { ++ nswe |= GeoStructure.CELL_FLAG_SE; ++ } ++ ++ return nswe; ++ } ++ ++ private static byte getNsweBelow(int geoX, int geoY, short worldZ) ++ { ++ // out of geo coordinates ++ if ((geoX < 0) || (geoX >= GeoStructure.REGION_CELLS_X)) ++ { ++ return 0; ++ } ++ ++ // out of geo coordinates ++ if ((geoY < 0) || (geoY >= GeoStructure.REGION_CELLS_Y)) ++ { ++ return 0; ++ } ++ ++ // get block ++ final ABlock block = _blocks[geoX / GeoStructure.BLOCK_CELLS_X][geoY / GeoStructure.BLOCK_CELLS_Y]; ++ ++ // get index, when valid, return nswe ++ final int index = block.getIndexBelow(geoX, geoY, worldZ); ++ return index == -1 ? 0 : block.getNswe(index); ++ } ++ ++ /** ++ * Save region file to file. ++ * @param filename : The name of file to save. ++ * @return boolean : True when successful. ++ */ ++ private static boolean saveGeoBlocks(String filename) ++ { ++ try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(Config.GEODATA_PATH + filename), GeoStructure.REGION_BLOCKS * GeoStructure.BLOCK_CELLS * 3)) ++ { ++ // loop over region blocks and save each block ++ for (int ix = 0; ix < GeoStructure.REGION_BLOCKS_X; ix++) ++ { ++ for (int iy = 0; iy < GeoStructure.REGION_BLOCKS_Y; iy++) ++ { ++ _blocks[ix][iy].saveBlock(bos); ++ } ++ } ++ ++ // flush data to file ++ bos.flush(); ++ ++ return true; ++ } ++ catch (Exception e) ++ { ++ return false; ++ } ++ } ++ ++ private static void loadGeoengineConfigs() ++ { ++ final PropertiesParser geoData = new PropertiesParser(Config.GEOENGINE_CONFIG_FILE); ++ Config.GEODATA_PATH = geoData.getString("GeoDataPath", "./data/geodata/"); ++ Config.COORD_SYNCHRONIZE = geoData.getInt("CoordSynchronize", -1); ++ Config.PART_OF_CHARACTER_HEIGHT = geoData.getInt("PartOfCharacterHeight", 75); ++ Config.MAX_OBSTACLE_HEIGHT = geoData.getInt("MaxObstacleHeight", 32); ++ Config.PATHFINDING = geoData.getBoolean("PathFinding", true); ++ Config.PATHFIND_BUFFERS = geoData.getString("PathFindBuffers", "100x6;128x6;192x6;256x4;320x4;384x4;500x2"); ++ Config.BASE_WEIGHT = geoData.getInt("BaseWeight", 10); ++ Config.DIAGONAL_WEIGHT = geoData.getInt("DiagonalWeight", 14); ++ Config.OBSTACLE_MULTIPLIER = geoData.getInt("ObstacleMultiplier", 10); ++ Config.HEURISTIC_WEIGHT = geoData.getInt("HeuristicWeight", 20); ++ Config.MAX_ITERATIONS = geoData.getInt("MaxIterations", 3500); ++ } ++} +\ No newline at end of file diff --git a/L2J_Mobius_Classic_2.2_Antharas/dist/game/data/geodata/L2D_GeoEngine.patch b/L2J_Mobius_Classic_2.2_Antharas/dist/game/data/geodata/L2D_GeoEngine.patch new file mode 100644 index 0000000000..63e2faf0c9 --- /dev/null +++ b/L2J_Mobius_Classic_2.2_Antharas/dist/game/data/geodata/L2D_GeoEngine.patch @@ -0,0 +1,6052 @@ +Index: dist/game/GeoDataConverter.bat +=================================================================== +--- dist/game/GeoDataConverter.bat (nonexistent) ++++ dist/game/GeoDataConverter.bat (working copy) +@@ -0,0 +1,6 @@ ++@echo off ++title L2D geodata converter ++ ++java -Xmx512m -cp ./../libs/* org.l2jmobius.tools.geodataconverter.GeoDataConverter ++ ++pause +Index: dist/game/GeoDataConverter.sh +=================================================================== +--- dist/game/GeoDataConverter.sh (nonexistent) ++++ dist/game/GeoDataConverter.sh (working copy) +@@ -0,0 +1,4 @@ ++#! /bin/sh ++ ++java -Xmx512m -cp ../libs/*: org.l2jmobius.tools.geodataconverter.GeoDataConverter > log/stdout.log 2>&1 ++ +Index: dist/game/config/GeoEngine.ini +=================================================================== +--- dist/game/config/GeoEngine.ini (revision 8397) ++++ dist/game/config/GeoEngine.ini (working copy) +@@ -1,6 +1,10 @@ + # ================================================================= + # Geodata + # ================================================================= ++# Because of real-time performance we are using geodata files only in ++# diagonal L2D format now (using filename e.g. 22_16.l2d). ++# L2D geodata can be obtained by conversion of existing L2J or L2OFF geodata. ++# Launch "GeoDataConverter.bat/sh" and follow instructions to start the conversion. + + # 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/ +@@ -13,6 +17,16 @@ + CoordSynchronize = 2 + + # ================================================================= ++# Path checking ++# ================================================================= ++ ++# 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 finding + # ================================================================= + +@@ -23,19 +37,23 @@ + # Pathfinding array buffers configuration, default: 100x6;128x6;192x6;256x4;320x4;384x4;500x2 + PathFindBuffers = 100x6;128x6;192x6;256x4;320x4;384x4;500x2 + +-# Weight for nodes without obstacles far from walls +-LowWeight = 0.5 ++# Base path weight, when moving from one node to another on axis direction, default: 10 ++BaseWeight = 10 + +-# Weight for nodes near walls +-MediumWeight = 2 ++# Path weight, when moving from one node to another on diagonal direction, default: BaseWeight * sqrt(2) = 14 ++DiagonalWeight = 14 + +-# Weight for nodes with obstacles +-HighWeight = 3 ++# When movement flags of target node is blocked to any direction, multiply movement weight by this multiplier. ++# This causes pathfinding algorithm to avoid path construction exactly near an obstacle, default: 10 ++ObstacleMultiplier = 10 + +-# Weight for diagonal movement. +-# Default: LowWeight * sqrt(2) +-DiagonalWeight = 0.707 ++# Weight of the heuristic algorithm, which is giving estimated cost from node to target, default: 20 ++# For proper function must be higher than BaseWeight and/or DiagonalWeight. ++HeuristicWeight = 20 + ++# Maximum number of generated nodes per one path-finding process, default 3500 ++MaxIterations = 3500 ++ + # ================================================================= + # Other + # ================================================================= +Index: dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java +=================================================================== +--- dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java (revision 8397) ++++ dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java (working copy) +@@ -55,9 +55,8 @@ + final int worldX = activeChar.getX(); + final int worldY = activeChar.getY(); + final int worldZ = activeChar.getZ(); +- final int geoX = GeoEngine.getInstance().getGeoX(worldX); +- final int geoY = GeoEngine.getInstance().getGeoY(worldY); +- ++ final int geoX = GeoEngine.getGeoX(worldX); ++ final int geoY = GeoEngine.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)); +@@ -73,9 +72,8 @@ + final int worldX = activeChar.getX(); + final int worldY = activeChar.getY(); + final int worldZ = activeChar.getZ(); +- final int geoX = GeoEngine.getInstance().getGeoX(worldX); +- final int geoY = GeoEngine.getInstance().getGeoY(worldY); +- ++ final int geoX = GeoEngine.getGeoX(worldX); ++ final int geoY = GeoEngine.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)); +Index: dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java +=================================================================== +--- dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java (revision 8397) ++++ dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java (working copy) +@@ -18,11 +18,11 @@ + + import java.util.List; + +-import org.l2jmobius.Config; +-import org.l2jmobius.gameserver.geoengine.GeoEnginePathfinding; +-import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; ++import org.l2jmobius.gameserver.geoengine.GeoEngine; + import org.l2jmobius.gameserver.handler.IAdminCommandHandler; ++import org.l2jmobius.gameserver.model.Location; + import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance; ++import org.l2jmobius.gameserver.network.SystemMessageId; + import org.l2jmobius.gameserver.util.BuilderUtil; + + public class AdminPathNode implements IAdminCommandHandler +@@ -37,29 +37,30 @@ + { + if (command.equals("admin_path_find")) + { +- if (!Config.PATHFINDING) +- { +- BuilderUtil.sendSysMessage(activeChar, "PathFinding is disabled."); +- return true; +- } + if (activeChar.getTarget() != null) + { +- final List path = GeoEnginePathfinding.getInstance().findPath(activeChar.getX(), activeChar.getY(), (short) activeChar.getZ(), activeChar.getTarget().getX(), activeChar.getTarget().getY(), (short) activeChar.getTarget().getZ(), activeChar.getInstanceWorld()); ++ 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()); + if (path == null) + { +- BuilderUtil.sendSysMessage(activeChar, "No Route!"); +- return true; ++ BuilderUtil.sendSysMessage(activeChar, "No route found or pathfinding disabled."); + } +- for (AbstractNodeLoc a : path) ++ else + { +- BuilderUtil.sendSysMessage(activeChar, "x:" + a.getX() + " y:" + a.getY() + " z:" + a.getZ()); ++ for (Location point : path) ++ { ++ BuilderUtil.sendSysMessage(activeChar, "x:" + point.getX() + " y:" + point.getY() + " z:" + point.getZ()); ++ } + } + } + else + { +- BuilderUtil.sendSysMessage(activeChar, "No Target!"); ++ activeChar.sendPacket(SystemMessageId.INVALID_TARGET); + } + } ++ else ++ { ++ return false; ++ } + return true; + } + +Index: java/org/l2jmobius/Config.java +=================================================================== +--- java/org/l2jmobius/Config.java (revision 8397) ++++ java/org/l2jmobius/Config.java (working copy) +@@ -32,7 +32,6 @@ + import java.net.UnknownHostException; + import java.nio.charset.StandardCharsets; + import java.nio.file.Files; +-import java.nio.file.Path; + import java.nio.file.Paths; + import java.time.Duration; + import java.util.ArrayList; +@@ -927,14 +926,17 @@ + // -------------------------------------------------- + // GeoEngine + // -------------------------------------------------- +- public static Path GEODATA_PATH; ++ public static String GEODATA_PATH; ++ public static int COORD_SYNCHRONIZE; ++ public static int PART_OF_CHARACTER_HEIGHT; ++ public static int MAX_OBSTACLE_HEIGHT; + public static boolean PATHFINDING; + public static String PATHFIND_BUFFERS; +- public static float LOW_WEIGHT; +- public static float MEDIUM_WEIGHT; +- public static float HIGH_WEIGHT; +- public static float DIAGONAL_WEIGHT; +- public static int COORD_SYNCHRONIZE; ++ public static int BASE_WEIGHT; ++ public static int DIAGONAL_WEIGHT; ++ public static int HEURISTIC_WEIGHT; ++ public static int OBSTACLE_MULTIPLIER; ++ public static int MAX_ITERATIONS; + public static boolean CORRECT_PLAYER_Z; + + /** Attribute System */ +@@ -2468,14 +2470,17 @@ + + // Load GeoEngine config file (if exists) + final PropertiesParser GeoEngine = new PropertiesParser(GEOENGINE_CONFIG_FILE); +- GEODATA_PATH = Paths.get(GeoEngine.getString("GeoDataPath", "./data/geodata")); ++ GEODATA_PATH = GeoEngine.getString("GeoDataPath", "./data/geodata/"); ++ COORD_SYNCHRONIZE = GeoEngine.getInt("CoordSynchronize", -1); ++ PART_OF_CHARACTER_HEIGHT = GeoEngine.getInt("PartOfCharacterHeight", 75); ++ MAX_OBSTACLE_HEIGHT = GeoEngine.getInt("MaxObstacleHeight", 32); + PATHFINDING = GeoEngine.getBoolean("PathFinding", true); + PATHFIND_BUFFERS = GeoEngine.getString("PathFindBuffers", "100x6;128x6;192x6;256x4;320x4;384x4;500x2"); +- LOW_WEIGHT = GeoEngine.getFloat("LowWeight", 0.5f); +- MEDIUM_WEIGHT = GeoEngine.getFloat("MediumWeight", 2); +- HIGH_WEIGHT = GeoEngine.getFloat("HighWeight", 3); +- DIAGONAL_WEIGHT = GeoEngine.getFloat("DiagonalWeight", 0.707f); +- COORD_SYNCHRONIZE = GeoEngine.getInt("CoordSynchronize", -1); ++ BASE_WEIGHT = GeoEngine.getInt("BaseWeight", 10); ++ DIAGONAL_WEIGHT = GeoEngine.getInt("DiagonalWeight", 14); ++ OBSTACLE_MULTIPLIER = GeoEngine.getInt("ObstacleMultiplier", 10); ++ HEURISTIC_WEIGHT = GeoEngine.getInt("HeuristicWeight", 20); ++ MAX_ITERATIONS = GeoEngine.getInt("MaxIterations", 3500); + CORRECT_PLAYER_Z = GeoEngine.getBoolean("CorrectPlayerZ", false); + + // Load AllowedPlayerRaces config file (if exists) +Index: java/org/l2jmobius/gameserver/geoengine/GeoEngine.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/GeoEngine.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/GeoEngine.java (working copy) +@@ -16,101 +16,91 @@ + */ + package org.l2jmobius.gameserver.geoengine; + +-import java.io.IOException; ++import java.io.File; + import java.io.RandomAccessFile; + import java.nio.ByteOrder; +-import java.nio.channels.FileChannel.MapMode; +-import java.nio.file.Files; +-import java.nio.file.Path; +-import java.util.concurrent.atomic.AtomicReferenceArray; +-import java.util.logging.Level; ++import java.nio.MappedByteBuffer; ++import java.nio.channels.FileChannel; ++import java.util.List; + 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.geoengine.geodata.Cell; +-import org.l2jmobius.gameserver.geoengine.geodata.IRegion; +-import org.l2jmobius.gameserver.geoengine.geodata.NullRegion; +-import org.l2jmobius.gameserver.geoengine.geodata.Region; ++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.GeoFormat; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoLocation; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; + import org.l2jmobius.gameserver.instancemanager.WarpedSpaceManager; + 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; ++import org.l2jmobius.gameserver.util.MathUtil; + + /** +- * @author -Nemesiss-, HorridoJoho ++ * @author Hasha + */ + public class GeoEngine + { +- private static final Logger LOGGER = Logger.getLogger(GeoEngine.class.getName()); ++ protected static final Logger LOGGER = Logger.getLogger(GeoEngine.class.getName()); + +- private static final int WORLD_MIN_X = -655360; +- private static final int WORLD_MIN_Y = -589824; +- private static final int WORLD_MIN_Z = -16384; ++ private final ABlock[][] _blocks; ++ private final BlockNull _nullBlock; + +- /** 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; +- +- /** The regions array */ +- private final AtomicReferenceArray _regions = new AtomicReferenceArray<>(GEO_REGIONS); +- +- 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; +- +- protected GeoEngine() ++ /** ++ * GeoEngine constructor. Loads all geodata files of chosen geodata format. ++ */ ++ public GeoEngine() + { + LOGGER.info("GeoEngine: Initializing..."); +- for (int i = 0; i < _regions.length(); i++) +- { +- _regions.set(i, NullRegion.INSTANCE); +- } + ++ // 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; +- try ++ long fileSize = 0; ++ for (int rx = World.TILE_X_MIN; rx <= World.TILE_X_MAX; rx++) + { +- for (int regionX = World.TILE_X_MIN; regionX <= World.TILE_X_MAX; regionX++) ++ for (int ry = World.TILE_Y_MIN; ry <= World.TILE_Y_MAX; ry++) + { +- for (int regionY = World.TILE_Y_MIN; regionY <= World.TILE_Y_MAX; regionY++) ++ final File f = new File(Config.GEODATA_PATH + String.format(GeoFormat.L2D.getFilename(), rx, ry)); ++ if (f.exists() && !f.isDirectory()) + { +- 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(rx, ry)) + { +- try (RandomAccessFile raf = new RandomAccessFile(geoFilePath.toFile(), "r")) +- { +- _regions.set((regionX * GEO_REGIONS_Y) + regionY, new Region(raf.getChannel().map(MapMode.READ_ONLY, 0, raf.length()).load().order(ByteOrder.LITTLE_ENDIAN))); +- loaded++; +- } ++ loaded++; ++ fileSize += f.length(); + } + } ++ else ++ { ++ // region file is not load-able, load null blocks ++ loadNullBlocks(rx, ry); ++ } + } + } +- catch (Exception e) ++ ++ LOGGER.info("GeoEngine: Loaded " + loaded + " geodata files."); ++ if (loaded > 0) + { +- LOGGER.log(Level.SEVERE, "GeoEngine: Failed to load geodata!", e); +- System.exit(1); ++ LOGGER.info("GeoEngine: Total geodata file size " + (fileSize / 1024 / 1024) + " MB."); + } + +- LOGGER.info("GeoEngine: Loaded " + loaded + " geodata files."); +- +- // Avoid wrong configs when no files are loaded. ++ // avoid wrong configs when no files are loaded + if (loaded == 0) + { + if (Config.PATHFINDING) +@@ -124,627 +114,832 @@ + LOGGER.info("GeoEngine: Forcing CoordSynchronize setting to -1."); + } + } ++ ++ // release multilayer block temporarily buffer ++ BlockMultilayer.release(); + } + + /** +- * @param geoX +- * @param geoY +- * @return the region ++ * 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 IRegion getRegion(int geoX, int geoY) ++ private final boolean loadGeoBlocks(int regionX, int regionY) + { +- final int region = ((geoX / IRegion.REGION_CELLS_X) * GEO_REGIONS_Y) + (geoY / IRegion.REGION_CELLS_Y); +- if ((region < 0) || (region >= _regions.length())) ++ final String filename = String.format(GeoFormat.L2D.getFilename(), regionX, regionY); ++ ++ // standard load ++ try (RandomAccessFile raf = new RandomAccessFile(Config.GEODATA_PATH + filename, "r"); ++ FileChannel fc = raf.getChannel()) + { +- return null; ++ // initialize file buffer ++ final MappedByteBuffer buffer = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()).load(); ++ buffer.order(ByteOrder.LITTLE_ENDIAN); ++ ++ // 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++) ++ { ++ // get block type ++ final byte type = buffer.get(); ++ ++ // load block according to block type ++ switch (type) ++ { ++ case GeoStructure.TYPE_FLAT_L2D: ++ { ++ _blocks[blockX + ix][blockY + iy] = new BlockFlat(buffer, GeoFormat.L2D); ++ break; ++ } ++ case GeoStructure.TYPE_COMPLEX_L2D: ++ { ++ _blocks[blockX + ix][blockY + iy] = new BlockComplex(buffer, GeoFormat.L2D); ++ break; ++ } ++ case GeoStructure.TYPE_MULTILAYER_L2D: ++ { ++ _blocks[blockX + ix][blockY + iy] = new BlockMultilayer(buffer, GeoFormat.L2D); ++ break; ++ } ++ default: ++ { ++ throw new IllegalArgumentException("Unknown block type: " + type); ++ } ++ } ++ } ++ } ++ ++ // check data consistency ++ if (buffer.remaining() > 0) ++ { ++ LOGGER.warning("GeoEngine: Region file " + filename + " can be corrupted, remaining " + buffer.remaining() + " bytes to read."); ++ } ++ ++ // loading was successful ++ return true; + } +- return _regions.get(region); +- } +- +- /** +- * @param filePath +- * @param regionX +- * @param regionY +- * @throws IOException +- */ +- 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")) ++ catch (Exception e) + { +- _regions.set(regionOffset, new Region(raf.getChannel().map(MapMode.READ_ONLY, 0, raf.length()).load().order(ByteOrder.LITTLE_ENDIAN))); ++ // an error occured while loading, load null blocks ++ LOGGER.warning("GeoEngine: Error while loading " + filename + " region file."); ++ LOGGER.warning(e.getMessage()); ++ ++ // replace whole region file with null blocks ++ loadNullBlocks(regionX, regionY); ++ ++ // loading was not successful ++ return false; + } + } + + /** +- * @param regionX +- * @param regionY ++ * 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. + */ +- public void unloadRegion(int regionX, int regionY) ++ private final void loadNullBlocks(int regionX, int regionY) + { +- _regions.set((regionX * GEO_REGIONS_Y) + regionY, NullRegion.INSTANCE); +- } +- +- /** +- * @param geoX +- * @param geoY +- * @return if geodata exist +- */ +- public boolean hasGeoPos(int geoX, int geoY) +- { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) ++ // 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++) + { +- return false; ++ for (int iy = 0; iy < GeoStructure.REGION_BLOCKS_Y; iy++) ++ { ++ _blocks[blockX + ix][blockY + iy] = _nullBlock; ++ } + } +- return region.hasGeo(); + } + ++ // GEODATA - GENERAL ++ + /** +- * Checks the specified position for available geodata. +- * @param x the world x +- * @param y the world y +- * @return {@code true} if there is geodata for the given coordinates, {@code false} otherwise ++ * Converts world X to geodata X. ++ * @param worldX ++ * @return int : Geo X + */ +- public boolean hasGeo(int x, int y) ++ public static int getGeoX(int worldX) + { +- return hasGeoPos(getGeoX(x), getGeoY(y)); ++ return (MathUtil.limit(worldX, World.MAP_MIN_X, World.MAP_MAX_X) - World.MAP_MIN_X) >> 4; + } + + /** +- * @param geoX +- * @param geoY +- * @param worldZ +- * @param nswe +- * @return the nearest nswe check ++ * Converts world Y to geodata Y. ++ * @param worldY ++ * @return int : Geo Y + */ +- public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) ++ public static int getGeoY(int worldY) + { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) +- { +- return true; +- } +- return region.checkNearestNswe(geoX, geoY, worldZ, nswe); ++ return (MathUtil.limit(worldY, World.MAP_MIN_Y, World.MAP_MAX_Y) - World.MAP_MIN_Y) >> 4; + } + + /** ++ * Converts geodata X to world X. + * @param geoX +- * @param geoY +- * @param worldZ +- * @param nswe +- * @return the nearest nswe anti-corner cut ++ * @return int : World X + */ +- public boolean checkNearestNsweAntiCornerCut(int geoX, int geoY, int worldZ, int nswe) ++ public static int getWorldX(int geoX) + { +- boolean can = true; +- if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) +- { +- can = checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_EAST) && checkNearestNswe(geoX + 1, geoY, worldZ, Cell.NSWE_NORTH); +- } +- if (can && ((nswe & Cell.NSWE_NORTH_WEST) == Cell.NSWE_NORTH_WEST)) +- { +- 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 = 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 = checkNearestNswe(geoX, geoY + 1, worldZ, Cell.NSWE_WEST) && checkNearestNswe(geoX - 1, geoY, worldZ, Cell.NSWE_SOUTH); +- } +- return can && checkNearestNswe(geoX, geoY, worldZ, nswe); ++ return (MathUtil.limit(geoX, 0, GeoStructure.GEO_CELLS_X) << 4) + World.MAP_MIN_X + 8; + } + + /** +- * @param geoX ++ * Converts geodata Y to world Y. + * @param geoY +- * @param worldZ +- * @return the nearest Z value ++ * @return int : World Y + */ +- public int getNearestZ(int geoX, int geoY, int worldZ) ++ public static int getWorldY(int geoY) + { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) +- { +- return worldZ; +- } +- return region.getNearestZ(geoX, geoY, worldZ); ++ return (MathUtil.limit(geoY, 0, GeoStructure.GEO_CELLS_Y) << 4) + World.MAP_MIN_Y + 8; + } + + /** +- * @param geoX +- * @param geoY +- * @param worldZ +- * @return the next lower Z value ++ * Returns block of geodata on given coordinates. ++ * @param geoX : Geodata X ++ * @param geoY : Geodata Y ++ * @return {@link ABlock} : Block of geodata. + */ +- public int getNextLowerZ(int geoX, int geoY, int worldZ) ++ private final ABlock getBlock(int geoX, int geoY) + { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) ++ final int x = geoX / GeoStructure.BLOCK_CELLS_X; ++ final int y = geoY / GeoStructure.BLOCK_CELLS_Y; ++ ++ // if x or y is out of array return null ++ if ((x > -1) && (y > -1) && (x < GeoStructure.GEO_BLOCKS_X) && (y < GeoStructure.GEO_BLOCKS_Y)) + { +- return worldZ; ++ return _blocks[x][y]; + } +- return region.getNextLowerZ(geoX, geoY, worldZ); ++ return null; + } + + /** +- * @param geoX +- * @param geoY +- * @param worldZ +- * @return the next higher Z value ++ * Check if geo coordinates has geo. ++ * @param geoX : Geodata X ++ * @param geoY : Geodata Y ++ * @return boolean : True, if given geo coordinates have geodata + */ +- public int getNextHigherZ(int geoX, int geoY, int worldZ) ++ public boolean hasGeoPos(int geoX, int geoY) + { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) ++ final ABlock block = getBlock(geoX, geoY); ++ if (block == null) // null block check + { +- return worldZ; ++ // TODO: Find when this can be null. (Bad geodata? Check World getRegion method.) ++ // LOGGER.warning("Could not find geodata block at " + getWorldX(geoX) + ", " + getWorldY(geoY) + "."); ++ return false; + } +- return region.getNextHigherZ(geoX, geoY, worldZ); ++ return block.hasGeoPos(); + } + + /** +- * Gets the Z height. +- * @param x the world x +- * @param y the world y +- * @param z the world z +- * @return the nearest Z height ++ * 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 int getHeight(int x, int y, int z) ++ public short getHeightNearest(int geoX, int geoY, int worldZ) + { +- return getNearestZ(getGeoX(x), getGeoY(y), z); ++ final ABlock block = getBlock(geoX, geoY); ++ return block != null ? block.getHeightNearest(geoX, geoY, worldZ) : (short) worldZ; + } + + /** +- * Gets the next lower Z height. +- * @param x the world x +- * @param y the world y +- * @param z the world z +- * @return the nearest Z height ++ * 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 int getLowerHeight(int x, int y, int z) ++ public byte getNsweNearest(int geoX, int geoY, int worldZ) + { +- return getNextLowerZ(getGeoX(x), getGeoY(y), z); ++ final ABlock block = getBlock(geoX, geoY); ++ return block != null ? block.getNsweNearest(geoX, geoY, worldZ) : (byte) 0xFF; + } + + /** +- * Gets the next higher Z height. +- * @param x the world x +- * @param y the world y +- * @param z the world z +- * @return the nearest Z height ++ * Check if world coordinates has geo. ++ * @param worldX : World X ++ * @param worldY : World Y ++ * @return boolean : True, if given world coordinates have geodata + */ +- public int getHigherHeight(int x, int y, int z) ++ public boolean hasGeo(int worldX, int worldY) + { +- return getNextHigherZ(getGeoX(x), getGeoY(y), z); ++ return hasGeoPos(getGeoX(worldX), getGeoY(worldY)); + } + + /** +- * @param worldX +- * @return the geo X ++ * 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 int getGeoX(int worldX) ++ public short getHeight(int worldX, int worldY, int worldZ) + { +- return (worldX - WORLD_MIN_X) / 16; ++ return getHeightNearest(getGeoX(worldX), getGeoY(worldY), worldZ); + } + +- /** +- * @param worldY +- * @return the geo Y +- */ +- public int getGeoY(int worldY) +- { +- return (worldY - WORLD_MIN_Y) / 16; +- } ++ // PATHFINDING + + /** +- * @param worldZ +- * @return the geo Z ++ * Check line of sight from {@link WorldObject} to {@link WorldObject}. ++ * @param origin : The origin object. ++ * @param target : The target object. ++ * @return {@code boolean} : True if origin can see target + */ +- public int getGeoZ(int worldZ) ++ public boolean canSeeTarget(WorldObject origin, WorldObject target) + { +- return (worldZ - WORLD_MIN_Z) / 16; +- } +- +- /** +- * @param geoX +- * @return the world X +- */ +- public int getWorldX(int geoX) +- { +- return (geoX * 16) + WORLD_MIN_X + 8; +- } +- +- /** +- * @param geoY +- * @return the world Y +- */ +- public int getWorldY(int geoY) +- { +- return (geoY * 16) + WORLD_MIN_Y + 8; +- } +- +- /** +- * @param geoZ +- * @return the world Z +- */ +- public int getWorldZ(int geoZ) +- { +- return (geoZ * 16) + WORLD_MIN_Z + 8; +- } +- +- /** +- * 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) +- { +- if (target.isDoor()) ++ if (target.isDoor() || target.isArtefact() || (target.isCreature() && ((Creature) target).isFlying())) + { +- // Can always see doors. + return true; + } +- return 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(), cha.getInstanceWorld(), worldPosition.getX(), worldPosition.getY(), worldPosition.getZ()); +- } +- +- /** +- * Can see target. Checks doors between. +- * @param x the x coordinate +- * @param y the y coordinate +- * @param z the z coordinate +- * @param world +- * @param tx the target's x coordinate +- * @param ty the target's y coordinate +- * @param tz the target's z coordinate +- * @param tworld the target's instanceId +- * @return +- */ +- public boolean canSeeTarget(int x, int y, int z, Instance world, int tx, int ty, int tz, Instance tworld) +- { +- return (world != tworld) ? false : canSeeTarget(x, y, z, world, tx, ty, tz); +- } +- +- /** +- * 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 +- * @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, Instance instance, int tx, int ty, int tz) +- { +- if (DoorData.getInstance().checkIfDoorsBetween(x, y, z, tx, ty, tz, instance, true)) ++ ++ // get origin and target world coordinates ++ final int ox = origin.getX(); ++ final int oy = origin.getY(); ++ final int oz = origin.getZ(); ++ final int tx = target.getX(); ++ final int ty = target.getY(); ++ final int tz = target.getZ(); ++ ++ if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld(), true)) + { + return false; + } +- return canSeeTarget(x, y, z, tx, ty, tz); +- } +- +- /** +- * @param prevX +- * @param prevY +- * @param prevGeoZ +- * @param curX +- * @param curY +- * @param nswe +- * @return the LOS Z value +- */ +- 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))) ++ if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld())) + { +- throw new RuntimeException("Multiple directions!"); ++ return false; + } ++ if (WarpedSpaceManager.getInstance().checkForWarpedSpace(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld())) ++ { ++ return false; ++ } + +- if (checkNearestNsweAntiCornerCut(prevX, prevY, prevGeoZ, nswe)) ++ // get origin and check existing geo coordinates ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) + { +- return getNearestZ(curX, curY, prevGeoZ); ++ return true; + } + +- return getNextHigherZ(curX, curY, prevGeoZ); ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coordinates ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) ++ { ++ return true; ++ } ++ ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // origin and target coordinates are same ++ if ((gox == gtx) && (goy == gty)) ++ { ++ return goz == gtz; ++ } ++ ++ // get origin and target height, real height = collision height * 2 ++ double oheight = 0; ++ if (origin.isCreature()) ++ { ++ oheight = ((Creature) origin).getCollisionHeight() * 2; ++ } ++ ++ double theight = 0; ++ if (target.isCreature()) ++ { ++ theight = ((Creature) target).getCollisionHeight() * 2; ++ } ++ ++ // perform geodata check ++ return checkSee(gox, goy, goz, oheight, gtx, gty, gtz, theight, origin.getInstanceWorld()); + } + + /** +- * Can see target. Does not check doors between. +- * @param xValue the x coordinate +- * @param yValue the y coordinate +- * @param zValue the z coordinate +- * @param txValue the target's x coordinate +- * @param tyValue the target's y coordinate +- * @param tzValue the target's z coordinate +- * @return {@code true} if there is line of sight between the given coordinate sets, {@code false} otherwise ++ * Check line of sight from {@link WorldObject} to {@link Location}. ++ * @param origin : The origin object. ++ * @param position : The target position. ++ * @return {@code boolean} : True if object can see position + */ +- public boolean canSeeTarget(int xValue, int yValue, int zValue, int txValue, int tyValue, int tzValue) ++ public boolean canSeeTarget(WorldObject origin, Location position) + { +- int x = xValue; +- int y = yValue; +- int tx = txValue; +- int ty = tyValue; ++ // get origin and target world coordinates ++ final int ox = origin.getX(); ++ final int oy = origin.getY(); ++ final int oz = origin.getZ(); ++ final int tx = position.getX(); ++ final int ty = position.getY(); ++ final int tz = position.getZ(); + +- int geoX = getGeoX(x); +- int geoY = getGeoY(y); +- int tGeoX = getGeoX(tx); +- int tGeoY = getGeoY(ty); ++ if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld(), true)) ++ { ++ return false; ++ } ++ if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld())) ++ { ++ return false; ++ } ++ if (WarpedSpaceManager.getInstance().checkForWarpedSpace(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld())) ++ { ++ return false; ++ } + +- int z = getNearestZ(geoX, geoY, zValue); +- int tz = getNearestZ(tGeoX, tGeoY, tzValue); ++ // get origin and check existing geo coordinates ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) ++ { ++ return true; ++ } + +- // Fastpath. +- if ((geoX == tGeoX) && (geoY == tGeoY)) ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coordinates ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) + { +- if (hasGeoPos(tGeoX, tGeoY)) +- { +- return z == tz; +- } + return true; + } + +- if (tz > z) ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // origin and target coordinates are same ++ if ((gox == gtx) && (goy == gty)) + { +- int tmp = tx; +- tx = x; +- x = tmp; +- +- tmp = ty; +- ty = y; +- y = tmp; +- +- tmp = tz; +- tz = z; +- z = tmp; +- +- tmp = tGeoX; +- tGeoX = geoX; +- geoX = tmp; +- +- tmp = tGeoY; +- tGeoY = geoY; +- geoY = tmp; ++ return goz == gtz; + } + +- final LinePointIterator3D pointIter = new LinePointIterator3D(geoX, geoY, z, tGeoX, tGeoY, tz); +- // 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()) ++ // get origin and target height, real height = collision height * 2 ++ double oheight = 0; ++ if (origin.isCreature()) + { +- final int curX = pointIter.x(); +- final int curY = pointIter.y(); ++ oheight = ((Creature) origin).getTemplate().getCollisionHeight(); ++ } ++ ++ // perform geodata check ++ return checkSee(gox, goy, goz, oheight, gtx, gty, gtz, 0, origin.getInstanceWorld()); ++ } ++ ++ /** ++ * Simple check for origin to target visibility. ++ * @param goxValue : origin X geodata coordinate ++ * @param goyValue : origin Y geodata coordinate ++ * @param gozValue : origin Z geodata coordinate ++ * @param oheight : origin height (if instance of {@link Character}) ++ * @param gtxValue : target X geodata coordinate ++ * @param gtyValue : target Y geodata coordinate ++ * @param gtzValue : target Z geodata coordinate ++ * @param theight : target height (if instance of {@link Character}) ++ * @param instance ++ * @return {@code boolean} : True, when target can be seen. ++ */ ++ private final boolean checkSee(int goxValue, int goyValue, int gozValue, double oheight, int gtxValue, int gtyValue, int gtzValue, double theight, Instance instance) ++ { ++ int goz = gozValue; ++ int gtz = gtzValue; ++ int gox = goxValue; ++ int goy = goyValue; ++ int gtx = gtxValue; ++ int gty = gtyValue; ++ ++ // get line of sight Z coordinates ++ double losoz = goz + ((oheight * Config.PART_OF_CHARACTER_HEIGHT) / 100); ++ double lostz = gtz + ((theight * Config.PART_OF_CHARACTER_HEIGHT) / 100); ++ ++ // get X delta and signum ++ final int dx = Math.abs(gtx - gox); ++ final int sx = gox < gtx ? 1 : -1; ++ final byte dirox = sx > 0 ? GeoStructure.CELL_FLAG_E : GeoStructure.CELL_FLAG_W; ++ final byte dirtx = sx > 0 ? GeoStructure.CELL_FLAG_W : GeoStructure.CELL_FLAG_E; ++ ++ // get Y delta and signum ++ final int dy = Math.abs(gty - goy); ++ final int sy = goy < gty ? 1 : -1; ++ final byte diroy = sy > 0 ? GeoStructure.CELL_FLAG_S : GeoStructure.CELL_FLAG_N; ++ final byte dirty = sy > 0 ? GeoStructure.CELL_FLAG_N : GeoStructure.CELL_FLAG_S; ++ ++ // get Z delta ++ final int dm = Math.max(dx, dy); ++ final double dz = (lostz - losoz) / dm; ++ ++ // get direction flag for diagonal movement ++ final byte diroxy = getDirXY(dirox, diroy); ++ final byte dirtxy = getDirXY(dirtx, dirty); ++ ++ // delta, determines axis to move on (+..X axis, -..Y axis) ++ int d = dx - dy; ++ ++ // NSWE direction of movement ++ byte diro; ++ byte dirt; ++ ++ // initialize node values ++ int nox = gox; ++ int noy = goy; ++ int ntx = gtx; ++ int nty = gty; ++ byte nsweo = getNsweNearest(gox, goy, goz); ++ byte nswet = getNsweNearest(gtx, gty, gtz); ++ ++ // loop ++ ABlock block; ++ int index; ++ for (int i = 0; i < ((dm + 1) / 2); i++) ++ { ++ // reset direction flag ++ diro = 0; ++ dirt = 0; + +- if ((curX == prevX) && (curY == prevY)) ++ // calculate next point coordinates ++ final int e2 = 2 * d; ++ if ((e2 > -dy) && (e2 < dx)) + { +- continue; ++ // calculate next point XY coordinates ++ d -= dy; ++ d += dx; ++ nox += sx; ++ ntx -= sx; ++ noy += sy; ++ nty -= sy; ++ diro |= diroxy; ++ dirt |= dirtxy; + } ++ else if (e2 > -dy) ++ { ++ // calculate next point X coordinate ++ d -= dy; ++ nox += sx; ++ ntx -= sx; ++ diro |= dirox; ++ dirt |= dirtx; ++ } ++ else if (e2 < dx) ++ { ++ // calculate next point Y coordinate ++ d += dx; ++ noy += sy; ++ nty -= sy; ++ diro |= diroy; ++ dirt |= dirty; ++ } + +- final int beeCurZ = pointIter.z(); +- int curGeoZ = prevGeoZ; +- +- // Check if the position has geodata. +- if (hasGeoPos(curX, curY)) + { +- final int beeCurGeoZ = getNearestZ(curX, curY, beeCurZ); +- final int nswe = GeoUtils.computeNswe(prevX, prevY, curX, curY); // .computeDirection(prevX, prevY, curX, curY); +- curGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, curX, curY, nswe); +- int maxHeight; +- if (ptIndex < ELEVATED_SEE_OVER_DISTANCE) ++ // get block of the next cell ++ block = getBlock(nox, noy); ++ ++ // get index of particular layer, based on movement conditions ++ if ((nsweo & diro) == 0) + { +- maxHeight = z + MAX_SEE_OVER_HEIGHT; ++ index = block.getIndexAbove(nox, noy, goz - GeoStructure.CELL_IGNORE_HEIGHT); + } + else + { +- maxHeight = beeCurZ + MAX_SEE_OVER_HEIGHT; ++ index = block.getIndexBelow(nox, noy, goz + GeoStructure.CELL_IGNORE_HEIGHT); + } + +- boolean canSeeThrough = false; +- if ((curGeoZ <= maxHeight) && (curGeoZ <= beeCurGeoZ)) ++ // layer does not exist, return ++ if (index == -1) + { +- if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) +- { +- 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 +- { +- canSeeThrough = true; +- } ++ return false; + } + +- if (!canSeeThrough) ++ // get layer and next line of sight Z coordinate ++ goz = block.getHeight(index); ++ losoz += dz; ++ ++ // perform line of sight check, return when fails ++ if ((goz - losoz) > Config.MAX_OBSTACLE_HEIGHT) + { + return false; + } ++ ++ // get layer nswe ++ nsweo = block.getNswe(index); + } ++ { ++ // get block of the next cell ++ block = getBlock(ntx, nty); ++ ++ // get index of particular layer, based on movement conditions ++ if ((nswet & dirt) == 0) ++ { ++ index = block.getIndexAbove(ntx, nty, gtz - GeoStructure.CELL_IGNORE_HEIGHT); ++ } ++ else ++ { ++ index = block.getIndexBelow(ntx, nty, gtz + GeoStructure.CELL_IGNORE_HEIGHT); ++ } ++ ++ // layer does not exist, return ++ if (index == -1) ++ { ++ return false; ++ } ++ ++ // get layer and next line of sight Z coordinate ++ gtz = block.getHeight(index); ++ lostz -= dz; ++ ++ // perform line of sight check, return when fails ++ if ((gtz - lostz) > Config.MAX_OBSTACLE_HEIGHT) ++ { ++ return false; ++ } ++ ++ // get layer nswe ++ nswet = block.getNswe(index); ++ } + +- prevX = curX; +- prevY = curY; +- prevGeoZ = curGeoZ; +- ++ptIndex; ++ // update coords ++ gox = nox; ++ goy = noy; ++ gtx = ntx; ++ gty = nty; + } + +- return true; ++ // when iteration is completed, compare final Z coordinates ++ return Math.abs(goz - gtz) < (GeoStructure.CELL_HEIGHT * 4); + } + + /** +- * Move check. +- * @param x the x coordinate +- * @param y the y coordinate +- * @param zValue the z coordinate +- * @param tx the target's x coordinate +- * @param ty the target's y coordinate +- * @param tzValue the target's z coordinate +- * @param instance the instance +- * @return the last Location (x,y,z) where player can walk - just before wall ++ * Check movement from coordinates to 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 {code boolean} : True if target coordinates are reachable from origin coordinates + */ +- public Location canMoveToTargetLoc(int x, int y, int zValue, int tx, int ty, int tzValue, Instance instance) ++ public boolean canMoveToTarget(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + { +- final int geoX = getGeoX(x); +- final int geoY = getGeoY(y); +- final int z = getNearestZ(geoX, geoY, zValue); +- final int tGeoX = getGeoX(tx); +- final int tGeoY = getGeoY(ty); +- final int tz = getNearestZ(tGeoX, tGeoY, tzValue); ++ // get origin and check existing geo coordinates ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) ++ { ++ return true; ++ } + +- if (DoorData.getInstance().checkIfDoorsBetween(x, y, z, tx, ty, tz, instance, false)) ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coordinates ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) + { +- return new Location(x, y, getHeight(x, y, z)); ++ return true; + } +- if (FenceData.getInstance().checkIfFenceBetween(x, y, z, tx, ty, tz, instance)) ++ ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // target coordinates reached ++ if ((gox == gtx) && (goy == gty) && (goz == gtz)) + { +- return new Location(x, y, getHeight(x, y, z)); ++ return true; + } +- if (WarpedSpaceManager.getInstance().checkForWarpedSpace(x, y, z, tx, ty, tz, instance)) ++ ++ // perform geodata check ++ final GeoLocation loc = checkMove(gox, goy, goz, gtx, gty, gtz, instance); ++ return (loc.getGeoX() == gtx) && (loc.getGeoY() == gty); ++ } ++ ++ /** ++ * Check movement from origin to target. Returns last available point in the checked path. ++ * @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 {@link Location} : Last point where object can walk (just before wall) ++ */ ++ public Location canMoveToTargetLoc(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) ++ { ++ // Mobius: Double check for doors before normal checkMove to avoid exploiting key movement. ++ if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, instance, false)) + { +- return new Location(x, y, getHeight(x, y, z)); ++ return new Location(ox, oy, oz); + } ++ if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, instance)) ++ { ++ return new Location(ox, oy, oz); ++ } + +- 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 = z; ++ // get origin and check existing geo coordinates ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) ++ { ++ return new Location(tx, ty, tz); ++ } + +- while (pointIter.next()) ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coordinates ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) + { +- final int curX = pointIter.x(); +- final int curY = pointIter.y(); +- final int curZ = getNearestZ(curX, curY, prevZ); +- +- if (hasGeoPos(prevX, prevY)) +- { +- final int nswe = GeoUtils.computeNswe(prevX, prevY, curX, curY); +- if (!checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, nswe)) +- { +- // Can't move, return previous location. +- return new Location(getWorldX(prevX), getWorldY(prevY), prevZ); +- } +- } +- +- prevX = curX; +- prevY = curY; +- prevZ = curZ; ++ return new Location(tx, ty, tz); + } + +- if (hasGeoPos(prevX, prevY) && (prevZ != tz)) ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // target coordinates reached ++ if ((gox == gtx) && (goy == gty) && (goz == gtz)) + { +- // Different floors, return start location. +- return new Location(x, y, z); ++ return new Location(tx, ty, tz); + } + +- return new Location(tx, ty, tz); ++ // perform geodata check ++ return checkMove(gox, goy, goz, gtx, gty, gtz, instance); + } + + /** +- * 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 fromZvalue 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 toZvalue 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 ++ * With this method you can check if a position is visible or can be reached by beeline movement.
++ * 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 gox : origin X geodata coordinate ++ * @param goy : origin Y geodata coordinate ++ * @param goz : origin Z geodata coordinate ++ * @param gtx : target X geodata coordinate ++ * @param gty : target Y geodata coordinate ++ * @param gtz : target Z geodata coordinate ++ * @param instance ++ * @return {@link GeoLocation} : The last allowed point of movement. + */ +- public boolean canMoveToTarget(int fromX, int fromY, int fromZvalue, int toX, int toY, int toZvalue, Instance instance) ++ protected final GeoLocation checkMove(int gox, int goy, int goz, int gtx, int gty, int gtz, Instance instance) + { +- final int geoX = getGeoX(fromX); +- final int geoY = getGeoY(fromY); +- final int fromZ = getNearestZ(geoX, geoY, fromZvalue); +- final int tGeoX = getGeoX(toX); +- final int tGeoY = getGeoY(toY); +- final int toZ = getNearestZ(tGeoX, tGeoY, toZvalue); +- +- if (DoorData.getInstance().checkIfDoorsBetween(fromX, fromY, fromZ, toX, toY, toZ, instance, false)) ++ if (DoorData.getInstance().checkIfDoorsBetween(gox, goy, goz, gtx, gty, gtz, instance, false)) + { +- return false; ++ return new GeoLocation(gox, goy, goz); + } +- if (FenceData.getInstance().checkIfFenceBetween(fromX, fromY, fromZ, toX, toY, toZ, instance)) ++ if (FenceData.getInstance().checkIfFenceBetween(gox, goy, goz, gtx, gty, gtz, instance)) + { +- return false; ++ return new GeoLocation(gox, goy, goz); + } +- if (WarpedSpaceManager.getInstance().checkForWarpedSpace(fromX, fromY, fromZ, toX, toY, toZ, instance)) ++ if (WarpedSpaceManager.getInstance().checkForWarpedSpace(gox, goy, goz, gtx, gty, gtz, instance)) + { +- return false; ++ return new GeoLocation(gox, goy, goz); + } + +- 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 = fromZ; ++ // get X delta, signum and direction flag ++ final int dx = Math.abs(gtx - gox); ++ final int sx = gox < gtx ? 1 : -1; ++ final byte dirX = sx > 0 ? GeoStructure.CELL_FLAG_E : GeoStructure.CELL_FLAG_W; + +- while (pointIter.next()) ++ // get Y delta, signum and direction flag ++ final int dy = Math.abs(gty - goy); ++ final int sy = goy < gty ? 1 : -1; ++ final byte dirY = sy > 0 ? GeoStructure.CELL_FLAG_S : GeoStructure.CELL_FLAG_N; ++ ++ // get direction flag for diagonal movement ++ final byte dirXY = getDirXY(dirX, dirY); ++ ++ // delta, determines axis to move on (+..X axis, -..Y axis) ++ int d = dx - dy; ++ ++ // NSWE direction of movement ++ byte direction; ++ ++ // load pointer coordinates ++ int gpx = gox; ++ int gpy = goy; ++ int gpz = goz; ++ ++ // load next pointer ++ int nx = gpx; ++ int ny = gpy; ++ ++ // loop ++ int count = 0; ++ while (count++ < Config.MAX_ITERATIONS) + { +- final int curX = pointIter.x(); +- final int curY = pointIter.y(); +- final int curZ = getNearestZ(curX, curY, prevZ); ++ direction = 0; + +- if (hasGeoPos(prevX, prevY)) ++ // calculate next point coordinates ++ final int e2 = 2 * d; ++ if ((e2 > -dy) && (e2 < dx)) + { +- final int nswe = GeoUtils.computeNswe(prevX, prevY, curX, curY); +- if (!checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, nswe)) ++ d -= dy; ++ d += dx; ++ nx += sx; ++ ny += sy; ++ direction |= dirXY; ++ } ++ else if (e2 > -dy) ++ { ++ d -= dy; ++ nx += sx; ++ direction |= dirX; ++ } ++ else if (e2 < dx) ++ { ++ d += dx; ++ ny += sy; ++ direction |= dirY; ++ } ++ ++ // obstacle found, return ++ if ((getNsweNearest(gpx, gpy, gpz) & direction) == 0) ++ { ++ return new GeoLocation(gpx, gpy, gpz); ++ } ++ ++ // update pointer coordinates ++ gpx = nx; ++ gpy = ny; ++ gpz = getHeightNearest(nx, ny, gpz); ++ ++ // target coordinates reached ++ if ((gpx == gtx) && (gpy == gty)) ++ { ++ if (gpz == gtz) + { +- return false; ++ // path found, Z coordinates are okay, return target point ++ return new GeoLocation(gtx, gty, gtz); + } ++ ++ // path found, Z coordinates are not okay, return last good point ++ return new GeoLocation(gpx, gpy, gpz); + } ++ } ++ ++ return new GeoLocation(gox, goy, goz); ++ } ++ ++ /** ++ * Returns diagonal NSWE flag format of combined two NSWE flags. ++ * @param dirX : X direction NSWE flag ++ * @param dirY : Y direction NSWE flag ++ * @return byte : NSWE flag of combined direction ++ */ ++ private static byte getDirXY(byte dirX, byte dirY) ++ { ++ // check axis directions ++ if (dirY == GeoStructure.CELL_FLAG_N) ++ { ++ if (dirX == GeoStructure.CELL_FLAG_W) ++ { ++ return GeoStructure.CELL_FLAG_NW; ++ } + +- prevX = curX; +- prevY = curY; +- prevZ = curZ; ++ return GeoStructure.CELL_FLAG_NE; + } + +- if (hasGeoPos(prevX, prevY) && (prevZ != toZ)) ++ if (dirX == GeoStructure.CELL_FLAG_W) + { +- // Different floors. +- return false; ++ return GeoStructure.CELL_FLAG_SW; + } + +- return true; ++ return GeoStructure.CELL_FLAG_SE; + } + ++ /** ++ * 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 ++ */ ++ public List findPath(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) ++ { ++ return null; ++ } ++ ++ /** ++ * Returns the instance of the {@link GeoEngine}. ++ * @return {@link GeoEngine} : The instance. ++ */ + public static GeoEngine getInstance() + { + return SingletonHolder.INSTANCE; +@@ -752,6 +947,6 @@ + + private static class SingletonHolder + { +- protected static final GeoEngine INSTANCE = new GeoEngine(); ++ protected static final GeoEngine INSTANCE = Config.PATHFINDING ? new GeoEnginePathfinding() : new GeoEngine(); + } +-} ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/GeoEnginePathfinding.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/GeoEnginePathfinding.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/GeoEnginePathfinding.java (working copy) +@@ -20,88 +20,84 @@ + import java.util.LinkedList; + import java.util.List; + import java.util.ListIterator; +-import java.util.logging.Level; +-import java.util.logging.Logger; + + import org.l2jmobius.Config; +-import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; +-import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +-import org.l2jmobius.gameserver.geoengine.pathfinding.CellNode; +-import org.l2jmobius.gameserver.geoengine.pathfinding.CellNodeBuffer; +-import org.l2jmobius.gameserver.geoengine.pathfinding.NodeLoc; +-import org.l2jmobius.gameserver.model.World; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoLocation; ++import org.l2jmobius.gameserver.geoengine.pathfinding.Node; ++import org.l2jmobius.gameserver.geoengine.pathfinding.NodeBuffer; ++import org.l2jmobius.gameserver.model.Location; + import org.l2jmobius.gameserver.model.instancezone.Instance; + + /** +- * @author -Nemesiss- ++ * @author Hasha + */ +-public class GeoEnginePathfinding ++final class GeoEnginePathfinding extends GeoEngine + { +- private static final Logger LOGGER = Logger.getLogger(GeoEnginePathfinding.class.getName()); ++ // pre-allocated buffers ++ private final BufferHolder[] _buffers; + +- private BufferInfo[] _buffers; +- + protected GeoEnginePathfinding() + { +- try ++ super(); ++ ++ final String[] array = Config.PATHFIND_BUFFERS.split(";"); ++ _buffers = new BufferHolder[array.length]; ++ int count = 0; ++ for (int i = 0; i < array.length; i++) + { +- final String[] array = Config.PATHFIND_BUFFERS.split(";"); ++ final String buf = array[i]; ++ final String[] args = buf.split("x"); + +- _buffers = new BufferInfo[array.length]; +- +- String buf; +- String[] args; +- for (int i = 0; i < array.length; i++) ++ try + { +- buf = array[i]; +- args = buf.split("x"); +- if (args.length != 2) +- { +- throw new Exception("Invalid buffer definition: " + buf); +- } +- +- _buffers[i] = new BufferInfo(Integer.parseInt(args[0]), Integer.parseInt(args[1])); ++ final int size = Integer.parseInt(args[1]); ++ count += size; ++ _buffers[i] = new BufferHolder(Integer.parseInt(args[0]), size); + } ++ catch (Exception e) ++ { ++ LOGGER.warning("GeoEnginePathfinding: Can not load buffer setting: " + buf); ++ } + } +- catch (Exception e) +- { +- LOGGER.log(Level.WARNING, "CellPathFinding: Problem during buffer init: " + e.getMessage(), e); +- throw new Error("CellPathFinding: load aborted"); +- } ++ ++ LOGGER.info("GeoEnginePathfinding: Loaded " + count + " node buffers."); + } + +- public boolean pathNodesExist(short regionoffset) ++ @Override ++ public List findPath(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + { +- return false; +- } +- +- public List findPath(int x, int y, int z, int tx, int ty, int tz, Instance instance) +- { +- final int gx = GeoEngine.getInstance().getGeoX(x); +- final int gy = GeoEngine.getInstance().getGeoY(y); +- if (!GeoEngine.getInstance().hasGeo(x, y)) ++ // get origin and check existing geo coords ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) + { + 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)) ++ ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coords ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) + { + 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)))); ++ ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // Prepare buffer for pathfinding calculations ++ final NodeBuffer buffer = getBuffer(64 + (2 * Math.max(Math.abs(gox - gtx), Math.abs(goy - gty)))); + if (buffer == null) + { + return null; + } + +- List path = null; ++ // find path ++ List path = null; + try + { +- final CellNode result = buffer.findPath(gx, gy, gz, gtx, gty, gtz); +- ++ final Node result = buffer.findPath(gox, goy, goz, gtx, gty, gtz); + if (result == null) + { + return null; +@@ -125,33 +121,45 @@ + return path; + } + +- int currentX, currentY, currentZ; +- ListIterator middlePoint; ++ // get path list iterator ++ final ListIterator point = path.listIterator(); + +- middlePoint = path.listIterator(); +- currentX = x; +- currentY = y; +- currentZ = z; ++ // get node A (origin) ++ int nodeAx = gox; ++ int nodeAy = goy; ++ short nodeAz = goz; + +- while (middlePoint.hasNext()) ++ // get node B ++ GeoLocation nodeB = (GeoLocation) point.next(); ++ ++ // iterate thought the path to optimize it ++ int count = 0; ++ while (point.hasNext() && (count++ < Config.MAX_ITERATIONS)) + { +- final AbstractNodeLoc locMiddle = middlePoint.next(); +- if (!middlePoint.hasNext()) +- { +- break; +- } ++ // get node C ++ final GeoLocation nodeC = (GeoLocation) path.get(point.nextIndex()); + +- final AbstractNodeLoc locEnd = path.get(middlePoint.nextIndex()); +- if (GeoEngine.getInstance().canMoveToTarget(currentX, currentY, currentZ, locEnd.getX(), locEnd.getY(), locEnd.getZ(), instance)) ++ // check movement from node A to node C ++ final GeoLocation loc = checkMove(nodeAx, nodeAy, nodeAz, nodeC.getGeoX(), nodeC.getGeoY(), nodeC.getZ(), instance); ++ if ((loc.getGeoX() == nodeC.getGeoX()) && (loc.getGeoY() == nodeC.getGeoY())) + { +- middlePoint.remove(); ++ // can move from node A to node C ++ ++ // remove node B ++ point.remove(); + } + else + { +- currentX = locMiddle.getX(); +- currentY = locMiddle.getY(); +- currentZ = locMiddle.getZ(); ++ // can not move from node A to node C ++ ++ // set node A (node B is part of path, update A coordinates) ++ nodeAx = nodeB.getGeoX(); ++ nodeAy = nodeB.getGeoY(); ++ nodeAz = (short) nodeB.getZ(); + } ++ ++ // set node B ++ nodeB = (GeoLocation) point.next(); + } + + return path; +@@ -158,144 +166,102 @@ + } + + /** +- * Convert geodata position to pathnode position +- * @param geo_pos +- * @return pathnode position ++ * Create list of node locations as result of calculated buffer node tree. ++ * @param node : the entry point ++ * @return List : list of node location + */ +- public short getNodePos(int geo_pos) ++ private static List constructPath(Node node) + { +- return (short) (geo_pos >> 3); // OK? +- } +- +- /** +- * Convert node position to pathnode block position +- * @param node_pos +- * @return pathnode block position (0...255) +- */ +- public short getNodeBlock(int node_pos) +- { +- return (short) (node_pos % 256); +- } +- +- public byte getRegionX(int node_pos) +- { +- return (byte) ((node_pos >> 8) + World.TILE_X_MIN); +- } +- +- public byte getRegionY(int node_pos) +- { +- return (byte) ((node_pos >> 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 node_x rx +- * @return +- */ +- public int calculateWorldX(short node_x) +- { +- return World.MAP_MIN_X + (node_x * 128) + 48; +- } +- +- /** +- * Convert pathnode y to World y position +- * @param node_y +- * @return +- */ +- public int calculateWorldY(short node_y) +- { +- return World.MAP_MIN_Y + (node_y * 128) + 48; +- } +- +- private List constructPath(AbstractNode nodeValue) +- { +- final LinkedList path = new LinkedList<>(); +- int previousDirectionX = Integer.MIN_VALUE; +- int previousDirectionY = Integer.MIN_VALUE; +- int directionX, directionY; ++ // create empty list ++ final LinkedList list = new LinkedList<>(); + +- AbstractNode node = nodeValue; +- while (node.getParent() != null) ++ // set direction X/Y ++ int dx = 0; ++ int dy = 0; ++ ++ // get target parent ++ Node target = node; ++ Node parent = target.getParent(); ++ ++ // while parent exists ++ int count = 0; ++ while ((parent != null) && (count++ < Config.MAX_ITERATIONS)) + { +- directionX = node.getLoc().getNodeX() - node.getParent().getLoc().getNodeX(); +- directionY = node.getLoc().getNodeY() - node.getParent().getLoc().getNodeY(); ++ // get parent <> target direction X/Y ++ final int nx = parent.getLoc().getGeoX() - target.getLoc().getGeoX(); ++ final int ny = parent.getLoc().getGeoY() - target.getLoc().getGeoY(); + +- // only add a new route point if moving direction changes +- if ((directionX != previousDirectionX) || (directionY != previousDirectionY)) ++ // direction has changed? ++ if ((dx != nx) || (dy != ny)) + { +- previousDirectionX = directionX; +- previousDirectionY = directionY; ++ // add node to the beginning of the list ++ list.addFirst(target.getLoc()); + +- path.addFirst(node.getLoc()); +- node.setLoc(null); ++ // update direction X/Y ++ dx = nx; ++ dy = ny; + } + +- node = node.getParent(); ++ // move to next node, set target and get its parent ++ target = parent; ++ parent = target.getParent(); + } + +- return path; ++ // return list ++ return list; + } + +- private CellNodeBuffer alloc(int size) ++ /** ++ * Provides optimize selection of the buffer. When all pre-initialized buffer are locked, creates new buffer. ++ * @param size : pre-calculated minimal required size ++ * @return NodeBuffer : buffer ++ */ ++ private final NodeBuffer getBuffer(int size) + { +- CellNodeBuffer current = null; +- for (BufferInfo i : _buffers) ++ NodeBuffer current = null; ++ for (BufferHolder holder : _buffers) + { +- if (i.mapSize >= size) ++ // Find proper size of buffer ++ if (holder._size < size) + { +- for (CellNodeBuffer buf : i.buffer) ++ continue; ++ } ++ ++ // Find unlocked NodeBuffer ++ for (NodeBuffer buffer : holder._buffer) ++ { ++ if (!buffer.isLocked()) + { +- if (buf.lock()) +- { +- current = buf; +- break; +- } ++ continue; + } +- if (current != null) +- { +- break; +- } + +- // not found, allocate temporary buffer +- current = new CellNodeBuffer(i.mapSize); +- current.lock(); +- if (i.buffer.size() < i.count) +- { +- i.buffer.add(current); +- break; +- } ++ return buffer; + } ++ ++ // NodeBuffer not found, allocate temporary buffer ++ current = new NodeBuffer(holder._size); ++ current.isLocked(); + } + + return current; + } + +- private static final class BufferInfo ++ /** ++ * NodeBuffer container with specified size and count of separate buffers. ++ */ ++ private static final class BufferHolder + { +- final int mapSize; +- final int count; +- ArrayList buffer; ++ final int _size; ++ List _buffer; + +- public BufferInfo(int size, int cnt) ++ public BufferHolder(int size, int count) + { +- mapSize = size; +- count = cnt; +- buffer = new ArrayList<>(count); ++ _size = size; ++ _buffer = new ArrayList<>(count); ++ for (int i = 0; i < count; i++) ++ { ++ _buffer.add(new NodeBuffer(size)); ++ } + } + } +- +- public static GeoEnginePathfinding getInstance() +- { +- return SingletonHolder.INSTANCE; +- } +- +- private static class SingletonHolder +- { +- protected static final GeoEnginePathfinding INSTANCE = new GeoEnginePathfinding(); +- } +-} ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java (working copy) +@@ -0,0 +1,197 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++import java.io.IOException; ++ ++/** ++ * @author Hasha ++ */ ++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 height of cell, which is closest to given coordinates.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @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 getHeightNearestOriginal(int geoX, int geoY, int worldZ); ++ ++ /** ++ * Returns the height of cell, which is first above 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, above given coordinates. ++ */ ++ public abstract short getHeightAbove(int geoX, int geoY, int worldZ); ++ ++ /** ++ * Returns the height of cell, which is first below 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, below given coordinates. ++ */ ++ public abstract short getHeightBelow(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 the NSWE flag byte of cell, which is closest to given coordinates.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @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 getNsweNearestOriginal(int geoX, int geoY, int worldZ); ++ ++ /** ++ * Returns the NSWE flag byte of cell, which is first above 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 getNsweAbove(int geoX, int geoY, int worldZ); ++ ++ /** ++ * Returns the NSWE flag byte of cell, which is first below 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 getNsweBelow(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 above given coordinates.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @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 getIndexAboveOriginal(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 index to data of the cell, which is first layer below given coordinates.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @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 getIndexBelowOriginal(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 height of cell given by cell index.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @param index : Index of the cell. ++ * @return short : Cell geodata Z coordinate, below given coordinates. ++ */ ++ public abstract short getHeightOriginal(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); ++ ++ /** ++ * Returns the NSWE flag byte of cell given by cell index.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @param index : Index of the cell. ++ * @return short : Cell geodata Z coordinate, below given coordinates. ++ */ ++ public abstract byte getNsweOriginal(int index); ++ ++ /** ++ * Sets the NSWE flag byte of cell given by cell index. ++ * @param index : Index of the cell. ++ * @param nswe : New NSWE flag byte. ++ */ ++ public abstract void setNswe(int index, byte nswe); ++ ++ /** ++ * Saves the block in L2D format to {@link BufferedOutputStream}. Used only for L2D geodata conversion. ++ * @param stream : The stream. ++ * @throws IOException : Can't save the block to steam. ++ */ ++ public abstract void saveBlock(BufferedOutputStream stream) throws IOException; ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java (working copy) +@@ -0,0 +1,252 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++import java.io.IOException; ++import java.nio.ByteBuffer; ++ ++/** ++ * @author Hasha ++ */ ++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. ++ * @param format : GeoFormat specifying format of loaded data. ++ */ ++ public BlockComplex(ByteBuffer bb, GeoFormat format) ++ { ++ // initialize buffer ++ _buffer = new byte[GeoStructure.BLOCK_CELLS * 3]; ++ ++ // load data ++ for (int i = 0; i < GeoStructure.BLOCK_CELLS; i++) ++ { ++ if (format != GeoFormat.L2D) ++ { ++ // 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); ++ } ++ else ++ { ++ // get nswe ++ final byte nswe = bb.get(); ++ _buffer[i * 3] = nswe; ++ ++ // get height ++ final short height = bb.getShort(); ++ _buffer[(i * 3) + 1] = (byte) (height & 0x00FF); ++ _buffer[(i * 3) + 2] = (byte) (height >> 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 short getHeightNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getHeightNearest(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeightAbove(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 short height = (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ ++ // check and return height ++ return height > worldZ ? height : Short.MIN_VALUE; ++ } ++ ++ @Override ++ public short getHeightBelow(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 short height = (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ ++ // check and return height ++ return height < worldZ ? height : Short.MAX_VALUE; ++ } ++ ++ @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 byte getNsweNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getNsweNearest(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public byte getNsweAbove(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 ? _buffer[index] : 0; ++ } ++ ++ @Override ++ public byte getNsweBelow(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 ? _buffer[index] : 0; ++ } ++ ++ @Override ++ public 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 getIndexAboveOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexAbove(geoX, geoY, worldZ); ++ } ++ ++ @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 int getIndexBelowOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexBelow(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeight(int index) ++ { ++ return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ } ++ ++ @Override ++ public short getHeightOriginal(int index) ++ { ++ return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ } ++ ++ @Override ++ public byte getNswe(int index) ++ { ++ return _buffer[index]; ++ } ++ ++ @Override ++ public byte getNsweOriginal(int index) ++ { ++ return _buffer[index]; ++ } ++ ++ @Override ++ public void setNswe(int index, byte nswe) ++ { ++ _buffer[index] = nswe; ++ } ++ ++ @Override ++ public void saveBlock(BufferedOutputStream stream) throws IOException ++ { ++ // write block type ++ stream.write(GeoStructure.TYPE_COMPLEX_L2D); ++ ++ // write block data ++ stream.write(_buffer, 0, GeoStructure.BLOCK_CELLS * 3); ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java (working copy) +@@ -0,0 +1,176 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++import java.io.IOException; ++import java.nio.ByteBuffer; ++ ++/** ++ * @author Hasha ++ */ ++public class BlockFlat extends ABlock ++{ ++ protected final short _height; ++ protected byte _nswe; ++ ++ /** ++ * Creates FlatBlock. ++ * @param bb : Input byte buffer. ++ * @param format : GeoFormat specifying format of loaded data. ++ */ ++ public BlockFlat(ByteBuffer bb, GeoFormat format) ++ { ++ _height = bb.getShort(); ++ _nswe = format != GeoFormat.L2D ? 0x0F : (byte) (0xFF); ++ if (format == GeoFormat.L2OFF) ++ { ++ bb.getShort(); ++ } ++ } ++ ++ @Override ++ public boolean hasGeoPos() ++ { ++ return true; ++ } ++ ++ @Override ++ public short getHeightNearest(int geoX, int geoY, int worldZ) ++ { ++ return _height; ++ } ++ ++ @Override ++ public short getHeightNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return _height; ++ } ++ ++ @Override ++ public short getHeightAbove(int geoX, int geoY, int worldZ) ++ { ++ // check and return height ++ return _height > worldZ ? _height : Short.MIN_VALUE; ++ } ++ ++ @Override ++ public short getHeightBelow(int geoX, int geoY, int worldZ) ++ { ++ // check and return height ++ return _height < worldZ ? _height : Short.MAX_VALUE; ++ } ++ ++ @Override ++ public byte getNsweNearest(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweAbove(int geoX, int geoY, int worldZ) ++ { ++ // check height and return nswe ++ return _height > worldZ ? _nswe : 0; ++ } ++ ++ @Override ++ public byte getNsweBelow(int geoX, int geoY, int worldZ) ++ { ++ // check height and return nswe ++ return _height < worldZ ? _nswe : 0; ++ } ++ ++ @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 getIndexAboveOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexAbove(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public int getIndexBelow(int geoX, int geoY, int worldZ) ++ { ++ // check height and return index ++ return _height < worldZ ? 0 : -1; ++ } ++ ++ @Override ++ public int getIndexBelowOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexBelow(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeight(int index) ++ { ++ return _height; ++ } ++ ++ @Override ++ public short getHeightOriginal(int index) ++ { ++ return _height; ++ } ++ ++ @Override ++ public byte getNswe(int index) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweOriginal(int index) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public void setNswe(int index, byte nswe) ++ { ++ _nswe = nswe; ++ } ++ ++ @Override ++ public void saveBlock(BufferedOutputStream stream) throws IOException ++ { ++ // write block type ++ stream.write(GeoStructure.TYPE_FLAT_L2D); ++ ++ // write height ++ stream.write((byte) (_height & 0x00FF)); ++ stream.write((byte) (_height >> 8)); ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java (working copy) +@@ -0,0 +1,465 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++import java.io.IOException; ++import java.nio.ByteBuffer; ++import java.nio.ByteOrder; ++import java.util.Arrays; ++ ++/** ++ * @author Hasha ++ */ ++public class BlockMultilayer extends ABlock ++{ ++ private static final int MAX_LAYERS = Byte.MAX_VALUE; ++ ++ private static ByteBuffer _temp; ++ ++ /** ++ * Initializes the temporarily buffer. ++ */ ++ public static void initialize() ++ { ++ // initialize temporarily buffer and sorting mechanism ++ _temp = ByteBuffer.allocate(GeoStructure.BLOCK_CELLS * MAX_LAYERS * 3); ++ _temp.order(ByteOrder.LITTLE_ENDIAN); ++ } ++ ++ /** ++ * Releases temporarily buffer. ++ */ ++ public static void release() ++ { ++ _temp = null; ++ } ++ ++ protected byte[] _buffer; ++ ++ /** ++ * Implicit constructor for children class. ++ */ ++ protected BlockMultilayer() ++ { ++ _buffer = null; ++ } ++ ++ /** ++ * Creates MultilayerBlock. ++ * @param bb : Input byte buffer. ++ * @param format : GeoFormat specifying format of loaded data. ++ */ ++ public BlockMultilayer(ByteBuffer bb, GeoFormat format) ++ { ++ // 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 = format != GeoFormat.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++) ++ { ++ if (format != GeoFormat.L2D) ++ { ++ // get data ++ final short data = bb.getShort(); ++ ++ // add nswe and height ++ _temp.put((byte) (data & 0x000F)); ++ _temp.putShort((short) ((short) (data & 0xFFF0) >> 1)); ++ } ++ else ++ { ++ // add nswe ++ _temp.put(bb.get()); ++ ++ // add height ++ _temp.putShort(bb.getShort()); ++ } ++ } ++ } ++ ++ // 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 short getHeightNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getHeightNearest(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeightAbove(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 height ++ if (height > worldZ) ++ { ++ return (short) height; ++ } ++ ++ // move index to next layer ++ index -= 3; ++ } ++ ++ // none layer found, return minimum value ++ return Short.MIN_VALUE; ++ } ++ ++ @Override ++ public short getHeightBelow(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 height ++ if (height < worldZ) ++ { ++ return (short) height; ++ } ++ ++ // move index to next layer ++ index += 3; ++ } ++ ++ // none layer found, return maximum value ++ return Short.MAX_VALUE; ++ } ++ ++ @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 byte getNsweNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getNsweNearest(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public byte getNsweAbove(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 nswe ++ if (height > worldZ) ++ { ++ return _buffer[index]; ++ } ++ ++ // move index to next layer ++ index -= 3; ++ } ++ ++ // none layer found, block movement ++ return 0; ++ } ++ ++ @Override ++ public byte getNsweBelow(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 nswe ++ if (height < worldZ) ++ { ++ return _buffer[index]; ++ } ++ ++ // move index to next layer ++ index += 3; ++ } ++ ++ // none layer found, block movement ++ return 0; ++ } ++ ++ @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 first layer data (first from bottom) ++ byte layers = _buffer[index++]; ++ ++ // loop though all cell layers, find closest layer ++ 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; ++ } ++ ++ // none layer found ++ return -1; ++ } ++ ++ @Override ++ public int getIndexAboveOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexAbove(geoX, geoY, worldZ); ++ } ++ ++ @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; ++ } ++ ++ // none layer found ++ return -1; ++ } ++ ++ @Override ++ public int getIndexBelowOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexBelow(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeight(int index) ++ { ++ // get height ++ return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ } ++ ++ @Override ++ public short getHeightOriginal(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]; ++ } ++ ++ @Override ++ public byte getNsweOriginal(int index) ++ { ++ // get nswe ++ return _buffer[index]; ++ } ++ ++ @Override ++ public void setNswe(int index, byte nswe) ++ { ++ // set nswe ++ _buffer[index] = nswe; ++ } ++ ++ @Override ++ public void saveBlock(BufferedOutputStream stream) throws IOException ++ { ++ // write block type ++ stream.write(GeoStructure.TYPE_MULTILAYER_L2D); ++ ++ // for each cell ++ int index = 0; ++ for (int i = 0; i < GeoStructure.BLOCK_CELLS; i++) ++ { ++ // write layers count ++ final byte layers = _buffer[index++]; ++ stream.write(layers); ++ ++ // write cell data ++ stream.write(_buffer, index, layers * 3); ++ ++ // move index to next cell ++ index += layers * 3; ++ } ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java (working copy) +@@ -0,0 +1,150 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++ ++/** ++ * @author Hasha ++ */ ++public class BlockNull extends ABlock ++{ ++ private final byte _nswe; ++ ++ public BlockNull() ++ { ++ _nswe = (byte) 0xFF; ++ } ++ ++ @Override ++ public boolean hasGeoPos() ++ { ++ return false; ++ } ++ ++ @Override ++ public short getHeightNearest(int geoX, int geoY, int worldZ) ++ { ++ return (short) worldZ; ++ } ++ ++ @Override ++ public short getHeightNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return (short) worldZ; ++ } ++ ++ @Override ++ public short getHeightAbove(int geoX, int geoY, int worldZ) ++ { ++ return (short) worldZ; ++ } ++ ++ @Override ++ public short getHeightBelow(int geoX, int geoY, int worldZ) ++ { ++ return (short) worldZ; ++ } ++ ++ @Override ++ public byte getNsweNearest(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweAbove(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweBelow(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) ++ { ++ return 0; ++ } ++ ++ @Override ++ public int getIndexAboveOriginal(int geoX, int geoY, int worldZ) ++ { ++ return 0; ++ } ++ ++ @Override ++ public int getIndexBelow(int geoX, int geoY, int worldZ) ++ { ++ return 0; ++ } ++ ++ @Override ++ public int getIndexBelowOriginal(int geoX, int geoY, int worldZ) ++ { ++ return 0; ++ } ++ ++ @Override ++ public short getHeight(int index) ++ { ++ return 0; ++ } ++ ++ @Override ++ public short getHeightOriginal(int index) ++ { ++ return 0; ++ } ++ ++ @Override ++ public byte getNswe(int index) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweOriginal(int index) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public void setNswe(int index, byte nswe) ++ { ++ } ++ ++ @Override ++ public void saveBlock(BufferedOutputStream stream) ++ { ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java (nonexistent) +@@ -1,48 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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() +- { +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/ComplexBlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/ComplexBlock.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/ComplexBlock.java (nonexistent) +@@ -1,77 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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; +- +-/** +- * @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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/FlatBlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/FlatBlock.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/FlatBlock.java (nonexistent) +@@ -1,56 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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; +- +-/** +- * @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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/GeoFormat.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/GeoFormat.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/GeoFormat.java (working copy) +@@ -0,0 +1,39 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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 Hasha ++ */ ++public enum GeoFormat ++{ ++ L2J("%d_%d.l2j"), ++ L2OFF("%d_%d_conv.dat"), ++ L2D("%d_%d.l2d"); ++ ++ private final String _filename; ++ ++ private GeoFormat(String filename) ++ { ++ _filename = filename; ++ } ++ ++ public String getFilename() ++ { ++ return _filename; ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/GeoLocation.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/GeoLocation.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/GeoLocation.java (working copy) +@@ -0,0 +1,67 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.geoengine.GeoEngine; ++import org.l2jmobius.gameserver.model.Location; ++ ++/** ++ * @author Hasha ++ */ ++public class GeoLocation extends Location ++{ ++ private byte _nswe; ++ ++ public GeoLocation(int x, int y, int z) ++ { ++ super(x, y, GeoEngine.getInstance().getHeightNearest(x, y, z)); ++ _nswe = GeoEngine.getInstance().getNsweNearest(x, y, z); ++ } ++ ++ public void set(int x, int y, short z) ++ { ++ super.setXYZ(x, y, GeoEngine.getInstance().getHeightNearest(x, y, z)); ++ _nswe = GeoEngine.getInstance().getNsweNearest(x, y, z); ++ } ++ ++ public int getGeoX() ++ { ++ return _x; ++ } ++ ++ public int getGeoY() ++ { ++ return _y; ++ } ++ ++ @Override ++ public int getX() ++ { ++ return GeoEngine.getWorldX(_x); ++ } ++ ++ @Override ++ public int getY() ++ { ++ return GeoEngine.getWorldY(_y); ++ } ++ ++ public byte getNSWE() ++ { ++ return _nswe; ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java (working copy) +@@ -0,0 +1,70 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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; ++ ++/** ++ * @author Hasha ++ */ ++public class GeoStructure ++{ ++ // cells ++ public static final byte CELL_FLAG_E = 1 << 0; ++ public static final byte CELL_FLAG_W = 1 << 1; ++ public static final byte CELL_FLAG_S = 1 << 2; ++ public static final byte CELL_FLAG_N = 1 << 3; ++ public static final byte CELL_FLAG_SE = 1 << 4; ++ public static final byte CELL_FLAG_SW = 1 << 5; ++ public static final byte CELL_FLAG_NE = 1 << 6; ++ public static final byte CELL_FLAG_NW = (byte) (1 << 7); ++ ++ public static final int CELL_HEIGHT = 8; ++ public static final int CELL_IGNORE_HEIGHT = CELL_HEIGHT * 6; ++ ++ // blocks ++ public static final byte TYPE_FLAT_L2J_L2OFF = 0; ++ public static final byte TYPE_FLAT_L2D = (byte) 0xD0; ++ public static final byte TYPE_COMPLEX_L2J = 1; ++ public static final byte TYPE_COMPLEX_L2OFF = 0x40; ++ public static final byte TYPE_COMPLEX_L2D = (byte) 0xD1; ++ 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) ++ public static final byte TYPE_MULTILAYER_L2D = (byte) 0xD2; ++ ++ 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; ++ ++ // regions ++ 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; ++ ++ // global geodata ++ private static final int GEO_REGIONS_X = ((World.TILE_X_MAX - World.TILE_X_MIN) + 1); ++ private 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 +Index: java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java (nonexistent) +@@ -1,42 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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); +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/IGeoObject.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/IGeoObject.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/IGeoObject.java (working copy) +@@ -0,0 +1,53 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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 Hasha ++ */ ++public interface IGeoObject ++{ ++ /** ++ * Returns geodata X coordinate of the {@link IGeoObject}. ++ * @return int : Geodata X coordinate. ++ */ ++ int getGeoX(); ++ ++ /** ++ * Returns geodata Y coordinate of the {@link IGeoObject}. ++ * @return int : Geodata Y coordinate. ++ */ ++ int getGeoY(); ++ ++ /** ++ * Returns geodata Z coordinate of the {@link IGeoObject}. ++ * @return int : Geodata Z coordinate. ++ */ ++ int getGeoZ(); ++ ++ /** ++ * Returns height of the {@link IGeoObject}. ++ * @return int : Height. ++ */ ++ int getHeight(); ++ ++ /** ++ * Returns {@link IGeoObject} data. ++ * @return byte[][] : {@link IGeoObject} data. ++ */ ++ byte[][] getObjectGeoData(); ++} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java (nonexistent) +@@ -1,47 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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(); +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/MultilayerBlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/MultilayerBlock.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/MultilayerBlock.java (nonexistent) +@@ -1,183 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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; +- +-/** +- * @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); +- +- // 1 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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/NullRegion.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/NullRegion.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/NullRegion.java (nonexistent) +@@ -1,55 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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 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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/Region.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/Region.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/Region.java (nonexistent) +@@ -1,92 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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; +- +-/** +- * @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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java (nonexistent) +@@ -1,87 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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 T _loc; +- private AbstractNode _parent; +- +- public AbstractNode(T loc) +- { +- _loc = loc; +- } +- +- public void setParent(AbstractNode p) +- { +- _parent = p; +- } +- +- public AbstractNode getParent() +- { +- return _parent; +- } +- +- public T getLoc() +- { +- return _loc; +- } +- +- public void setLoc(T l) +- { +- _loc = l; +- } +- +- @Override +- public int hashCode() +- { +- final int prime = 31; +- int result = 1; +- result = (prime * result) + ((_loc == null) ? 0 : _loc.hashCode()); +- return result; +- } +- +- @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 +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java (nonexistent) +@@ -1,33 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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 int getNodeX(); +- +- public abstract int getNodeY(); +-} +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNode.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNode.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNode.java (nonexistent) +@@ -1,67 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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 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 +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNodeBuffer.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNodeBuffer.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNodeBuffer.java (nonexistent) +@@ -1,348 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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.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 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) +- { +- _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(); +- } +- +- 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 void getNeighbors() +- { +- if (!_current.getLoc().canGoAll()) +- { +- 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); +- } +- +- // SouthEast +- if ((nodeE != null) && (nodeS != null)) +- { +- if (nodeE.getLoc().canGoSouth() && nodeS.getLoc().canGoEast()) +- { +- addNode(x + 1, y + 1, z, true); +- } +- } +- +- // SouthWest +- if ((nodeS != null) && (nodeW != null)) +- { +- if (nodeW.getLoc().canGoSouth() && nodeS.getLoc().canGoWest()) +- { +- addNode(x - 1, y + 1, z, true); +- } +- } +- +- // NorthEast +- if ((nodeN != null) && (nodeE != null)) +- { +- if (nodeE.getLoc().canGoNorth() && nodeN.getLoc().canGoEast()) +- { +- addNode(x + 1, y - 1, z, true); +- } +- } +- +- // NorthWest +- if ((nodeN != null) && (nodeW != null)) +- { +- if (nodeW.getLoc().canGoNorth() && nodeN.getLoc().canGoWest()) +- { +- addNode(x - 1, y - 1, z, true); +- } +- } +- } +- +- private 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(); +- // reinit node if needed +- if (result.getLoc() != null) +- { +- result.getLoc().set(x, y, z); +- } +- else +- { +- result.setLoc(new NodeLoc(x, y, z)); +- } +- } +- +- return result; +- } +- +- private 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 boolean isHighWeight(int x, int y, int z) +- { +- final CellNode result = getNode(x, y, z); +- if (result == null) +- { +- return true; +- } +- +- if (!result.getLoc().canGoAll()) +- { +- return true; +- } +- if (Math.abs(result.getLoc().getZ() - z) > 16) +- { +- return true; +- } +- +- return false; +- } +- +- private 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 +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java (working copy) +@@ -0,0 +1,87 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.geodata.GeoLocation; ++ ++/** ++ * @author Hasha ++ */ ++public class Node ++{ ++ // node coords and nswe flag ++ private GeoLocation _loc; ++ ++ // node parent (for reverse path construction) ++ private Node _parent; ++ // node child (for moving over nodes during iteration) ++ private Node _child; ++ ++ // node G cost (movement cost = parent movement cost + current movement cost) ++ private double _cost = -1000; ++ ++ public void setLoc(int x, int y, int z) ++ { ++ _loc = new GeoLocation(x, y, z); ++ } ++ ++ public GeoLocation getLoc() ++ { ++ return _loc; ++ } ++ ++ public void setParent(Node parent) ++ { ++ _parent = parent; ++ } ++ ++ public Node getParent() ++ { ++ return _parent; ++ } ++ ++ public void setChild(Node child) ++ { ++ _child = child; ++ } ++ ++ public Node getChild() ++ { ++ return _child; ++ } ++ ++ public void setCost(double cost) ++ { ++ _cost = cost; ++ } ++ ++ public double getCost() ++ { ++ return _cost; ++ } ++ ++ public void free() ++ { ++ // reset node location ++ _loc = null; ++ ++ // reset node parent, child and cost ++ _parent = null; ++ _child = null; ++ _cost = -1000; ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java (working copy) +@@ -0,0 +1,306 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.concurrent.locks.ReentrantLock; ++ ++import org.l2jmobius.Config; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; ++ ++/** ++ * @author DS, Hasha; Credits to Diamond ++ */ ++public class NodeBuffer ++{ ++ private final ReentrantLock _lock = new ReentrantLock(); ++ private final int _size; ++ private final Node[][] _buffer; ++ ++ // center coordinates ++ private int _cx = 0; ++ private int _cy = 0; ++ ++ // target coordinates ++ private int _gtx = 0; ++ private int _gty = 0; ++ private short _gtz = 0; ++ ++ private Node _current = null; ++ ++ /** ++ * Constructor of NodeBuffer. ++ * @param size : one dimension size of buffer ++ */ ++ public NodeBuffer(int size) ++ { ++ // set size ++ _size = size; ++ ++ // initialize buffer ++ _buffer = new Node[size][size]; ++ for (int x = 0; x < size; x++) ++ { ++ for (int y = 0; y < size; y++) ++ { ++ _buffer[x][y] = 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 Node : first node of path ++ */ ++ public Node findPath(int gox, int goy, short goz, int gtx, int gty, short gtz) ++ { ++ // set coordinates (middle of the line (gox,goy) - (gtx,gty), will be in the center of the buffer) ++ _cx = gox + ((gtx - gox - _size) / 2); ++ _cy = goy + ((gty - goy - _size) / 2); ++ ++ _gtx = gtx; ++ _gty = gty; ++ _gtz = gtz; ++ ++ _current = getNode(gox, goy, goz); ++ _current.setCost(getCostH(gox, goy, goz)); ++ ++ int count = 0; ++ do ++ { ++ // reached target? ++ if ((_current.getLoc().getGeoX() == _gtx) && (_current.getLoc().getGeoY() == _gty) && (Math.abs(_current.getLoc().getZ() - _gtz) < 8)) ++ { ++ return _current; ++ } ++ ++ // expand current node ++ expand(); ++ ++ // move pointer ++ _current = _current.getChild(); ++ } ++ while ((_current != null) && (count++ < Config.MAX_ITERATIONS)); ++ ++ return null; ++ } ++ ++ public boolean isLocked() ++ { ++ return _lock.tryLock(); ++ } ++ ++ public void free() ++ { ++ _current = null; ++ ++ for (Node[] nodes : _buffer) ++ { ++ for (Node node : nodes) ++ { ++ if (node.getLoc() != null) ++ { ++ node.free(); ++ } ++ } ++ } ++ ++ _lock.unlock(); ++ } ++ ++ /** ++ * Check _current Node and add its neighbors to the buffer. ++ */ ++ private final void expand() ++ { ++ // can't move anywhere, don't expand ++ final byte nswe = _current.getLoc().getNSWE(); ++ if (nswe == 0) ++ { ++ return; ++ } ++ ++ // get geo coords of the node to be expanded ++ final int x = _current.getLoc().getGeoX(); ++ final int y = _current.getLoc().getGeoY(); ++ final short z = (short) _current.getLoc().getZ(); ++ ++ // can move north, expand ++ if ((nswe & GeoStructure.CELL_FLAG_N) != 0) ++ { ++ addNode(x, y - 1, z, Config.BASE_WEIGHT); ++ } ++ ++ // can move south, expand ++ if ((nswe & GeoStructure.CELL_FLAG_S) != 0) ++ { ++ addNode(x, y + 1, z, Config.BASE_WEIGHT); ++ } ++ ++ // can move west, expand ++ if ((nswe & GeoStructure.CELL_FLAG_W) != 0) ++ { ++ addNode(x - 1, y, z, Config.BASE_WEIGHT); ++ } ++ ++ // can move east, expand ++ if ((nswe & GeoStructure.CELL_FLAG_E) != 0) ++ { ++ addNode(x + 1, y, z, Config.BASE_WEIGHT); ++ } ++ ++ // can move north-west, expand ++ if ((nswe & GeoStructure.CELL_FLAG_NW) != 0) ++ { ++ addNode(x - 1, y - 1, z, Config.DIAGONAL_WEIGHT); ++ } ++ ++ // can move north-east, expand ++ if ((nswe & GeoStructure.CELL_FLAG_NE) != 0) ++ { ++ addNode(x + 1, y - 1, z, Config.DIAGONAL_WEIGHT); ++ } ++ ++ // can move south-west, expand ++ if ((nswe & GeoStructure.CELL_FLAG_SW) != 0) ++ { ++ addNode(x - 1, y + 1, z, Config.DIAGONAL_WEIGHT); ++ } ++ ++ // can move south-east, expand ++ if ((nswe & GeoStructure.CELL_FLAG_SE) != 0) ++ { ++ addNode(x + 1, y + 1, z, Config.DIAGONAL_WEIGHT); ++ } ++ } ++ ++ /** ++ * Returns node, if it exists in buffer. ++ * @param x : node X coord ++ * @param y : node Y coord ++ * @param z : node Z coord ++ * @return Node : node, if exits in buffer ++ */ ++ private final Node getNode(int x, int y, short z) ++ { ++ // check node X out of coordinates ++ final int ix = x - _cx; ++ if ((ix < 0) || (ix >= _size)) ++ { ++ return null; ++ } ++ ++ // check node Y out of coordinates ++ final int iy = y - _cy; ++ if ((iy < 0) || (iy >= _size)) ++ { ++ return null; ++ } ++ ++ // get node ++ final Node result = _buffer[ix][iy]; ++ ++ // check and update ++ if (result.getLoc() == null) ++ { ++ result.setLoc(x, y, z); ++ } ++ ++ // return node ++ return result; ++ } ++ ++ /** ++ * Add node given by coordinates to the buffer. ++ * @param x : geo X coord ++ * @param y : geo Y coord ++ * @param z : geo Z coord ++ * @param weight : weight of movement to new node ++ */ ++ private final void addNode(int x, int y, short z, int weight) ++ { ++ // get node to be expanded ++ final Node node = getNode(x, y, z); ++ if (node == null) ++ { ++ return; ++ } ++ ++ // Z distance between nearby cells is higher than cell size ++ if (node.getLoc().getZ() > (z + (2 * GeoStructure.CELL_HEIGHT))) ++ { ++ return; ++ } ++ ++ // node was already expanded, return ++ if (node.getCost() >= 0) ++ { ++ return; ++ } ++ ++ node.setParent(_current); ++ if (node.getLoc().getNSWE() != (byte) 0xFF) ++ { ++ node.setCost(getCostH(x, y, node.getLoc().getZ()) + (weight * Config.OBSTACLE_MULTIPLIER)); ++ } ++ else ++ { ++ node.setCost(getCostH(x, y, node.getLoc().getZ()) + weight); ++ } ++ ++ Node current = _current; ++ int count = 0; ++ while ((current.getChild() != null) && (count < (Config.MAX_ITERATIONS * 4))) ++ { ++ count++; ++ if (current.getChild().getCost() > node.getCost()) ++ { ++ node.setChild(current.getChild()); ++ break; ++ } ++ current = current.getChild(); ++ } ++ ++ if (count >= (Config.MAX_ITERATIONS * 4)) ++ { ++ System.err.println("Pathfinding: too long loop detected, cost:" + node.getCost()); ++ } ++ ++ current.setChild(node); ++ } ++ ++ /** ++ * @param x : node X coord ++ * @param y : node Y coord ++ * @param i : node Z coord ++ * @return double : node cost ++ */ ++ private final double getCostH(int x, int y, int i) ++ { ++ final int dX = x - _gtx; ++ final int dY = y - _gty; ++ final int dZ = (i - _gtz) / GeoStructure.CELL_HEIGHT; ++ ++ // return (Math.abs(dX) + Math.abs(dY) + Math.abs(dZ)) * Config.HEURISTIC_WEIGHT; // Manhattan distance ++ return Math.sqrt((dX * dX) + (dY * dY) + (dZ * dZ)) * Config.HEURISTIC_WEIGHT; // Direct distance ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeLoc.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeLoc.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeLoc.java (nonexistent) +@@ -1,183 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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.Cell; +- +-/** +- * @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 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 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; +- // return super.hashCode(); +- } +- +- @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; +- if (_x != other._x) +- { +- return false; +- } +- if (_y != other._y) +- { +- return false; +- } +- if (_goNorth != other._goNorth) +- { +- return false; +- } +- if (_goEast != other._goEast) +- { +- return false; +- } +- if (_goSouth != other._goSouth) +- { +- return false; +- } +- if (_goWest != other._goWest) +- { +- return false; +- } +- if (_geoHeight != other._geoHeight) +- { +- return false; +- } +- return true; +- } +-} +Index: java/org/l2jmobius/gameserver/model/actor/Creature.java +=================================================================== +--- java/org/l2jmobius/gameserver/model/actor/Creature.java (revision 8399) ++++ java/org/l2jmobius/gameserver/model/actor/Creature.java (working copy) +@@ -66,8 +66,6 @@ + import org.l2jmobius.gameserver.enums.TeleportWhereType; + import org.l2jmobius.gameserver.enums.UserInfoType; + import org.l2jmobius.gameserver.geoengine.GeoEngine; +-import org.l2jmobius.gameserver.geoengine.GeoEnginePathfinding; +-import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; + import org.l2jmobius.gameserver.instancemanager.IdManager; + import org.l2jmobius.gameserver.instancemanager.MapRegionManager; + import org.l2jmobius.gameserver.instancemanager.QuestManager; +@@ -2577,7 +2575,7 @@ + + public boolean disregardingGeodata; + public int onGeodataPathIndex; +- public List geoPath; ++ public List geoPath; + public int geoPathAccurateTx; + public int geoPathAccurateTy; + public int geoPathGtx; +@@ -3466,7 +3464,7 @@ + if (((originalDistance - distance) > 30) && !isControlBlocked() && !isInVehicle) + { + // Path calculation -- overrides previous movement check +- m.geoPath = GeoEnginePathfinding.getInstance().findPath(curX, curY, curZ, originalX, originalY, originalZ, getInstanceWorld()); ++ m.geoPath = GeoEngine.getInstance().findPath(curX, curY, curZ, originalX, originalY, originalZ, getInstanceWorld()); + if ((m.geoPath == null) || (m.geoPath.size() < 2)) // No path found + { + if (isPlayer() && !_isFlying && !isInWater) +Index: java/org/l2jmobius/gameserver/model/actor/instance/PlayerInstance.java +=================================================================== +--- java/org/l2jmobius/gameserver/model/actor/instance/PlayerInstance.java (revision 8397) ++++ java/org/l2jmobius/gameserver/model/actor/instance/PlayerInstance.java (working copy) +@@ -12704,7 +12704,7 @@ + { + if (Config.CORRECT_PLAYER_Z) + { +- final int nearestZ = GeoEngine.getInstance().getHigherHeight(getX(), getY(), getZ()); ++ final int nearestZ = GeoEngine.getInstance().getHeightNearest(getX(), getY(), getZ()); + if (getZ() < nearestZ) + { + teleToLocation(new Location(getX(), getY(), nearestZ)); +Index: java/org/l2jmobius/gameserver/util/GeoUtils.java +=================================================================== +--- java/org/l2jmobius/gameserver/util/GeoUtils.java (revision 8397) ++++ java/org/l2jmobius/gameserver/util/GeoUtils.java (working copy) +@@ -19,7 +19,7 @@ + import java.awt.Color; + + import org.l2jmobius.gameserver.geoengine.GeoEngine; +-import org.l2jmobius.gameserver.geoengine.geodata.Cell; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; + import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance; + import org.l2jmobius.gameserver.network.serverpackets.ExServerPrimitive; + +@@ -26,25 +26,25 @@ + /** + * @author HorridoJoho + */ +-public final class GeoUtils ++public class GeoUtils + { + public static void debug2DLine(PlayerInstance player, int x, int y, int tx, int ty, int z) + { +- final int gx = GeoEngine.getInstance().getGeoX(x); +- final int gy = GeoEngine.getInstance().getGeoY(y); ++ final int gx = GeoEngine.getGeoX(x); ++ final int gy = GeoEngine.getGeoY(y); + +- final int tgx = GeoEngine.getInstance().getGeoX(tx); +- final int tgy = GeoEngine.getInstance().getGeoY(ty); ++ final int tgx = GeoEngine.getGeoX(tx); ++ final int tgy = GeoEngine.getGeoY(ty); + + final ExServerPrimitive prim = new ExServerPrimitive("Debug2DLine", x, y, z); +- prim.addLine(Color.BLUE, GeoEngine.getInstance().getWorldX(gx), GeoEngine.getInstance().getWorldY(gy), z, GeoEngine.getInstance().getWorldX(tgx), GeoEngine.getInstance().getWorldY(tgy), z); ++ prim.addLine(Color.BLUE, GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), z, GeoEngine.getWorldX(tgx), GeoEngine.getWorldY(tgy), z); + + final LinePointIterator iter = new LinePointIterator(gx, gy, tgx, tgy); + + while (iter.next()) + { +- final int wx = GeoEngine.getInstance().getWorldX(iter.x()); +- final int wy = GeoEngine.getInstance().getWorldY(iter.y()); ++ final int wx = GeoEngine.getWorldX(iter.x()); ++ final int wy = GeoEngine.getWorldY(iter.y()); + + prim.addPoint(Color.RED, wx, wy, z); + } +@@ -53,21 +53,21 @@ + + public static void debug3DLine(PlayerInstance player, int x, int y, int z, int tx, int ty, int tz) + { +- final int gx = GeoEngine.getInstance().getGeoX(x); +- final int gy = GeoEngine.getInstance().getGeoY(y); ++ final int gx = GeoEngine.getGeoX(x); ++ final int gy = GeoEngine.getGeoY(y); + +- final int tgx = GeoEngine.getInstance().getGeoX(tx); +- final int tgy = GeoEngine.getInstance().getGeoY(ty); ++ final int tgx = GeoEngine.getGeoX(tx); ++ final int tgy = GeoEngine.getGeoY(ty); + + final ExServerPrimitive prim = new ExServerPrimitive("Debug3DLine", x, y, z); +- prim.addLine(Color.BLUE, GeoEngine.getInstance().getWorldX(gx), GeoEngine.getInstance().getWorldY(gy), z, GeoEngine.getInstance().getWorldX(tgx), GeoEngine.getInstance().getWorldY(tgy), tz); ++ prim.addLine(Color.BLUE, GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), z, GeoEngine.getWorldX(tgx), GeoEngine.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.getInstance().getWorldX(prevX); +- int wy = GeoEngine.getInstance().getWorldY(prevY); ++ int wx = GeoEngine.getWorldX(prevX); ++ int wy = GeoEngine.getWorldY(prevY); + int wz = iter.z(); + prim.addPoint(Color.RED, wx, wy, wz); + +@@ -78,8 +78,8 @@ + + if ((curX != prevX) || (curY != prevY)) + { +- wx = GeoEngine.getInstance().getWorldX(curX); +- wy = GeoEngine.getInstance().getWorldY(curY); ++ wx = GeoEngine.getWorldX(curX); ++ wy = GeoEngine.getWorldY(curY); + wz = iter.z(); + + prim.addPoint(Color.RED, wx, wy, wz); +@@ -93,7 +93,7 @@ + + private static Color getDirectionColor(int x, int y, int z, int nswe) + { +- if (GeoEngine.getInstance().checkNearestNswe(x, y, z, nswe)) ++ if ((GeoEngine.getInstance().getNsweNearest(x, y, z) & nswe) == nswe) + { + return Color.GREEN; + } +@@ -109,9 +109,8 @@ + int iPacket = 0; + + ExServerPrimitive exsp = null; +- final GeoEngine ge = GeoEngine.getInstance(); +- final int playerGx = ge.getGeoX(player.getX()); +- final int playerGy = ge.getGeoY(player.getY()); ++ final int playerGx = GeoEngine.getGeoX(player.getX()); ++ final int playerGy = GeoEngine.getGeoY(player.getY()); + for (int dx = -geoRadius; dx <= geoRadius; ++dx) + { + for (int dy = -geoRadius; dy <= geoRadius; ++dy) +@@ -135,12 +134,12 @@ + final int gx = playerGx + dx; + final int gy = playerGy + dy; + +- final int x = ge.getWorldX(gx); +- final int y = ge.getWorldY(gy); +- final int z = ge.getNearestZ(gx, gy, player.getZ()); ++ final int x = GeoEngine.getWorldX(gx); ++ final int y = GeoEngine.getWorldY(gy); ++ final int z = GeoEngine.getInstance().getHeightNearest(gx, gy, player.getZ()); + + // north arrow +- Color col = getDirectionColor(gx, gy, z, Cell.NSWE_NORTH); ++ Color col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_N); + 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); +@@ -147,7 +146,7 @@ + exsp.addLine(col, x - 4, y - 4, z, x + 4, y - 4, z); + + // east arrow +- col = getDirectionColor(gx, gy, z, Cell.NSWE_EAST); ++ col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_E); + 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); +@@ -154,13 +153,13 @@ + exsp.addLine(col, x + 4, y - 4, z, x + 4, y + 4, z); + + // south arrow +- col = getDirectionColor(gx, gy, z, Cell.NSWE_SOUTH); ++ col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_S); + 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, Cell.NSWE_WEST); ++ col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_W); + 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); +@@ -188,15 +187,15 @@ + { + if (y > lastY) + { +- return Cell.NSWE_SOUTH_EAST; ++ return GeoStructure.CELL_FLAG_SE; // Direction.SOUTH_EAST; + } + else if (y < lastY) + { +- return Cell.NSWE_NORTH_EAST; ++ return GeoStructure.CELL_FLAG_NE; // Direction.NORTH_EAST; + } + else + { +- return Cell.NSWE_EAST; ++ return GeoStructure.CELL_FLAG_E; // Direction.EAST; + } + } + else if (x < lastX) // west +@@ -203,26 +202,27 @@ + { + if (y > lastY) + { +- return Cell.NSWE_SOUTH_WEST; ++ return GeoStructure.CELL_FLAG_SW; // Direction.SOUTH_WEST; + } + else if (y < lastY) + { +- return Cell.NSWE_NORTH_WEST; ++ return GeoStructure.CELL_FLAG_NW; // Direction.NORTH_WEST; + } + else + { +- return Cell.NSWE_WEST; ++ return GeoStructure.CELL_FLAG_W; // Direction.WEST; + } + } +- else // unchanged x ++ else ++ // unchanged x + { + if (y > lastY) + { +- return Cell.NSWE_SOUTH; ++ return GeoStructure.CELL_FLAG_S; // Direction.SOUTH; + } + else if (y < lastY) + { +- return Cell.NSWE_NORTH; ++ return GeoStructure.CELL_FLAG_N; // Direction.NORTH; + } + else + { +Index: java/org/l2jmobius/tools/geodataconverter/GeoDataConverter.java +=================================================================== +--- java/org/l2jmobius/tools/geodataconverter/GeoDataConverter.java (nonexistent) ++++ java/org/l2jmobius/tools/geodataconverter/GeoDataConverter.java (working copy) +@@ -0,0 +1,386 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ */ ++package org.l2jmobius.tools.geodataconverter; ++ ++import java.io.BufferedOutputStream; ++import java.io.File; ++import java.io.FileOutputStream; ++import java.io.RandomAccessFile; ++import java.nio.ByteOrder; ++import java.nio.MappedByteBuffer; ++import java.nio.channels.FileChannel; ++import java.util.Scanner; ++ ++import org.l2jmobius.Config; ++import org.l2jmobius.commons.util.PropertiesParser; ++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.GeoFormat; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; ++import org.l2jmobius.gameserver.model.World; ++ ++/** ++ * @author Hasha ++ */ ++public class GeoDataConverter ++{ ++ private static GeoFormat _format; ++ private static ABlock[][] _blocks; ++ ++ public static void main(String[] args) ++ { ++ // initialize config ++ loadGeoengineConfigs(); ++ ++ // get geodata format ++ String type = ""; ++ try (Scanner scn = new Scanner(System.in)) ++ { ++ while (!(type.equalsIgnoreCase("J") || type.equalsIgnoreCase("O") || type.equalsIgnoreCase("E"))) ++ { ++ System.out.println("GeoDataConverter: Select source geodata type:"); ++ System.out.println(" J: L2J (e.g. 23_22.l2j)"); ++ System.out.println(" O: L2OFF (e.g. 23_22_conv.dat)"); ++ System.out.println(" E: Exit"); ++ System.out.print("Choice: "); ++ type = scn.next(); ++ } ++ } ++ if (type.equalsIgnoreCase("E")) ++ { ++ System.exit(0); ++ } ++ ++ _format = type.equalsIgnoreCase("J") ? GeoFormat.L2J : GeoFormat.L2OFF; ++ ++ // start conversion ++ System.out.println("GeoDataConverter: Converting all " + _format + " files."); ++ ++ // initialize geodata container ++ _blocks = new ABlock[GeoStructure.REGION_BLOCKS_X][GeoStructure.REGION_BLOCKS_Y]; ++ ++ // initialize multilayer temporarily buffer ++ BlockMultilayer.initialize(); ++ ++ // load geo files ++ int converted = 0; ++ for (int rx = World.TILE_X_MIN; rx <= World.TILE_X_MAX; rx++) ++ { ++ for (int ry = World.TILE_Y_MIN; ry <= World.TILE_Y_MAX; ry++) ++ { ++ final String input = String.format(_format.getFilename(), rx, ry); ++ final String filepath = Config.GEODATA_PATH; ++ final File f = new File(filepath + input); ++ if (f.exists() && !f.isDirectory()) ++ { ++ // load geodata ++ if (!loadGeoBlocks(input)) ++ { ++ System.out.println("GeoDataConverter: Unable to load " + input + " region file."); ++ continue; ++ } ++ ++ // recalculate nswe ++ if (!recalculateNswe()) ++ { ++ System.out.println("GeoDataConverter: Unable to convert " + input + " region file."); ++ continue; ++ } ++ ++ // save geodata ++ final String output = String.format(GeoFormat.L2D.getFilename(), rx, ry); ++ if (!saveGeoBlocks(output)) ++ { ++ System.out.println("GeoDataConverter: Unable to save " + output + " region file."); ++ continue; ++ } ++ ++ converted++; ++ System.out.println("GeoDataConverter: Created " + output + " region file."); ++ } ++ } ++ } ++ System.out.println("GeoDataConverter: Converted " + converted + " " + _format + " to L2D region file(s)."); ++ ++ // release multilayer block temporarily buffer ++ BlockMultilayer.release(); ++ } ++ ++ /** ++ * Loads geo blocks from buffer of the region file. ++ * @param filename : The name of the to load. ++ * @return boolean : True when successful. ++ */ ++ private static boolean loadGeoBlocks(String filename) ++ { ++ // region file is load-able, try to load it ++ try (RandomAccessFile raf = new RandomAccessFile(Config.GEODATA_PATH + filename, "r"); ++ FileChannel fc = raf.getChannel()) ++ { ++ final MappedByteBuffer buffer = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()).load(); ++ buffer.order(ByteOrder.LITTLE_ENDIAN); ++ ++ // load 18B header for L2off geodata (1st and 2nd byte...region X and Y) ++ if (_format == GeoFormat.L2OFF) ++ { ++ for (int i = 0; i < 18; i++) ++ { ++ buffer.get(); ++ } ++ } ++ ++ // 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 (_format == GeoFormat.L2J) ++ { ++ // get block type ++ final byte type = buffer.get(); ++ ++ // load block according to block type ++ switch (type) ++ { ++ case GeoStructure.TYPE_FLAT_L2J_L2OFF: ++ { ++ _blocks[ix][iy] = new BlockFlat(buffer, _format); ++ break; ++ } ++ case GeoStructure.TYPE_COMPLEX_L2J: ++ { ++ _blocks[ix][iy] = new BlockComplex(buffer, _format); ++ break; ++ } ++ case GeoStructure.TYPE_MULTILAYER_L2J: ++ { ++ _blocks[ix][iy] = new BlockMultilayer(buffer, _format); ++ break; ++ } ++ default: ++ { ++ throw new IllegalArgumentException("Unknown block type: " + type); ++ } ++ } ++ } ++ else ++ { ++ // get block type ++ final short type = buffer.getShort(); ++ ++ // load block according to block type ++ switch (type) ++ { ++ case GeoStructure.TYPE_FLAT_L2J_L2OFF: ++ { ++ _blocks[ix][iy] = new BlockFlat(buffer, _format); ++ break; ++ } ++ case GeoStructure.TYPE_COMPLEX_L2OFF: ++ { ++ _blocks[ix][iy] = new BlockComplex(buffer, _format); ++ break; ++ } ++ default: ++ { ++ _blocks[ix][iy] = new BlockMultilayer(buffer, _format); ++ break; ++ } ++ } ++ } ++ } ++ } ++ ++ if (buffer.remaining() > 0) ++ { ++ System.out.println("GeoDataConverter: Region file " + filename + " can be corrupted, remaining " + buffer.remaining() + " bytes to read."); ++ return false; ++ } ++ ++ return true; ++ } ++ catch (Exception e) ++ { ++ System.out.println("GeoDataConverter: Error while loading " + filename + " region file."); ++ return false; ++ } ++ } ++ ++ /** ++ * Recalculate diagonal flags for the region file. ++ * @return boolean : True when successful. ++ */ ++ private static boolean recalculateNswe() ++ { ++ try ++ { ++ for (int x = 0; x < GeoStructure.REGION_CELLS_X; x++) ++ { ++ for (int y = 0; y < GeoStructure.REGION_CELLS_Y; y++) ++ { ++ // get block ++ final ABlock block = _blocks[x / GeoStructure.BLOCK_CELLS_X][y / GeoStructure.BLOCK_CELLS_Y]; ++ ++ // skip flat blocks ++ if (block instanceof BlockFlat) ++ { ++ continue; ++ } ++ ++ // for complex and multilayer blocks go though all layers ++ short height = Short.MAX_VALUE; ++ int index; ++ while ((index = block.getIndexBelow(x, y, height)) != -1) ++ { ++ // get height and nswe ++ height = block.getHeight(index); ++ byte nswe = block.getNswe(index); ++ ++ // update nswe with diagonal flags ++ nswe = updateNsweBelow(x, y, height, nswe); ++ ++ // set nswe of the cell ++ block.setNswe(index, nswe); ++ } ++ } ++ } ++ ++ return true; ++ } ++ catch (Exception e) ++ { ++ return false; ++ } ++ } ++ ++ /** ++ * Updates the NSWE flag with diagonal flags. ++ * @param x : Geodata X coordinate. ++ * @param y : Geodata Y coordinate. ++ * @param z : Geodata Z coordinate. ++ * @param nsweValue : NSWE flag to be updated. ++ * @return byte : Updated NSWE flag. ++ */ ++ private static byte updateNsweBelow(int x, int y, short z, byte nsweValue) ++ { ++ byte nswe = nsweValue; ++ ++ // calculate virtual layer height ++ final short height = (short) (z + GeoStructure.CELL_IGNORE_HEIGHT); ++ ++ // get NSWE of neighbor cells below virtual layer (NPC/PC can fall down of clif, but can not climb it -> NSWE of cell below) ++ final byte nsweN = getNsweBelow(x, y - 1, height); ++ final byte nsweS = getNsweBelow(x, y + 1, height); ++ final byte nsweW = getNsweBelow(x - 1, y, height); ++ final byte nsweE = getNsweBelow(x + 1, y, height); ++ ++ // north-west ++ if ((((nswe & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) || (((nswe & GeoStructure.CELL_FLAG_W) != 0) && ((nsweW & GeoStructure.CELL_FLAG_N) != 0))) ++ { ++ nswe |= GeoStructure.CELL_FLAG_NW; ++ } ++ ++ // north-east ++ if ((((nswe & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) || (((nswe & GeoStructure.CELL_FLAG_E) != 0) && ((nsweE & GeoStructure.CELL_FLAG_N) != 0))) ++ { ++ nswe |= GeoStructure.CELL_FLAG_NE; ++ } ++ ++ // south-west ++ if ((((nswe & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) || (((nswe & GeoStructure.CELL_FLAG_W) != 0) && ((nsweW & GeoStructure.CELL_FLAG_S) != 0))) ++ { ++ nswe |= GeoStructure.CELL_FLAG_SW; ++ } ++ ++ // south-east ++ if ((((nswe & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) || (((nswe & GeoStructure.CELL_FLAG_E) != 0) && ((nsweE & GeoStructure.CELL_FLAG_S) != 0))) ++ { ++ nswe |= GeoStructure.CELL_FLAG_SE; ++ } ++ ++ return nswe; ++ } ++ ++ private static byte getNsweBelow(int geoX, int geoY, short worldZ) ++ { ++ // out of geo coordinates ++ if ((geoX < 0) || (geoX >= GeoStructure.REGION_CELLS_X)) ++ { ++ return 0; ++ } ++ ++ // out of geo coordinates ++ if ((geoY < 0) || (geoY >= GeoStructure.REGION_CELLS_Y)) ++ { ++ return 0; ++ } ++ ++ // get block ++ final ABlock block = _blocks[geoX / GeoStructure.BLOCK_CELLS_X][geoY / GeoStructure.BLOCK_CELLS_Y]; ++ ++ // get index, when valid, return nswe ++ final int index = block.getIndexBelow(geoX, geoY, worldZ); ++ return index == -1 ? 0 : block.getNswe(index); ++ } ++ ++ /** ++ * Save region file to file. ++ * @param filename : The name of file to save. ++ * @return boolean : True when successful. ++ */ ++ private static boolean saveGeoBlocks(String filename) ++ { ++ try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(Config.GEODATA_PATH + filename), GeoStructure.REGION_BLOCKS * GeoStructure.BLOCK_CELLS * 3)) ++ { ++ // loop over region blocks and save each block ++ for (int ix = 0; ix < GeoStructure.REGION_BLOCKS_X; ix++) ++ { ++ for (int iy = 0; iy < GeoStructure.REGION_BLOCKS_Y; iy++) ++ { ++ _blocks[ix][iy].saveBlock(bos); ++ } ++ } ++ ++ // flush data to file ++ bos.flush(); ++ ++ return true; ++ } ++ catch (Exception e) ++ { ++ return false; ++ } ++ } ++ ++ private static void loadGeoengineConfigs() ++ { ++ final PropertiesParser geoData = new PropertiesParser(Config.GEOENGINE_CONFIG_FILE); ++ Config.GEODATA_PATH = geoData.getString("GeoDataPath", "./data/geodata/"); ++ Config.COORD_SYNCHRONIZE = geoData.getInt("CoordSynchronize", -1); ++ Config.PART_OF_CHARACTER_HEIGHT = geoData.getInt("PartOfCharacterHeight", 75); ++ Config.MAX_OBSTACLE_HEIGHT = geoData.getInt("MaxObstacleHeight", 32); ++ Config.PATHFINDING = geoData.getBoolean("PathFinding", true); ++ Config.PATHFIND_BUFFERS = geoData.getString("PathFindBuffers", "100x6;128x6;192x6;256x4;320x4;384x4;500x2"); ++ Config.BASE_WEIGHT = geoData.getInt("BaseWeight", 10); ++ Config.DIAGONAL_WEIGHT = geoData.getInt("DiagonalWeight", 14); ++ Config.OBSTACLE_MULTIPLIER = geoData.getInt("ObstacleMultiplier", 10); ++ Config.HEURISTIC_WEIGHT = geoData.getInt("HeuristicWeight", 20); ++ Config.MAX_ITERATIONS = geoData.getInt("MaxIterations", 3500); ++ } ++} +\ No newline at end of file diff --git a/L2J_Mobius_Classic_2.3_SevenSigns/dist/game/data/geodata/L2D_GeoEngine.patch b/L2J_Mobius_Classic_2.3_SevenSigns/dist/game/data/geodata/L2D_GeoEngine.patch new file mode 100644 index 0000000000..63e2faf0c9 --- /dev/null +++ b/L2J_Mobius_Classic_2.3_SevenSigns/dist/game/data/geodata/L2D_GeoEngine.patch @@ -0,0 +1,6052 @@ +Index: dist/game/GeoDataConverter.bat +=================================================================== +--- dist/game/GeoDataConverter.bat (nonexistent) ++++ dist/game/GeoDataConverter.bat (working copy) +@@ -0,0 +1,6 @@ ++@echo off ++title L2D geodata converter ++ ++java -Xmx512m -cp ./../libs/* org.l2jmobius.tools.geodataconverter.GeoDataConverter ++ ++pause +Index: dist/game/GeoDataConverter.sh +=================================================================== +--- dist/game/GeoDataConverter.sh (nonexistent) ++++ dist/game/GeoDataConverter.sh (working copy) +@@ -0,0 +1,4 @@ ++#! /bin/sh ++ ++java -Xmx512m -cp ../libs/*: org.l2jmobius.tools.geodataconverter.GeoDataConverter > log/stdout.log 2>&1 ++ +Index: dist/game/config/GeoEngine.ini +=================================================================== +--- dist/game/config/GeoEngine.ini (revision 8397) ++++ dist/game/config/GeoEngine.ini (working copy) +@@ -1,6 +1,10 @@ + # ================================================================= + # Geodata + # ================================================================= ++# Because of real-time performance we are using geodata files only in ++# diagonal L2D format now (using filename e.g. 22_16.l2d). ++# L2D geodata can be obtained by conversion of existing L2J or L2OFF geodata. ++# Launch "GeoDataConverter.bat/sh" and follow instructions to start the conversion. + + # 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/ +@@ -13,6 +17,16 @@ + CoordSynchronize = 2 + + # ================================================================= ++# Path checking ++# ================================================================= ++ ++# 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 finding + # ================================================================= + +@@ -23,19 +37,23 @@ + # Pathfinding array buffers configuration, default: 100x6;128x6;192x6;256x4;320x4;384x4;500x2 + PathFindBuffers = 100x6;128x6;192x6;256x4;320x4;384x4;500x2 + +-# Weight for nodes without obstacles far from walls +-LowWeight = 0.5 ++# Base path weight, when moving from one node to another on axis direction, default: 10 ++BaseWeight = 10 + +-# Weight for nodes near walls +-MediumWeight = 2 ++# Path weight, when moving from one node to another on diagonal direction, default: BaseWeight * sqrt(2) = 14 ++DiagonalWeight = 14 + +-# Weight for nodes with obstacles +-HighWeight = 3 ++# When movement flags of target node is blocked to any direction, multiply movement weight by this multiplier. ++# This causes pathfinding algorithm to avoid path construction exactly near an obstacle, default: 10 ++ObstacleMultiplier = 10 + +-# Weight for diagonal movement. +-# Default: LowWeight * sqrt(2) +-DiagonalWeight = 0.707 ++# Weight of the heuristic algorithm, which is giving estimated cost from node to target, default: 20 ++# For proper function must be higher than BaseWeight and/or DiagonalWeight. ++HeuristicWeight = 20 + ++# Maximum number of generated nodes per one path-finding process, default 3500 ++MaxIterations = 3500 ++ + # ================================================================= + # Other + # ================================================================= +Index: dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java +=================================================================== +--- dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java (revision 8397) ++++ dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java (working copy) +@@ -55,9 +55,8 @@ + final int worldX = activeChar.getX(); + final int worldY = activeChar.getY(); + final int worldZ = activeChar.getZ(); +- final int geoX = GeoEngine.getInstance().getGeoX(worldX); +- final int geoY = GeoEngine.getInstance().getGeoY(worldY); +- ++ final int geoX = GeoEngine.getGeoX(worldX); ++ final int geoY = GeoEngine.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)); +@@ -73,9 +72,8 @@ + final int worldX = activeChar.getX(); + final int worldY = activeChar.getY(); + final int worldZ = activeChar.getZ(); +- final int geoX = GeoEngine.getInstance().getGeoX(worldX); +- final int geoY = GeoEngine.getInstance().getGeoY(worldY); +- ++ final int geoX = GeoEngine.getGeoX(worldX); ++ final int geoY = GeoEngine.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)); +Index: dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java +=================================================================== +--- dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java (revision 8397) ++++ dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java (working copy) +@@ -18,11 +18,11 @@ + + import java.util.List; + +-import org.l2jmobius.Config; +-import org.l2jmobius.gameserver.geoengine.GeoEnginePathfinding; +-import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; ++import org.l2jmobius.gameserver.geoengine.GeoEngine; + import org.l2jmobius.gameserver.handler.IAdminCommandHandler; ++import org.l2jmobius.gameserver.model.Location; + import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance; ++import org.l2jmobius.gameserver.network.SystemMessageId; + import org.l2jmobius.gameserver.util.BuilderUtil; + + public class AdminPathNode implements IAdminCommandHandler +@@ -37,29 +37,30 @@ + { + if (command.equals("admin_path_find")) + { +- if (!Config.PATHFINDING) +- { +- BuilderUtil.sendSysMessage(activeChar, "PathFinding is disabled."); +- return true; +- } + if (activeChar.getTarget() != null) + { +- final List path = GeoEnginePathfinding.getInstance().findPath(activeChar.getX(), activeChar.getY(), (short) activeChar.getZ(), activeChar.getTarget().getX(), activeChar.getTarget().getY(), (short) activeChar.getTarget().getZ(), activeChar.getInstanceWorld()); ++ 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()); + if (path == null) + { +- BuilderUtil.sendSysMessage(activeChar, "No Route!"); +- return true; ++ BuilderUtil.sendSysMessage(activeChar, "No route found or pathfinding disabled."); + } +- for (AbstractNodeLoc a : path) ++ else + { +- BuilderUtil.sendSysMessage(activeChar, "x:" + a.getX() + " y:" + a.getY() + " z:" + a.getZ()); ++ for (Location point : path) ++ { ++ BuilderUtil.sendSysMessage(activeChar, "x:" + point.getX() + " y:" + point.getY() + " z:" + point.getZ()); ++ } + } + } + else + { +- BuilderUtil.sendSysMessage(activeChar, "No Target!"); ++ activeChar.sendPacket(SystemMessageId.INVALID_TARGET); + } + } ++ else ++ { ++ return false; ++ } + return true; + } + +Index: java/org/l2jmobius/Config.java +=================================================================== +--- java/org/l2jmobius/Config.java (revision 8397) ++++ java/org/l2jmobius/Config.java (working copy) +@@ -32,7 +32,6 @@ + import java.net.UnknownHostException; + import java.nio.charset.StandardCharsets; + import java.nio.file.Files; +-import java.nio.file.Path; + import java.nio.file.Paths; + import java.time.Duration; + import java.util.ArrayList; +@@ -927,14 +926,17 @@ + // -------------------------------------------------- + // GeoEngine + // -------------------------------------------------- +- public static Path GEODATA_PATH; ++ public static String GEODATA_PATH; ++ public static int COORD_SYNCHRONIZE; ++ public static int PART_OF_CHARACTER_HEIGHT; ++ public static int MAX_OBSTACLE_HEIGHT; + public static boolean PATHFINDING; + public static String PATHFIND_BUFFERS; +- public static float LOW_WEIGHT; +- public static float MEDIUM_WEIGHT; +- public static float HIGH_WEIGHT; +- public static float DIAGONAL_WEIGHT; +- public static int COORD_SYNCHRONIZE; ++ public static int BASE_WEIGHT; ++ public static int DIAGONAL_WEIGHT; ++ public static int HEURISTIC_WEIGHT; ++ public static int OBSTACLE_MULTIPLIER; ++ public static int MAX_ITERATIONS; + public static boolean CORRECT_PLAYER_Z; + + /** Attribute System */ +@@ -2468,14 +2470,17 @@ + + // Load GeoEngine config file (if exists) + final PropertiesParser GeoEngine = new PropertiesParser(GEOENGINE_CONFIG_FILE); +- GEODATA_PATH = Paths.get(GeoEngine.getString("GeoDataPath", "./data/geodata")); ++ GEODATA_PATH = GeoEngine.getString("GeoDataPath", "./data/geodata/"); ++ COORD_SYNCHRONIZE = GeoEngine.getInt("CoordSynchronize", -1); ++ PART_OF_CHARACTER_HEIGHT = GeoEngine.getInt("PartOfCharacterHeight", 75); ++ MAX_OBSTACLE_HEIGHT = GeoEngine.getInt("MaxObstacleHeight", 32); + PATHFINDING = GeoEngine.getBoolean("PathFinding", true); + PATHFIND_BUFFERS = GeoEngine.getString("PathFindBuffers", "100x6;128x6;192x6;256x4;320x4;384x4;500x2"); +- LOW_WEIGHT = GeoEngine.getFloat("LowWeight", 0.5f); +- MEDIUM_WEIGHT = GeoEngine.getFloat("MediumWeight", 2); +- HIGH_WEIGHT = GeoEngine.getFloat("HighWeight", 3); +- DIAGONAL_WEIGHT = GeoEngine.getFloat("DiagonalWeight", 0.707f); +- COORD_SYNCHRONIZE = GeoEngine.getInt("CoordSynchronize", -1); ++ BASE_WEIGHT = GeoEngine.getInt("BaseWeight", 10); ++ DIAGONAL_WEIGHT = GeoEngine.getInt("DiagonalWeight", 14); ++ OBSTACLE_MULTIPLIER = GeoEngine.getInt("ObstacleMultiplier", 10); ++ HEURISTIC_WEIGHT = GeoEngine.getInt("HeuristicWeight", 20); ++ MAX_ITERATIONS = GeoEngine.getInt("MaxIterations", 3500); + CORRECT_PLAYER_Z = GeoEngine.getBoolean("CorrectPlayerZ", false); + + // Load AllowedPlayerRaces config file (if exists) +Index: java/org/l2jmobius/gameserver/geoengine/GeoEngine.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/GeoEngine.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/GeoEngine.java (working copy) +@@ -16,101 +16,91 @@ + */ + package org.l2jmobius.gameserver.geoengine; + +-import java.io.IOException; ++import java.io.File; + import java.io.RandomAccessFile; + import java.nio.ByteOrder; +-import java.nio.channels.FileChannel.MapMode; +-import java.nio.file.Files; +-import java.nio.file.Path; +-import java.util.concurrent.atomic.AtomicReferenceArray; +-import java.util.logging.Level; ++import java.nio.MappedByteBuffer; ++import java.nio.channels.FileChannel; ++import java.util.List; + 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.geoengine.geodata.Cell; +-import org.l2jmobius.gameserver.geoengine.geodata.IRegion; +-import org.l2jmobius.gameserver.geoengine.geodata.NullRegion; +-import org.l2jmobius.gameserver.geoengine.geodata.Region; ++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.GeoFormat; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoLocation; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; + import org.l2jmobius.gameserver.instancemanager.WarpedSpaceManager; + 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; ++import org.l2jmobius.gameserver.util.MathUtil; + + /** +- * @author -Nemesiss-, HorridoJoho ++ * @author Hasha + */ + public class GeoEngine + { +- private static final Logger LOGGER = Logger.getLogger(GeoEngine.class.getName()); ++ protected static final Logger LOGGER = Logger.getLogger(GeoEngine.class.getName()); + +- private static final int WORLD_MIN_X = -655360; +- private static final int WORLD_MIN_Y = -589824; +- private static final int WORLD_MIN_Z = -16384; ++ private final ABlock[][] _blocks; ++ private final BlockNull _nullBlock; + +- /** 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; +- +- /** The regions array */ +- private final AtomicReferenceArray _regions = new AtomicReferenceArray<>(GEO_REGIONS); +- +- 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; +- +- protected GeoEngine() ++ /** ++ * GeoEngine constructor. Loads all geodata files of chosen geodata format. ++ */ ++ public GeoEngine() + { + LOGGER.info("GeoEngine: Initializing..."); +- for (int i = 0; i < _regions.length(); i++) +- { +- _regions.set(i, NullRegion.INSTANCE); +- } + ++ // 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; +- try ++ long fileSize = 0; ++ for (int rx = World.TILE_X_MIN; rx <= World.TILE_X_MAX; rx++) + { +- for (int regionX = World.TILE_X_MIN; regionX <= World.TILE_X_MAX; regionX++) ++ for (int ry = World.TILE_Y_MIN; ry <= World.TILE_Y_MAX; ry++) + { +- for (int regionY = World.TILE_Y_MIN; regionY <= World.TILE_Y_MAX; regionY++) ++ final File f = new File(Config.GEODATA_PATH + String.format(GeoFormat.L2D.getFilename(), rx, ry)); ++ if (f.exists() && !f.isDirectory()) + { +- 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(rx, ry)) + { +- try (RandomAccessFile raf = new RandomAccessFile(geoFilePath.toFile(), "r")) +- { +- _regions.set((regionX * GEO_REGIONS_Y) + regionY, new Region(raf.getChannel().map(MapMode.READ_ONLY, 0, raf.length()).load().order(ByteOrder.LITTLE_ENDIAN))); +- loaded++; +- } ++ loaded++; ++ fileSize += f.length(); + } + } ++ else ++ { ++ // region file is not load-able, load null blocks ++ loadNullBlocks(rx, ry); ++ } + } + } +- catch (Exception e) ++ ++ LOGGER.info("GeoEngine: Loaded " + loaded + " geodata files."); ++ if (loaded > 0) + { +- LOGGER.log(Level.SEVERE, "GeoEngine: Failed to load geodata!", e); +- System.exit(1); ++ LOGGER.info("GeoEngine: Total geodata file size " + (fileSize / 1024 / 1024) + " MB."); + } + +- LOGGER.info("GeoEngine: Loaded " + loaded + " geodata files."); +- +- // Avoid wrong configs when no files are loaded. ++ // avoid wrong configs when no files are loaded + if (loaded == 0) + { + if (Config.PATHFINDING) +@@ -124,627 +114,832 @@ + LOGGER.info("GeoEngine: Forcing CoordSynchronize setting to -1."); + } + } ++ ++ // release multilayer block temporarily buffer ++ BlockMultilayer.release(); + } + + /** +- * @param geoX +- * @param geoY +- * @return the region ++ * 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 IRegion getRegion(int geoX, int geoY) ++ private final boolean loadGeoBlocks(int regionX, int regionY) + { +- final int region = ((geoX / IRegion.REGION_CELLS_X) * GEO_REGIONS_Y) + (geoY / IRegion.REGION_CELLS_Y); +- if ((region < 0) || (region >= _regions.length())) ++ final String filename = String.format(GeoFormat.L2D.getFilename(), regionX, regionY); ++ ++ // standard load ++ try (RandomAccessFile raf = new RandomAccessFile(Config.GEODATA_PATH + filename, "r"); ++ FileChannel fc = raf.getChannel()) + { +- return null; ++ // initialize file buffer ++ final MappedByteBuffer buffer = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()).load(); ++ buffer.order(ByteOrder.LITTLE_ENDIAN); ++ ++ // 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++) ++ { ++ // get block type ++ final byte type = buffer.get(); ++ ++ // load block according to block type ++ switch (type) ++ { ++ case GeoStructure.TYPE_FLAT_L2D: ++ { ++ _blocks[blockX + ix][blockY + iy] = new BlockFlat(buffer, GeoFormat.L2D); ++ break; ++ } ++ case GeoStructure.TYPE_COMPLEX_L2D: ++ { ++ _blocks[blockX + ix][blockY + iy] = new BlockComplex(buffer, GeoFormat.L2D); ++ break; ++ } ++ case GeoStructure.TYPE_MULTILAYER_L2D: ++ { ++ _blocks[blockX + ix][blockY + iy] = new BlockMultilayer(buffer, GeoFormat.L2D); ++ break; ++ } ++ default: ++ { ++ throw new IllegalArgumentException("Unknown block type: " + type); ++ } ++ } ++ } ++ } ++ ++ // check data consistency ++ if (buffer.remaining() > 0) ++ { ++ LOGGER.warning("GeoEngine: Region file " + filename + " can be corrupted, remaining " + buffer.remaining() + " bytes to read."); ++ } ++ ++ // loading was successful ++ return true; + } +- return _regions.get(region); +- } +- +- /** +- * @param filePath +- * @param regionX +- * @param regionY +- * @throws IOException +- */ +- 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")) ++ catch (Exception e) + { +- _regions.set(regionOffset, new Region(raf.getChannel().map(MapMode.READ_ONLY, 0, raf.length()).load().order(ByteOrder.LITTLE_ENDIAN))); ++ // an error occured while loading, load null blocks ++ LOGGER.warning("GeoEngine: Error while loading " + filename + " region file."); ++ LOGGER.warning(e.getMessage()); ++ ++ // replace whole region file with null blocks ++ loadNullBlocks(regionX, regionY); ++ ++ // loading was not successful ++ return false; + } + } + + /** +- * @param regionX +- * @param regionY ++ * 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. + */ +- public void unloadRegion(int regionX, int regionY) ++ private final void loadNullBlocks(int regionX, int regionY) + { +- _regions.set((regionX * GEO_REGIONS_Y) + regionY, NullRegion.INSTANCE); +- } +- +- /** +- * @param geoX +- * @param geoY +- * @return if geodata exist +- */ +- public boolean hasGeoPos(int geoX, int geoY) +- { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) ++ // 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++) + { +- return false; ++ for (int iy = 0; iy < GeoStructure.REGION_BLOCKS_Y; iy++) ++ { ++ _blocks[blockX + ix][blockY + iy] = _nullBlock; ++ } + } +- return region.hasGeo(); + } + ++ // GEODATA - GENERAL ++ + /** +- * Checks the specified position for available geodata. +- * @param x the world x +- * @param y the world y +- * @return {@code true} if there is geodata for the given coordinates, {@code false} otherwise ++ * Converts world X to geodata X. ++ * @param worldX ++ * @return int : Geo X + */ +- public boolean hasGeo(int x, int y) ++ public static int getGeoX(int worldX) + { +- return hasGeoPos(getGeoX(x), getGeoY(y)); ++ return (MathUtil.limit(worldX, World.MAP_MIN_X, World.MAP_MAX_X) - World.MAP_MIN_X) >> 4; + } + + /** +- * @param geoX +- * @param geoY +- * @param worldZ +- * @param nswe +- * @return the nearest nswe check ++ * Converts world Y to geodata Y. ++ * @param worldY ++ * @return int : Geo Y + */ +- public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) ++ public static int getGeoY(int worldY) + { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) +- { +- return true; +- } +- return region.checkNearestNswe(geoX, geoY, worldZ, nswe); ++ return (MathUtil.limit(worldY, World.MAP_MIN_Y, World.MAP_MAX_Y) - World.MAP_MIN_Y) >> 4; + } + + /** ++ * Converts geodata X to world X. + * @param geoX +- * @param geoY +- * @param worldZ +- * @param nswe +- * @return the nearest nswe anti-corner cut ++ * @return int : World X + */ +- public boolean checkNearestNsweAntiCornerCut(int geoX, int geoY, int worldZ, int nswe) ++ public static int getWorldX(int geoX) + { +- boolean can = true; +- if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) +- { +- can = checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_EAST) && checkNearestNswe(geoX + 1, geoY, worldZ, Cell.NSWE_NORTH); +- } +- if (can && ((nswe & Cell.NSWE_NORTH_WEST) == Cell.NSWE_NORTH_WEST)) +- { +- 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 = 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 = checkNearestNswe(geoX, geoY + 1, worldZ, Cell.NSWE_WEST) && checkNearestNswe(geoX - 1, geoY, worldZ, Cell.NSWE_SOUTH); +- } +- return can && checkNearestNswe(geoX, geoY, worldZ, nswe); ++ return (MathUtil.limit(geoX, 0, GeoStructure.GEO_CELLS_X) << 4) + World.MAP_MIN_X + 8; + } + + /** +- * @param geoX ++ * Converts geodata Y to world Y. + * @param geoY +- * @param worldZ +- * @return the nearest Z value ++ * @return int : World Y + */ +- public int getNearestZ(int geoX, int geoY, int worldZ) ++ public static int getWorldY(int geoY) + { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) +- { +- return worldZ; +- } +- return region.getNearestZ(geoX, geoY, worldZ); ++ return (MathUtil.limit(geoY, 0, GeoStructure.GEO_CELLS_Y) << 4) + World.MAP_MIN_Y + 8; + } + + /** +- * @param geoX +- * @param geoY +- * @param worldZ +- * @return the next lower Z value ++ * Returns block of geodata on given coordinates. ++ * @param geoX : Geodata X ++ * @param geoY : Geodata Y ++ * @return {@link ABlock} : Block of geodata. + */ +- public int getNextLowerZ(int geoX, int geoY, int worldZ) ++ private final ABlock getBlock(int geoX, int geoY) + { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) ++ final int x = geoX / GeoStructure.BLOCK_CELLS_X; ++ final int y = geoY / GeoStructure.BLOCK_CELLS_Y; ++ ++ // if x or y is out of array return null ++ if ((x > -1) && (y > -1) && (x < GeoStructure.GEO_BLOCKS_X) && (y < GeoStructure.GEO_BLOCKS_Y)) + { +- return worldZ; ++ return _blocks[x][y]; + } +- return region.getNextLowerZ(geoX, geoY, worldZ); ++ return null; + } + + /** +- * @param geoX +- * @param geoY +- * @param worldZ +- * @return the next higher Z value ++ * Check if geo coordinates has geo. ++ * @param geoX : Geodata X ++ * @param geoY : Geodata Y ++ * @return boolean : True, if given geo coordinates have geodata + */ +- public int getNextHigherZ(int geoX, int geoY, int worldZ) ++ public boolean hasGeoPos(int geoX, int geoY) + { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) ++ final ABlock block = getBlock(geoX, geoY); ++ if (block == null) // null block check + { +- return worldZ; ++ // TODO: Find when this can be null. (Bad geodata? Check World getRegion method.) ++ // LOGGER.warning("Could not find geodata block at " + getWorldX(geoX) + ", " + getWorldY(geoY) + "."); ++ return false; + } +- return region.getNextHigherZ(geoX, geoY, worldZ); ++ return block.hasGeoPos(); + } + + /** +- * Gets the Z height. +- * @param x the world x +- * @param y the world y +- * @param z the world z +- * @return the nearest Z height ++ * 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 int getHeight(int x, int y, int z) ++ public short getHeightNearest(int geoX, int geoY, int worldZ) + { +- return getNearestZ(getGeoX(x), getGeoY(y), z); ++ final ABlock block = getBlock(geoX, geoY); ++ return block != null ? block.getHeightNearest(geoX, geoY, worldZ) : (short) worldZ; + } + + /** +- * Gets the next lower Z height. +- * @param x the world x +- * @param y the world y +- * @param z the world z +- * @return the nearest Z height ++ * 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 int getLowerHeight(int x, int y, int z) ++ public byte getNsweNearest(int geoX, int geoY, int worldZ) + { +- return getNextLowerZ(getGeoX(x), getGeoY(y), z); ++ final ABlock block = getBlock(geoX, geoY); ++ return block != null ? block.getNsweNearest(geoX, geoY, worldZ) : (byte) 0xFF; + } + + /** +- * Gets the next higher Z height. +- * @param x the world x +- * @param y the world y +- * @param z the world z +- * @return the nearest Z height ++ * Check if world coordinates has geo. ++ * @param worldX : World X ++ * @param worldY : World Y ++ * @return boolean : True, if given world coordinates have geodata + */ +- public int getHigherHeight(int x, int y, int z) ++ public boolean hasGeo(int worldX, int worldY) + { +- return getNextHigherZ(getGeoX(x), getGeoY(y), z); ++ return hasGeoPos(getGeoX(worldX), getGeoY(worldY)); + } + + /** +- * @param worldX +- * @return the geo X ++ * 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 int getGeoX(int worldX) ++ public short getHeight(int worldX, int worldY, int worldZ) + { +- return (worldX - WORLD_MIN_X) / 16; ++ return getHeightNearest(getGeoX(worldX), getGeoY(worldY), worldZ); + } + +- /** +- * @param worldY +- * @return the geo Y +- */ +- public int getGeoY(int worldY) +- { +- return (worldY - WORLD_MIN_Y) / 16; +- } ++ // PATHFINDING + + /** +- * @param worldZ +- * @return the geo Z ++ * Check line of sight from {@link WorldObject} to {@link WorldObject}. ++ * @param origin : The origin object. ++ * @param target : The target object. ++ * @return {@code boolean} : True if origin can see target + */ +- public int getGeoZ(int worldZ) ++ public boolean canSeeTarget(WorldObject origin, WorldObject target) + { +- return (worldZ - WORLD_MIN_Z) / 16; +- } +- +- /** +- * @param geoX +- * @return the world X +- */ +- public int getWorldX(int geoX) +- { +- return (geoX * 16) + WORLD_MIN_X + 8; +- } +- +- /** +- * @param geoY +- * @return the world Y +- */ +- public int getWorldY(int geoY) +- { +- return (geoY * 16) + WORLD_MIN_Y + 8; +- } +- +- /** +- * @param geoZ +- * @return the world Z +- */ +- public int getWorldZ(int geoZ) +- { +- return (geoZ * 16) + WORLD_MIN_Z + 8; +- } +- +- /** +- * 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) +- { +- if (target.isDoor()) ++ if (target.isDoor() || target.isArtefact() || (target.isCreature() && ((Creature) target).isFlying())) + { +- // Can always see doors. + return true; + } +- return 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(), cha.getInstanceWorld(), worldPosition.getX(), worldPosition.getY(), worldPosition.getZ()); +- } +- +- /** +- * Can see target. Checks doors between. +- * @param x the x coordinate +- * @param y the y coordinate +- * @param z the z coordinate +- * @param world +- * @param tx the target's x coordinate +- * @param ty the target's y coordinate +- * @param tz the target's z coordinate +- * @param tworld the target's instanceId +- * @return +- */ +- public boolean canSeeTarget(int x, int y, int z, Instance world, int tx, int ty, int tz, Instance tworld) +- { +- return (world != tworld) ? false : canSeeTarget(x, y, z, world, tx, ty, tz); +- } +- +- /** +- * 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 +- * @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, Instance instance, int tx, int ty, int tz) +- { +- if (DoorData.getInstance().checkIfDoorsBetween(x, y, z, tx, ty, tz, instance, true)) ++ ++ // get origin and target world coordinates ++ final int ox = origin.getX(); ++ final int oy = origin.getY(); ++ final int oz = origin.getZ(); ++ final int tx = target.getX(); ++ final int ty = target.getY(); ++ final int tz = target.getZ(); ++ ++ if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld(), true)) + { + return false; + } +- return canSeeTarget(x, y, z, tx, ty, tz); +- } +- +- /** +- * @param prevX +- * @param prevY +- * @param prevGeoZ +- * @param curX +- * @param curY +- * @param nswe +- * @return the LOS Z value +- */ +- 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))) ++ if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld())) + { +- throw new RuntimeException("Multiple directions!"); ++ return false; + } ++ if (WarpedSpaceManager.getInstance().checkForWarpedSpace(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld())) ++ { ++ return false; ++ } + +- if (checkNearestNsweAntiCornerCut(prevX, prevY, prevGeoZ, nswe)) ++ // get origin and check existing geo coordinates ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) + { +- return getNearestZ(curX, curY, prevGeoZ); ++ return true; + } + +- return getNextHigherZ(curX, curY, prevGeoZ); ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coordinates ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) ++ { ++ return true; ++ } ++ ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // origin and target coordinates are same ++ if ((gox == gtx) && (goy == gty)) ++ { ++ return goz == gtz; ++ } ++ ++ // get origin and target height, real height = collision height * 2 ++ double oheight = 0; ++ if (origin.isCreature()) ++ { ++ oheight = ((Creature) origin).getCollisionHeight() * 2; ++ } ++ ++ double theight = 0; ++ if (target.isCreature()) ++ { ++ theight = ((Creature) target).getCollisionHeight() * 2; ++ } ++ ++ // perform geodata check ++ return checkSee(gox, goy, goz, oheight, gtx, gty, gtz, theight, origin.getInstanceWorld()); + } + + /** +- * Can see target. Does not check doors between. +- * @param xValue the x coordinate +- * @param yValue the y coordinate +- * @param zValue the z coordinate +- * @param txValue the target's x coordinate +- * @param tyValue the target's y coordinate +- * @param tzValue the target's z coordinate +- * @return {@code true} if there is line of sight between the given coordinate sets, {@code false} otherwise ++ * Check line of sight from {@link WorldObject} to {@link Location}. ++ * @param origin : The origin object. ++ * @param position : The target position. ++ * @return {@code boolean} : True if object can see position + */ +- public boolean canSeeTarget(int xValue, int yValue, int zValue, int txValue, int tyValue, int tzValue) ++ public boolean canSeeTarget(WorldObject origin, Location position) + { +- int x = xValue; +- int y = yValue; +- int tx = txValue; +- int ty = tyValue; ++ // get origin and target world coordinates ++ final int ox = origin.getX(); ++ final int oy = origin.getY(); ++ final int oz = origin.getZ(); ++ final int tx = position.getX(); ++ final int ty = position.getY(); ++ final int tz = position.getZ(); + +- int geoX = getGeoX(x); +- int geoY = getGeoY(y); +- int tGeoX = getGeoX(tx); +- int tGeoY = getGeoY(ty); ++ if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld(), true)) ++ { ++ return false; ++ } ++ if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld())) ++ { ++ return false; ++ } ++ if (WarpedSpaceManager.getInstance().checkForWarpedSpace(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld())) ++ { ++ return false; ++ } + +- int z = getNearestZ(geoX, geoY, zValue); +- int tz = getNearestZ(tGeoX, tGeoY, tzValue); ++ // get origin and check existing geo coordinates ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) ++ { ++ return true; ++ } + +- // Fastpath. +- if ((geoX == tGeoX) && (geoY == tGeoY)) ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coordinates ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) + { +- if (hasGeoPos(tGeoX, tGeoY)) +- { +- return z == tz; +- } + return true; + } + +- if (tz > z) ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // origin and target coordinates are same ++ if ((gox == gtx) && (goy == gty)) + { +- int tmp = tx; +- tx = x; +- x = tmp; +- +- tmp = ty; +- ty = y; +- y = tmp; +- +- tmp = tz; +- tz = z; +- z = tmp; +- +- tmp = tGeoX; +- tGeoX = geoX; +- geoX = tmp; +- +- tmp = tGeoY; +- tGeoY = geoY; +- geoY = tmp; ++ return goz == gtz; + } + +- final LinePointIterator3D pointIter = new LinePointIterator3D(geoX, geoY, z, tGeoX, tGeoY, tz); +- // 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()) ++ // get origin and target height, real height = collision height * 2 ++ double oheight = 0; ++ if (origin.isCreature()) + { +- final int curX = pointIter.x(); +- final int curY = pointIter.y(); ++ oheight = ((Creature) origin).getTemplate().getCollisionHeight(); ++ } ++ ++ // perform geodata check ++ return checkSee(gox, goy, goz, oheight, gtx, gty, gtz, 0, origin.getInstanceWorld()); ++ } ++ ++ /** ++ * Simple check for origin to target visibility. ++ * @param goxValue : origin X geodata coordinate ++ * @param goyValue : origin Y geodata coordinate ++ * @param gozValue : origin Z geodata coordinate ++ * @param oheight : origin height (if instance of {@link Character}) ++ * @param gtxValue : target X geodata coordinate ++ * @param gtyValue : target Y geodata coordinate ++ * @param gtzValue : target Z geodata coordinate ++ * @param theight : target height (if instance of {@link Character}) ++ * @param instance ++ * @return {@code boolean} : True, when target can be seen. ++ */ ++ private final boolean checkSee(int goxValue, int goyValue, int gozValue, double oheight, int gtxValue, int gtyValue, int gtzValue, double theight, Instance instance) ++ { ++ int goz = gozValue; ++ int gtz = gtzValue; ++ int gox = goxValue; ++ int goy = goyValue; ++ int gtx = gtxValue; ++ int gty = gtyValue; ++ ++ // get line of sight Z coordinates ++ double losoz = goz + ((oheight * Config.PART_OF_CHARACTER_HEIGHT) / 100); ++ double lostz = gtz + ((theight * Config.PART_OF_CHARACTER_HEIGHT) / 100); ++ ++ // get X delta and signum ++ final int dx = Math.abs(gtx - gox); ++ final int sx = gox < gtx ? 1 : -1; ++ final byte dirox = sx > 0 ? GeoStructure.CELL_FLAG_E : GeoStructure.CELL_FLAG_W; ++ final byte dirtx = sx > 0 ? GeoStructure.CELL_FLAG_W : GeoStructure.CELL_FLAG_E; ++ ++ // get Y delta and signum ++ final int dy = Math.abs(gty - goy); ++ final int sy = goy < gty ? 1 : -1; ++ final byte diroy = sy > 0 ? GeoStructure.CELL_FLAG_S : GeoStructure.CELL_FLAG_N; ++ final byte dirty = sy > 0 ? GeoStructure.CELL_FLAG_N : GeoStructure.CELL_FLAG_S; ++ ++ // get Z delta ++ final int dm = Math.max(dx, dy); ++ final double dz = (lostz - losoz) / dm; ++ ++ // get direction flag for diagonal movement ++ final byte diroxy = getDirXY(dirox, diroy); ++ final byte dirtxy = getDirXY(dirtx, dirty); ++ ++ // delta, determines axis to move on (+..X axis, -..Y axis) ++ int d = dx - dy; ++ ++ // NSWE direction of movement ++ byte diro; ++ byte dirt; ++ ++ // initialize node values ++ int nox = gox; ++ int noy = goy; ++ int ntx = gtx; ++ int nty = gty; ++ byte nsweo = getNsweNearest(gox, goy, goz); ++ byte nswet = getNsweNearest(gtx, gty, gtz); ++ ++ // loop ++ ABlock block; ++ int index; ++ for (int i = 0; i < ((dm + 1) / 2); i++) ++ { ++ // reset direction flag ++ diro = 0; ++ dirt = 0; + +- if ((curX == prevX) && (curY == prevY)) ++ // calculate next point coordinates ++ final int e2 = 2 * d; ++ if ((e2 > -dy) && (e2 < dx)) + { +- continue; ++ // calculate next point XY coordinates ++ d -= dy; ++ d += dx; ++ nox += sx; ++ ntx -= sx; ++ noy += sy; ++ nty -= sy; ++ diro |= diroxy; ++ dirt |= dirtxy; + } ++ else if (e2 > -dy) ++ { ++ // calculate next point X coordinate ++ d -= dy; ++ nox += sx; ++ ntx -= sx; ++ diro |= dirox; ++ dirt |= dirtx; ++ } ++ else if (e2 < dx) ++ { ++ // calculate next point Y coordinate ++ d += dx; ++ noy += sy; ++ nty -= sy; ++ diro |= diroy; ++ dirt |= dirty; ++ } + +- final int beeCurZ = pointIter.z(); +- int curGeoZ = prevGeoZ; +- +- // Check if the position has geodata. +- if (hasGeoPos(curX, curY)) + { +- final int beeCurGeoZ = getNearestZ(curX, curY, beeCurZ); +- final int nswe = GeoUtils.computeNswe(prevX, prevY, curX, curY); // .computeDirection(prevX, prevY, curX, curY); +- curGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, curX, curY, nswe); +- int maxHeight; +- if (ptIndex < ELEVATED_SEE_OVER_DISTANCE) ++ // get block of the next cell ++ block = getBlock(nox, noy); ++ ++ // get index of particular layer, based on movement conditions ++ if ((nsweo & diro) == 0) + { +- maxHeight = z + MAX_SEE_OVER_HEIGHT; ++ index = block.getIndexAbove(nox, noy, goz - GeoStructure.CELL_IGNORE_HEIGHT); + } + else + { +- maxHeight = beeCurZ + MAX_SEE_OVER_HEIGHT; ++ index = block.getIndexBelow(nox, noy, goz + GeoStructure.CELL_IGNORE_HEIGHT); + } + +- boolean canSeeThrough = false; +- if ((curGeoZ <= maxHeight) && (curGeoZ <= beeCurGeoZ)) ++ // layer does not exist, return ++ if (index == -1) + { +- if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) +- { +- 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 +- { +- canSeeThrough = true; +- } ++ return false; + } + +- if (!canSeeThrough) ++ // get layer and next line of sight Z coordinate ++ goz = block.getHeight(index); ++ losoz += dz; ++ ++ // perform line of sight check, return when fails ++ if ((goz - losoz) > Config.MAX_OBSTACLE_HEIGHT) + { + return false; + } ++ ++ // get layer nswe ++ nsweo = block.getNswe(index); + } ++ { ++ // get block of the next cell ++ block = getBlock(ntx, nty); ++ ++ // get index of particular layer, based on movement conditions ++ if ((nswet & dirt) == 0) ++ { ++ index = block.getIndexAbove(ntx, nty, gtz - GeoStructure.CELL_IGNORE_HEIGHT); ++ } ++ else ++ { ++ index = block.getIndexBelow(ntx, nty, gtz + GeoStructure.CELL_IGNORE_HEIGHT); ++ } ++ ++ // layer does not exist, return ++ if (index == -1) ++ { ++ return false; ++ } ++ ++ // get layer and next line of sight Z coordinate ++ gtz = block.getHeight(index); ++ lostz -= dz; ++ ++ // perform line of sight check, return when fails ++ if ((gtz - lostz) > Config.MAX_OBSTACLE_HEIGHT) ++ { ++ return false; ++ } ++ ++ // get layer nswe ++ nswet = block.getNswe(index); ++ } + +- prevX = curX; +- prevY = curY; +- prevGeoZ = curGeoZ; +- ++ptIndex; ++ // update coords ++ gox = nox; ++ goy = noy; ++ gtx = ntx; ++ gty = nty; + } + +- return true; ++ // when iteration is completed, compare final Z coordinates ++ return Math.abs(goz - gtz) < (GeoStructure.CELL_HEIGHT * 4); + } + + /** +- * Move check. +- * @param x the x coordinate +- * @param y the y coordinate +- * @param zValue the z coordinate +- * @param tx the target's x coordinate +- * @param ty the target's y coordinate +- * @param tzValue the target's z coordinate +- * @param instance the instance +- * @return the last Location (x,y,z) where player can walk - just before wall ++ * Check movement from coordinates to 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 {code boolean} : True if target coordinates are reachable from origin coordinates + */ +- public Location canMoveToTargetLoc(int x, int y, int zValue, int tx, int ty, int tzValue, Instance instance) ++ public boolean canMoveToTarget(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + { +- final int geoX = getGeoX(x); +- final int geoY = getGeoY(y); +- final int z = getNearestZ(geoX, geoY, zValue); +- final int tGeoX = getGeoX(tx); +- final int tGeoY = getGeoY(ty); +- final int tz = getNearestZ(tGeoX, tGeoY, tzValue); ++ // get origin and check existing geo coordinates ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) ++ { ++ return true; ++ } + +- if (DoorData.getInstance().checkIfDoorsBetween(x, y, z, tx, ty, tz, instance, false)) ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coordinates ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) + { +- return new Location(x, y, getHeight(x, y, z)); ++ return true; + } +- if (FenceData.getInstance().checkIfFenceBetween(x, y, z, tx, ty, tz, instance)) ++ ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // target coordinates reached ++ if ((gox == gtx) && (goy == gty) && (goz == gtz)) + { +- return new Location(x, y, getHeight(x, y, z)); ++ return true; + } +- if (WarpedSpaceManager.getInstance().checkForWarpedSpace(x, y, z, tx, ty, tz, instance)) ++ ++ // perform geodata check ++ final GeoLocation loc = checkMove(gox, goy, goz, gtx, gty, gtz, instance); ++ return (loc.getGeoX() == gtx) && (loc.getGeoY() == gty); ++ } ++ ++ /** ++ * Check movement from origin to target. Returns last available point in the checked path. ++ * @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 {@link Location} : Last point where object can walk (just before wall) ++ */ ++ public Location canMoveToTargetLoc(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) ++ { ++ // Mobius: Double check for doors before normal checkMove to avoid exploiting key movement. ++ if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, instance, false)) + { +- return new Location(x, y, getHeight(x, y, z)); ++ return new Location(ox, oy, oz); + } ++ if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, instance)) ++ { ++ return new Location(ox, oy, oz); ++ } + +- 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 = z; ++ // get origin and check existing geo coordinates ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) ++ { ++ return new Location(tx, ty, tz); ++ } + +- while (pointIter.next()) ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coordinates ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) + { +- final int curX = pointIter.x(); +- final int curY = pointIter.y(); +- final int curZ = getNearestZ(curX, curY, prevZ); +- +- if (hasGeoPos(prevX, prevY)) +- { +- final int nswe = GeoUtils.computeNswe(prevX, prevY, curX, curY); +- if (!checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, nswe)) +- { +- // Can't move, return previous location. +- return new Location(getWorldX(prevX), getWorldY(prevY), prevZ); +- } +- } +- +- prevX = curX; +- prevY = curY; +- prevZ = curZ; ++ return new Location(tx, ty, tz); + } + +- if (hasGeoPos(prevX, prevY) && (prevZ != tz)) ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // target coordinates reached ++ if ((gox == gtx) && (goy == gty) && (goz == gtz)) + { +- // Different floors, return start location. +- return new Location(x, y, z); ++ return new Location(tx, ty, tz); + } + +- return new Location(tx, ty, tz); ++ // perform geodata check ++ return checkMove(gox, goy, goz, gtx, gty, gtz, instance); + } + + /** +- * 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 fromZvalue 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 toZvalue 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 ++ * With this method you can check if a position is visible or can be reached by beeline movement.
++ * 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 gox : origin X geodata coordinate ++ * @param goy : origin Y geodata coordinate ++ * @param goz : origin Z geodata coordinate ++ * @param gtx : target X geodata coordinate ++ * @param gty : target Y geodata coordinate ++ * @param gtz : target Z geodata coordinate ++ * @param instance ++ * @return {@link GeoLocation} : The last allowed point of movement. + */ +- public boolean canMoveToTarget(int fromX, int fromY, int fromZvalue, int toX, int toY, int toZvalue, Instance instance) ++ protected final GeoLocation checkMove(int gox, int goy, int goz, int gtx, int gty, int gtz, Instance instance) + { +- final int geoX = getGeoX(fromX); +- final int geoY = getGeoY(fromY); +- final int fromZ = getNearestZ(geoX, geoY, fromZvalue); +- final int tGeoX = getGeoX(toX); +- final int tGeoY = getGeoY(toY); +- final int toZ = getNearestZ(tGeoX, tGeoY, toZvalue); +- +- if (DoorData.getInstance().checkIfDoorsBetween(fromX, fromY, fromZ, toX, toY, toZ, instance, false)) ++ if (DoorData.getInstance().checkIfDoorsBetween(gox, goy, goz, gtx, gty, gtz, instance, false)) + { +- return false; ++ return new GeoLocation(gox, goy, goz); + } +- if (FenceData.getInstance().checkIfFenceBetween(fromX, fromY, fromZ, toX, toY, toZ, instance)) ++ if (FenceData.getInstance().checkIfFenceBetween(gox, goy, goz, gtx, gty, gtz, instance)) + { +- return false; ++ return new GeoLocation(gox, goy, goz); + } +- if (WarpedSpaceManager.getInstance().checkForWarpedSpace(fromX, fromY, fromZ, toX, toY, toZ, instance)) ++ if (WarpedSpaceManager.getInstance().checkForWarpedSpace(gox, goy, goz, gtx, gty, gtz, instance)) + { +- return false; ++ return new GeoLocation(gox, goy, goz); + } + +- 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 = fromZ; ++ // get X delta, signum and direction flag ++ final int dx = Math.abs(gtx - gox); ++ final int sx = gox < gtx ? 1 : -1; ++ final byte dirX = sx > 0 ? GeoStructure.CELL_FLAG_E : GeoStructure.CELL_FLAG_W; + +- while (pointIter.next()) ++ // get Y delta, signum and direction flag ++ final int dy = Math.abs(gty - goy); ++ final int sy = goy < gty ? 1 : -1; ++ final byte dirY = sy > 0 ? GeoStructure.CELL_FLAG_S : GeoStructure.CELL_FLAG_N; ++ ++ // get direction flag for diagonal movement ++ final byte dirXY = getDirXY(dirX, dirY); ++ ++ // delta, determines axis to move on (+..X axis, -..Y axis) ++ int d = dx - dy; ++ ++ // NSWE direction of movement ++ byte direction; ++ ++ // load pointer coordinates ++ int gpx = gox; ++ int gpy = goy; ++ int gpz = goz; ++ ++ // load next pointer ++ int nx = gpx; ++ int ny = gpy; ++ ++ // loop ++ int count = 0; ++ while (count++ < Config.MAX_ITERATIONS) + { +- final int curX = pointIter.x(); +- final int curY = pointIter.y(); +- final int curZ = getNearestZ(curX, curY, prevZ); ++ direction = 0; + +- if (hasGeoPos(prevX, prevY)) ++ // calculate next point coordinates ++ final int e2 = 2 * d; ++ if ((e2 > -dy) && (e2 < dx)) + { +- final int nswe = GeoUtils.computeNswe(prevX, prevY, curX, curY); +- if (!checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, nswe)) ++ d -= dy; ++ d += dx; ++ nx += sx; ++ ny += sy; ++ direction |= dirXY; ++ } ++ else if (e2 > -dy) ++ { ++ d -= dy; ++ nx += sx; ++ direction |= dirX; ++ } ++ else if (e2 < dx) ++ { ++ d += dx; ++ ny += sy; ++ direction |= dirY; ++ } ++ ++ // obstacle found, return ++ if ((getNsweNearest(gpx, gpy, gpz) & direction) == 0) ++ { ++ return new GeoLocation(gpx, gpy, gpz); ++ } ++ ++ // update pointer coordinates ++ gpx = nx; ++ gpy = ny; ++ gpz = getHeightNearest(nx, ny, gpz); ++ ++ // target coordinates reached ++ if ((gpx == gtx) && (gpy == gty)) ++ { ++ if (gpz == gtz) + { +- return false; ++ // path found, Z coordinates are okay, return target point ++ return new GeoLocation(gtx, gty, gtz); + } ++ ++ // path found, Z coordinates are not okay, return last good point ++ return new GeoLocation(gpx, gpy, gpz); + } ++ } ++ ++ return new GeoLocation(gox, goy, goz); ++ } ++ ++ /** ++ * Returns diagonal NSWE flag format of combined two NSWE flags. ++ * @param dirX : X direction NSWE flag ++ * @param dirY : Y direction NSWE flag ++ * @return byte : NSWE flag of combined direction ++ */ ++ private static byte getDirXY(byte dirX, byte dirY) ++ { ++ // check axis directions ++ if (dirY == GeoStructure.CELL_FLAG_N) ++ { ++ if (dirX == GeoStructure.CELL_FLAG_W) ++ { ++ return GeoStructure.CELL_FLAG_NW; ++ } + +- prevX = curX; +- prevY = curY; +- prevZ = curZ; ++ return GeoStructure.CELL_FLAG_NE; + } + +- if (hasGeoPos(prevX, prevY) && (prevZ != toZ)) ++ if (dirX == GeoStructure.CELL_FLAG_W) + { +- // Different floors. +- return false; ++ return GeoStructure.CELL_FLAG_SW; + } + +- return true; ++ return GeoStructure.CELL_FLAG_SE; + } + ++ /** ++ * 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 ++ */ ++ public List findPath(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) ++ { ++ return null; ++ } ++ ++ /** ++ * Returns the instance of the {@link GeoEngine}. ++ * @return {@link GeoEngine} : The instance. ++ */ + public static GeoEngine getInstance() + { + return SingletonHolder.INSTANCE; +@@ -752,6 +947,6 @@ + + private static class SingletonHolder + { +- protected static final GeoEngine INSTANCE = new GeoEngine(); ++ protected static final GeoEngine INSTANCE = Config.PATHFINDING ? new GeoEnginePathfinding() : new GeoEngine(); + } +-} ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/GeoEnginePathfinding.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/GeoEnginePathfinding.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/GeoEnginePathfinding.java (working copy) +@@ -20,88 +20,84 @@ + import java.util.LinkedList; + import java.util.List; + import java.util.ListIterator; +-import java.util.logging.Level; +-import java.util.logging.Logger; + + import org.l2jmobius.Config; +-import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; +-import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +-import org.l2jmobius.gameserver.geoengine.pathfinding.CellNode; +-import org.l2jmobius.gameserver.geoengine.pathfinding.CellNodeBuffer; +-import org.l2jmobius.gameserver.geoengine.pathfinding.NodeLoc; +-import org.l2jmobius.gameserver.model.World; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoLocation; ++import org.l2jmobius.gameserver.geoengine.pathfinding.Node; ++import org.l2jmobius.gameserver.geoengine.pathfinding.NodeBuffer; ++import org.l2jmobius.gameserver.model.Location; + import org.l2jmobius.gameserver.model.instancezone.Instance; + + /** +- * @author -Nemesiss- ++ * @author Hasha + */ +-public class GeoEnginePathfinding ++final class GeoEnginePathfinding extends GeoEngine + { +- private static final Logger LOGGER = Logger.getLogger(GeoEnginePathfinding.class.getName()); ++ // pre-allocated buffers ++ private final BufferHolder[] _buffers; + +- private BufferInfo[] _buffers; +- + protected GeoEnginePathfinding() + { +- try ++ super(); ++ ++ final String[] array = Config.PATHFIND_BUFFERS.split(";"); ++ _buffers = new BufferHolder[array.length]; ++ int count = 0; ++ for (int i = 0; i < array.length; i++) + { +- final String[] array = Config.PATHFIND_BUFFERS.split(";"); ++ final String buf = array[i]; ++ final String[] args = buf.split("x"); + +- _buffers = new BufferInfo[array.length]; +- +- String buf; +- String[] args; +- for (int i = 0; i < array.length; i++) ++ try + { +- buf = array[i]; +- args = buf.split("x"); +- if (args.length != 2) +- { +- throw new Exception("Invalid buffer definition: " + buf); +- } +- +- _buffers[i] = new BufferInfo(Integer.parseInt(args[0]), Integer.parseInt(args[1])); ++ final int size = Integer.parseInt(args[1]); ++ count += size; ++ _buffers[i] = new BufferHolder(Integer.parseInt(args[0]), size); + } ++ catch (Exception e) ++ { ++ LOGGER.warning("GeoEnginePathfinding: Can not load buffer setting: " + buf); ++ } + } +- catch (Exception e) +- { +- LOGGER.log(Level.WARNING, "CellPathFinding: Problem during buffer init: " + e.getMessage(), e); +- throw new Error("CellPathFinding: load aborted"); +- } ++ ++ LOGGER.info("GeoEnginePathfinding: Loaded " + count + " node buffers."); + } + +- public boolean pathNodesExist(short regionoffset) ++ @Override ++ public List findPath(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + { +- return false; +- } +- +- public List findPath(int x, int y, int z, int tx, int ty, int tz, Instance instance) +- { +- final int gx = GeoEngine.getInstance().getGeoX(x); +- final int gy = GeoEngine.getInstance().getGeoY(y); +- if (!GeoEngine.getInstance().hasGeo(x, y)) ++ // get origin and check existing geo coords ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) + { + 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)) ++ ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coords ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) + { + 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)))); ++ ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // Prepare buffer for pathfinding calculations ++ final NodeBuffer buffer = getBuffer(64 + (2 * Math.max(Math.abs(gox - gtx), Math.abs(goy - gty)))); + if (buffer == null) + { + return null; + } + +- List path = null; ++ // find path ++ List path = null; + try + { +- final CellNode result = buffer.findPath(gx, gy, gz, gtx, gty, gtz); +- ++ final Node result = buffer.findPath(gox, goy, goz, gtx, gty, gtz); + if (result == null) + { + return null; +@@ -125,33 +121,45 @@ + return path; + } + +- int currentX, currentY, currentZ; +- ListIterator middlePoint; ++ // get path list iterator ++ final ListIterator point = path.listIterator(); + +- middlePoint = path.listIterator(); +- currentX = x; +- currentY = y; +- currentZ = z; ++ // get node A (origin) ++ int nodeAx = gox; ++ int nodeAy = goy; ++ short nodeAz = goz; + +- while (middlePoint.hasNext()) ++ // get node B ++ GeoLocation nodeB = (GeoLocation) point.next(); ++ ++ // iterate thought the path to optimize it ++ int count = 0; ++ while (point.hasNext() && (count++ < Config.MAX_ITERATIONS)) + { +- final AbstractNodeLoc locMiddle = middlePoint.next(); +- if (!middlePoint.hasNext()) +- { +- break; +- } ++ // get node C ++ final GeoLocation nodeC = (GeoLocation) path.get(point.nextIndex()); + +- final AbstractNodeLoc locEnd = path.get(middlePoint.nextIndex()); +- if (GeoEngine.getInstance().canMoveToTarget(currentX, currentY, currentZ, locEnd.getX(), locEnd.getY(), locEnd.getZ(), instance)) ++ // check movement from node A to node C ++ final GeoLocation loc = checkMove(nodeAx, nodeAy, nodeAz, nodeC.getGeoX(), nodeC.getGeoY(), nodeC.getZ(), instance); ++ if ((loc.getGeoX() == nodeC.getGeoX()) && (loc.getGeoY() == nodeC.getGeoY())) + { +- middlePoint.remove(); ++ // can move from node A to node C ++ ++ // remove node B ++ point.remove(); + } + else + { +- currentX = locMiddle.getX(); +- currentY = locMiddle.getY(); +- currentZ = locMiddle.getZ(); ++ // can not move from node A to node C ++ ++ // set node A (node B is part of path, update A coordinates) ++ nodeAx = nodeB.getGeoX(); ++ nodeAy = nodeB.getGeoY(); ++ nodeAz = (short) nodeB.getZ(); + } ++ ++ // set node B ++ nodeB = (GeoLocation) point.next(); + } + + return path; +@@ -158,144 +166,102 @@ + } + + /** +- * Convert geodata position to pathnode position +- * @param geo_pos +- * @return pathnode position ++ * Create list of node locations as result of calculated buffer node tree. ++ * @param node : the entry point ++ * @return List : list of node location + */ +- public short getNodePos(int geo_pos) ++ private static List constructPath(Node node) + { +- return (short) (geo_pos >> 3); // OK? +- } +- +- /** +- * Convert node position to pathnode block position +- * @param node_pos +- * @return pathnode block position (0...255) +- */ +- public short getNodeBlock(int node_pos) +- { +- return (short) (node_pos % 256); +- } +- +- public byte getRegionX(int node_pos) +- { +- return (byte) ((node_pos >> 8) + World.TILE_X_MIN); +- } +- +- public byte getRegionY(int node_pos) +- { +- return (byte) ((node_pos >> 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 node_x rx +- * @return +- */ +- public int calculateWorldX(short node_x) +- { +- return World.MAP_MIN_X + (node_x * 128) + 48; +- } +- +- /** +- * Convert pathnode y to World y position +- * @param node_y +- * @return +- */ +- public int calculateWorldY(short node_y) +- { +- return World.MAP_MIN_Y + (node_y * 128) + 48; +- } +- +- private List constructPath(AbstractNode nodeValue) +- { +- final LinkedList path = new LinkedList<>(); +- int previousDirectionX = Integer.MIN_VALUE; +- int previousDirectionY = Integer.MIN_VALUE; +- int directionX, directionY; ++ // create empty list ++ final LinkedList list = new LinkedList<>(); + +- AbstractNode node = nodeValue; +- while (node.getParent() != null) ++ // set direction X/Y ++ int dx = 0; ++ int dy = 0; ++ ++ // get target parent ++ Node target = node; ++ Node parent = target.getParent(); ++ ++ // while parent exists ++ int count = 0; ++ while ((parent != null) && (count++ < Config.MAX_ITERATIONS)) + { +- directionX = node.getLoc().getNodeX() - node.getParent().getLoc().getNodeX(); +- directionY = node.getLoc().getNodeY() - node.getParent().getLoc().getNodeY(); ++ // get parent <> target direction X/Y ++ final int nx = parent.getLoc().getGeoX() - target.getLoc().getGeoX(); ++ final int ny = parent.getLoc().getGeoY() - target.getLoc().getGeoY(); + +- // only add a new route point if moving direction changes +- if ((directionX != previousDirectionX) || (directionY != previousDirectionY)) ++ // direction has changed? ++ if ((dx != nx) || (dy != ny)) + { +- previousDirectionX = directionX; +- previousDirectionY = directionY; ++ // add node to the beginning of the list ++ list.addFirst(target.getLoc()); + +- path.addFirst(node.getLoc()); +- node.setLoc(null); ++ // update direction X/Y ++ dx = nx; ++ dy = ny; + } + +- node = node.getParent(); ++ // move to next node, set target and get its parent ++ target = parent; ++ parent = target.getParent(); + } + +- return path; ++ // return list ++ return list; + } + +- private CellNodeBuffer alloc(int size) ++ /** ++ * Provides optimize selection of the buffer. When all pre-initialized buffer are locked, creates new buffer. ++ * @param size : pre-calculated minimal required size ++ * @return NodeBuffer : buffer ++ */ ++ private final NodeBuffer getBuffer(int size) + { +- CellNodeBuffer current = null; +- for (BufferInfo i : _buffers) ++ NodeBuffer current = null; ++ for (BufferHolder holder : _buffers) + { +- if (i.mapSize >= size) ++ // Find proper size of buffer ++ if (holder._size < size) + { +- for (CellNodeBuffer buf : i.buffer) ++ continue; ++ } ++ ++ // Find unlocked NodeBuffer ++ for (NodeBuffer buffer : holder._buffer) ++ { ++ if (!buffer.isLocked()) + { +- if (buf.lock()) +- { +- current = buf; +- break; +- } ++ continue; + } +- if (current != null) +- { +- break; +- } + +- // not found, allocate temporary buffer +- current = new CellNodeBuffer(i.mapSize); +- current.lock(); +- if (i.buffer.size() < i.count) +- { +- i.buffer.add(current); +- break; +- } ++ return buffer; + } ++ ++ // NodeBuffer not found, allocate temporary buffer ++ current = new NodeBuffer(holder._size); ++ current.isLocked(); + } + + return current; + } + +- private static final class BufferInfo ++ /** ++ * NodeBuffer container with specified size and count of separate buffers. ++ */ ++ private static final class BufferHolder + { +- final int mapSize; +- final int count; +- ArrayList buffer; ++ final int _size; ++ List _buffer; + +- public BufferInfo(int size, int cnt) ++ public BufferHolder(int size, int count) + { +- mapSize = size; +- count = cnt; +- buffer = new ArrayList<>(count); ++ _size = size; ++ _buffer = new ArrayList<>(count); ++ for (int i = 0; i < count; i++) ++ { ++ _buffer.add(new NodeBuffer(size)); ++ } + } + } +- +- public static GeoEnginePathfinding getInstance() +- { +- return SingletonHolder.INSTANCE; +- } +- +- private static class SingletonHolder +- { +- protected static final GeoEnginePathfinding INSTANCE = new GeoEnginePathfinding(); +- } +-} ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java (working copy) +@@ -0,0 +1,197 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++import java.io.IOException; ++ ++/** ++ * @author Hasha ++ */ ++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 height of cell, which is closest to given coordinates.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @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 getHeightNearestOriginal(int geoX, int geoY, int worldZ); ++ ++ /** ++ * Returns the height of cell, which is first above 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, above given coordinates. ++ */ ++ public abstract short getHeightAbove(int geoX, int geoY, int worldZ); ++ ++ /** ++ * Returns the height of cell, which is first below 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, below given coordinates. ++ */ ++ public abstract short getHeightBelow(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 the NSWE flag byte of cell, which is closest to given coordinates.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @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 getNsweNearestOriginal(int geoX, int geoY, int worldZ); ++ ++ /** ++ * Returns the NSWE flag byte of cell, which is first above 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 getNsweAbove(int geoX, int geoY, int worldZ); ++ ++ /** ++ * Returns the NSWE flag byte of cell, which is first below 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 getNsweBelow(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 above given coordinates.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @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 getIndexAboveOriginal(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 index to data of the cell, which is first layer below given coordinates.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @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 getIndexBelowOriginal(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 height of cell given by cell index.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @param index : Index of the cell. ++ * @return short : Cell geodata Z coordinate, below given coordinates. ++ */ ++ public abstract short getHeightOriginal(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); ++ ++ /** ++ * Returns the NSWE flag byte of cell given by cell index.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @param index : Index of the cell. ++ * @return short : Cell geodata Z coordinate, below given coordinates. ++ */ ++ public abstract byte getNsweOriginal(int index); ++ ++ /** ++ * Sets the NSWE flag byte of cell given by cell index. ++ * @param index : Index of the cell. ++ * @param nswe : New NSWE flag byte. ++ */ ++ public abstract void setNswe(int index, byte nswe); ++ ++ /** ++ * Saves the block in L2D format to {@link BufferedOutputStream}. Used only for L2D geodata conversion. ++ * @param stream : The stream. ++ * @throws IOException : Can't save the block to steam. ++ */ ++ public abstract void saveBlock(BufferedOutputStream stream) throws IOException; ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java (working copy) +@@ -0,0 +1,252 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++import java.io.IOException; ++import java.nio.ByteBuffer; ++ ++/** ++ * @author Hasha ++ */ ++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. ++ * @param format : GeoFormat specifying format of loaded data. ++ */ ++ public BlockComplex(ByteBuffer bb, GeoFormat format) ++ { ++ // initialize buffer ++ _buffer = new byte[GeoStructure.BLOCK_CELLS * 3]; ++ ++ // load data ++ for (int i = 0; i < GeoStructure.BLOCK_CELLS; i++) ++ { ++ if (format != GeoFormat.L2D) ++ { ++ // 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); ++ } ++ else ++ { ++ // get nswe ++ final byte nswe = bb.get(); ++ _buffer[i * 3] = nswe; ++ ++ // get height ++ final short height = bb.getShort(); ++ _buffer[(i * 3) + 1] = (byte) (height & 0x00FF); ++ _buffer[(i * 3) + 2] = (byte) (height >> 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 short getHeightNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getHeightNearest(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeightAbove(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 short height = (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ ++ // check and return height ++ return height > worldZ ? height : Short.MIN_VALUE; ++ } ++ ++ @Override ++ public short getHeightBelow(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 short height = (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ ++ // check and return height ++ return height < worldZ ? height : Short.MAX_VALUE; ++ } ++ ++ @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 byte getNsweNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getNsweNearest(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public byte getNsweAbove(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 ? _buffer[index] : 0; ++ } ++ ++ @Override ++ public byte getNsweBelow(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 ? _buffer[index] : 0; ++ } ++ ++ @Override ++ public 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 getIndexAboveOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexAbove(geoX, geoY, worldZ); ++ } ++ ++ @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 int getIndexBelowOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexBelow(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeight(int index) ++ { ++ return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ } ++ ++ @Override ++ public short getHeightOriginal(int index) ++ { ++ return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ } ++ ++ @Override ++ public byte getNswe(int index) ++ { ++ return _buffer[index]; ++ } ++ ++ @Override ++ public byte getNsweOriginal(int index) ++ { ++ return _buffer[index]; ++ } ++ ++ @Override ++ public void setNswe(int index, byte nswe) ++ { ++ _buffer[index] = nswe; ++ } ++ ++ @Override ++ public void saveBlock(BufferedOutputStream stream) throws IOException ++ { ++ // write block type ++ stream.write(GeoStructure.TYPE_COMPLEX_L2D); ++ ++ // write block data ++ stream.write(_buffer, 0, GeoStructure.BLOCK_CELLS * 3); ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java (working copy) +@@ -0,0 +1,176 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++import java.io.IOException; ++import java.nio.ByteBuffer; ++ ++/** ++ * @author Hasha ++ */ ++public class BlockFlat extends ABlock ++{ ++ protected final short _height; ++ protected byte _nswe; ++ ++ /** ++ * Creates FlatBlock. ++ * @param bb : Input byte buffer. ++ * @param format : GeoFormat specifying format of loaded data. ++ */ ++ public BlockFlat(ByteBuffer bb, GeoFormat format) ++ { ++ _height = bb.getShort(); ++ _nswe = format != GeoFormat.L2D ? 0x0F : (byte) (0xFF); ++ if (format == GeoFormat.L2OFF) ++ { ++ bb.getShort(); ++ } ++ } ++ ++ @Override ++ public boolean hasGeoPos() ++ { ++ return true; ++ } ++ ++ @Override ++ public short getHeightNearest(int geoX, int geoY, int worldZ) ++ { ++ return _height; ++ } ++ ++ @Override ++ public short getHeightNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return _height; ++ } ++ ++ @Override ++ public short getHeightAbove(int geoX, int geoY, int worldZ) ++ { ++ // check and return height ++ return _height > worldZ ? _height : Short.MIN_VALUE; ++ } ++ ++ @Override ++ public short getHeightBelow(int geoX, int geoY, int worldZ) ++ { ++ // check and return height ++ return _height < worldZ ? _height : Short.MAX_VALUE; ++ } ++ ++ @Override ++ public byte getNsweNearest(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweAbove(int geoX, int geoY, int worldZ) ++ { ++ // check height and return nswe ++ return _height > worldZ ? _nswe : 0; ++ } ++ ++ @Override ++ public byte getNsweBelow(int geoX, int geoY, int worldZ) ++ { ++ // check height and return nswe ++ return _height < worldZ ? _nswe : 0; ++ } ++ ++ @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 getIndexAboveOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexAbove(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public int getIndexBelow(int geoX, int geoY, int worldZ) ++ { ++ // check height and return index ++ return _height < worldZ ? 0 : -1; ++ } ++ ++ @Override ++ public int getIndexBelowOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexBelow(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeight(int index) ++ { ++ return _height; ++ } ++ ++ @Override ++ public short getHeightOriginal(int index) ++ { ++ return _height; ++ } ++ ++ @Override ++ public byte getNswe(int index) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweOriginal(int index) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public void setNswe(int index, byte nswe) ++ { ++ _nswe = nswe; ++ } ++ ++ @Override ++ public void saveBlock(BufferedOutputStream stream) throws IOException ++ { ++ // write block type ++ stream.write(GeoStructure.TYPE_FLAT_L2D); ++ ++ // write height ++ stream.write((byte) (_height & 0x00FF)); ++ stream.write((byte) (_height >> 8)); ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java (working copy) +@@ -0,0 +1,465 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++import java.io.IOException; ++import java.nio.ByteBuffer; ++import java.nio.ByteOrder; ++import java.util.Arrays; ++ ++/** ++ * @author Hasha ++ */ ++public class BlockMultilayer extends ABlock ++{ ++ private static final int MAX_LAYERS = Byte.MAX_VALUE; ++ ++ private static ByteBuffer _temp; ++ ++ /** ++ * Initializes the temporarily buffer. ++ */ ++ public static void initialize() ++ { ++ // initialize temporarily buffer and sorting mechanism ++ _temp = ByteBuffer.allocate(GeoStructure.BLOCK_CELLS * MAX_LAYERS * 3); ++ _temp.order(ByteOrder.LITTLE_ENDIAN); ++ } ++ ++ /** ++ * Releases temporarily buffer. ++ */ ++ public static void release() ++ { ++ _temp = null; ++ } ++ ++ protected byte[] _buffer; ++ ++ /** ++ * Implicit constructor for children class. ++ */ ++ protected BlockMultilayer() ++ { ++ _buffer = null; ++ } ++ ++ /** ++ * Creates MultilayerBlock. ++ * @param bb : Input byte buffer. ++ * @param format : GeoFormat specifying format of loaded data. ++ */ ++ public BlockMultilayer(ByteBuffer bb, GeoFormat format) ++ { ++ // 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 = format != GeoFormat.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++) ++ { ++ if (format != GeoFormat.L2D) ++ { ++ // get data ++ final short data = bb.getShort(); ++ ++ // add nswe and height ++ _temp.put((byte) (data & 0x000F)); ++ _temp.putShort((short) ((short) (data & 0xFFF0) >> 1)); ++ } ++ else ++ { ++ // add nswe ++ _temp.put(bb.get()); ++ ++ // add height ++ _temp.putShort(bb.getShort()); ++ } ++ } ++ } ++ ++ // 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 short getHeightNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getHeightNearest(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeightAbove(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 height ++ if (height > worldZ) ++ { ++ return (short) height; ++ } ++ ++ // move index to next layer ++ index -= 3; ++ } ++ ++ // none layer found, return minimum value ++ return Short.MIN_VALUE; ++ } ++ ++ @Override ++ public short getHeightBelow(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 height ++ if (height < worldZ) ++ { ++ return (short) height; ++ } ++ ++ // move index to next layer ++ index += 3; ++ } ++ ++ // none layer found, return maximum value ++ return Short.MAX_VALUE; ++ } ++ ++ @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 byte getNsweNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getNsweNearest(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public byte getNsweAbove(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 nswe ++ if (height > worldZ) ++ { ++ return _buffer[index]; ++ } ++ ++ // move index to next layer ++ index -= 3; ++ } ++ ++ // none layer found, block movement ++ return 0; ++ } ++ ++ @Override ++ public byte getNsweBelow(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 nswe ++ if (height < worldZ) ++ { ++ return _buffer[index]; ++ } ++ ++ // move index to next layer ++ index += 3; ++ } ++ ++ // none layer found, block movement ++ return 0; ++ } ++ ++ @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 first layer data (first from bottom) ++ byte layers = _buffer[index++]; ++ ++ // loop though all cell layers, find closest layer ++ 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; ++ } ++ ++ // none layer found ++ return -1; ++ } ++ ++ @Override ++ public int getIndexAboveOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexAbove(geoX, geoY, worldZ); ++ } ++ ++ @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; ++ } ++ ++ // none layer found ++ return -1; ++ } ++ ++ @Override ++ public int getIndexBelowOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexBelow(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeight(int index) ++ { ++ // get height ++ return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ } ++ ++ @Override ++ public short getHeightOriginal(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]; ++ } ++ ++ @Override ++ public byte getNsweOriginal(int index) ++ { ++ // get nswe ++ return _buffer[index]; ++ } ++ ++ @Override ++ public void setNswe(int index, byte nswe) ++ { ++ // set nswe ++ _buffer[index] = nswe; ++ } ++ ++ @Override ++ public void saveBlock(BufferedOutputStream stream) throws IOException ++ { ++ // write block type ++ stream.write(GeoStructure.TYPE_MULTILAYER_L2D); ++ ++ // for each cell ++ int index = 0; ++ for (int i = 0; i < GeoStructure.BLOCK_CELLS; i++) ++ { ++ // write layers count ++ final byte layers = _buffer[index++]; ++ stream.write(layers); ++ ++ // write cell data ++ stream.write(_buffer, index, layers * 3); ++ ++ // move index to next cell ++ index += layers * 3; ++ } ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java (working copy) +@@ -0,0 +1,150 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++ ++/** ++ * @author Hasha ++ */ ++public class BlockNull extends ABlock ++{ ++ private final byte _nswe; ++ ++ public BlockNull() ++ { ++ _nswe = (byte) 0xFF; ++ } ++ ++ @Override ++ public boolean hasGeoPos() ++ { ++ return false; ++ } ++ ++ @Override ++ public short getHeightNearest(int geoX, int geoY, int worldZ) ++ { ++ return (short) worldZ; ++ } ++ ++ @Override ++ public short getHeightNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return (short) worldZ; ++ } ++ ++ @Override ++ public short getHeightAbove(int geoX, int geoY, int worldZ) ++ { ++ return (short) worldZ; ++ } ++ ++ @Override ++ public short getHeightBelow(int geoX, int geoY, int worldZ) ++ { ++ return (short) worldZ; ++ } ++ ++ @Override ++ public byte getNsweNearest(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweAbove(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweBelow(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) ++ { ++ return 0; ++ } ++ ++ @Override ++ public int getIndexAboveOriginal(int geoX, int geoY, int worldZ) ++ { ++ return 0; ++ } ++ ++ @Override ++ public int getIndexBelow(int geoX, int geoY, int worldZ) ++ { ++ return 0; ++ } ++ ++ @Override ++ public int getIndexBelowOriginal(int geoX, int geoY, int worldZ) ++ { ++ return 0; ++ } ++ ++ @Override ++ public short getHeight(int index) ++ { ++ return 0; ++ } ++ ++ @Override ++ public short getHeightOriginal(int index) ++ { ++ return 0; ++ } ++ ++ @Override ++ public byte getNswe(int index) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweOriginal(int index) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public void setNswe(int index, byte nswe) ++ { ++ } ++ ++ @Override ++ public void saveBlock(BufferedOutputStream stream) ++ { ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java (nonexistent) +@@ -1,48 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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() +- { +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/ComplexBlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/ComplexBlock.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/ComplexBlock.java (nonexistent) +@@ -1,77 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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; +- +-/** +- * @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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/FlatBlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/FlatBlock.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/FlatBlock.java (nonexistent) +@@ -1,56 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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; +- +-/** +- * @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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/GeoFormat.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/GeoFormat.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/GeoFormat.java (working copy) +@@ -0,0 +1,39 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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 Hasha ++ */ ++public enum GeoFormat ++{ ++ L2J("%d_%d.l2j"), ++ L2OFF("%d_%d_conv.dat"), ++ L2D("%d_%d.l2d"); ++ ++ private final String _filename; ++ ++ private GeoFormat(String filename) ++ { ++ _filename = filename; ++ } ++ ++ public String getFilename() ++ { ++ return _filename; ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/GeoLocation.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/GeoLocation.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/GeoLocation.java (working copy) +@@ -0,0 +1,67 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.geoengine.GeoEngine; ++import org.l2jmobius.gameserver.model.Location; ++ ++/** ++ * @author Hasha ++ */ ++public class GeoLocation extends Location ++{ ++ private byte _nswe; ++ ++ public GeoLocation(int x, int y, int z) ++ { ++ super(x, y, GeoEngine.getInstance().getHeightNearest(x, y, z)); ++ _nswe = GeoEngine.getInstance().getNsweNearest(x, y, z); ++ } ++ ++ public void set(int x, int y, short z) ++ { ++ super.setXYZ(x, y, GeoEngine.getInstance().getHeightNearest(x, y, z)); ++ _nswe = GeoEngine.getInstance().getNsweNearest(x, y, z); ++ } ++ ++ public int getGeoX() ++ { ++ return _x; ++ } ++ ++ public int getGeoY() ++ { ++ return _y; ++ } ++ ++ @Override ++ public int getX() ++ { ++ return GeoEngine.getWorldX(_x); ++ } ++ ++ @Override ++ public int getY() ++ { ++ return GeoEngine.getWorldY(_y); ++ } ++ ++ public byte getNSWE() ++ { ++ return _nswe; ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java (working copy) +@@ -0,0 +1,70 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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; ++ ++/** ++ * @author Hasha ++ */ ++public class GeoStructure ++{ ++ // cells ++ public static final byte CELL_FLAG_E = 1 << 0; ++ public static final byte CELL_FLAG_W = 1 << 1; ++ public static final byte CELL_FLAG_S = 1 << 2; ++ public static final byte CELL_FLAG_N = 1 << 3; ++ public static final byte CELL_FLAG_SE = 1 << 4; ++ public static final byte CELL_FLAG_SW = 1 << 5; ++ public static final byte CELL_FLAG_NE = 1 << 6; ++ public static final byte CELL_FLAG_NW = (byte) (1 << 7); ++ ++ public static final int CELL_HEIGHT = 8; ++ public static final int CELL_IGNORE_HEIGHT = CELL_HEIGHT * 6; ++ ++ // blocks ++ public static final byte TYPE_FLAT_L2J_L2OFF = 0; ++ public static final byte TYPE_FLAT_L2D = (byte) 0xD0; ++ public static final byte TYPE_COMPLEX_L2J = 1; ++ public static final byte TYPE_COMPLEX_L2OFF = 0x40; ++ public static final byte TYPE_COMPLEX_L2D = (byte) 0xD1; ++ 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) ++ public static final byte TYPE_MULTILAYER_L2D = (byte) 0xD2; ++ ++ 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; ++ ++ // regions ++ 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; ++ ++ // global geodata ++ private static final int GEO_REGIONS_X = ((World.TILE_X_MAX - World.TILE_X_MIN) + 1); ++ private 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 +Index: java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java (nonexistent) +@@ -1,42 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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); +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/IGeoObject.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/IGeoObject.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/IGeoObject.java (working copy) +@@ -0,0 +1,53 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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 Hasha ++ */ ++public interface IGeoObject ++{ ++ /** ++ * Returns geodata X coordinate of the {@link IGeoObject}. ++ * @return int : Geodata X coordinate. ++ */ ++ int getGeoX(); ++ ++ /** ++ * Returns geodata Y coordinate of the {@link IGeoObject}. ++ * @return int : Geodata Y coordinate. ++ */ ++ int getGeoY(); ++ ++ /** ++ * Returns geodata Z coordinate of the {@link IGeoObject}. ++ * @return int : Geodata Z coordinate. ++ */ ++ int getGeoZ(); ++ ++ /** ++ * Returns height of the {@link IGeoObject}. ++ * @return int : Height. ++ */ ++ int getHeight(); ++ ++ /** ++ * Returns {@link IGeoObject} data. ++ * @return byte[][] : {@link IGeoObject} data. ++ */ ++ byte[][] getObjectGeoData(); ++} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java (nonexistent) +@@ -1,47 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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(); +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/MultilayerBlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/MultilayerBlock.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/MultilayerBlock.java (nonexistent) +@@ -1,183 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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; +- +-/** +- * @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); +- +- // 1 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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/NullRegion.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/NullRegion.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/NullRegion.java (nonexistent) +@@ -1,55 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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 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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/Region.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/Region.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/Region.java (nonexistent) +@@ -1,92 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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; +- +-/** +- * @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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java (nonexistent) +@@ -1,87 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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 T _loc; +- private AbstractNode _parent; +- +- public AbstractNode(T loc) +- { +- _loc = loc; +- } +- +- public void setParent(AbstractNode p) +- { +- _parent = p; +- } +- +- public AbstractNode getParent() +- { +- return _parent; +- } +- +- public T getLoc() +- { +- return _loc; +- } +- +- public void setLoc(T l) +- { +- _loc = l; +- } +- +- @Override +- public int hashCode() +- { +- final int prime = 31; +- int result = 1; +- result = (prime * result) + ((_loc == null) ? 0 : _loc.hashCode()); +- return result; +- } +- +- @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 +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java (nonexistent) +@@ -1,33 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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 int getNodeX(); +- +- public abstract int getNodeY(); +-} +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNode.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNode.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNode.java (nonexistent) +@@ -1,67 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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 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 +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNodeBuffer.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNodeBuffer.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNodeBuffer.java (nonexistent) +@@ -1,348 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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.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 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) +- { +- _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(); +- } +- +- 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 void getNeighbors() +- { +- if (!_current.getLoc().canGoAll()) +- { +- 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); +- } +- +- // SouthEast +- if ((nodeE != null) && (nodeS != null)) +- { +- if (nodeE.getLoc().canGoSouth() && nodeS.getLoc().canGoEast()) +- { +- addNode(x + 1, y + 1, z, true); +- } +- } +- +- // SouthWest +- if ((nodeS != null) && (nodeW != null)) +- { +- if (nodeW.getLoc().canGoSouth() && nodeS.getLoc().canGoWest()) +- { +- addNode(x - 1, y + 1, z, true); +- } +- } +- +- // NorthEast +- if ((nodeN != null) && (nodeE != null)) +- { +- if (nodeE.getLoc().canGoNorth() && nodeN.getLoc().canGoEast()) +- { +- addNode(x + 1, y - 1, z, true); +- } +- } +- +- // NorthWest +- if ((nodeN != null) && (nodeW != null)) +- { +- if (nodeW.getLoc().canGoNorth() && nodeN.getLoc().canGoWest()) +- { +- addNode(x - 1, y - 1, z, true); +- } +- } +- } +- +- private 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(); +- // reinit node if needed +- if (result.getLoc() != null) +- { +- result.getLoc().set(x, y, z); +- } +- else +- { +- result.setLoc(new NodeLoc(x, y, z)); +- } +- } +- +- return result; +- } +- +- private 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 boolean isHighWeight(int x, int y, int z) +- { +- final CellNode result = getNode(x, y, z); +- if (result == null) +- { +- return true; +- } +- +- if (!result.getLoc().canGoAll()) +- { +- return true; +- } +- if (Math.abs(result.getLoc().getZ() - z) > 16) +- { +- return true; +- } +- +- return false; +- } +- +- private 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 +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java (working copy) +@@ -0,0 +1,87 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.geodata.GeoLocation; ++ ++/** ++ * @author Hasha ++ */ ++public class Node ++{ ++ // node coords and nswe flag ++ private GeoLocation _loc; ++ ++ // node parent (for reverse path construction) ++ private Node _parent; ++ // node child (for moving over nodes during iteration) ++ private Node _child; ++ ++ // node G cost (movement cost = parent movement cost + current movement cost) ++ private double _cost = -1000; ++ ++ public void setLoc(int x, int y, int z) ++ { ++ _loc = new GeoLocation(x, y, z); ++ } ++ ++ public GeoLocation getLoc() ++ { ++ return _loc; ++ } ++ ++ public void setParent(Node parent) ++ { ++ _parent = parent; ++ } ++ ++ public Node getParent() ++ { ++ return _parent; ++ } ++ ++ public void setChild(Node child) ++ { ++ _child = child; ++ } ++ ++ public Node getChild() ++ { ++ return _child; ++ } ++ ++ public void setCost(double cost) ++ { ++ _cost = cost; ++ } ++ ++ public double getCost() ++ { ++ return _cost; ++ } ++ ++ public void free() ++ { ++ // reset node location ++ _loc = null; ++ ++ // reset node parent, child and cost ++ _parent = null; ++ _child = null; ++ _cost = -1000; ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java (working copy) +@@ -0,0 +1,306 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.concurrent.locks.ReentrantLock; ++ ++import org.l2jmobius.Config; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; ++ ++/** ++ * @author DS, Hasha; Credits to Diamond ++ */ ++public class NodeBuffer ++{ ++ private final ReentrantLock _lock = new ReentrantLock(); ++ private final int _size; ++ private final Node[][] _buffer; ++ ++ // center coordinates ++ private int _cx = 0; ++ private int _cy = 0; ++ ++ // target coordinates ++ private int _gtx = 0; ++ private int _gty = 0; ++ private short _gtz = 0; ++ ++ private Node _current = null; ++ ++ /** ++ * Constructor of NodeBuffer. ++ * @param size : one dimension size of buffer ++ */ ++ public NodeBuffer(int size) ++ { ++ // set size ++ _size = size; ++ ++ // initialize buffer ++ _buffer = new Node[size][size]; ++ for (int x = 0; x < size; x++) ++ { ++ for (int y = 0; y < size; y++) ++ { ++ _buffer[x][y] = 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 Node : first node of path ++ */ ++ public Node findPath(int gox, int goy, short goz, int gtx, int gty, short gtz) ++ { ++ // set coordinates (middle of the line (gox,goy) - (gtx,gty), will be in the center of the buffer) ++ _cx = gox + ((gtx - gox - _size) / 2); ++ _cy = goy + ((gty - goy - _size) / 2); ++ ++ _gtx = gtx; ++ _gty = gty; ++ _gtz = gtz; ++ ++ _current = getNode(gox, goy, goz); ++ _current.setCost(getCostH(gox, goy, goz)); ++ ++ int count = 0; ++ do ++ { ++ // reached target? ++ if ((_current.getLoc().getGeoX() == _gtx) && (_current.getLoc().getGeoY() == _gty) && (Math.abs(_current.getLoc().getZ() - _gtz) < 8)) ++ { ++ return _current; ++ } ++ ++ // expand current node ++ expand(); ++ ++ // move pointer ++ _current = _current.getChild(); ++ } ++ while ((_current != null) && (count++ < Config.MAX_ITERATIONS)); ++ ++ return null; ++ } ++ ++ public boolean isLocked() ++ { ++ return _lock.tryLock(); ++ } ++ ++ public void free() ++ { ++ _current = null; ++ ++ for (Node[] nodes : _buffer) ++ { ++ for (Node node : nodes) ++ { ++ if (node.getLoc() != null) ++ { ++ node.free(); ++ } ++ } ++ } ++ ++ _lock.unlock(); ++ } ++ ++ /** ++ * Check _current Node and add its neighbors to the buffer. ++ */ ++ private final void expand() ++ { ++ // can't move anywhere, don't expand ++ final byte nswe = _current.getLoc().getNSWE(); ++ if (nswe == 0) ++ { ++ return; ++ } ++ ++ // get geo coords of the node to be expanded ++ final int x = _current.getLoc().getGeoX(); ++ final int y = _current.getLoc().getGeoY(); ++ final short z = (short) _current.getLoc().getZ(); ++ ++ // can move north, expand ++ if ((nswe & GeoStructure.CELL_FLAG_N) != 0) ++ { ++ addNode(x, y - 1, z, Config.BASE_WEIGHT); ++ } ++ ++ // can move south, expand ++ if ((nswe & GeoStructure.CELL_FLAG_S) != 0) ++ { ++ addNode(x, y + 1, z, Config.BASE_WEIGHT); ++ } ++ ++ // can move west, expand ++ if ((nswe & GeoStructure.CELL_FLAG_W) != 0) ++ { ++ addNode(x - 1, y, z, Config.BASE_WEIGHT); ++ } ++ ++ // can move east, expand ++ if ((nswe & GeoStructure.CELL_FLAG_E) != 0) ++ { ++ addNode(x + 1, y, z, Config.BASE_WEIGHT); ++ } ++ ++ // can move north-west, expand ++ if ((nswe & GeoStructure.CELL_FLAG_NW) != 0) ++ { ++ addNode(x - 1, y - 1, z, Config.DIAGONAL_WEIGHT); ++ } ++ ++ // can move north-east, expand ++ if ((nswe & GeoStructure.CELL_FLAG_NE) != 0) ++ { ++ addNode(x + 1, y - 1, z, Config.DIAGONAL_WEIGHT); ++ } ++ ++ // can move south-west, expand ++ if ((nswe & GeoStructure.CELL_FLAG_SW) != 0) ++ { ++ addNode(x - 1, y + 1, z, Config.DIAGONAL_WEIGHT); ++ } ++ ++ // can move south-east, expand ++ if ((nswe & GeoStructure.CELL_FLAG_SE) != 0) ++ { ++ addNode(x + 1, y + 1, z, Config.DIAGONAL_WEIGHT); ++ } ++ } ++ ++ /** ++ * Returns node, if it exists in buffer. ++ * @param x : node X coord ++ * @param y : node Y coord ++ * @param z : node Z coord ++ * @return Node : node, if exits in buffer ++ */ ++ private final Node getNode(int x, int y, short z) ++ { ++ // check node X out of coordinates ++ final int ix = x - _cx; ++ if ((ix < 0) || (ix >= _size)) ++ { ++ return null; ++ } ++ ++ // check node Y out of coordinates ++ final int iy = y - _cy; ++ if ((iy < 0) || (iy >= _size)) ++ { ++ return null; ++ } ++ ++ // get node ++ final Node result = _buffer[ix][iy]; ++ ++ // check and update ++ if (result.getLoc() == null) ++ { ++ result.setLoc(x, y, z); ++ } ++ ++ // return node ++ return result; ++ } ++ ++ /** ++ * Add node given by coordinates to the buffer. ++ * @param x : geo X coord ++ * @param y : geo Y coord ++ * @param z : geo Z coord ++ * @param weight : weight of movement to new node ++ */ ++ private final void addNode(int x, int y, short z, int weight) ++ { ++ // get node to be expanded ++ final Node node = getNode(x, y, z); ++ if (node == null) ++ { ++ return; ++ } ++ ++ // Z distance between nearby cells is higher than cell size ++ if (node.getLoc().getZ() > (z + (2 * GeoStructure.CELL_HEIGHT))) ++ { ++ return; ++ } ++ ++ // node was already expanded, return ++ if (node.getCost() >= 0) ++ { ++ return; ++ } ++ ++ node.setParent(_current); ++ if (node.getLoc().getNSWE() != (byte) 0xFF) ++ { ++ node.setCost(getCostH(x, y, node.getLoc().getZ()) + (weight * Config.OBSTACLE_MULTIPLIER)); ++ } ++ else ++ { ++ node.setCost(getCostH(x, y, node.getLoc().getZ()) + weight); ++ } ++ ++ Node current = _current; ++ int count = 0; ++ while ((current.getChild() != null) && (count < (Config.MAX_ITERATIONS * 4))) ++ { ++ count++; ++ if (current.getChild().getCost() > node.getCost()) ++ { ++ node.setChild(current.getChild()); ++ break; ++ } ++ current = current.getChild(); ++ } ++ ++ if (count >= (Config.MAX_ITERATIONS * 4)) ++ { ++ System.err.println("Pathfinding: too long loop detected, cost:" + node.getCost()); ++ } ++ ++ current.setChild(node); ++ } ++ ++ /** ++ * @param x : node X coord ++ * @param y : node Y coord ++ * @param i : node Z coord ++ * @return double : node cost ++ */ ++ private final double getCostH(int x, int y, int i) ++ { ++ final int dX = x - _gtx; ++ final int dY = y - _gty; ++ final int dZ = (i - _gtz) / GeoStructure.CELL_HEIGHT; ++ ++ // return (Math.abs(dX) + Math.abs(dY) + Math.abs(dZ)) * Config.HEURISTIC_WEIGHT; // Manhattan distance ++ return Math.sqrt((dX * dX) + (dY * dY) + (dZ * dZ)) * Config.HEURISTIC_WEIGHT; // Direct distance ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeLoc.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeLoc.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeLoc.java (nonexistent) +@@ -1,183 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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.Cell; +- +-/** +- * @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 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 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; +- // return super.hashCode(); +- } +- +- @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; +- if (_x != other._x) +- { +- return false; +- } +- if (_y != other._y) +- { +- return false; +- } +- if (_goNorth != other._goNorth) +- { +- return false; +- } +- if (_goEast != other._goEast) +- { +- return false; +- } +- if (_goSouth != other._goSouth) +- { +- return false; +- } +- if (_goWest != other._goWest) +- { +- return false; +- } +- if (_geoHeight != other._geoHeight) +- { +- return false; +- } +- return true; +- } +-} +Index: java/org/l2jmobius/gameserver/model/actor/Creature.java +=================================================================== +--- java/org/l2jmobius/gameserver/model/actor/Creature.java (revision 8399) ++++ java/org/l2jmobius/gameserver/model/actor/Creature.java (working copy) +@@ -66,8 +66,6 @@ + import org.l2jmobius.gameserver.enums.TeleportWhereType; + import org.l2jmobius.gameserver.enums.UserInfoType; + import org.l2jmobius.gameserver.geoengine.GeoEngine; +-import org.l2jmobius.gameserver.geoengine.GeoEnginePathfinding; +-import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; + import org.l2jmobius.gameserver.instancemanager.IdManager; + import org.l2jmobius.gameserver.instancemanager.MapRegionManager; + import org.l2jmobius.gameserver.instancemanager.QuestManager; +@@ -2577,7 +2575,7 @@ + + public boolean disregardingGeodata; + public int onGeodataPathIndex; +- public List geoPath; ++ public List geoPath; + public int geoPathAccurateTx; + public int geoPathAccurateTy; + public int geoPathGtx; +@@ -3466,7 +3464,7 @@ + if (((originalDistance - distance) > 30) && !isControlBlocked() && !isInVehicle) + { + // Path calculation -- overrides previous movement check +- m.geoPath = GeoEnginePathfinding.getInstance().findPath(curX, curY, curZ, originalX, originalY, originalZ, getInstanceWorld()); ++ m.geoPath = GeoEngine.getInstance().findPath(curX, curY, curZ, originalX, originalY, originalZ, getInstanceWorld()); + if ((m.geoPath == null) || (m.geoPath.size() < 2)) // No path found + { + if (isPlayer() && !_isFlying && !isInWater) +Index: java/org/l2jmobius/gameserver/model/actor/instance/PlayerInstance.java +=================================================================== +--- java/org/l2jmobius/gameserver/model/actor/instance/PlayerInstance.java (revision 8397) ++++ java/org/l2jmobius/gameserver/model/actor/instance/PlayerInstance.java (working copy) +@@ -12704,7 +12704,7 @@ + { + if (Config.CORRECT_PLAYER_Z) + { +- final int nearestZ = GeoEngine.getInstance().getHigherHeight(getX(), getY(), getZ()); ++ final int nearestZ = GeoEngine.getInstance().getHeightNearest(getX(), getY(), getZ()); + if (getZ() < nearestZ) + { + teleToLocation(new Location(getX(), getY(), nearestZ)); +Index: java/org/l2jmobius/gameserver/util/GeoUtils.java +=================================================================== +--- java/org/l2jmobius/gameserver/util/GeoUtils.java (revision 8397) ++++ java/org/l2jmobius/gameserver/util/GeoUtils.java (working copy) +@@ -19,7 +19,7 @@ + import java.awt.Color; + + import org.l2jmobius.gameserver.geoengine.GeoEngine; +-import org.l2jmobius.gameserver.geoengine.geodata.Cell; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; + import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance; + import org.l2jmobius.gameserver.network.serverpackets.ExServerPrimitive; + +@@ -26,25 +26,25 @@ + /** + * @author HorridoJoho + */ +-public final class GeoUtils ++public class GeoUtils + { + public static void debug2DLine(PlayerInstance player, int x, int y, int tx, int ty, int z) + { +- final int gx = GeoEngine.getInstance().getGeoX(x); +- final int gy = GeoEngine.getInstance().getGeoY(y); ++ final int gx = GeoEngine.getGeoX(x); ++ final int gy = GeoEngine.getGeoY(y); + +- final int tgx = GeoEngine.getInstance().getGeoX(tx); +- final int tgy = GeoEngine.getInstance().getGeoY(ty); ++ final int tgx = GeoEngine.getGeoX(tx); ++ final int tgy = GeoEngine.getGeoY(ty); + + final ExServerPrimitive prim = new ExServerPrimitive("Debug2DLine", x, y, z); +- prim.addLine(Color.BLUE, GeoEngine.getInstance().getWorldX(gx), GeoEngine.getInstance().getWorldY(gy), z, GeoEngine.getInstance().getWorldX(tgx), GeoEngine.getInstance().getWorldY(tgy), z); ++ prim.addLine(Color.BLUE, GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), z, GeoEngine.getWorldX(tgx), GeoEngine.getWorldY(tgy), z); + + final LinePointIterator iter = new LinePointIterator(gx, gy, tgx, tgy); + + while (iter.next()) + { +- final int wx = GeoEngine.getInstance().getWorldX(iter.x()); +- final int wy = GeoEngine.getInstance().getWorldY(iter.y()); ++ final int wx = GeoEngine.getWorldX(iter.x()); ++ final int wy = GeoEngine.getWorldY(iter.y()); + + prim.addPoint(Color.RED, wx, wy, z); + } +@@ -53,21 +53,21 @@ + + public static void debug3DLine(PlayerInstance player, int x, int y, int z, int tx, int ty, int tz) + { +- final int gx = GeoEngine.getInstance().getGeoX(x); +- final int gy = GeoEngine.getInstance().getGeoY(y); ++ final int gx = GeoEngine.getGeoX(x); ++ final int gy = GeoEngine.getGeoY(y); + +- final int tgx = GeoEngine.getInstance().getGeoX(tx); +- final int tgy = GeoEngine.getInstance().getGeoY(ty); ++ final int tgx = GeoEngine.getGeoX(tx); ++ final int tgy = GeoEngine.getGeoY(ty); + + final ExServerPrimitive prim = new ExServerPrimitive("Debug3DLine", x, y, z); +- prim.addLine(Color.BLUE, GeoEngine.getInstance().getWorldX(gx), GeoEngine.getInstance().getWorldY(gy), z, GeoEngine.getInstance().getWorldX(tgx), GeoEngine.getInstance().getWorldY(tgy), tz); ++ prim.addLine(Color.BLUE, GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), z, GeoEngine.getWorldX(tgx), GeoEngine.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.getInstance().getWorldX(prevX); +- int wy = GeoEngine.getInstance().getWorldY(prevY); ++ int wx = GeoEngine.getWorldX(prevX); ++ int wy = GeoEngine.getWorldY(prevY); + int wz = iter.z(); + prim.addPoint(Color.RED, wx, wy, wz); + +@@ -78,8 +78,8 @@ + + if ((curX != prevX) || (curY != prevY)) + { +- wx = GeoEngine.getInstance().getWorldX(curX); +- wy = GeoEngine.getInstance().getWorldY(curY); ++ wx = GeoEngine.getWorldX(curX); ++ wy = GeoEngine.getWorldY(curY); + wz = iter.z(); + + prim.addPoint(Color.RED, wx, wy, wz); +@@ -93,7 +93,7 @@ + + private static Color getDirectionColor(int x, int y, int z, int nswe) + { +- if (GeoEngine.getInstance().checkNearestNswe(x, y, z, nswe)) ++ if ((GeoEngine.getInstance().getNsweNearest(x, y, z) & nswe) == nswe) + { + return Color.GREEN; + } +@@ -109,9 +109,8 @@ + int iPacket = 0; + + ExServerPrimitive exsp = null; +- final GeoEngine ge = GeoEngine.getInstance(); +- final int playerGx = ge.getGeoX(player.getX()); +- final int playerGy = ge.getGeoY(player.getY()); ++ final int playerGx = GeoEngine.getGeoX(player.getX()); ++ final int playerGy = GeoEngine.getGeoY(player.getY()); + for (int dx = -geoRadius; dx <= geoRadius; ++dx) + { + for (int dy = -geoRadius; dy <= geoRadius; ++dy) +@@ -135,12 +134,12 @@ + final int gx = playerGx + dx; + final int gy = playerGy + dy; + +- final int x = ge.getWorldX(gx); +- final int y = ge.getWorldY(gy); +- final int z = ge.getNearestZ(gx, gy, player.getZ()); ++ final int x = GeoEngine.getWorldX(gx); ++ final int y = GeoEngine.getWorldY(gy); ++ final int z = GeoEngine.getInstance().getHeightNearest(gx, gy, player.getZ()); + + // north arrow +- Color col = getDirectionColor(gx, gy, z, Cell.NSWE_NORTH); ++ Color col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_N); + 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); +@@ -147,7 +146,7 @@ + exsp.addLine(col, x - 4, y - 4, z, x + 4, y - 4, z); + + // east arrow +- col = getDirectionColor(gx, gy, z, Cell.NSWE_EAST); ++ col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_E); + 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); +@@ -154,13 +153,13 @@ + exsp.addLine(col, x + 4, y - 4, z, x + 4, y + 4, z); + + // south arrow +- col = getDirectionColor(gx, gy, z, Cell.NSWE_SOUTH); ++ col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_S); + 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, Cell.NSWE_WEST); ++ col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_W); + 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); +@@ -188,15 +187,15 @@ + { + if (y > lastY) + { +- return Cell.NSWE_SOUTH_EAST; ++ return GeoStructure.CELL_FLAG_SE; // Direction.SOUTH_EAST; + } + else if (y < lastY) + { +- return Cell.NSWE_NORTH_EAST; ++ return GeoStructure.CELL_FLAG_NE; // Direction.NORTH_EAST; + } + else + { +- return Cell.NSWE_EAST; ++ return GeoStructure.CELL_FLAG_E; // Direction.EAST; + } + } + else if (x < lastX) // west +@@ -203,26 +202,27 @@ + { + if (y > lastY) + { +- return Cell.NSWE_SOUTH_WEST; ++ return GeoStructure.CELL_FLAG_SW; // Direction.SOUTH_WEST; + } + else if (y < lastY) + { +- return Cell.NSWE_NORTH_WEST; ++ return GeoStructure.CELL_FLAG_NW; // Direction.NORTH_WEST; + } + else + { +- return Cell.NSWE_WEST; ++ return GeoStructure.CELL_FLAG_W; // Direction.WEST; + } + } +- else // unchanged x ++ else ++ // unchanged x + { + if (y > lastY) + { +- return Cell.NSWE_SOUTH; ++ return GeoStructure.CELL_FLAG_S; // Direction.SOUTH; + } + else if (y < lastY) + { +- return Cell.NSWE_NORTH; ++ return GeoStructure.CELL_FLAG_N; // Direction.NORTH; + } + else + { +Index: java/org/l2jmobius/tools/geodataconverter/GeoDataConverter.java +=================================================================== +--- java/org/l2jmobius/tools/geodataconverter/GeoDataConverter.java (nonexistent) ++++ java/org/l2jmobius/tools/geodataconverter/GeoDataConverter.java (working copy) +@@ -0,0 +1,386 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ */ ++package org.l2jmobius.tools.geodataconverter; ++ ++import java.io.BufferedOutputStream; ++import java.io.File; ++import java.io.FileOutputStream; ++import java.io.RandomAccessFile; ++import java.nio.ByteOrder; ++import java.nio.MappedByteBuffer; ++import java.nio.channels.FileChannel; ++import java.util.Scanner; ++ ++import org.l2jmobius.Config; ++import org.l2jmobius.commons.util.PropertiesParser; ++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.GeoFormat; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; ++import org.l2jmobius.gameserver.model.World; ++ ++/** ++ * @author Hasha ++ */ ++public class GeoDataConverter ++{ ++ private static GeoFormat _format; ++ private static ABlock[][] _blocks; ++ ++ public static void main(String[] args) ++ { ++ // initialize config ++ loadGeoengineConfigs(); ++ ++ // get geodata format ++ String type = ""; ++ try (Scanner scn = new Scanner(System.in)) ++ { ++ while (!(type.equalsIgnoreCase("J") || type.equalsIgnoreCase("O") || type.equalsIgnoreCase("E"))) ++ { ++ System.out.println("GeoDataConverter: Select source geodata type:"); ++ System.out.println(" J: L2J (e.g. 23_22.l2j)"); ++ System.out.println(" O: L2OFF (e.g. 23_22_conv.dat)"); ++ System.out.println(" E: Exit"); ++ System.out.print("Choice: "); ++ type = scn.next(); ++ } ++ } ++ if (type.equalsIgnoreCase("E")) ++ { ++ System.exit(0); ++ } ++ ++ _format = type.equalsIgnoreCase("J") ? GeoFormat.L2J : GeoFormat.L2OFF; ++ ++ // start conversion ++ System.out.println("GeoDataConverter: Converting all " + _format + " files."); ++ ++ // initialize geodata container ++ _blocks = new ABlock[GeoStructure.REGION_BLOCKS_X][GeoStructure.REGION_BLOCKS_Y]; ++ ++ // initialize multilayer temporarily buffer ++ BlockMultilayer.initialize(); ++ ++ // load geo files ++ int converted = 0; ++ for (int rx = World.TILE_X_MIN; rx <= World.TILE_X_MAX; rx++) ++ { ++ for (int ry = World.TILE_Y_MIN; ry <= World.TILE_Y_MAX; ry++) ++ { ++ final String input = String.format(_format.getFilename(), rx, ry); ++ final String filepath = Config.GEODATA_PATH; ++ final File f = new File(filepath + input); ++ if (f.exists() && !f.isDirectory()) ++ { ++ // load geodata ++ if (!loadGeoBlocks(input)) ++ { ++ System.out.println("GeoDataConverter: Unable to load " + input + " region file."); ++ continue; ++ } ++ ++ // recalculate nswe ++ if (!recalculateNswe()) ++ { ++ System.out.println("GeoDataConverter: Unable to convert " + input + " region file."); ++ continue; ++ } ++ ++ // save geodata ++ final String output = String.format(GeoFormat.L2D.getFilename(), rx, ry); ++ if (!saveGeoBlocks(output)) ++ { ++ System.out.println("GeoDataConverter: Unable to save " + output + " region file."); ++ continue; ++ } ++ ++ converted++; ++ System.out.println("GeoDataConverter: Created " + output + " region file."); ++ } ++ } ++ } ++ System.out.println("GeoDataConverter: Converted " + converted + " " + _format + " to L2D region file(s)."); ++ ++ // release multilayer block temporarily buffer ++ BlockMultilayer.release(); ++ } ++ ++ /** ++ * Loads geo blocks from buffer of the region file. ++ * @param filename : The name of the to load. ++ * @return boolean : True when successful. ++ */ ++ private static boolean loadGeoBlocks(String filename) ++ { ++ // region file is load-able, try to load it ++ try (RandomAccessFile raf = new RandomAccessFile(Config.GEODATA_PATH + filename, "r"); ++ FileChannel fc = raf.getChannel()) ++ { ++ final MappedByteBuffer buffer = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()).load(); ++ buffer.order(ByteOrder.LITTLE_ENDIAN); ++ ++ // load 18B header for L2off geodata (1st and 2nd byte...region X and Y) ++ if (_format == GeoFormat.L2OFF) ++ { ++ for (int i = 0; i < 18; i++) ++ { ++ buffer.get(); ++ } ++ } ++ ++ // 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 (_format == GeoFormat.L2J) ++ { ++ // get block type ++ final byte type = buffer.get(); ++ ++ // load block according to block type ++ switch (type) ++ { ++ case GeoStructure.TYPE_FLAT_L2J_L2OFF: ++ { ++ _blocks[ix][iy] = new BlockFlat(buffer, _format); ++ break; ++ } ++ case GeoStructure.TYPE_COMPLEX_L2J: ++ { ++ _blocks[ix][iy] = new BlockComplex(buffer, _format); ++ break; ++ } ++ case GeoStructure.TYPE_MULTILAYER_L2J: ++ { ++ _blocks[ix][iy] = new BlockMultilayer(buffer, _format); ++ break; ++ } ++ default: ++ { ++ throw new IllegalArgumentException("Unknown block type: " + type); ++ } ++ } ++ } ++ else ++ { ++ // get block type ++ final short type = buffer.getShort(); ++ ++ // load block according to block type ++ switch (type) ++ { ++ case GeoStructure.TYPE_FLAT_L2J_L2OFF: ++ { ++ _blocks[ix][iy] = new BlockFlat(buffer, _format); ++ break; ++ } ++ case GeoStructure.TYPE_COMPLEX_L2OFF: ++ { ++ _blocks[ix][iy] = new BlockComplex(buffer, _format); ++ break; ++ } ++ default: ++ { ++ _blocks[ix][iy] = new BlockMultilayer(buffer, _format); ++ break; ++ } ++ } ++ } ++ } ++ } ++ ++ if (buffer.remaining() > 0) ++ { ++ System.out.println("GeoDataConverter: Region file " + filename + " can be corrupted, remaining " + buffer.remaining() + " bytes to read."); ++ return false; ++ } ++ ++ return true; ++ } ++ catch (Exception e) ++ { ++ System.out.println("GeoDataConverter: Error while loading " + filename + " region file."); ++ return false; ++ } ++ } ++ ++ /** ++ * Recalculate diagonal flags for the region file. ++ * @return boolean : True when successful. ++ */ ++ private static boolean recalculateNswe() ++ { ++ try ++ { ++ for (int x = 0; x < GeoStructure.REGION_CELLS_X; x++) ++ { ++ for (int y = 0; y < GeoStructure.REGION_CELLS_Y; y++) ++ { ++ // get block ++ final ABlock block = _blocks[x / GeoStructure.BLOCK_CELLS_X][y / GeoStructure.BLOCK_CELLS_Y]; ++ ++ // skip flat blocks ++ if (block instanceof BlockFlat) ++ { ++ continue; ++ } ++ ++ // for complex and multilayer blocks go though all layers ++ short height = Short.MAX_VALUE; ++ int index; ++ while ((index = block.getIndexBelow(x, y, height)) != -1) ++ { ++ // get height and nswe ++ height = block.getHeight(index); ++ byte nswe = block.getNswe(index); ++ ++ // update nswe with diagonal flags ++ nswe = updateNsweBelow(x, y, height, nswe); ++ ++ // set nswe of the cell ++ block.setNswe(index, nswe); ++ } ++ } ++ } ++ ++ return true; ++ } ++ catch (Exception e) ++ { ++ return false; ++ } ++ } ++ ++ /** ++ * Updates the NSWE flag with diagonal flags. ++ * @param x : Geodata X coordinate. ++ * @param y : Geodata Y coordinate. ++ * @param z : Geodata Z coordinate. ++ * @param nsweValue : NSWE flag to be updated. ++ * @return byte : Updated NSWE flag. ++ */ ++ private static byte updateNsweBelow(int x, int y, short z, byte nsweValue) ++ { ++ byte nswe = nsweValue; ++ ++ // calculate virtual layer height ++ final short height = (short) (z + GeoStructure.CELL_IGNORE_HEIGHT); ++ ++ // get NSWE of neighbor cells below virtual layer (NPC/PC can fall down of clif, but can not climb it -> NSWE of cell below) ++ final byte nsweN = getNsweBelow(x, y - 1, height); ++ final byte nsweS = getNsweBelow(x, y + 1, height); ++ final byte nsweW = getNsweBelow(x - 1, y, height); ++ final byte nsweE = getNsweBelow(x + 1, y, height); ++ ++ // north-west ++ if ((((nswe & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) || (((nswe & GeoStructure.CELL_FLAG_W) != 0) && ((nsweW & GeoStructure.CELL_FLAG_N) != 0))) ++ { ++ nswe |= GeoStructure.CELL_FLAG_NW; ++ } ++ ++ // north-east ++ if ((((nswe & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) || (((nswe & GeoStructure.CELL_FLAG_E) != 0) && ((nsweE & GeoStructure.CELL_FLAG_N) != 0))) ++ { ++ nswe |= GeoStructure.CELL_FLAG_NE; ++ } ++ ++ // south-west ++ if ((((nswe & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) || (((nswe & GeoStructure.CELL_FLAG_W) != 0) && ((nsweW & GeoStructure.CELL_FLAG_S) != 0))) ++ { ++ nswe |= GeoStructure.CELL_FLAG_SW; ++ } ++ ++ // south-east ++ if ((((nswe & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) || (((nswe & GeoStructure.CELL_FLAG_E) != 0) && ((nsweE & GeoStructure.CELL_FLAG_S) != 0))) ++ { ++ nswe |= GeoStructure.CELL_FLAG_SE; ++ } ++ ++ return nswe; ++ } ++ ++ private static byte getNsweBelow(int geoX, int geoY, short worldZ) ++ { ++ // out of geo coordinates ++ if ((geoX < 0) || (geoX >= GeoStructure.REGION_CELLS_X)) ++ { ++ return 0; ++ } ++ ++ // out of geo coordinates ++ if ((geoY < 0) || (geoY >= GeoStructure.REGION_CELLS_Y)) ++ { ++ return 0; ++ } ++ ++ // get block ++ final ABlock block = _blocks[geoX / GeoStructure.BLOCK_CELLS_X][geoY / GeoStructure.BLOCK_CELLS_Y]; ++ ++ // get index, when valid, return nswe ++ final int index = block.getIndexBelow(geoX, geoY, worldZ); ++ return index == -1 ? 0 : block.getNswe(index); ++ } ++ ++ /** ++ * Save region file to file. ++ * @param filename : The name of file to save. ++ * @return boolean : True when successful. ++ */ ++ private static boolean saveGeoBlocks(String filename) ++ { ++ try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(Config.GEODATA_PATH + filename), GeoStructure.REGION_BLOCKS * GeoStructure.BLOCK_CELLS * 3)) ++ { ++ // loop over region blocks and save each block ++ for (int ix = 0; ix < GeoStructure.REGION_BLOCKS_X; ix++) ++ { ++ for (int iy = 0; iy < GeoStructure.REGION_BLOCKS_Y; iy++) ++ { ++ _blocks[ix][iy].saveBlock(bos); ++ } ++ } ++ ++ // flush data to file ++ bos.flush(); ++ ++ return true; ++ } ++ catch (Exception e) ++ { ++ return false; ++ } ++ } ++ ++ private static void loadGeoengineConfigs() ++ { ++ final PropertiesParser geoData = new PropertiesParser(Config.GEOENGINE_CONFIG_FILE); ++ Config.GEODATA_PATH = geoData.getString("GeoDataPath", "./data/geodata/"); ++ Config.COORD_SYNCHRONIZE = geoData.getInt("CoordSynchronize", -1); ++ Config.PART_OF_CHARACTER_HEIGHT = geoData.getInt("PartOfCharacterHeight", 75); ++ Config.MAX_OBSTACLE_HEIGHT = geoData.getInt("MaxObstacleHeight", 32); ++ Config.PATHFINDING = geoData.getBoolean("PathFinding", true); ++ Config.PATHFIND_BUFFERS = geoData.getString("PathFindBuffers", "100x6;128x6;192x6;256x4;320x4;384x4;500x2"); ++ Config.BASE_WEIGHT = geoData.getInt("BaseWeight", 10); ++ Config.DIAGONAL_WEIGHT = geoData.getInt("DiagonalWeight", 14); ++ Config.OBSTACLE_MULTIPLIER = geoData.getInt("ObstacleMultiplier", 10); ++ Config.HEURISTIC_WEIGHT = geoData.getInt("HeuristicWeight", 20); ++ Config.MAX_ITERATIONS = geoData.getInt("MaxIterations", 3500); ++ } ++} +\ No newline at end of file diff --git a/L2J_Mobius_Classic_2.4_SecretOfEmpire/dist/game/data/geodata/L2D_GeoEngine.patch b/L2J_Mobius_Classic_2.4_SecretOfEmpire/dist/game/data/geodata/L2D_GeoEngine.patch new file mode 100644 index 0000000000..63e2faf0c9 --- /dev/null +++ b/L2J_Mobius_Classic_2.4_SecretOfEmpire/dist/game/data/geodata/L2D_GeoEngine.patch @@ -0,0 +1,6052 @@ +Index: dist/game/GeoDataConverter.bat +=================================================================== +--- dist/game/GeoDataConverter.bat (nonexistent) ++++ dist/game/GeoDataConverter.bat (working copy) +@@ -0,0 +1,6 @@ ++@echo off ++title L2D geodata converter ++ ++java -Xmx512m -cp ./../libs/* org.l2jmobius.tools.geodataconverter.GeoDataConverter ++ ++pause +Index: dist/game/GeoDataConverter.sh +=================================================================== +--- dist/game/GeoDataConverter.sh (nonexistent) ++++ dist/game/GeoDataConverter.sh (working copy) +@@ -0,0 +1,4 @@ ++#! /bin/sh ++ ++java -Xmx512m -cp ../libs/*: org.l2jmobius.tools.geodataconverter.GeoDataConverter > log/stdout.log 2>&1 ++ +Index: dist/game/config/GeoEngine.ini +=================================================================== +--- dist/game/config/GeoEngine.ini (revision 8397) ++++ dist/game/config/GeoEngine.ini (working copy) +@@ -1,6 +1,10 @@ + # ================================================================= + # Geodata + # ================================================================= ++# Because of real-time performance we are using geodata files only in ++# diagonal L2D format now (using filename e.g. 22_16.l2d). ++# L2D geodata can be obtained by conversion of existing L2J or L2OFF geodata. ++# Launch "GeoDataConverter.bat/sh" and follow instructions to start the conversion. + + # 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/ +@@ -13,6 +17,16 @@ + CoordSynchronize = 2 + + # ================================================================= ++# Path checking ++# ================================================================= ++ ++# 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 finding + # ================================================================= + +@@ -23,19 +37,23 @@ + # Pathfinding array buffers configuration, default: 100x6;128x6;192x6;256x4;320x4;384x4;500x2 + PathFindBuffers = 100x6;128x6;192x6;256x4;320x4;384x4;500x2 + +-# Weight for nodes without obstacles far from walls +-LowWeight = 0.5 ++# Base path weight, when moving from one node to another on axis direction, default: 10 ++BaseWeight = 10 + +-# Weight for nodes near walls +-MediumWeight = 2 ++# Path weight, when moving from one node to another on diagonal direction, default: BaseWeight * sqrt(2) = 14 ++DiagonalWeight = 14 + +-# Weight for nodes with obstacles +-HighWeight = 3 ++# When movement flags of target node is blocked to any direction, multiply movement weight by this multiplier. ++# This causes pathfinding algorithm to avoid path construction exactly near an obstacle, default: 10 ++ObstacleMultiplier = 10 + +-# Weight for diagonal movement. +-# Default: LowWeight * sqrt(2) +-DiagonalWeight = 0.707 ++# Weight of the heuristic algorithm, which is giving estimated cost from node to target, default: 20 ++# For proper function must be higher than BaseWeight and/or DiagonalWeight. ++HeuristicWeight = 20 + ++# Maximum number of generated nodes per one path-finding process, default 3500 ++MaxIterations = 3500 ++ + # ================================================================= + # Other + # ================================================================= +Index: dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java +=================================================================== +--- dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java (revision 8397) ++++ dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java (working copy) +@@ -55,9 +55,8 @@ + final int worldX = activeChar.getX(); + final int worldY = activeChar.getY(); + final int worldZ = activeChar.getZ(); +- final int geoX = GeoEngine.getInstance().getGeoX(worldX); +- final int geoY = GeoEngine.getInstance().getGeoY(worldY); +- ++ final int geoX = GeoEngine.getGeoX(worldX); ++ final int geoY = GeoEngine.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)); +@@ -73,9 +72,8 @@ + final int worldX = activeChar.getX(); + final int worldY = activeChar.getY(); + final int worldZ = activeChar.getZ(); +- final int geoX = GeoEngine.getInstance().getGeoX(worldX); +- final int geoY = GeoEngine.getInstance().getGeoY(worldY); +- ++ final int geoX = GeoEngine.getGeoX(worldX); ++ final int geoY = GeoEngine.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)); +Index: dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java +=================================================================== +--- dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java (revision 8397) ++++ dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java (working copy) +@@ -18,11 +18,11 @@ + + import java.util.List; + +-import org.l2jmobius.Config; +-import org.l2jmobius.gameserver.geoengine.GeoEnginePathfinding; +-import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; ++import org.l2jmobius.gameserver.geoengine.GeoEngine; + import org.l2jmobius.gameserver.handler.IAdminCommandHandler; ++import org.l2jmobius.gameserver.model.Location; + import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance; ++import org.l2jmobius.gameserver.network.SystemMessageId; + import org.l2jmobius.gameserver.util.BuilderUtil; + + public class AdminPathNode implements IAdminCommandHandler +@@ -37,29 +37,30 @@ + { + if (command.equals("admin_path_find")) + { +- if (!Config.PATHFINDING) +- { +- BuilderUtil.sendSysMessage(activeChar, "PathFinding is disabled."); +- return true; +- } + if (activeChar.getTarget() != null) + { +- final List path = GeoEnginePathfinding.getInstance().findPath(activeChar.getX(), activeChar.getY(), (short) activeChar.getZ(), activeChar.getTarget().getX(), activeChar.getTarget().getY(), (short) activeChar.getTarget().getZ(), activeChar.getInstanceWorld()); ++ 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()); + if (path == null) + { +- BuilderUtil.sendSysMessage(activeChar, "No Route!"); +- return true; ++ BuilderUtil.sendSysMessage(activeChar, "No route found or pathfinding disabled."); + } +- for (AbstractNodeLoc a : path) ++ else + { +- BuilderUtil.sendSysMessage(activeChar, "x:" + a.getX() + " y:" + a.getY() + " z:" + a.getZ()); ++ for (Location point : path) ++ { ++ BuilderUtil.sendSysMessage(activeChar, "x:" + point.getX() + " y:" + point.getY() + " z:" + point.getZ()); ++ } + } + } + else + { +- BuilderUtil.sendSysMessage(activeChar, "No Target!"); ++ activeChar.sendPacket(SystemMessageId.INVALID_TARGET); + } + } ++ else ++ { ++ return false; ++ } + return true; + } + +Index: java/org/l2jmobius/Config.java +=================================================================== +--- java/org/l2jmobius/Config.java (revision 8397) ++++ java/org/l2jmobius/Config.java (working copy) +@@ -32,7 +32,6 @@ + import java.net.UnknownHostException; + import java.nio.charset.StandardCharsets; + import java.nio.file.Files; +-import java.nio.file.Path; + import java.nio.file.Paths; + import java.time.Duration; + import java.util.ArrayList; +@@ -927,14 +926,17 @@ + // -------------------------------------------------- + // GeoEngine + // -------------------------------------------------- +- public static Path GEODATA_PATH; ++ public static String GEODATA_PATH; ++ public static int COORD_SYNCHRONIZE; ++ public static int PART_OF_CHARACTER_HEIGHT; ++ public static int MAX_OBSTACLE_HEIGHT; + public static boolean PATHFINDING; + public static String PATHFIND_BUFFERS; +- public static float LOW_WEIGHT; +- public static float MEDIUM_WEIGHT; +- public static float HIGH_WEIGHT; +- public static float DIAGONAL_WEIGHT; +- public static int COORD_SYNCHRONIZE; ++ public static int BASE_WEIGHT; ++ public static int DIAGONAL_WEIGHT; ++ public static int HEURISTIC_WEIGHT; ++ public static int OBSTACLE_MULTIPLIER; ++ public static int MAX_ITERATIONS; + public static boolean CORRECT_PLAYER_Z; + + /** Attribute System */ +@@ -2468,14 +2470,17 @@ + + // Load GeoEngine config file (if exists) + final PropertiesParser GeoEngine = new PropertiesParser(GEOENGINE_CONFIG_FILE); +- GEODATA_PATH = Paths.get(GeoEngine.getString("GeoDataPath", "./data/geodata")); ++ GEODATA_PATH = GeoEngine.getString("GeoDataPath", "./data/geodata/"); ++ COORD_SYNCHRONIZE = GeoEngine.getInt("CoordSynchronize", -1); ++ PART_OF_CHARACTER_HEIGHT = GeoEngine.getInt("PartOfCharacterHeight", 75); ++ MAX_OBSTACLE_HEIGHT = GeoEngine.getInt("MaxObstacleHeight", 32); + PATHFINDING = GeoEngine.getBoolean("PathFinding", true); + PATHFIND_BUFFERS = GeoEngine.getString("PathFindBuffers", "100x6;128x6;192x6;256x4;320x4;384x4;500x2"); +- LOW_WEIGHT = GeoEngine.getFloat("LowWeight", 0.5f); +- MEDIUM_WEIGHT = GeoEngine.getFloat("MediumWeight", 2); +- HIGH_WEIGHT = GeoEngine.getFloat("HighWeight", 3); +- DIAGONAL_WEIGHT = GeoEngine.getFloat("DiagonalWeight", 0.707f); +- COORD_SYNCHRONIZE = GeoEngine.getInt("CoordSynchronize", -1); ++ BASE_WEIGHT = GeoEngine.getInt("BaseWeight", 10); ++ DIAGONAL_WEIGHT = GeoEngine.getInt("DiagonalWeight", 14); ++ OBSTACLE_MULTIPLIER = GeoEngine.getInt("ObstacleMultiplier", 10); ++ HEURISTIC_WEIGHT = GeoEngine.getInt("HeuristicWeight", 20); ++ MAX_ITERATIONS = GeoEngine.getInt("MaxIterations", 3500); + CORRECT_PLAYER_Z = GeoEngine.getBoolean("CorrectPlayerZ", false); + + // Load AllowedPlayerRaces config file (if exists) +Index: java/org/l2jmobius/gameserver/geoengine/GeoEngine.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/GeoEngine.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/GeoEngine.java (working copy) +@@ -16,101 +16,91 @@ + */ + package org.l2jmobius.gameserver.geoengine; + +-import java.io.IOException; ++import java.io.File; + import java.io.RandomAccessFile; + import java.nio.ByteOrder; +-import java.nio.channels.FileChannel.MapMode; +-import java.nio.file.Files; +-import java.nio.file.Path; +-import java.util.concurrent.atomic.AtomicReferenceArray; +-import java.util.logging.Level; ++import java.nio.MappedByteBuffer; ++import java.nio.channels.FileChannel; ++import java.util.List; + 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.geoengine.geodata.Cell; +-import org.l2jmobius.gameserver.geoengine.geodata.IRegion; +-import org.l2jmobius.gameserver.geoengine.geodata.NullRegion; +-import org.l2jmobius.gameserver.geoengine.geodata.Region; ++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.GeoFormat; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoLocation; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; + import org.l2jmobius.gameserver.instancemanager.WarpedSpaceManager; + 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; ++import org.l2jmobius.gameserver.util.MathUtil; + + /** +- * @author -Nemesiss-, HorridoJoho ++ * @author Hasha + */ + public class GeoEngine + { +- private static final Logger LOGGER = Logger.getLogger(GeoEngine.class.getName()); ++ protected static final Logger LOGGER = Logger.getLogger(GeoEngine.class.getName()); + +- private static final int WORLD_MIN_X = -655360; +- private static final int WORLD_MIN_Y = -589824; +- private static final int WORLD_MIN_Z = -16384; ++ private final ABlock[][] _blocks; ++ private final BlockNull _nullBlock; + +- /** 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; +- +- /** The regions array */ +- private final AtomicReferenceArray _regions = new AtomicReferenceArray<>(GEO_REGIONS); +- +- 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; +- +- protected GeoEngine() ++ /** ++ * GeoEngine constructor. Loads all geodata files of chosen geodata format. ++ */ ++ public GeoEngine() + { + LOGGER.info("GeoEngine: Initializing..."); +- for (int i = 0; i < _regions.length(); i++) +- { +- _regions.set(i, NullRegion.INSTANCE); +- } + ++ // 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; +- try ++ long fileSize = 0; ++ for (int rx = World.TILE_X_MIN; rx <= World.TILE_X_MAX; rx++) + { +- for (int regionX = World.TILE_X_MIN; regionX <= World.TILE_X_MAX; regionX++) ++ for (int ry = World.TILE_Y_MIN; ry <= World.TILE_Y_MAX; ry++) + { +- for (int regionY = World.TILE_Y_MIN; regionY <= World.TILE_Y_MAX; regionY++) ++ final File f = new File(Config.GEODATA_PATH + String.format(GeoFormat.L2D.getFilename(), rx, ry)); ++ if (f.exists() && !f.isDirectory()) + { +- 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(rx, ry)) + { +- try (RandomAccessFile raf = new RandomAccessFile(geoFilePath.toFile(), "r")) +- { +- _regions.set((regionX * GEO_REGIONS_Y) + regionY, new Region(raf.getChannel().map(MapMode.READ_ONLY, 0, raf.length()).load().order(ByteOrder.LITTLE_ENDIAN))); +- loaded++; +- } ++ loaded++; ++ fileSize += f.length(); + } + } ++ else ++ { ++ // region file is not load-able, load null blocks ++ loadNullBlocks(rx, ry); ++ } + } + } +- catch (Exception e) ++ ++ LOGGER.info("GeoEngine: Loaded " + loaded + " geodata files."); ++ if (loaded > 0) + { +- LOGGER.log(Level.SEVERE, "GeoEngine: Failed to load geodata!", e); +- System.exit(1); ++ LOGGER.info("GeoEngine: Total geodata file size " + (fileSize / 1024 / 1024) + " MB."); + } + +- LOGGER.info("GeoEngine: Loaded " + loaded + " geodata files."); +- +- // Avoid wrong configs when no files are loaded. ++ // avoid wrong configs when no files are loaded + if (loaded == 0) + { + if (Config.PATHFINDING) +@@ -124,627 +114,832 @@ + LOGGER.info("GeoEngine: Forcing CoordSynchronize setting to -1."); + } + } ++ ++ // release multilayer block temporarily buffer ++ BlockMultilayer.release(); + } + + /** +- * @param geoX +- * @param geoY +- * @return the region ++ * 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 IRegion getRegion(int geoX, int geoY) ++ private final boolean loadGeoBlocks(int regionX, int regionY) + { +- final int region = ((geoX / IRegion.REGION_CELLS_X) * GEO_REGIONS_Y) + (geoY / IRegion.REGION_CELLS_Y); +- if ((region < 0) || (region >= _regions.length())) ++ final String filename = String.format(GeoFormat.L2D.getFilename(), regionX, regionY); ++ ++ // standard load ++ try (RandomAccessFile raf = new RandomAccessFile(Config.GEODATA_PATH + filename, "r"); ++ FileChannel fc = raf.getChannel()) + { +- return null; ++ // initialize file buffer ++ final MappedByteBuffer buffer = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()).load(); ++ buffer.order(ByteOrder.LITTLE_ENDIAN); ++ ++ // 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++) ++ { ++ // get block type ++ final byte type = buffer.get(); ++ ++ // load block according to block type ++ switch (type) ++ { ++ case GeoStructure.TYPE_FLAT_L2D: ++ { ++ _blocks[blockX + ix][blockY + iy] = new BlockFlat(buffer, GeoFormat.L2D); ++ break; ++ } ++ case GeoStructure.TYPE_COMPLEX_L2D: ++ { ++ _blocks[blockX + ix][blockY + iy] = new BlockComplex(buffer, GeoFormat.L2D); ++ break; ++ } ++ case GeoStructure.TYPE_MULTILAYER_L2D: ++ { ++ _blocks[blockX + ix][blockY + iy] = new BlockMultilayer(buffer, GeoFormat.L2D); ++ break; ++ } ++ default: ++ { ++ throw new IllegalArgumentException("Unknown block type: " + type); ++ } ++ } ++ } ++ } ++ ++ // check data consistency ++ if (buffer.remaining() > 0) ++ { ++ LOGGER.warning("GeoEngine: Region file " + filename + " can be corrupted, remaining " + buffer.remaining() + " bytes to read."); ++ } ++ ++ // loading was successful ++ return true; + } +- return _regions.get(region); +- } +- +- /** +- * @param filePath +- * @param regionX +- * @param regionY +- * @throws IOException +- */ +- 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")) ++ catch (Exception e) + { +- _regions.set(regionOffset, new Region(raf.getChannel().map(MapMode.READ_ONLY, 0, raf.length()).load().order(ByteOrder.LITTLE_ENDIAN))); ++ // an error occured while loading, load null blocks ++ LOGGER.warning("GeoEngine: Error while loading " + filename + " region file."); ++ LOGGER.warning(e.getMessage()); ++ ++ // replace whole region file with null blocks ++ loadNullBlocks(regionX, regionY); ++ ++ // loading was not successful ++ return false; + } + } + + /** +- * @param regionX +- * @param regionY ++ * 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. + */ +- public void unloadRegion(int regionX, int regionY) ++ private final void loadNullBlocks(int regionX, int regionY) + { +- _regions.set((regionX * GEO_REGIONS_Y) + regionY, NullRegion.INSTANCE); +- } +- +- /** +- * @param geoX +- * @param geoY +- * @return if geodata exist +- */ +- public boolean hasGeoPos(int geoX, int geoY) +- { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) ++ // 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++) + { +- return false; ++ for (int iy = 0; iy < GeoStructure.REGION_BLOCKS_Y; iy++) ++ { ++ _blocks[blockX + ix][blockY + iy] = _nullBlock; ++ } + } +- return region.hasGeo(); + } + ++ // GEODATA - GENERAL ++ + /** +- * Checks the specified position for available geodata. +- * @param x the world x +- * @param y the world y +- * @return {@code true} if there is geodata for the given coordinates, {@code false} otherwise ++ * Converts world X to geodata X. ++ * @param worldX ++ * @return int : Geo X + */ +- public boolean hasGeo(int x, int y) ++ public static int getGeoX(int worldX) + { +- return hasGeoPos(getGeoX(x), getGeoY(y)); ++ return (MathUtil.limit(worldX, World.MAP_MIN_X, World.MAP_MAX_X) - World.MAP_MIN_X) >> 4; + } + + /** +- * @param geoX +- * @param geoY +- * @param worldZ +- * @param nswe +- * @return the nearest nswe check ++ * Converts world Y to geodata Y. ++ * @param worldY ++ * @return int : Geo Y + */ +- public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) ++ public static int getGeoY(int worldY) + { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) +- { +- return true; +- } +- return region.checkNearestNswe(geoX, geoY, worldZ, nswe); ++ return (MathUtil.limit(worldY, World.MAP_MIN_Y, World.MAP_MAX_Y) - World.MAP_MIN_Y) >> 4; + } + + /** ++ * Converts geodata X to world X. + * @param geoX +- * @param geoY +- * @param worldZ +- * @param nswe +- * @return the nearest nswe anti-corner cut ++ * @return int : World X + */ +- public boolean checkNearestNsweAntiCornerCut(int geoX, int geoY, int worldZ, int nswe) ++ public static int getWorldX(int geoX) + { +- boolean can = true; +- if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) +- { +- can = checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_EAST) && checkNearestNswe(geoX + 1, geoY, worldZ, Cell.NSWE_NORTH); +- } +- if (can && ((nswe & Cell.NSWE_NORTH_WEST) == Cell.NSWE_NORTH_WEST)) +- { +- 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 = 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 = checkNearestNswe(geoX, geoY + 1, worldZ, Cell.NSWE_WEST) && checkNearestNswe(geoX - 1, geoY, worldZ, Cell.NSWE_SOUTH); +- } +- return can && checkNearestNswe(geoX, geoY, worldZ, nswe); ++ return (MathUtil.limit(geoX, 0, GeoStructure.GEO_CELLS_X) << 4) + World.MAP_MIN_X + 8; + } + + /** +- * @param geoX ++ * Converts geodata Y to world Y. + * @param geoY +- * @param worldZ +- * @return the nearest Z value ++ * @return int : World Y + */ +- public int getNearestZ(int geoX, int geoY, int worldZ) ++ public static int getWorldY(int geoY) + { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) +- { +- return worldZ; +- } +- return region.getNearestZ(geoX, geoY, worldZ); ++ return (MathUtil.limit(geoY, 0, GeoStructure.GEO_CELLS_Y) << 4) + World.MAP_MIN_Y + 8; + } + + /** +- * @param geoX +- * @param geoY +- * @param worldZ +- * @return the next lower Z value ++ * Returns block of geodata on given coordinates. ++ * @param geoX : Geodata X ++ * @param geoY : Geodata Y ++ * @return {@link ABlock} : Block of geodata. + */ +- public int getNextLowerZ(int geoX, int geoY, int worldZ) ++ private final ABlock getBlock(int geoX, int geoY) + { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) ++ final int x = geoX / GeoStructure.BLOCK_CELLS_X; ++ final int y = geoY / GeoStructure.BLOCK_CELLS_Y; ++ ++ // if x or y is out of array return null ++ if ((x > -1) && (y > -1) && (x < GeoStructure.GEO_BLOCKS_X) && (y < GeoStructure.GEO_BLOCKS_Y)) + { +- return worldZ; ++ return _blocks[x][y]; + } +- return region.getNextLowerZ(geoX, geoY, worldZ); ++ return null; + } + + /** +- * @param geoX +- * @param geoY +- * @param worldZ +- * @return the next higher Z value ++ * Check if geo coordinates has geo. ++ * @param geoX : Geodata X ++ * @param geoY : Geodata Y ++ * @return boolean : True, if given geo coordinates have geodata + */ +- public int getNextHigherZ(int geoX, int geoY, int worldZ) ++ public boolean hasGeoPos(int geoX, int geoY) + { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) ++ final ABlock block = getBlock(geoX, geoY); ++ if (block == null) // null block check + { +- return worldZ; ++ // TODO: Find when this can be null. (Bad geodata? Check World getRegion method.) ++ // LOGGER.warning("Could not find geodata block at " + getWorldX(geoX) + ", " + getWorldY(geoY) + "."); ++ return false; + } +- return region.getNextHigherZ(geoX, geoY, worldZ); ++ return block.hasGeoPos(); + } + + /** +- * Gets the Z height. +- * @param x the world x +- * @param y the world y +- * @param z the world z +- * @return the nearest Z height ++ * 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 int getHeight(int x, int y, int z) ++ public short getHeightNearest(int geoX, int geoY, int worldZ) + { +- return getNearestZ(getGeoX(x), getGeoY(y), z); ++ final ABlock block = getBlock(geoX, geoY); ++ return block != null ? block.getHeightNearest(geoX, geoY, worldZ) : (short) worldZ; + } + + /** +- * Gets the next lower Z height. +- * @param x the world x +- * @param y the world y +- * @param z the world z +- * @return the nearest Z height ++ * 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 int getLowerHeight(int x, int y, int z) ++ public byte getNsweNearest(int geoX, int geoY, int worldZ) + { +- return getNextLowerZ(getGeoX(x), getGeoY(y), z); ++ final ABlock block = getBlock(geoX, geoY); ++ return block != null ? block.getNsweNearest(geoX, geoY, worldZ) : (byte) 0xFF; + } + + /** +- * Gets the next higher Z height. +- * @param x the world x +- * @param y the world y +- * @param z the world z +- * @return the nearest Z height ++ * Check if world coordinates has geo. ++ * @param worldX : World X ++ * @param worldY : World Y ++ * @return boolean : True, if given world coordinates have geodata + */ +- public int getHigherHeight(int x, int y, int z) ++ public boolean hasGeo(int worldX, int worldY) + { +- return getNextHigherZ(getGeoX(x), getGeoY(y), z); ++ return hasGeoPos(getGeoX(worldX), getGeoY(worldY)); + } + + /** +- * @param worldX +- * @return the geo X ++ * 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 int getGeoX(int worldX) ++ public short getHeight(int worldX, int worldY, int worldZ) + { +- return (worldX - WORLD_MIN_X) / 16; ++ return getHeightNearest(getGeoX(worldX), getGeoY(worldY), worldZ); + } + +- /** +- * @param worldY +- * @return the geo Y +- */ +- public int getGeoY(int worldY) +- { +- return (worldY - WORLD_MIN_Y) / 16; +- } ++ // PATHFINDING + + /** +- * @param worldZ +- * @return the geo Z ++ * Check line of sight from {@link WorldObject} to {@link WorldObject}. ++ * @param origin : The origin object. ++ * @param target : The target object. ++ * @return {@code boolean} : True if origin can see target + */ +- public int getGeoZ(int worldZ) ++ public boolean canSeeTarget(WorldObject origin, WorldObject target) + { +- return (worldZ - WORLD_MIN_Z) / 16; +- } +- +- /** +- * @param geoX +- * @return the world X +- */ +- public int getWorldX(int geoX) +- { +- return (geoX * 16) + WORLD_MIN_X + 8; +- } +- +- /** +- * @param geoY +- * @return the world Y +- */ +- public int getWorldY(int geoY) +- { +- return (geoY * 16) + WORLD_MIN_Y + 8; +- } +- +- /** +- * @param geoZ +- * @return the world Z +- */ +- public int getWorldZ(int geoZ) +- { +- return (geoZ * 16) + WORLD_MIN_Z + 8; +- } +- +- /** +- * 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) +- { +- if (target.isDoor()) ++ if (target.isDoor() || target.isArtefact() || (target.isCreature() && ((Creature) target).isFlying())) + { +- // Can always see doors. + return true; + } +- return 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(), cha.getInstanceWorld(), worldPosition.getX(), worldPosition.getY(), worldPosition.getZ()); +- } +- +- /** +- * Can see target. Checks doors between. +- * @param x the x coordinate +- * @param y the y coordinate +- * @param z the z coordinate +- * @param world +- * @param tx the target's x coordinate +- * @param ty the target's y coordinate +- * @param tz the target's z coordinate +- * @param tworld the target's instanceId +- * @return +- */ +- public boolean canSeeTarget(int x, int y, int z, Instance world, int tx, int ty, int tz, Instance tworld) +- { +- return (world != tworld) ? false : canSeeTarget(x, y, z, world, tx, ty, tz); +- } +- +- /** +- * 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 +- * @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, Instance instance, int tx, int ty, int tz) +- { +- if (DoorData.getInstance().checkIfDoorsBetween(x, y, z, tx, ty, tz, instance, true)) ++ ++ // get origin and target world coordinates ++ final int ox = origin.getX(); ++ final int oy = origin.getY(); ++ final int oz = origin.getZ(); ++ final int tx = target.getX(); ++ final int ty = target.getY(); ++ final int tz = target.getZ(); ++ ++ if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld(), true)) + { + return false; + } +- return canSeeTarget(x, y, z, tx, ty, tz); +- } +- +- /** +- * @param prevX +- * @param prevY +- * @param prevGeoZ +- * @param curX +- * @param curY +- * @param nswe +- * @return the LOS Z value +- */ +- 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))) ++ if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld())) + { +- throw new RuntimeException("Multiple directions!"); ++ return false; + } ++ if (WarpedSpaceManager.getInstance().checkForWarpedSpace(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld())) ++ { ++ return false; ++ } + +- if (checkNearestNsweAntiCornerCut(prevX, prevY, prevGeoZ, nswe)) ++ // get origin and check existing geo coordinates ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) + { +- return getNearestZ(curX, curY, prevGeoZ); ++ return true; + } + +- return getNextHigherZ(curX, curY, prevGeoZ); ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coordinates ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) ++ { ++ return true; ++ } ++ ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // origin and target coordinates are same ++ if ((gox == gtx) && (goy == gty)) ++ { ++ return goz == gtz; ++ } ++ ++ // get origin and target height, real height = collision height * 2 ++ double oheight = 0; ++ if (origin.isCreature()) ++ { ++ oheight = ((Creature) origin).getCollisionHeight() * 2; ++ } ++ ++ double theight = 0; ++ if (target.isCreature()) ++ { ++ theight = ((Creature) target).getCollisionHeight() * 2; ++ } ++ ++ // perform geodata check ++ return checkSee(gox, goy, goz, oheight, gtx, gty, gtz, theight, origin.getInstanceWorld()); + } + + /** +- * Can see target. Does not check doors between. +- * @param xValue the x coordinate +- * @param yValue the y coordinate +- * @param zValue the z coordinate +- * @param txValue the target's x coordinate +- * @param tyValue the target's y coordinate +- * @param tzValue the target's z coordinate +- * @return {@code true} if there is line of sight between the given coordinate sets, {@code false} otherwise ++ * Check line of sight from {@link WorldObject} to {@link Location}. ++ * @param origin : The origin object. ++ * @param position : The target position. ++ * @return {@code boolean} : True if object can see position + */ +- public boolean canSeeTarget(int xValue, int yValue, int zValue, int txValue, int tyValue, int tzValue) ++ public boolean canSeeTarget(WorldObject origin, Location position) + { +- int x = xValue; +- int y = yValue; +- int tx = txValue; +- int ty = tyValue; ++ // get origin and target world coordinates ++ final int ox = origin.getX(); ++ final int oy = origin.getY(); ++ final int oz = origin.getZ(); ++ final int tx = position.getX(); ++ final int ty = position.getY(); ++ final int tz = position.getZ(); + +- int geoX = getGeoX(x); +- int geoY = getGeoY(y); +- int tGeoX = getGeoX(tx); +- int tGeoY = getGeoY(ty); ++ if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld(), true)) ++ { ++ return false; ++ } ++ if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld())) ++ { ++ return false; ++ } ++ if (WarpedSpaceManager.getInstance().checkForWarpedSpace(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld())) ++ { ++ return false; ++ } + +- int z = getNearestZ(geoX, geoY, zValue); +- int tz = getNearestZ(tGeoX, tGeoY, tzValue); ++ // get origin and check existing geo coordinates ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) ++ { ++ return true; ++ } + +- // Fastpath. +- if ((geoX == tGeoX) && (geoY == tGeoY)) ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coordinates ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) + { +- if (hasGeoPos(tGeoX, tGeoY)) +- { +- return z == tz; +- } + return true; + } + +- if (tz > z) ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // origin and target coordinates are same ++ if ((gox == gtx) && (goy == gty)) + { +- int tmp = tx; +- tx = x; +- x = tmp; +- +- tmp = ty; +- ty = y; +- y = tmp; +- +- tmp = tz; +- tz = z; +- z = tmp; +- +- tmp = tGeoX; +- tGeoX = geoX; +- geoX = tmp; +- +- tmp = tGeoY; +- tGeoY = geoY; +- geoY = tmp; ++ return goz == gtz; + } + +- final LinePointIterator3D pointIter = new LinePointIterator3D(geoX, geoY, z, tGeoX, tGeoY, tz); +- // 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()) ++ // get origin and target height, real height = collision height * 2 ++ double oheight = 0; ++ if (origin.isCreature()) + { +- final int curX = pointIter.x(); +- final int curY = pointIter.y(); ++ oheight = ((Creature) origin).getTemplate().getCollisionHeight(); ++ } ++ ++ // perform geodata check ++ return checkSee(gox, goy, goz, oheight, gtx, gty, gtz, 0, origin.getInstanceWorld()); ++ } ++ ++ /** ++ * Simple check for origin to target visibility. ++ * @param goxValue : origin X geodata coordinate ++ * @param goyValue : origin Y geodata coordinate ++ * @param gozValue : origin Z geodata coordinate ++ * @param oheight : origin height (if instance of {@link Character}) ++ * @param gtxValue : target X geodata coordinate ++ * @param gtyValue : target Y geodata coordinate ++ * @param gtzValue : target Z geodata coordinate ++ * @param theight : target height (if instance of {@link Character}) ++ * @param instance ++ * @return {@code boolean} : True, when target can be seen. ++ */ ++ private final boolean checkSee(int goxValue, int goyValue, int gozValue, double oheight, int gtxValue, int gtyValue, int gtzValue, double theight, Instance instance) ++ { ++ int goz = gozValue; ++ int gtz = gtzValue; ++ int gox = goxValue; ++ int goy = goyValue; ++ int gtx = gtxValue; ++ int gty = gtyValue; ++ ++ // get line of sight Z coordinates ++ double losoz = goz + ((oheight * Config.PART_OF_CHARACTER_HEIGHT) / 100); ++ double lostz = gtz + ((theight * Config.PART_OF_CHARACTER_HEIGHT) / 100); ++ ++ // get X delta and signum ++ final int dx = Math.abs(gtx - gox); ++ final int sx = gox < gtx ? 1 : -1; ++ final byte dirox = sx > 0 ? GeoStructure.CELL_FLAG_E : GeoStructure.CELL_FLAG_W; ++ final byte dirtx = sx > 0 ? GeoStructure.CELL_FLAG_W : GeoStructure.CELL_FLAG_E; ++ ++ // get Y delta and signum ++ final int dy = Math.abs(gty - goy); ++ final int sy = goy < gty ? 1 : -1; ++ final byte diroy = sy > 0 ? GeoStructure.CELL_FLAG_S : GeoStructure.CELL_FLAG_N; ++ final byte dirty = sy > 0 ? GeoStructure.CELL_FLAG_N : GeoStructure.CELL_FLAG_S; ++ ++ // get Z delta ++ final int dm = Math.max(dx, dy); ++ final double dz = (lostz - losoz) / dm; ++ ++ // get direction flag for diagonal movement ++ final byte diroxy = getDirXY(dirox, diroy); ++ final byte dirtxy = getDirXY(dirtx, dirty); ++ ++ // delta, determines axis to move on (+..X axis, -..Y axis) ++ int d = dx - dy; ++ ++ // NSWE direction of movement ++ byte diro; ++ byte dirt; ++ ++ // initialize node values ++ int nox = gox; ++ int noy = goy; ++ int ntx = gtx; ++ int nty = gty; ++ byte nsweo = getNsweNearest(gox, goy, goz); ++ byte nswet = getNsweNearest(gtx, gty, gtz); ++ ++ // loop ++ ABlock block; ++ int index; ++ for (int i = 0; i < ((dm + 1) / 2); i++) ++ { ++ // reset direction flag ++ diro = 0; ++ dirt = 0; + +- if ((curX == prevX) && (curY == prevY)) ++ // calculate next point coordinates ++ final int e2 = 2 * d; ++ if ((e2 > -dy) && (e2 < dx)) + { +- continue; ++ // calculate next point XY coordinates ++ d -= dy; ++ d += dx; ++ nox += sx; ++ ntx -= sx; ++ noy += sy; ++ nty -= sy; ++ diro |= diroxy; ++ dirt |= dirtxy; + } ++ else if (e2 > -dy) ++ { ++ // calculate next point X coordinate ++ d -= dy; ++ nox += sx; ++ ntx -= sx; ++ diro |= dirox; ++ dirt |= dirtx; ++ } ++ else if (e2 < dx) ++ { ++ // calculate next point Y coordinate ++ d += dx; ++ noy += sy; ++ nty -= sy; ++ diro |= diroy; ++ dirt |= dirty; ++ } + +- final int beeCurZ = pointIter.z(); +- int curGeoZ = prevGeoZ; +- +- // Check if the position has geodata. +- if (hasGeoPos(curX, curY)) + { +- final int beeCurGeoZ = getNearestZ(curX, curY, beeCurZ); +- final int nswe = GeoUtils.computeNswe(prevX, prevY, curX, curY); // .computeDirection(prevX, prevY, curX, curY); +- curGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, curX, curY, nswe); +- int maxHeight; +- if (ptIndex < ELEVATED_SEE_OVER_DISTANCE) ++ // get block of the next cell ++ block = getBlock(nox, noy); ++ ++ // get index of particular layer, based on movement conditions ++ if ((nsweo & diro) == 0) + { +- maxHeight = z + MAX_SEE_OVER_HEIGHT; ++ index = block.getIndexAbove(nox, noy, goz - GeoStructure.CELL_IGNORE_HEIGHT); + } + else + { +- maxHeight = beeCurZ + MAX_SEE_OVER_HEIGHT; ++ index = block.getIndexBelow(nox, noy, goz + GeoStructure.CELL_IGNORE_HEIGHT); + } + +- boolean canSeeThrough = false; +- if ((curGeoZ <= maxHeight) && (curGeoZ <= beeCurGeoZ)) ++ // layer does not exist, return ++ if (index == -1) + { +- if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) +- { +- 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 +- { +- canSeeThrough = true; +- } ++ return false; + } + +- if (!canSeeThrough) ++ // get layer and next line of sight Z coordinate ++ goz = block.getHeight(index); ++ losoz += dz; ++ ++ // perform line of sight check, return when fails ++ if ((goz - losoz) > Config.MAX_OBSTACLE_HEIGHT) + { + return false; + } ++ ++ // get layer nswe ++ nsweo = block.getNswe(index); + } ++ { ++ // get block of the next cell ++ block = getBlock(ntx, nty); ++ ++ // get index of particular layer, based on movement conditions ++ if ((nswet & dirt) == 0) ++ { ++ index = block.getIndexAbove(ntx, nty, gtz - GeoStructure.CELL_IGNORE_HEIGHT); ++ } ++ else ++ { ++ index = block.getIndexBelow(ntx, nty, gtz + GeoStructure.CELL_IGNORE_HEIGHT); ++ } ++ ++ // layer does not exist, return ++ if (index == -1) ++ { ++ return false; ++ } ++ ++ // get layer and next line of sight Z coordinate ++ gtz = block.getHeight(index); ++ lostz -= dz; ++ ++ // perform line of sight check, return when fails ++ if ((gtz - lostz) > Config.MAX_OBSTACLE_HEIGHT) ++ { ++ return false; ++ } ++ ++ // get layer nswe ++ nswet = block.getNswe(index); ++ } + +- prevX = curX; +- prevY = curY; +- prevGeoZ = curGeoZ; +- ++ptIndex; ++ // update coords ++ gox = nox; ++ goy = noy; ++ gtx = ntx; ++ gty = nty; + } + +- return true; ++ // when iteration is completed, compare final Z coordinates ++ return Math.abs(goz - gtz) < (GeoStructure.CELL_HEIGHT * 4); + } + + /** +- * Move check. +- * @param x the x coordinate +- * @param y the y coordinate +- * @param zValue the z coordinate +- * @param tx the target's x coordinate +- * @param ty the target's y coordinate +- * @param tzValue the target's z coordinate +- * @param instance the instance +- * @return the last Location (x,y,z) where player can walk - just before wall ++ * Check movement from coordinates to 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 {code boolean} : True if target coordinates are reachable from origin coordinates + */ +- public Location canMoveToTargetLoc(int x, int y, int zValue, int tx, int ty, int tzValue, Instance instance) ++ public boolean canMoveToTarget(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + { +- final int geoX = getGeoX(x); +- final int geoY = getGeoY(y); +- final int z = getNearestZ(geoX, geoY, zValue); +- final int tGeoX = getGeoX(tx); +- final int tGeoY = getGeoY(ty); +- final int tz = getNearestZ(tGeoX, tGeoY, tzValue); ++ // get origin and check existing geo coordinates ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) ++ { ++ return true; ++ } + +- if (DoorData.getInstance().checkIfDoorsBetween(x, y, z, tx, ty, tz, instance, false)) ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coordinates ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) + { +- return new Location(x, y, getHeight(x, y, z)); ++ return true; + } +- if (FenceData.getInstance().checkIfFenceBetween(x, y, z, tx, ty, tz, instance)) ++ ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // target coordinates reached ++ if ((gox == gtx) && (goy == gty) && (goz == gtz)) + { +- return new Location(x, y, getHeight(x, y, z)); ++ return true; + } +- if (WarpedSpaceManager.getInstance().checkForWarpedSpace(x, y, z, tx, ty, tz, instance)) ++ ++ // perform geodata check ++ final GeoLocation loc = checkMove(gox, goy, goz, gtx, gty, gtz, instance); ++ return (loc.getGeoX() == gtx) && (loc.getGeoY() == gty); ++ } ++ ++ /** ++ * Check movement from origin to target. Returns last available point in the checked path. ++ * @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 {@link Location} : Last point where object can walk (just before wall) ++ */ ++ public Location canMoveToTargetLoc(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) ++ { ++ // Mobius: Double check for doors before normal checkMove to avoid exploiting key movement. ++ if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, instance, false)) + { +- return new Location(x, y, getHeight(x, y, z)); ++ return new Location(ox, oy, oz); + } ++ if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, instance)) ++ { ++ return new Location(ox, oy, oz); ++ } + +- 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 = z; ++ // get origin and check existing geo coordinates ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) ++ { ++ return new Location(tx, ty, tz); ++ } + +- while (pointIter.next()) ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coordinates ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) + { +- final int curX = pointIter.x(); +- final int curY = pointIter.y(); +- final int curZ = getNearestZ(curX, curY, prevZ); +- +- if (hasGeoPos(prevX, prevY)) +- { +- final int nswe = GeoUtils.computeNswe(prevX, prevY, curX, curY); +- if (!checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, nswe)) +- { +- // Can't move, return previous location. +- return new Location(getWorldX(prevX), getWorldY(prevY), prevZ); +- } +- } +- +- prevX = curX; +- prevY = curY; +- prevZ = curZ; ++ return new Location(tx, ty, tz); + } + +- if (hasGeoPos(prevX, prevY) && (prevZ != tz)) ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // target coordinates reached ++ if ((gox == gtx) && (goy == gty) && (goz == gtz)) + { +- // Different floors, return start location. +- return new Location(x, y, z); ++ return new Location(tx, ty, tz); + } + +- return new Location(tx, ty, tz); ++ // perform geodata check ++ return checkMove(gox, goy, goz, gtx, gty, gtz, instance); + } + + /** +- * 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 fromZvalue 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 toZvalue 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 ++ * With this method you can check if a position is visible or can be reached by beeline movement.
++ * 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 gox : origin X geodata coordinate ++ * @param goy : origin Y geodata coordinate ++ * @param goz : origin Z geodata coordinate ++ * @param gtx : target X geodata coordinate ++ * @param gty : target Y geodata coordinate ++ * @param gtz : target Z geodata coordinate ++ * @param instance ++ * @return {@link GeoLocation} : The last allowed point of movement. + */ +- public boolean canMoveToTarget(int fromX, int fromY, int fromZvalue, int toX, int toY, int toZvalue, Instance instance) ++ protected final GeoLocation checkMove(int gox, int goy, int goz, int gtx, int gty, int gtz, Instance instance) + { +- final int geoX = getGeoX(fromX); +- final int geoY = getGeoY(fromY); +- final int fromZ = getNearestZ(geoX, geoY, fromZvalue); +- final int tGeoX = getGeoX(toX); +- final int tGeoY = getGeoY(toY); +- final int toZ = getNearestZ(tGeoX, tGeoY, toZvalue); +- +- if (DoorData.getInstance().checkIfDoorsBetween(fromX, fromY, fromZ, toX, toY, toZ, instance, false)) ++ if (DoorData.getInstance().checkIfDoorsBetween(gox, goy, goz, gtx, gty, gtz, instance, false)) + { +- return false; ++ return new GeoLocation(gox, goy, goz); + } +- if (FenceData.getInstance().checkIfFenceBetween(fromX, fromY, fromZ, toX, toY, toZ, instance)) ++ if (FenceData.getInstance().checkIfFenceBetween(gox, goy, goz, gtx, gty, gtz, instance)) + { +- return false; ++ return new GeoLocation(gox, goy, goz); + } +- if (WarpedSpaceManager.getInstance().checkForWarpedSpace(fromX, fromY, fromZ, toX, toY, toZ, instance)) ++ if (WarpedSpaceManager.getInstance().checkForWarpedSpace(gox, goy, goz, gtx, gty, gtz, instance)) + { +- return false; ++ return new GeoLocation(gox, goy, goz); + } + +- 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 = fromZ; ++ // get X delta, signum and direction flag ++ final int dx = Math.abs(gtx - gox); ++ final int sx = gox < gtx ? 1 : -1; ++ final byte dirX = sx > 0 ? GeoStructure.CELL_FLAG_E : GeoStructure.CELL_FLAG_W; + +- while (pointIter.next()) ++ // get Y delta, signum and direction flag ++ final int dy = Math.abs(gty - goy); ++ final int sy = goy < gty ? 1 : -1; ++ final byte dirY = sy > 0 ? GeoStructure.CELL_FLAG_S : GeoStructure.CELL_FLAG_N; ++ ++ // get direction flag for diagonal movement ++ final byte dirXY = getDirXY(dirX, dirY); ++ ++ // delta, determines axis to move on (+..X axis, -..Y axis) ++ int d = dx - dy; ++ ++ // NSWE direction of movement ++ byte direction; ++ ++ // load pointer coordinates ++ int gpx = gox; ++ int gpy = goy; ++ int gpz = goz; ++ ++ // load next pointer ++ int nx = gpx; ++ int ny = gpy; ++ ++ // loop ++ int count = 0; ++ while (count++ < Config.MAX_ITERATIONS) + { +- final int curX = pointIter.x(); +- final int curY = pointIter.y(); +- final int curZ = getNearestZ(curX, curY, prevZ); ++ direction = 0; + +- if (hasGeoPos(prevX, prevY)) ++ // calculate next point coordinates ++ final int e2 = 2 * d; ++ if ((e2 > -dy) && (e2 < dx)) + { +- final int nswe = GeoUtils.computeNswe(prevX, prevY, curX, curY); +- if (!checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, nswe)) ++ d -= dy; ++ d += dx; ++ nx += sx; ++ ny += sy; ++ direction |= dirXY; ++ } ++ else if (e2 > -dy) ++ { ++ d -= dy; ++ nx += sx; ++ direction |= dirX; ++ } ++ else if (e2 < dx) ++ { ++ d += dx; ++ ny += sy; ++ direction |= dirY; ++ } ++ ++ // obstacle found, return ++ if ((getNsweNearest(gpx, gpy, gpz) & direction) == 0) ++ { ++ return new GeoLocation(gpx, gpy, gpz); ++ } ++ ++ // update pointer coordinates ++ gpx = nx; ++ gpy = ny; ++ gpz = getHeightNearest(nx, ny, gpz); ++ ++ // target coordinates reached ++ if ((gpx == gtx) && (gpy == gty)) ++ { ++ if (gpz == gtz) + { +- return false; ++ // path found, Z coordinates are okay, return target point ++ return new GeoLocation(gtx, gty, gtz); + } ++ ++ // path found, Z coordinates are not okay, return last good point ++ return new GeoLocation(gpx, gpy, gpz); + } ++ } ++ ++ return new GeoLocation(gox, goy, goz); ++ } ++ ++ /** ++ * Returns diagonal NSWE flag format of combined two NSWE flags. ++ * @param dirX : X direction NSWE flag ++ * @param dirY : Y direction NSWE flag ++ * @return byte : NSWE flag of combined direction ++ */ ++ private static byte getDirXY(byte dirX, byte dirY) ++ { ++ // check axis directions ++ if (dirY == GeoStructure.CELL_FLAG_N) ++ { ++ if (dirX == GeoStructure.CELL_FLAG_W) ++ { ++ return GeoStructure.CELL_FLAG_NW; ++ } + +- prevX = curX; +- prevY = curY; +- prevZ = curZ; ++ return GeoStructure.CELL_FLAG_NE; + } + +- if (hasGeoPos(prevX, prevY) && (prevZ != toZ)) ++ if (dirX == GeoStructure.CELL_FLAG_W) + { +- // Different floors. +- return false; ++ return GeoStructure.CELL_FLAG_SW; + } + +- return true; ++ return GeoStructure.CELL_FLAG_SE; + } + ++ /** ++ * 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 ++ */ ++ public List findPath(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) ++ { ++ return null; ++ } ++ ++ /** ++ * Returns the instance of the {@link GeoEngine}. ++ * @return {@link GeoEngine} : The instance. ++ */ + public static GeoEngine getInstance() + { + return SingletonHolder.INSTANCE; +@@ -752,6 +947,6 @@ + + private static class SingletonHolder + { +- protected static final GeoEngine INSTANCE = new GeoEngine(); ++ protected static final GeoEngine INSTANCE = Config.PATHFINDING ? new GeoEnginePathfinding() : new GeoEngine(); + } +-} ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/GeoEnginePathfinding.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/GeoEnginePathfinding.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/GeoEnginePathfinding.java (working copy) +@@ -20,88 +20,84 @@ + import java.util.LinkedList; + import java.util.List; + import java.util.ListIterator; +-import java.util.logging.Level; +-import java.util.logging.Logger; + + import org.l2jmobius.Config; +-import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; +-import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +-import org.l2jmobius.gameserver.geoengine.pathfinding.CellNode; +-import org.l2jmobius.gameserver.geoengine.pathfinding.CellNodeBuffer; +-import org.l2jmobius.gameserver.geoengine.pathfinding.NodeLoc; +-import org.l2jmobius.gameserver.model.World; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoLocation; ++import org.l2jmobius.gameserver.geoengine.pathfinding.Node; ++import org.l2jmobius.gameserver.geoengine.pathfinding.NodeBuffer; ++import org.l2jmobius.gameserver.model.Location; + import org.l2jmobius.gameserver.model.instancezone.Instance; + + /** +- * @author -Nemesiss- ++ * @author Hasha + */ +-public class GeoEnginePathfinding ++final class GeoEnginePathfinding extends GeoEngine + { +- private static final Logger LOGGER = Logger.getLogger(GeoEnginePathfinding.class.getName()); ++ // pre-allocated buffers ++ private final BufferHolder[] _buffers; + +- private BufferInfo[] _buffers; +- + protected GeoEnginePathfinding() + { +- try ++ super(); ++ ++ final String[] array = Config.PATHFIND_BUFFERS.split(";"); ++ _buffers = new BufferHolder[array.length]; ++ int count = 0; ++ for (int i = 0; i < array.length; i++) + { +- final String[] array = Config.PATHFIND_BUFFERS.split(";"); ++ final String buf = array[i]; ++ final String[] args = buf.split("x"); + +- _buffers = new BufferInfo[array.length]; +- +- String buf; +- String[] args; +- for (int i = 0; i < array.length; i++) ++ try + { +- buf = array[i]; +- args = buf.split("x"); +- if (args.length != 2) +- { +- throw new Exception("Invalid buffer definition: " + buf); +- } +- +- _buffers[i] = new BufferInfo(Integer.parseInt(args[0]), Integer.parseInt(args[1])); ++ final int size = Integer.parseInt(args[1]); ++ count += size; ++ _buffers[i] = new BufferHolder(Integer.parseInt(args[0]), size); + } ++ catch (Exception e) ++ { ++ LOGGER.warning("GeoEnginePathfinding: Can not load buffer setting: " + buf); ++ } + } +- catch (Exception e) +- { +- LOGGER.log(Level.WARNING, "CellPathFinding: Problem during buffer init: " + e.getMessage(), e); +- throw new Error("CellPathFinding: load aborted"); +- } ++ ++ LOGGER.info("GeoEnginePathfinding: Loaded " + count + " node buffers."); + } + +- public boolean pathNodesExist(short regionoffset) ++ @Override ++ public List findPath(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + { +- return false; +- } +- +- public List findPath(int x, int y, int z, int tx, int ty, int tz, Instance instance) +- { +- final int gx = GeoEngine.getInstance().getGeoX(x); +- final int gy = GeoEngine.getInstance().getGeoY(y); +- if (!GeoEngine.getInstance().hasGeo(x, y)) ++ // get origin and check existing geo coords ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) + { + 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)) ++ ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coords ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) + { + 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)))); ++ ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // Prepare buffer for pathfinding calculations ++ final NodeBuffer buffer = getBuffer(64 + (2 * Math.max(Math.abs(gox - gtx), Math.abs(goy - gty)))); + if (buffer == null) + { + return null; + } + +- List path = null; ++ // find path ++ List path = null; + try + { +- final CellNode result = buffer.findPath(gx, gy, gz, gtx, gty, gtz); +- ++ final Node result = buffer.findPath(gox, goy, goz, gtx, gty, gtz); + if (result == null) + { + return null; +@@ -125,33 +121,45 @@ + return path; + } + +- int currentX, currentY, currentZ; +- ListIterator middlePoint; ++ // get path list iterator ++ final ListIterator point = path.listIterator(); + +- middlePoint = path.listIterator(); +- currentX = x; +- currentY = y; +- currentZ = z; ++ // get node A (origin) ++ int nodeAx = gox; ++ int nodeAy = goy; ++ short nodeAz = goz; + +- while (middlePoint.hasNext()) ++ // get node B ++ GeoLocation nodeB = (GeoLocation) point.next(); ++ ++ // iterate thought the path to optimize it ++ int count = 0; ++ while (point.hasNext() && (count++ < Config.MAX_ITERATIONS)) + { +- final AbstractNodeLoc locMiddle = middlePoint.next(); +- if (!middlePoint.hasNext()) +- { +- break; +- } ++ // get node C ++ final GeoLocation nodeC = (GeoLocation) path.get(point.nextIndex()); + +- final AbstractNodeLoc locEnd = path.get(middlePoint.nextIndex()); +- if (GeoEngine.getInstance().canMoveToTarget(currentX, currentY, currentZ, locEnd.getX(), locEnd.getY(), locEnd.getZ(), instance)) ++ // check movement from node A to node C ++ final GeoLocation loc = checkMove(nodeAx, nodeAy, nodeAz, nodeC.getGeoX(), nodeC.getGeoY(), nodeC.getZ(), instance); ++ if ((loc.getGeoX() == nodeC.getGeoX()) && (loc.getGeoY() == nodeC.getGeoY())) + { +- middlePoint.remove(); ++ // can move from node A to node C ++ ++ // remove node B ++ point.remove(); + } + else + { +- currentX = locMiddle.getX(); +- currentY = locMiddle.getY(); +- currentZ = locMiddle.getZ(); ++ // can not move from node A to node C ++ ++ // set node A (node B is part of path, update A coordinates) ++ nodeAx = nodeB.getGeoX(); ++ nodeAy = nodeB.getGeoY(); ++ nodeAz = (short) nodeB.getZ(); + } ++ ++ // set node B ++ nodeB = (GeoLocation) point.next(); + } + + return path; +@@ -158,144 +166,102 @@ + } + + /** +- * Convert geodata position to pathnode position +- * @param geo_pos +- * @return pathnode position ++ * Create list of node locations as result of calculated buffer node tree. ++ * @param node : the entry point ++ * @return List : list of node location + */ +- public short getNodePos(int geo_pos) ++ private static List constructPath(Node node) + { +- return (short) (geo_pos >> 3); // OK? +- } +- +- /** +- * Convert node position to pathnode block position +- * @param node_pos +- * @return pathnode block position (0...255) +- */ +- public short getNodeBlock(int node_pos) +- { +- return (short) (node_pos % 256); +- } +- +- public byte getRegionX(int node_pos) +- { +- return (byte) ((node_pos >> 8) + World.TILE_X_MIN); +- } +- +- public byte getRegionY(int node_pos) +- { +- return (byte) ((node_pos >> 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 node_x rx +- * @return +- */ +- public int calculateWorldX(short node_x) +- { +- return World.MAP_MIN_X + (node_x * 128) + 48; +- } +- +- /** +- * Convert pathnode y to World y position +- * @param node_y +- * @return +- */ +- public int calculateWorldY(short node_y) +- { +- return World.MAP_MIN_Y + (node_y * 128) + 48; +- } +- +- private List constructPath(AbstractNode nodeValue) +- { +- final LinkedList path = new LinkedList<>(); +- int previousDirectionX = Integer.MIN_VALUE; +- int previousDirectionY = Integer.MIN_VALUE; +- int directionX, directionY; ++ // create empty list ++ final LinkedList list = new LinkedList<>(); + +- AbstractNode node = nodeValue; +- while (node.getParent() != null) ++ // set direction X/Y ++ int dx = 0; ++ int dy = 0; ++ ++ // get target parent ++ Node target = node; ++ Node parent = target.getParent(); ++ ++ // while parent exists ++ int count = 0; ++ while ((parent != null) && (count++ < Config.MAX_ITERATIONS)) + { +- directionX = node.getLoc().getNodeX() - node.getParent().getLoc().getNodeX(); +- directionY = node.getLoc().getNodeY() - node.getParent().getLoc().getNodeY(); ++ // get parent <> target direction X/Y ++ final int nx = parent.getLoc().getGeoX() - target.getLoc().getGeoX(); ++ final int ny = parent.getLoc().getGeoY() - target.getLoc().getGeoY(); + +- // only add a new route point if moving direction changes +- if ((directionX != previousDirectionX) || (directionY != previousDirectionY)) ++ // direction has changed? ++ if ((dx != nx) || (dy != ny)) + { +- previousDirectionX = directionX; +- previousDirectionY = directionY; ++ // add node to the beginning of the list ++ list.addFirst(target.getLoc()); + +- path.addFirst(node.getLoc()); +- node.setLoc(null); ++ // update direction X/Y ++ dx = nx; ++ dy = ny; + } + +- node = node.getParent(); ++ // move to next node, set target and get its parent ++ target = parent; ++ parent = target.getParent(); + } + +- return path; ++ // return list ++ return list; + } + +- private CellNodeBuffer alloc(int size) ++ /** ++ * Provides optimize selection of the buffer. When all pre-initialized buffer are locked, creates new buffer. ++ * @param size : pre-calculated minimal required size ++ * @return NodeBuffer : buffer ++ */ ++ private final NodeBuffer getBuffer(int size) + { +- CellNodeBuffer current = null; +- for (BufferInfo i : _buffers) ++ NodeBuffer current = null; ++ for (BufferHolder holder : _buffers) + { +- if (i.mapSize >= size) ++ // Find proper size of buffer ++ if (holder._size < size) + { +- for (CellNodeBuffer buf : i.buffer) ++ continue; ++ } ++ ++ // Find unlocked NodeBuffer ++ for (NodeBuffer buffer : holder._buffer) ++ { ++ if (!buffer.isLocked()) + { +- if (buf.lock()) +- { +- current = buf; +- break; +- } ++ continue; + } +- if (current != null) +- { +- break; +- } + +- // not found, allocate temporary buffer +- current = new CellNodeBuffer(i.mapSize); +- current.lock(); +- if (i.buffer.size() < i.count) +- { +- i.buffer.add(current); +- break; +- } ++ return buffer; + } ++ ++ // NodeBuffer not found, allocate temporary buffer ++ current = new NodeBuffer(holder._size); ++ current.isLocked(); + } + + return current; + } + +- private static final class BufferInfo ++ /** ++ * NodeBuffer container with specified size and count of separate buffers. ++ */ ++ private static final class BufferHolder + { +- final int mapSize; +- final int count; +- ArrayList buffer; ++ final int _size; ++ List _buffer; + +- public BufferInfo(int size, int cnt) ++ public BufferHolder(int size, int count) + { +- mapSize = size; +- count = cnt; +- buffer = new ArrayList<>(count); ++ _size = size; ++ _buffer = new ArrayList<>(count); ++ for (int i = 0; i < count; i++) ++ { ++ _buffer.add(new NodeBuffer(size)); ++ } + } + } +- +- public static GeoEnginePathfinding getInstance() +- { +- return SingletonHolder.INSTANCE; +- } +- +- private static class SingletonHolder +- { +- protected static final GeoEnginePathfinding INSTANCE = new GeoEnginePathfinding(); +- } +-} ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java (working copy) +@@ -0,0 +1,197 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++import java.io.IOException; ++ ++/** ++ * @author Hasha ++ */ ++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 height of cell, which is closest to given coordinates.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @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 getHeightNearestOriginal(int geoX, int geoY, int worldZ); ++ ++ /** ++ * Returns the height of cell, which is first above 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, above given coordinates. ++ */ ++ public abstract short getHeightAbove(int geoX, int geoY, int worldZ); ++ ++ /** ++ * Returns the height of cell, which is first below 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, below given coordinates. ++ */ ++ public abstract short getHeightBelow(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 the NSWE flag byte of cell, which is closest to given coordinates.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @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 getNsweNearestOriginal(int geoX, int geoY, int worldZ); ++ ++ /** ++ * Returns the NSWE flag byte of cell, which is first above 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 getNsweAbove(int geoX, int geoY, int worldZ); ++ ++ /** ++ * Returns the NSWE flag byte of cell, which is first below 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 getNsweBelow(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 above given coordinates.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @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 getIndexAboveOriginal(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 index to data of the cell, which is first layer below given coordinates.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @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 getIndexBelowOriginal(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 height of cell given by cell index.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @param index : Index of the cell. ++ * @return short : Cell geodata Z coordinate, below given coordinates. ++ */ ++ public abstract short getHeightOriginal(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); ++ ++ /** ++ * Returns the NSWE flag byte of cell given by cell index.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @param index : Index of the cell. ++ * @return short : Cell geodata Z coordinate, below given coordinates. ++ */ ++ public abstract byte getNsweOriginal(int index); ++ ++ /** ++ * Sets the NSWE flag byte of cell given by cell index. ++ * @param index : Index of the cell. ++ * @param nswe : New NSWE flag byte. ++ */ ++ public abstract void setNswe(int index, byte nswe); ++ ++ /** ++ * Saves the block in L2D format to {@link BufferedOutputStream}. Used only for L2D geodata conversion. ++ * @param stream : The stream. ++ * @throws IOException : Can't save the block to steam. ++ */ ++ public abstract void saveBlock(BufferedOutputStream stream) throws IOException; ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java (working copy) +@@ -0,0 +1,252 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++import java.io.IOException; ++import java.nio.ByteBuffer; ++ ++/** ++ * @author Hasha ++ */ ++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. ++ * @param format : GeoFormat specifying format of loaded data. ++ */ ++ public BlockComplex(ByteBuffer bb, GeoFormat format) ++ { ++ // initialize buffer ++ _buffer = new byte[GeoStructure.BLOCK_CELLS * 3]; ++ ++ // load data ++ for (int i = 0; i < GeoStructure.BLOCK_CELLS; i++) ++ { ++ if (format != GeoFormat.L2D) ++ { ++ // 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); ++ } ++ else ++ { ++ // get nswe ++ final byte nswe = bb.get(); ++ _buffer[i * 3] = nswe; ++ ++ // get height ++ final short height = bb.getShort(); ++ _buffer[(i * 3) + 1] = (byte) (height & 0x00FF); ++ _buffer[(i * 3) + 2] = (byte) (height >> 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 short getHeightNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getHeightNearest(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeightAbove(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 short height = (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ ++ // check and return height ++ return height > worldZ ? height : Short.MIN_VALUE; ++ } ++ ++ @Override ++ public short getHeightBelow(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 short height = (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ ++ // check and return height ++ return height < worldZ ? height : Short.MAX_VALUE; ++ } ++ ++ @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 byte getNsweNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getNsweNearest(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public byte getNsweAbove(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 ? _buffer[index] : 0; ++ } ++ ++ @Override ++ public byte getNsweBelow(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 ? _buffer[index] : 0; ++ } ++ ++ @Override ++ public 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 getIndexAboveOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexAbove(geoX, geoY, worldZ); ++ } ++ ++ @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 int getIndexBelowOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexBelow(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeight(int index) ++ { ++ return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ } ++ ++ @Override ++ public short getHeightOriginal(int index) ++ { ++ return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ } ++ ++ @Override ++ public byte getNswe(int index) ++ { ++ return _buffer[index]; ++ } ++ ++ @Override ++ public byte getNsweOriginal(int index) ++ { ++ return _buffer[index]; ++ } ++ ++ @Override ++ public void setNswe(int index, byte nswe) ++ { ++ _buffer[index] = nswe; ++ } ++ ++ @Override ++ public void saveBlock(BufferedOutputStream stream) throws IOException ++ { ++ // write block type ++ stream.write(GeoStructure.TYPE_COMPLEX_L2D); ++ ++ // write block data ++ stream.write(_buffer, 0, GeoStructure.BLOCK_CELLS * 3); ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java (working copy) +@@ -0,0 +1,176 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++import java.io.IOException; ++import java.nio.ByteBuffer; ++ ++/** ++ * @author Hasha ++ */ ++public class BlockFlat extends ABlock ++{ ++ protected final short _height; ++ protected byte _nswe; ++ ++ /** ++ * Creates FlatBlock. ++ * @param bb : Input byte buffer. ++ * @param format : GeoFormat specifying format of loaded data. ++ */ ++ public BlockFlat(ByteBuffer bb, GeoFormat format) ++ { ++ _height = bb.getShort(); ++ _nswe = format != GeoFormat.L2D ? 0x0F : (byte) (0xFF); ++ if (format == GeoFormat.L2OFF) ++ { ++ bb.getShort(); ++ } ++ } ++ ++ @Override ++ public boolean hasGeoPos() ++ { ++ return true; ++ } ++ ++ @Override ++ public short getHeightNearest(int geoX, int geoY, int worldZ) ++ { ++ return _height; ++ } ++ ++ @Override ++ public short getHeightNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return _height; ++ } ++ ++ @Override ++ public short getHeightAbove(int geoX, int geoY, int worldZ) ++ { ++ // check and return height ++ return _height > worldZ ? _height : Short.MIN_VALUE; ++ } ++ ++ @Override ++ public short getHeightBelow(int geoX, int geoY, int worldZ) ++ { ++ // check and return height ++ return _height < worldZ ? _height : Short.MAX_VALUE; ++ } ++ ++ @Override ++ public byte getNsweNearest(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweAbove(int geoX, int geoY, int worldZ) ++ { ++ // check height and return nswe ++ return _height > worldZ ? _nswe : 0; ++ } ++ ++ @Override ++ public byte getNsweBelow(int geoX, int geoY, int worldZ) ++ { ++ // check height and return nswe ++ return _height < worldZ ? _nswe : 0; ++ } ++ ++ @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 getIndexAboveOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexAbove(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public int getIndexBelow(int geoX, int geoY, int worldZ) ++ { ++ // check height and return index ++ return _height < worldZ ? 0 : -1; ++ } ++ ++ @Override ++ public int getIndexBelowOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexBelow(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeight(int index) ++ { ++ return _height; ++ } ++ ++ @Override ++ public short getHeightOriginal(int index) ++ { ++ return _height; ++ } ++ ++ @Override ++ public byte getNswe(int index) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweOriginal(int index) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public void setNswe(int index, byte nswe) ++ { ++ _nswe = nswe; ++ } ++ ++ @Override ++ public void saveBlock(BufferedOutputStream stream) throws IOException ++ { ++ // write block type ++ stream.write(GeoStructure.TYPE_FLAT_L2D); ++ ++ // write height ++ stream.write((byte) (_height & 0x00FF)); ++ stream.write((byte) (_height >> 8)); ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java (working copy) +@@ -0,0 +1,465 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++import java.io.IOException; ++import java.nio.ByteBuffer; ++import java.nio.ByteOrder; ++import java.util.Arrays; ++ ++/** ++ * @author Hasha ++ */ ++public class BlockMultilayer extends ABlock ++{ ++ private static final int MAX_LAYERS = Byte.MAX_VALUE; ++ ++ private static ByteBuffer _temp; ++ ++ /** ++ * Initializes the temporarily buffer. ++ */ ++ public static void initialize() ++ { ++ // initialize temporarily buffer and sorting mechanism ++ _temp = ByteBuffer.allocate(GeoStructure.BLOCK_CELLS * MAX_LAYERS * 3); ++ _temp.order(ByteOrder.LITTLE_ENDIAN); ++ } ++ ++ /** ++ * Releases temporarily buffer. ++ */ ++ public static void release() ++ { ++ _temp = null; ++ } ++ ++ protected byte[] _buffer; ++ ++ /** ++ * Implicit constructor for children class. ++ */ ++ protected BlockMultilayer() ++ { ++ _buffer = null; ++ } ++ ++ /** ++ * Creates MultilayerBlock. ++ * @param bb : Input byte buffer. ++ * @param format : GeoFormat specifying format of loaded data. ++ */ ++ public BlockMultilayer(ByteBuffer bb, GeoFormat format) ++ { ++ // 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 = format != GeoFormat.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++) ++ { ++ if (format != GeoFormat.L2D) ++ { ++ // get data ++ final short data = bb.getShort(); ++ ++ // add nswe and height ++ _temp.put((byte) (data & 0x000F)); ++ _temp.putShort((short) ((short) (data & 0xFFF0) >> 1)); ++ } ++ else ++ { ++ // add nswe ++ _temp.put(bb.get()); ++ ++ // add height ++ _temp.putShort(bb.getShort()); ++ } ++ } ++ } ++ ++ // 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 short getHeightNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getHeightNearest(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeightAbove(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 height ++ if (height > worldZ) ++ { ++ return (short) height; ++ } ++ ++ // move index to next layer ++ index -= 3; ++ } ++ ++ // none layer found, return minimum value ++ return Short.MIN_VALUE; ++ } ++ ++ @Override ++ public short getHeightBelow(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 height ++ if (height < worldZ) ++ { ++ return (short) height; ++ } ++ ++ // move index to next layer ++ index += 3; ++ } ++ ++ // none layer found, return maximum value ++ return Short.MAX_VALUE; ++ } ++ ++ @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 byte getNsweNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getNsweNearest(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public byte getNsweAbove(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 nswe ++ if (height > worldZ) ++ { ++ return _buffer[index]; ++ } ++ ++ // move index to next layer ++ index -= 3; ++ } ++ ++ // none layer found, block movement ++ return 0; ++ } ++ ++ @Override ++ public byte getNsweBelow(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 nswe ++ if (height < worldZ) ++ { ++ return _buffer[index]; ++ } ++ ++ // move index to next layer ++ index += 3; ++ } ++ ++ // none layer found, block movement ++ return 0; ++ } ++ ++ @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 first layer data (first from bottom) ++ byte layers = _buffer[index++]; ++ ++ // loop though all cell layers, find closest layer ++ 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; ++ } ++ ++ // none layer found ++ return -1; ++ } ++ ++ @Override ++ public int getIndexAboveOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexAbove(geoX, geoY, worldZ); ++ } ++ ++ @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; ++ } ++ ++ // none layer found ++ return -1; ++ } ++ ++ @Override ++ public int getIndexBelowOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexBelow(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeight(int index) ++ { ++ // get height ++ return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ } ++ ++ @Override ++ public short getHeightOriginal(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]; ++ } ++ ++ @Override ++ public byte getNsweOriginal(int index) ++ { ++ // get nswe ++ return _buffer[index]; ++ } ++ ++ @Override ++ public void setNswe(int index, byte nswe) ++ { ++ // set nswe ++ _buffer[index] = nswe; ++ } ++ ++ @Override ++ public void saveBlock(BufferedOutputStream stream) throws IOException ++ { ++ // write block type ++ stream.write(GeoStructure.TYPE_MULTILAYER_L2D); ++ ++ // for each cell ++ int index = 0; ++ for (int i = 0; i < GeoStructure.BLOCK_CELLS; i++) ++ { ++ // write layers count ++ final byte layers = _buffer[index++]; ++ stream.write(layers); ++ ++ // write cell data ++ stream.write(_buffer, index, layers * 3); ++ ++ // move index to next cell ++ index += layers * 3; ++ } ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java (working copy) +@@ -0,0 +1,150 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++ ++/** ++ * @author Hasha ++ */ ++public class BlockNull extends ABlock ++{ ++ private final byte _nswe; ++ ++ public BlockNull() ++ { ++ _nswe = (byte) 0xFF; ++ } ++ ++ @Override ++ public boolean hasGeoPos() ++ { ++ return false; ++ } ++ ++ @Override ++ public short getHeightNearest(int geoX, int geoY, int worldZ) ++ { ++ return (short) worldZ; ++ } ++ ++ @Override ++ public short getHeightNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return (short) worldZ; ++ } ++ ++ @Override ++ public short getHeightAbove(int geoX, int geoY, int worldZ) ++ { ++ return (short) worldZ; ++ } ++ ++ @Override ++ public short getHeightBelow(int geoX, int geoY, int worldZ) ++ { ++ return (short) worldZ; ++ } ++ ++ @Override ++ public byte getNsweNearest(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweAbove(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweBelow(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) ++ { ++ return 0; ++ } ++ ++ @Override ++ public int getIndexAboveOriginal(int geoX, int geoY, int worldZ) ++ { ++ return 0; ++ } ++ ++ @Override ++ public int getIndexBelow(int geoX, int geoY, int worldZ) ++ { ++ return 0; ++ } ++ ++ @Override ++ public int getIndexBelowOriginal(int geoX, int geoY, int worldZ) ++ { ++ return 0; ++ } ++ ++ @Override ++ public short getHeight(int index) ++ { ++ return 0; ++ } ++ ++ @Override ++ public short getHeightOriginal(int index) ++ { ++ return 0; ++ } ++ ++ @Override ++ public byte getNswe(int index) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweOriginal(int index) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public void setNswe(int index, byte nswe) ++ { ++ } ++ ++ @Override ++ public void saveBlock(BufferedOutputStream stream) ++ { ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java (nonexistent) +@@ -1,48 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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() +- { +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/ComplexBlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/ComplexBlock.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/ComplexBlock.java (nonexistent) +@@ -1,77 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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; +- +-/** +- * @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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/FlatBlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/FlatBlock.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/FlatBlock.java (nonexistent) +@@ -1,56 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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; +- +-/** +- * @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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/GeoFormat.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/GeoFormat.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/GeoFormat.java (working copy) +@@ -0,0 +1,39 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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 Hasha ++ */ ++public enum GeoFormat ++{ ++ L2J("%d_%d.l2j"), ++ L2OFF("%d_%d_conv.dat"), ++ L2D("%d_%d.l2d"); ++ ++ private final String _filename; ++ ++ private GeoFormat(String filename) ++ { ++ _filename = filename; ++ } ++ ++ public String getFilename() ++ { ++ return _filename; ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/GeoLocation.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/GeoLocation.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/GeoLocation.java (working copy) +@@ -0,0 +1,67 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.geoengine.GeoEngine; ++import org.l2jmobius.gameserver.model.Location; ++ ++/** ++ * @author Hasha ++ */ ++public class GeoLocation extends Location ++{ ++ private byte _nswe; ++ ++ public GeoLocation(int x, int y, int z) ++ { ++ super(x, y, GeoEngine.getInstance().getHeightNearest(x, y, z)); ++ _nswe = GeoEngine.getInstance().getNsweNearest(x, y, z); ++ } ++ ++ public void set(int x, int y, short z) ++ { ++ super.setXYZ(x, y, GeoEngine.getInstance().getHeightNearest(x, y, z)); ++ _nswe = GeoEngine.getInstance().getNsweNearest(x, y, z); ++ } ++ ++ public int getGeoX() ++ { ++ return _x; ++ } ++ ++ public int getGeoY() ++ { ++ return _y; ++ } ++ ++ @Override ++ public int getX() ++ { ++ return GeoEngine.getWorldX(_x); ++ } ++ ++ @Override ++ public int getY() ++ { ++ return GeoEngine.getWorldY(_y); ++ } ++ ++ public byte getNSWE() ++ { ++ return _nswe; ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java (working copy) +@@ -0,0 +1,70 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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; ++ ++/** ++ * @author Hasha ++ */ ++public class GeoStructure ++{ ++ // cells ++ public static final byte CELL_FLAG_E = 1 << 0; ++ public static final byte CELL_FLAG_W = 1 << 1; ++ public static final byte CELL_FLAG_S = 1 << 2; ++ public static final byte CELL_FLAG_N = 1 << 3; ++ public static final byte CELL_FLAG_SE = 1 << 4; ++ public static final byte CELL_FLAG_SW = 1 << 5; ++ public static final byte CELL_FLAG_NE = 1 << 6; ++ public static final byte CELL_FLAG_NW = (byte) (1 << 7); ++ ++ public static final int CELL_HEIGHT = 8; ++ public static final int CELL_IGNORE_HEIGHT = CELL_HEIGHT * 6; ++ ++ // blocks ++ public static final byte TYPE_FLAT_L2J_L2OFF = 0; ++ public static final byte TYPE_FLAT_L2D = (byte) 0xD0; ++ public static final byte TYPE_COMPLEX_L2J = 1; ++ public static final byte TYPE_COMPLEX_L2OFF = 0x40; ++ public static final byte TYPE_COMPLEX_L2D = (byte) 0xD1; ++ 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) ++ public static final byte TYPE_MULTILAYER_L2D = (byte) 0xD2; ++ ++ 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; ++ ++ // regions ++ 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; ++ ++ // global geodata ++ private static final int GEO_REGIONS_X = ((World.TILE_X_MAX - World.TILE_X_MIN) + 1); ++ private 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 +Index: java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java (nonexistent) +@@ -1,42 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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); +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/IGeoObject.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/IGeoObject.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/IGeoObject.java (working copy) +@@ -0,0 +1,53 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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 Hasha ++ */ ++public interface IGeoObject ++{ ++ /** ++ * Returns geodata X coordinate of the {@link IGeoObject}. ++ * @return int : Geodata X coordinate. ++ */ ++ int getGeoX(); ++ ++ /** ++ * Returns geodata Y coordinate of the {@link IGeoObject}. ++ * @return int : Geodata Y coordinate. ++ */ ++ int getGeoY(); ++ ++ /** ++ * Returns geodata Z coordinate of the {@link IGeoObject}. ++ * @return int : Geodata Z coordinate. ++ */ ++ int getGeoZ(); ++ ++ /** ++ * Returns height of the {@link IGeoObject}. ++ * @return int : Height. ++ */ ++ int getHeight(); ++ ++ /** ++ * Returns {@link IGeoObject} data. ++ * @return byte[][] : {@link IGeoObject} data. ++ */ ++ byte[][] getObjectGeoData(); ++} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java (nonexistent) +@@ -1,47 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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(); +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/MultilayerBlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/MultilayerBlock.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/MultilayerBlock.java (nonexistent) +@@ -1,183 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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; +- +-/** +- * @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); +- +- // 1 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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/NullRegion.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/NullRegion.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/NullRegion.java (nonexistent) +@@ -1,55 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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 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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/Region.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/Region.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/Region.java (nonexistent) +@@ -1,92 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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; +- +-/** +- * @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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java (nonexistent) +@@ -1,87 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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 T _loc; +- private AbstractNode _parent; +- +- public AbstractNode(T loc) +- { +- _loc = loc; +- } +- +- public void setParent(AbstractNode p) +- { +- _parent = p; +- } +- +- public AbstractNode getParent() +- { +- return _parent; +- } +- +- public T getLoc() +- { +- return _loc; +- } +- +- public void setLoc(T l) +- { +- _loc = l; +- } +- +- @Override +- public int hashCode() +- { +- final int prime = 31; +- int result = 1; +- result = (prime * result) + ((_loc == null) ? 0 : _loc.hashCode()); +- return result; +- } +- +- @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 +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java (nonexistent) +@@ -1,33 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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 int getNodeX(); +- +- public abstract int getNodeY(); +-} +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNode.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNode.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNode.java (nonexistent) +@@ -1,67 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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 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 +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNodeBuffer.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNodeBuffer.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNodeBuffer.java (nonexistent) +@@ -1,348 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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.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 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) +- { +- _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(); +- } +- +- 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 void getNeighbors() +- { +- if (!_current.getLoc().canGoAll()) +- { +- 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); +- } +- +- // SouthEast +- if ((nodeE != null) && (nodeS != null)) +- { +- if (nodeE.getLoc().canGoSouth() && nodeS.getLoc().canGoEast()) +- { +- addNode(x + 1, y + 1, z, true); +- } +- } +- +- // SouthWest +- if ((nodeS != null) && (nodeW != null)) +- { +- if (nodeW.getLoc().canGoSouth() && nodeS.getLoc().canGoWest()) +- { +- addNode(x - 1, y + 1, z, true); +- } +- } +- +- // NorthEast +- if ((nodeN != null) && (nodeE != null)) +- { +- if (nodeE.getLoc().canGoNorth() && nodeN.getLoc().canGoEast()) +- { +- addNode(x + 1, y - 1, z, true); +- } +- } +- +- // NorthWest +- if ((nodeN != null) && (nodeW != null)) +- { +- if (nodeW.getLoc().canGoNorth() && nodeN.getLoc().canGoWest()) +- { +- addNode(x - 1, y - 1, z, true); +- } +- } +- } +- +- private 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(); +- // reinit node if needed +- if (result.getLoc() != null) +- { +- result.getLoc().set(x, y, z); +- } +- else +- { +- result.setLoc(new NodeLoc(x, y, z)); +- } +- } +- +- return result; +- } +- +- private 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 boolean isHighWeight(int x, int y, int z) +- { +- final CellNode result = getNode(x, y, z); +- if (result == null) +- { +- return true; +- } +- +- if (!result.getLoc().canGoAll()) +- { +- return true; +- } +- if (Math.abs(result.getLoc().getZ() - z) > 16) +- { +- return true; +- } +- +- return false; +- } +- +- private 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 +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java (working copy) +@@ -0,0 +1,87 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.geodata.GeoLocation; ++ ++/** ++ * @author Hasha ++ */ ++public class Node ++{ ++ // node coords and nswe flag ++ private GeoLocation _loc; ++ ++ // node parent (for reverse path construction) ++ private Node _parent; ++ // node child (for moving over nodes during iteration) ++ private Node _child; ++ ++ // node G cost (movement cost = parent movement cost + current movement cost) ++ private double _cost = -1000; ++ ++ public void setLoc(int x, int y, int z) ++ { ++ _loc = new GeoLocation(x, y, z); ++ } ++ ++ public GeoLocation getLoc() ++ { ++ return _loc; ++ } ++ ++ public void setParent(Node parent) ++ { ++ _parent = parent; ++ } ++ ++ public Node getParent() ++ { ++ return _parent; ++ } ++ ++ public void setChild(Node child) ++ { ++ _child = child; ++ } ++ ++ public Node getChild() ++ { ++ return _child; ++ } ++ ++ public void setCost(double cost) ++ { ++ _cost = cost; ++ } ++ ++ public double getCost() ++ { ++ return _cost; ++ } ++ ++ public void free() ++ { ++ // reset node location ++ _loc = null; ++ ++ // reset node parent, child and cost ++ _parent = null; ++ _child = null; ++ _cost = -1000; ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java (working copy) +@@ -0,0 +1,306 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.concurrent.locks.ReentrantLock; ++ ++import org.l2jmobius.Config; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; ++ ++/** ++ * @author DS, Hasha; Credits to Diamond ++ */ ++public class NodeBuffer ++{ ++ private final ReentrantLock _lock = new ReentrantLock(); ++ private final int _size; ++ private final Node[][] _buffer; ++ ++ // center coordinates ++ private int _cx = 0; ++ private int _cy = 0; ++ ++ // target coordinates ++ private int _gtx = 0; ++ private int _gty = 0; ++ private short _gtz = 0; ++ ++ private Node _current = null; ++ ++ /** ++ * Constructor of NodeBuffer. ++ * @param size : one dimension size of buffer ++ */ ++ public NodeBuffer(int size) ++ { ++ // set size ++ _size = size; ++ ++ // initialize buffer ++ _buffer = new Node[size][size]; ++ for (int x = 0; x < size; x++) ++ { ++ for (int y = 0; y < size; y++) ++ { ++ _buffer[x][y] = 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 Node : first node of path ++ */ ++ public Node findPath(int gox, int goy, short goz, int gtx, int gty, short gtz) ++ { ++ // set coordinates (middle of the line (gox,goy) - (gtx,gty), will be in the center of the buffer) ++ _cx = gox + ((gtx - gox - _size) / 2); ++ _cy = goy + ((gty - goy - _size) / 2); ++ ++ _gtx = gtx; ++ _gty = gty; ++ _gtz = gtz; ++ ++ _current = getNode(gox, goy, goz); ++ _current.setCost(getCostH(gox, goy, goz)); ++ ++ int count = 0; ++ do ++ { ++ // reached target? ++ if ((_current.getLoc().getGeoX() == _gtx) && (_current.getLoc().getGeoY() == _gty) && (Math.abs(_current.getLoc().getZ() - _gtz) < 8)) ++ { ++ return _current; ++ } ++ ++ // expand current node ++ expand(); ++ ++ // move pointer ++ _current = _current.getChild(); ++ } ++ while ((_current != null) && (count++ < Config.MAX_ITERATIONS)); ++ ++ return null; ++ } ++ ++ public boolean isLocked() ++ { ++ return _lock.tryLock(); ++ } ++ ++ public void free() ++ { ++ _current = null; ++ ++ for (Node[] nodes : _buffer) ++ { ++ for (Node node : nodes) ++ { ++ if (node.getLoc() != null) ++ { ++ node.free(); ++ } ++ } ++ } ++ ++ _lock.unlock(); ++ } ++ ++ /** ++ * Check _current Node and add its neighbors to the buffer. ++ */ ++ private final void expand() ++ { ++ // can't move anywhere, don't expand ++ final byte nswe = _current.getLoc().getNSWE(); ++ if (nswe == 0) ++ { ++ return; ++ } ++ ++ // get geo coords of the node to be expanded ++ final int x = _current.getLoc().getGeoX(); ++ final int y = _current.getLoc().getGeoY(); ++ final short z = (short) _current.getLoc().getZ(); ++ ++ // can move north, expand ++ if ((nswe & GeoStructure.CELL_FLAG_N) != 0) ++ { ++ addNode(x, y - 1, z, Config.BASE_WEIGHT); ++ } ++ ++ // can move south, expand ++ if ((nswe & GeoStructure.CELL_FLAG_S) != 0) ++ { ++ addNode(x, y + 1, z, Config.BASE_WEIGHT); ++ } ++ ++ // can move west, expand ++ if ((nswe & GeoStructure.CELL_FLAG_W) != 0) ++ { ++ addNode(x - 1, y, z, Config.BASE_WEIGHT); ++ } ++ ++ // can move east, expand ++ if ((nswe & GeoStructure.CELL_FLAG_E) != 0) ++ { ++ addNode(x + 1, y, z, Config.BASE_WEIGHT); ++ } ++ ++ // can move north-west, expand ++ if ((nswe & GeoStructure.CELL_FLAG_NW) != 0) ++ { ++ addNode(x - 1, y - 1, z, Config.DIAGONAL_WEIGHT); ++ } ++ ++ // can move north-east, expand ++ if ((nswe & GeoStructure.CELL_FLAG_NE) != 0) ++ { ++ addNode(x + 1, y - 1, z, Config.DIAGONAL_WEIGHT); ++ } ++ ++ // can move south-west, expand ++ if ((nswe & GeoStructure.CELL_FLAG_SW) != 0) ++ { ++ addNode(x - 1, y + 1, z, Config.DIAGONAL_WEIGHT); ++ } ++ ++ // can move south-east, expand ++ if ((nswe & GeoStructure.CELL_FLAG_SE) != 0) ++ { ++ addNode(x + 1, y + 1, z, Config.DIAGONAL_WEIGHT); ++ } ++ } ++ ++ /** ++ * Returns node, if it exists in buffer. ++ * @param x : node X coord ++ * @param y : node Y coord ++ * @param z : node Z coord ++ * @return Node : node, if exits in buffer ++ */ ++ private final Node getNode(int x, int y, short z) ++ { ++ // check node X out of coordinates ++ final int ix = x - _cx; ++ if ((ix < 0) || (ix >= _size)) ++ { ++ return null; ++ } ++ ++ // check node Y out of coordinates ++ final int iy = y - _cy; ++ if ((iy < 0) || (iy >= _size)) ++ { ++ return null; ++ } ++ ++ // get node ++ final Node result = _buffer[ix][iy]; ++ ++ // check and update ++ if (result.getLoc() == null) ++ { ++ result.setLoc(x, y, z); ++ } ++ ++ // return node ++ return result; ++ } ++ ++ /** ++ * Add node given by coordinates to the buffer. ++ * @param x : geo X coord ++ * @param y : geo Y coord ++ * @param z : geo Z coord ++ * @param weight : weight of movement to new node ++ */ ++ private final void addNode(int x, int y, short z, int weight) ++ { ++ // get node to be expanded ++ final Node node = getNode(x, y, z); ++ if (node == null) ++ { ++ return; ++ } ++ ++ // Z distance between nearby cells is higher than cell size ++ if (node.getLoc().getZ() > (z + (2 * GeoStructure.CELL_HEIGHT))) ++ { ++ return; ++ } ++ ++ // node was already expanded, return ++ if (node.getCost() >= 0) ++ { ++ return; ++ } ++ ++ node.setParent(_current); ++ if (node.getLoc().getNSWE() != (byte) 0xFF) ++ { ++ node.setCost(getCostH(x, y, node.getLoc().getZ()) + (weight * Config.OBSTACLE_MULTIPLIER)); ++ } ++ else ++ { ++ node.setCost(getCostH(x, y, node.getLoc().getZ()) + weight); ++ } ++ ++ Node current = _current; ++ int count = 0; ++ while ((current.getChild() != null) && (count < (Config.MAX_ITERATIONS * 4))) ++ { ++ count++; ++ if (current.getChild().getCost() > node.getCost()) ++ { ++ node.setChild(current.getChild()); ++ break; ++ } ++ current = current.getChild(); ++ } ++ ++ if (count >= (Config.MAX_ITERATIONS * 4)) ++ { ++ System.err.println("Pathfinding: too long loop detected, cost:" + node.getCost()); ++ } ++ ++ current.setChild(node); ++ } ++ ++ /** ++ * @param x : node X coord ++ * @param y : node Y coord ++ * @param i : node Z coord ++ * @return double : node cost ++ */ ++ private final double getCostH(int x, int y, int i) ++ { ++ final int dX = x - _gtx; ++ final int dY = y - _gty; ++ final int dZ = (i - _gtz) / GeoStructure.CELL_HEIGHT; ++ ++ // return (Math.abs(dX) + Math.abs(dY) + Math.abs(dZ)) * Config.HEURISTIC_WEIGHT; // Manhattan distance ++ return Math.sqrt((dX * dX) + (dY * dY) + (dZ * dZ)) * Config.HEURISTIC_WEIGHT; // Direct distance ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeLoc.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeLoc.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeLoc.java (nonexistent) +@@ -1,183 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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.Cell; +- +-/** +- * @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 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 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; +- // return super.hashCode(); +- } +- +- @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; +- if (_x != other._x) +- { +- return false; +- } +- if (_y != other._y) +- { +- return false; +- } +- if (_goNorth != other._goNorth) +- { +- return false; +- } +- if (_goEast != other._goEast) +- { +- return false; +- } +- if (_goSouth != other._goSouth) +- { +- return false; +- } +- if (_goWest != other._goWest) +- { +- return false; +- } +- if (_geoHeight != other._geoHeight) +- { +- return false; +- } +- return true; +- } +-} +Index: java/org/l2jmobius/gameserver/model/actor/Creature.java +=================================================================== +--- java/org/l2jmobius/gameserver/model/actor/Creature.java (revision 8399) ++++ java/org/l2jmobius/gameserver/model/actor/Creature.java (working copy) +@@ -66,8 +66,6 @@ + import org.l2jmobius.gameserver.enums.TeleportWhereType; + import org.l2jmobius.gameserver.enums.UserInfoType; + import org.l2jmobius.gameserver.geoengine.GeoEngine; +-import org.l2jmobius.gameserver.geoengine.GeoEnginePathfinding; +-import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; + import org.l2jmobius.gameserver.instancemanager.IdManager; + import org.l2jmobius.gameserver.instancemanager.MapRegionManager; + import org.l2jmobius.gameserver.instancemanager.QuestManager; +@@ -2577,7 +2575,7 @@ + + public boolean disregardingGeodata; + public int onGeodataPathIndex; +- public List geoPath; ++ public List geoPath; + public int geoPathAccurateTx; + public int geoPathAccurateTy; + public int geoPathGtx; +@@ -3466,7 +3464,7 @@ + if (((originalDistance - distance) > 30) && !isControlBlocked() && !isInVehicle) + { + // Path calculation -- overrides previous movement check +- m.geoPath = GeoEnginePathfinding.getInstance().findPath(curX, curY, curZ, originalX, originalY, originalZ, getInstanceWorld()); ++ m.geoPath = GeoEngine.getInstance().findPath(curX, curY, curZ, originalX, originalY, originalZ, getInstanceWorld()); + if ((m.geoPath == null) || (m.geoPath.size() < 2)) // No path found + { + if (isPlayer() && !_isFlying && !isInWater) +Index: java/org/l2jmobius/gameserver/model/actor/instance/PlayerInstance.java +=================================================================== +--- java/org/l2jmobius/gameserver/model/actor/instance/PlayerInstance.java (revision 8397) ++++ java/org/l2jmobius/gameserver/model/actor/instance/PlayerInstance.java (working copy) +@@ -12704,7 +12704,7 @@ + { + if (Config.CORRECT_PLAYER_Z) + { +- final int nearestZ = GeoEngine.getInstance().getHigherHeight(getX(), getY(), getZ()); ++ final int nearestZ = GeoEngine.getInstance().getHeightNearest(getX(), getY(), getZ()); + if (getZ() < nearestZ) + { + teleToLocation(new Location(getX(), getY(), nearestZ)); +Index: java/org/l2jmobius/gameserver/util/GeoUtils.java +=================================================================== +--- java/org/l2jmobius/gameserver/util/GeoUtils.java (revision 8397) ++++ java/org/l2jmobius/gameserver/util/GeoUtils.java (working copy) +@@ -19,7 +19,7 @@ + import java.awt.Color; + + import org.l2jmobius.gameserver.geoengine.GeoEngine; +-import org.l2jmobius.gameserver.geoengine.geodata.Cell; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; + import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance; + import org.l2jmobius.gameserver.network.serverpackets.ExServerPrimitive; + +@@ -26,25 +26,25 @@ + /** + * @author HorridoJoho + */ +-public final class GeoUtils ++public class GeoUtils + { + public static void debug2DLine(PlayerInstance player, int x, int y, int tx, int ty, int z) + { +- final int gx = GeoEngine.getInstance().getGeoX(x); +- final int gy = GeoEngine.getInstance().getGeoY(y); ++ final int gx = GeoEngine.getGeoX(x); ++ final int gy = GeoEngine.getGeoY(y); + +- final int tgx = GeoEngine.getInstance().getGeoX(tx); +- final int tgy = GeoEngine.getInstance().getGeoY(ty); ++ final int tgx = GeoEngine.getGeoX(tx); ++ final int tgy = GeoEngine.getGeoY(ty); + + final ExServerPrimitive prim = new ExServerPrimitive("Debug2DLine", x, y, z); +- prim.addLine(Color.BLUE, GeoEngine.getInstance().getWorldX(gx), GeoEngine.getInstance().getWorldY(gy), z, GeoEngine.getInstance().getWorldX(tgx), GeoEngine.getInstance().getWorldY(tgy), z); ++ prim.addLine(Color.BLUE, GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), z, GeoEngine.getWorldX(tgx), GeoEngine.getWorldY(tgy), z); + + final LinePointIterator iter = new LinePointIterator(gx, gy, tgx, tgy); + + while (iter.next()) + { +- final int wx = GeoEngine.getInstance().getWorldX(iter.x()); +- final int wy = GeoEngine.getInstance().getWorldY(iter.y()); ++ final int wx = GeoEngine.getWorldX(iter.x()); ++ final int wy = GeoEngine.getWorldY(iter.y()); + + prim.addPoint(Color.RED, wx, wy, z); + } +@@ -53,21 +53,21 @@ + + public static void debug3DLine(PlayerInstance player, int x, int y, int z, int tx, int ty, int tz) + { +- final int gx = GeoEngine.getInstance().getGeoX(x); +- final int gy = GeoEngine.getInstance().getGeoY(y); ++ final int gx = GeoEngine.getGeoX(x); ++ final int gy = GeoEngine.getGeoY(y); + +- final int tgx = GeoEngine.getInstance().getGeoX(tx); +- final int tgy = GeoEngine.getInstance().getGeoY(ty); ++ final int tgx = GeoEngine.getGeoX(tx); ++ final int tgy = GeoEngine.getGeoY(ty); + + final ExServerPrimitive prim = new ExServerPrimitive("Debug3DLine", x, y, z); +- prim.addLine(Color.BLUE, GeoEngine.getInstance().getWorldX(gx), GeoEngine.getInstance().getWorldY(gy), z, GeoEngine.getInstance().getWorldX(tgx), GeoEngine.getInstance().getWorldY(tgy), tz); ++ prim.addLine(Color.BLUE, GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), z, GeoEngine.getWorldX(tgx), GeoEngine.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.getInstance().getWorldX(prevX); +- int wy = GeoEngine.getInstance().getWorldY(prevY); ++ int wx = GeoEngine.getWorldX(prevX); ++ int wy = GeoEngine.getWorldY(prevY); + int wz = iter.z(); + prim.addPoint(Color.RED, wx, wy, wz); + +@@ -78,8 +78,8 @@ + + if ((curX != prevX) || (curY != prevY)) + { +- wx = GeoEngine.getInstance().getWorldX(curX); +- wy = GeoEngine.getInstance().getWorldY(curY); ++ wx = GeoEngine.getWorldX(curX); ++ wy = GeoEngine.getWorldY(curY); + wz = iter.z(); + + prim.addPoint(Color.RED, wx, wy, wz); +@@ -93,7 +93,7 @@ + + private static Color getDirectionColor(int x, int y, int z, int nswe) + { +- if (GeoEngine.getInstance().checkNearestNswe(x, y, z, nswe)) ++ if ((GeoEngine.getInstance().getNsweNearest(x, y, z) & nswe) == nswe) + { + return Color.GREEN; + } +@@ -109,9 +109,8 @@ + int iPacket = 0; + + ExServerPrimitive exsp = null; +- final GeoEngine ge = GeoEngine.getInstance(); +- final int playerGx = ge.getGeoX(player.getX()); +- final int playerGy = ge.getGeoY(player.getY()); ++ final int playerGx = GeoEngine.getGeoX(player.getX()); ++ final int playerGy = GeoEngine.getGeoY(player.getY()); + for (int dx = -geoRadius; dx <= geoRadius; ++dx) + { + for (int dy = -geoRadius; dy <= geoRadius; ++dy) +@@ -135,12 +134,12 @@ + final int gx = playerGx + dx; + final int gy = playerGy + dy; + +- final int x = ge.getWorldX(gx); +- final int y = ge.getWorldY(gy); +- final int z = ge.getNearestZ(gx, gy, player.getZ()); ++ final int x = GeoEngine.getWorldX(gx); ++ final int y = GeoEngine.getWorldY(gy); ++ final int z = GeoEngine.getInstance().getHeightNearest(gx, gy, player.getZ()); + + // north arrow +- Color col = getDirectionColor(gx, gy, z, Cell.NSWE_NORTH); ++ Color col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_N); + 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); +@@ -147,7 +146,7 @@ + exsp.addLine(col, x - 4, y - 4, z, x + 4, y - 4, z); + + // east arrow +- col = getDirectionColor(gx, gy, z, Cell.NSWE_EAST); ++ col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_E); + 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); +@@ -154,13 +153,13 @@ + exsp.addLine(col, x + 4, y - 4, z, x + 4, y + 4, z); + + // south arrow +- col = getDirectionColor(gx, gy, z, Cell.NSWE_SOUTH); ++ col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_S); + 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, Cell.NSWE_WEST); ++ col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_W); + 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); +@@ -188,15 +187,15 @@ + { + if (y > lastY) + { +- return Cell.NSWE_SOUTH_EAST; ++ return GeoStructure.CELL_FLAG_SE; // Direction.SOUTH_EAST; + } + else if (y < lastY) + { +- return Cell.NSWE_NORTH_EAST; ++ return GeoStructure.CELL_FLAG_NE; // Direction.NORTH_EAST; + } + else + { +- return Cell.NSWE_EAST; ++ return GeoStructure.CELL_FLAG_E; // Direction.EAST; + } + } + else if (x < lastX) // west +@@ -203,26 +202,27 @@ + { + if (y > lastY) + { +- return Cell.NSWE_SOUTH_WEST; ++ return GeoStructure.CELL_FLAG_SW; // Direction.SOUTH_WEST; + } + else if (y < lastY) + { +- return Cell.NSWE_NORTH_WEST; ++ return GeoStructure.CELL_FLAG_NW; // Direction.NORTH_WEST; + } + else + { +- return Cell.NSWE_WEST; ++ return GeoStructure.CELL_FLAG_W; // Direction.WEST; + } + } +- else // unchanged x ++ else ++ // unchanged x + { + if (y > lastY) + { +- return Cell.NSWE_SOUTH; ++ return GeoStructure.CELL_FLAG_S; // Direction.SOUTH; + } + else if (y < lastY) + { +- return Cell.NSWE_NORTH; ++ return GeoStructure.CELL_FLAG_N; // Direction.NORTH; + } + else + { +Index: java/org/l2jmobius/tools/geodataconverter/GeoDataConverter.java +=================================================================== +--- java/org/l2jmobius/tools/geodataconverter/GeoDataConverter.java (nonexistent) ++++ java/org/l2jmobius/tools/geodataconverter/GeoDataConverter.java (working copy) +@@ -0,0 +1,386 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ */ ++package org.l2jmobius.tools.geodataconverter; ++ ++import java.io.BufferedOutputStream; ++import java.io.File; ++import java.io.FileOutputStream; ++import java.io.RandomAccessFile; ++import java.nio.ByteOrder; ++import java.nio.MappedByteBuffer; ++import java.nio.channels.FileChannel; ++import java.util.Scanner; ++ ++import org.l2jmobius.Config; ++import org.l2jmobius.commons.util.PropertiesParser; ++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.GeoFormat; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; ++import org.l2jmobius.gameserver.model.World; ++ ++/** ++ * @author Hasha ++ */ ++public class GeoDataConverter ++{ ++ private static GeoFormat _format; ++ private static ABlock[][] _blocks; ++ ++ public static void main(String[] args) ++ { ++ // initialize config ++ loadGeoengineConfigs(); ++ ++ // get geodata format ++ String type = ""; ++ try (Scanner scn = new Scanner(System.in)) ++ { ++ while (!(type.equalsIgnoreCase("J") || type.equalsIgnoreCase("O") || type.equalsIgnoreCase("E"))) ++ { ++ System.out.println("GeoDataConverter: Select source geodata type:"); ++ System.out.println(" J: L2J (e.g. 23_22.l2j)"); ++ System.out.println(" O: L2OFF (e.g. 23_22_conv.dat)"); ++ System.out.println(" E: Exit"); ++ System.out.print("Choice: "); ++ type = scn.next(); ++ } ++ } ++ if (type.equalsIgnoreCase("E")) ++ { ++ System.exit(0); ++ } ++ ++ _format = type.equalsIgnoreCase("J") ? GeoFormat.L2J : GeoFormat.L2OFF; ++ ++ // start conversion ++ System.out.println("GeoDataConverter: Converting all " + _format + " files."); ++ ++ // initialize geodata container ++ _blocks = new ABlock[GeoStructure.REGION_BLOCKS_X][GeoStructure.REGION_BLOCKS_Y]; ++ ++ // initialize multilayer temporarily buffer ++ BlockMultilayer.initialize(); ++ ++ // load geo files ++ int converted = 0; ++ for (int rx = World.TILE_X_MIN; rx <= World.TILE_X_MAX; rx++) ++ { ++ for (int ry = World.TILE_Y_MIN; ry <= World.TILE_Y_MAX; ry++) ++ { ++ final String input = String.format(_format.getFilename(), rx, ry); ++ final String filepath = Config.GEODATA_PATH; ++ final File f = new File(filepath + input); ++ if (f.exists() && !f.isDirectory()) ++ { ++ // load geodata ++ if (!loadGeoBlocks(input)) ++ { ++ System.out.println("GeoDataConverter: Unable to load " + input + " region file."); ++ continue; ++ } ++ ++ // recalculate nswe ++ if (!recalculateNswe()) ++ { ++ System.out.println("GeoDataConverter: Unable to convert " + input + " region file."); ++ continue; ++ } ++ ++ // save geodata ++ final String output = String.format(GeoFormat.L2D.getFilename(), rx, ry); ++ if (!saveGeoBlocks(output)) ++ { ++ System.out.println("GeoDataConverter: Unable to save " + output + " region file."); ++ continue; ++ } ++ ++ converted++; ++ System.out.println("GeoDataConverter: Created " + output + " region file."); ++ } ++ } ++ } ++ System.out.println("GeoDataConverter: Converted " + converted + " " + _format + " to L2D region file(s)."); ++ ++ // release multilayer block temporarily buffer ++ BlockMultilayer.release(); ++ } ++ ++ /** ++ * Loads geo blocks from buffer of the region file. ++ * @param filename : The name of the to load. ++ * @return boolean : True when successful. ++ */ ++ private static boolean loadGeoBlocks(String filename) ++ { ++ // region file is load-able, try to load it ++ try (RandomAccessFile raf = new RandomAccessFile(Config.GEODATA_PATH + filename, "r"); ++ FileChannel fc = raf.getChannel()) ++ { ++ final MappedByteBuffer buffer = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()).load(); ++ buffer.order(ByteOrder.LITTLE_ENDIAN); ++ ++ // load 18B header for L2off geodata (1st and 2nd byte...region X and Y) ++ if (_format == GeoFormat.L2OFF) ++ { ++ for (int i = 0; i < 18; i++) ++ { ++ buffer.get(); ++ } ++ } ++ ++ // 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 (_format == GeoFormat.L2J) ++ { ++ // get block type ++ final byte type = buffer.get(); ++ ++ // load block according to block type ++ switch (type) ++ { ++ case GeoStructure.TYPE_FLAT_L2J_L2OFF: ++ { ++ _blocks[ix][iy] = new BlockFlat(buffer, _format); ++ break; ++ } ++ case GeoStructure.TYPE_COMPLEX_L2J: ++ { ++ _blocks[ix][iy] = new BlockComplex(buffer, _format); ++ break; ++ } ++ case GeoStructure.TYPE_MULTILAYER_L2J: ++ { ++ _blocks[ix][iy] = new BlockMultilayer(buffer, _format); ++ break; ++ } ++ default: ++ { ++ throw new IllegalArgumentException("Unknown block type: " + type); ++ } ++ } ++ } ++ else ++ { ++ // get block type ++ final short type = buffer.getShort(); ++ ++ // load block according to block type ++ switch (type) ++ { ++ case GeoStructure.TYPE_FLAT_L2J_L2OFF: ++ { ++ _blocks[ix][iy] = new BlockFlat(buffer, _format); ++ break; ++ } ++ case GeoStructure.TYPE_COMPLEX_L2OFF: ++ { ++ _blocks[ix][iy] = new BlockComplex(buffer, _format); ++ break; ++ } ++ default: ++ { ++ _blocks[ix][iy] = new BlockMultilayer(buffer, _format); ++ break; ++ } ++ } ++ } ++ } ++ } ++ ++ if (buffer.remaining() > 0) ++ { ++ System.out.println("GeoDataConverter: Region file " + filename + " can be corrupted, remaining " + buffer.remaining() + " bytes to read."); ++ return false; ++ } ++ ++ return true; ++ } ++ catch (Exception e) ++ { ++ System.out.println("GeoDataConverter: Error while loading " + filename + " region file."); ++ return false; ++ } ++ } ++ ++ /** ++ * Recalculate diagonal flags for the region file. ++ * @return boolean : True when successful. ++ */ ++ private static boolean recalculateNswe() ++ { ++ try ++ { ++ for (int x = 0; x < GeoStructure.REGION_CELLS_X; x++) ++ { ++ for (int y = 0; y < GeoStructure.REGION_CELLS_Y; y++) ++ { ++ // get block ++ final ABlock block = _blocks[x / GeoStructure.BLOCK_CELLS_X][y / GeoStructure.BLOCK_CELLS_Y]; ++ ++ // skip flat blocks ++ if (block instanceof BlockFlat) ++ { ++ continue; ++ } ++ ++ // for complex and multilayer blocks go though all layers ++ short height = Short.MAX_VALUE; ++ int index; ++ while ((index = block.getIndexBelow(x, y, height)) != -1) ++ { ++ // get height and nswe ++ height = block.getHeight(index); ++ byte nswe = block.getNswe(index); ++ ++ // update nswe with diagonal flags ++ nswe = updateNsweBelow(x, y, height, nswe); ++ ++ // set nswe of the cell ++ block.setNswe(index, nswe); ++ } ++ } ++ } ++ ++ return true; ++ } ++ catch (Exception e) ++ { ++ return false; ++ } ++ } ++ ++ /** ++ * Updates the NSWE flag with diagonal flags. ++ * @param x : Geodata X coordinate. ++ * @param y : Geodata Y coordinate. ++ * @param z : Geodata Z coordinate. ++ * @param nsweValue : NSWE flag to be updated. ++ * @return byte : Updated NSWE flag. ++ */ ++ private static byte updateNsweBelow(int x, int y, short z, byte nsweValue) ++ { ++ byte nswe = nsweValue; ++ ++ // calculate virtual layer height ++ final short height = (short) (z + GeoStructure.CELL_IGNORE_HEIGHT); ++ ++ // get NSWE of neighbor cells below virtual layer (NPC/PC can fall down of clif, but can not climb it -> NSWE of cell below) ++ final byte nsweN = getNsweBelow(x, y - 1, height); ++ final byte nsweS = getNsweBelow(x, y + 1, height); ++ final byte nsweW = getNsweBelow(x - 1, y, height); ++ final byte nsweE = getNsweBelow(x + 1, y, height); ++ ++ // north-west ++ if ((((nswe & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) || (((nswe & GeoStructure.CELL_FLAG_W) != 0) && ((nsweW & GeoStructure.CELL_FLAG_N) != 0))) ++ { ++ nswe |= GeoStructure.CELL_FLAG_NW; ++ } ++ ++ // north-east ++ if ((((nswe & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) || (((nswe & GeoStructure.CELL_FLAG_E) != 0) && ((nsweE & GeoStructure.CELL_FLAG_N) != 0))) ++ { ++ nswe |= GeoStructure.CELL_FLAG_NE; ++ } ++ ++ // south-west ++ if ((((nswe & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) || (((nswe & GeoStructure.CELL_FLAG_W) != 0) && ((nsweW & GeoStructure.CELL_FLAG_S) != 0))) ++ { ++ nswe |= GeoStructure.CELL_FLAG_SW; ++ } ++ ++ // south-east ++ if ((((nswe & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) || (((nswe & GeoStructure.CELL_FLAG_E) != 0) && ((nsweE & GeoStructure.CELL_FLAG_S) != 0))) ++ { ++ nswe |= GeoStructure.CELL_FLAG_SE; ++ } ++ ++ return nswe; ++ } ++ ++ private static byte getNsweBelow(int geoX, int geoY, short worldZ) ++ { ++ // out of geo coordinates ++ if ((geoX < 0) || (geoX >= GeoStructure.REGION_CELLS_X)) ++ { ++ return 0; ++ } ++ ++ // out of geo coordinates ++ if ((geoY < 0) || (geoY >= GeoStructure.REGION_CELLS_Y)) ++ { ++ return 0; ++ } ++ ++ // get block ++ final ABlock block = _blocks[geoX / GeoStructure.BLOCK_CELLS_X][geoY / GeoStructure.BLOCK_CELLS_Y]; ++ ++ // get index, when valid, return nswe ++ final int index = block.getIndexBelow(geoX, geoY, worldZ); ++ return index == -1 ? 0 : block.getNswe(index); ++ } ++ ++ /** ++ * Save region file to file. ++ * @param filename : The name of file to save. ++ * @return boolean : True when successful. ++ */ ++ private static boolean saveGeoBlocks(String filename) ++ { ++ try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(Config.GEODATA_PATH + filename), GeoStructure.REGION_BLOCKS * GeoStructure.BLOCK_CELLS * 3)) ++ { ++ // loop over region blocks and save each block ++ for (int ix = 0; ix < GeoStructure.REGION_BLOCKS_X; ix++) ++ { ++ for (int iy = 0; iy < GeoStructure.REGION_BLOCKS_Y; iy++) ++ { ++ _blocks[ix][iy].saveBlock(bos); ++ } ++ } ++ ++ // flush data to file ++ bos.flush(); ++ ++ return true; ++ } ++ catch (Exception e) ++ { ++ return false; ++ } ++ } ++ ++ private static void loadGeoengineConfigs() ++ { ++ final PropertiesParser geoData = new PropertiesParser(Config.GEOENGINE_CONFIG_FILE); ++ Config.GEODATA_PATH = geoData.getString("GeoDataPath", "./data/geodata/"); ++ Config.COORD_SYNCHRONIZE = geoData.getInt("CoordSynchronize", -1); ++ Config.PART_OF_CHARACTER_HEIGHT = geoData.getInt("PartOfCharacterHeight", 75); ++ Config.MAX_OBSTACLE_HEIGHT = geoData.getInt("MaxObstacleHeight", 32); ++ Config.PATHFINDING = geoData.getBoolean("PathFinding", true); ++ Config.PATHFIND_BUFFERS = geoData.getString("PathFindBuffers", "100x6;128x6;192x6;256x4;320x4;384x4;500x2"); ++ Config.BASE_WEIGHT = geoData.getInt("BaseWeight", 10); ++ Config.DIAGONAL_WEIGHT = geoData.getInt("DiagonalWeight", 14); ++ Config.OBSTACLE_MULTIPLIER = geoData.getInt("ObstacleMultiplier", 10); ++ Config.HEURISTIC_WEIGHT = geoData.getInt("HeuristicWeight", 20); ++ Config.MAX_ITERATIONS = geoData.getInt("MaxIterations", 3500); ++ } ++} +\ No newline at end of file diff --git a/L2J_Mobius_Classic_3.0_TheKamael/dist/game/data/geodata/L2D_GeoEngine.patch b/L2J_Mobius_Classic_3.0_TheKamael/dist/game/data/geodata/L2D_GeoEngine.patch new file mode 100644 index 0000000000..63e2faf0c9 --- /dev/null +++ b/L2J_Mobius_Classic_3.0_TheKamael/dist/game/data/geodata/L2D_GeoEngine.patch @@ -0,0 +1,6052 @@ +Index: dist/game/GeoDataConverter.bat +=================================================================== +--- dist/game/GeoDataConverter.bat (nonexistent) ++++ dist/game/GeoDataConverter.bat (working copy) +@@ -0,0 +1,6 @@ ++@echo off ++title L2D geodata converter ++ ++java -Xmx512m -cp ./../libs/* org.l2jmobius.tools.geodataconverter.GeoDataConverter ++ ++pause +Index: dist/game/GeoDataConverter.sh +=================================================================== +--- dist/game/GeoDataConverter.sh (nonexistent) ++++ dist/game/GeoDataConverter.sh (working copy) +@@ -0,0 +1,4 @@ ++#! /bin/sh ++ ++java -Xmx512m -cp ../libs/*: org.l2jmobius.tools.geodataconverter.GeoDataConverter > log/stdout.log 2>&1 ++ +Index: dist/game/config/GeoEngine.ini +=================================================================== +--- dist/game/config/GeoEngine.ini (revision 8397) ++++ dist/game/config/GeoEngine.ini (working copy) +@@ -1,6 +1,10 @@ + # ================================================================= + # Geodata + # ================================================================= ++# Because of real-time performance we are using geodata files only in ++# diagonal L2D format now (using filename e.g. 22_16.l2d). ++# L2D geodata can be obtained by conversion of existing L2J or L2OFF geodata. ++# Launch "GeoDataConverter.bat/sh" and follow instructions to start the conversion. + + # 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/ +@@ -13,6 +17,16 @@ + CoordSynchronize = 2 + + # ================================================================= ++# Path checking ++# ================================================================= ++ ++# 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 finding + # ================================================================= + +@@ -23,19 +37,23 @@ + # Pathfinding array buffers configuration, default: 100x6;128x6;192x6;256x4;320x4;384x4;500x2 + PathFindBuffers = 100x6;128x6;192x6;256x4;320x4;384x4;500x2 + +-# Weight for nodes without obstacles far from walls +-LowWeight = 0.5 ++# Base path weight, when moving from one node to another on axis direction, default: 10 ++BaseWeight = 10 + +-# Weight for nodes near walls +-MediumWeight = 2 ++# Path weight, when moving from one node to another on diagonal direction, default: BaseWeight * sqrt(2) = 14 ++DiagonalWeight = 14 + +-# Weight for nodes with obstacles +-HighWeight = 3 ++# When movement flags of target node is blocked to any direction, multiply movement weight by this multiplier. ++# This causes pathfinding algorithm to avoid path construction exactly near an obstacle, default: 10 ++ObstacleMultiplier = 10 + +-# Weight for diagonal movement. +-# Default: LowWeight * sqrt(2) +-DiagonalWeight = 0.707 ++# Weight of the heuristic algorithm, which is giving estimated cost from node to target, default: 20 ++# For proper function must be higher than BaseWeight and/or DiagonalWeight. ++HeuristicWeight = 20 + ++# Maximum number of generated nodes per one path-finding process, default 3500 ++MaxIterations = 3500 ++ + # ================================================================= + # Other + # ================================================================= +Index: dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java +=================================================================== +--- dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java (revision 8397) ++++ dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java (working copy) +@@ -55,9 +55,8 @@ + final int worldX = activeChar.getX(); + final int worldY = activeChar.getY(); + final int worldZ = activeChar.getZ(); +- final int geoX = GeoEngine.getInstance().getGeoX(worldX); +- final int geoY = GeoEngine.getInstance().getGeoY(worldY); +- ++ final int geoX = GeoEngine.getGeoX(worldX); ++ final int geoY = GeoEngine.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)); +@@ -73,9 +72,8 @@ + final int worldX = activeChar.getX(); + final int worldY = activeChar.getY(); + final int worldZ = activeChar.getZ(); +- final int geoX = GeoEngine.getInstance().getGeoX(worldX); +- final int geoY = GeoEngine.getInstance().getGeoY(worldY); +- ++ final int geoX = GeoEngine.getGeoX(worldX); ++ final int geoY = GeoEngine.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)); +Index: dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java +=================================================================== +--- dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java (revision 8397) ++++ dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java (working copy) +@@ -18,11 +18,11 @@ + + import java.util.List; + +-import org.l2jmobius.Config; +-import org.l2jmobius.gameserver.geoengine.GeoEnginePathfinding; +-import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; ++import org.l2jmobius.gameserver.geoengine.GeoEngine; + import org.l2jmobius.gameserver.handler.IAdminCommandHandler; ++import org.l2jmobius.gameserver.model.Location; + import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance; ++import org.l2jmobius.gameserver.network.SystemMessageId; + import org.l2jmobius.gameserver.util.BuilderUtil; + + public class AdminPathNode implements IAdminCommandHandler +@@ -37,29 +37,30 @@ + { + if (command.equals("admin_path_find")) + { +- if (!Config.PATHFINDING) +- { +- BuilderUtil.sendSysMessage(activeChar, "PathFinding is disabled."); +- return true; +- } + if (activeChar.getTarget() != null) + { +- final List path = GeoEnginePathfinding.getInstance().findPath(activeChar.getX(), activeChar.getY(), (short) activeChar.getZ(), activeChar.getTarget().getX(), activeChar.getTarget().getY(), (short) activeChar.getTarget().getZ(), activeChar.getInstanceWorld()); ++ 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()); + if (path == null) + { +- BuilderUtil.sendSysMessage(activeChar, "No Route!"); +- return true; ++ BuilderUtil.sendSysMessage(activeChar, "No route found or pathfinding disabled."); + } +- for (AbstractNodeLoc a : path) ++ else + { +- BuilderUtil.sendSysMessage(activeChar, "x:" + a.getX() + " y:" + a.getY() + " z:" + a.getZ()); ++ for (Location point : path) ++ { ++ BuilderUtil.sendSysMessage(activeChar, "x:" + point.getX() + " y:" + point.getY() + " z:" + point.getZ()); ++ } + } + } + else + { +- BuilderUtil.sendSysMessage(activeChar, "No Target!"); ++ activeChar.sendPacket(SystemMessageId.INVALID_TARGET); + } + } ++ else ++ { ++ return false; ++ } + return true; + } + +Index: java/org/l2jmobius/Config.java +=================================================================== +--- java/org/l2jmobius/Config.java (revision 8397) ++++ java/org/l2jmobius/Config.java (working copy) +@@ -32,7 +32,6 @@ + import java.net.UnknownHostException; + import java.nio.charset.StandardCharsets; + import java.nio.file.Files; +-import java.nio.file.Path; + import java.nio.file.Paths; + import java.time.Duration; + import java.util.ArrayList; +@@ -927,14 +926,17 @@ + // -------------------------------------------------- + // GeoEngine + // -------------------------------------------------- +- public static Path GEODATA_PATH; ++ public static String GEODATA_PATH; ++ public static int COORD_SYNCHRONIZE; ++ public static int PART_OF_CHARACTER_HEIGHT; ++ public static int MAX_OBSTACLE_HEIGHT; + public static boolean PATHFINDING; + public static String PATHFIND_BUFFERS; +- public static float LOW_WEIGHT; +- public static float MEDIUM_WEIGHT; +- public static float HIGH_WEIGHT; +- public static float DIAGONAL_WEIGHT; +- public static int COORD_SYNCHRONIZE; ++ public static int BASE_WEIGHT; ++ public static int DIAGONAL_WEIGHT; ++ public static int HEURISTIC_WEIGHT; ++ public static int OBSTACLE_MULTIPLIER; ++ public static int MAX_ITERATIONS; + public static boolean CORRECT_PLAYER_Z; + + /** Attribute System */ +@@ -2468,14 +2470,17 @@ + + // Load GeoEngine config file (if exists) + final PropertiesParser GeoEngine = new PropertiesParser(GEOENGINE_CONFIG_FILE); +- GEODATA_PATH = Paths.get(GeoEngine.getString("GeoDataPath", "./data/geodata")); ++ GEODATA_PATH = GeoEngine.getString("GeoDataPath", "./data/geodata/"); ++ COORD_SYNCHRONIZE = GeoEngine.getInt("CoordSynchronize", -1); ++ PART_OF_CHARACTER_HEIGHT = GeoEngine.getInt("PartOfCharacterHeight", 75); ++ MAX_OBSTACLE_HEIGHT = GeoEngine.getInt("MaxObstacleHeight", 32); + PATHFINDING = GeoEngine.getBoolean("PathFinding", true); + PATHFIND_BUFFERS = GeoEngine.getString("PathFindBuffers", "100x6;128x6;192x6;256x4;320x4;384x4;500x2"); +- LOW_WEIGHT = GeoEngine.getFloat("LowWeight", 0.5f); +- MEDIUM_WEIGHT = GeoEngine.getFloat("MediumWeight", 2); +- HIGH_WEIGHT = GeoEngine.getFloat("HighWeight", 3); +- DIAGONAL_WEIGHT = GeoEngine.getFloat("DiagonalWeight", 0.707f); +- COORD_SYNCHRONIZE = GeoEngine.getInt("CoordSynchronize", -1); ++ BASE_WEIGHT = GeoEngine.getInt("BaseWeight", 10); ++ DIAGONAL_WEIGHT = GeoEngine.getInt("DiagonalWeight", 14); ++ OBSTACLE_MULTIPLIER = GeoEngine.getInt("ObstacleMultiplier", 10); ++ HEURISTIC_WEIGHT = GeoEngine.getInt("HeuristicWeight", 20); ++ MAX_ITERATIONS = GeoEngine.getInt("MaxIterations", 3500); + CORRECT_PLAYER_Z = GeoEngine.getBoolean("CorrectPlayerZ", false); + + // Load AllowedPlayerRaces config file (if exists) +Index: java/org/l2jmobius/gameserver/geoengine/GeoEngine.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/GeoEngine.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/GeoEngine.java (working copy) +@@ -16,101 +16,91 @@ + */ + package org.l2jmobius.gameserver.geoengine; + +-import java.io.IOException; ++import java.io.File; + import java.io.RandomAccessFile; + import java.nio.ByteOrder; +-import java.nio.channels.FileChannel.MapMode; +-import java.nio.file.Files; +-import java.nio.file.Path; +-import java.util.concurrent.atomic.AtomicReferenceArray; +-import java.util.logging.Level; ++import java.nio.MappedByteBuffer; ++import java.nio.channels.FileChannel; ++import java.util.List; + 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.geoengine.geodata.Cell; +-import org.l2jmobius.gameserver.geoengine.geodata.IRegion; +-import org.l2jmobius.gameserver.geoengine.geodata.NullRegion; +-import org.l2jmobius.gameserver.geoengine.geodata.Region; ++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.GeoFormat; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoLocation; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; + import org.l2jmobius.gameserver.instancemanager.WarpedSpaceManager; + 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; ++import org.l2jmobius.gameserver.util.MathUtil; + + /** +- * @author -Nemesiss-, HorridoJoho ++ * @author Hasha + */ + public class GeoEngine + { +- private static final Logger LOGGER = Logger.getLogger(GeoEngine.class.getName()); ++ protected static final Logger LOGGER = Logger.getLogger(GeoEngine.class.getName()); + +- private static final int WORLD_MIN_X = -655360; +- private static final int WORLD_MIN_Y = -589824; +- private static final int WORLD_MIN_Z = -16384; ++ private final ABlock[][] _blocks; ++ private final BlockNull _nullBlock; + +- /** 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; +- +- /** The regions array */ +- private final AtomicReferenceArray _regions = new AtomicReferenceArray<>(GEO_REGIONS); +- +- 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; +- +- protected GeoEngine() ++ /** ++ * GeoEngine constructor. Loads all geodata files of chosen geodata format. ++ */ ++ public GeoEngine() + { + LOGGER.info("GeoEngine: Initializing..."); +- for (int i = 0; i < _regions.length(); i++) +- { +- _regions.set(i, NullRegion.INSTANCE); +- } + ++ // 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; +- try ++ long fileSize = 0; ++ for (int rx = World.TILE_X_MIN; rx <= World.TILE_X_MAX; rx++) + { +- for (int regionX = World.TILE_X_MIN; regionX <= World.TILE_X_MAX; regionX++) ++ for (int ry = World.TILE_Y_MIN; ry <= World.TILE_Y_MAX; ry++) + { +- for (int regionY = World.TILE_Y_MIN; regionY <= World.TILE_Y_MAX; regionY++) ++ final File f = new File(Config.GEODATA_PATH + String.format(GeoFormat.L2D.getFilename(), rx, ry)); ++ if (f.exists() && !f.isDirectory()) + { +- 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(rx, ry)) + { +- try (RandomAccessFile raf = new RandomAccessFile(geoFilePath.toFile(), "r")) +- { +- _regions.set((regionX * GEO_REGIONS_Y) + regionY, new Region(raf.getChannel().map(MapMode.READ_ONLY, 0, raf.length()).load().order(ByteOrder.LITTLE_ENDIAN))); +- loaded++; +- } ++ loaded++; ++ fileSize += f.length(); + } + } ++ else ++ { ++ // region file is not load-able, load null blocks ++ loadNullBlocks(rx, ry); ++ } + } + } +- catch (Exception e) ++ ++ LOGGER.info("GeoEngine: Loaded " + loaded + " geodata files."); ++ if (loaded > 0) + { +- LOGGER.log(Level.SEVERE, "GeoEngine: Failed to load geodata!", e); +- System.exit(1); ++ LOGGER.info("GeoEngine: Total geodata file size " + (fileSize / 1024 / 1024) + " MB."); + } + +- LOGGER.info("GeoEngine: Loaded " + loaded + " geodata files."); +- +- // Avoid wrong configs when no files are loaded. ++ // avoid wrong configs when no files are loaded + if (loaded == 0) + { + if (Config.PATHFINDING) +@@ -124,627 +114,832 @@ + LOGGER.info("GeoEngine: Forcing CoordSynchronize setting to -1."); + } + } ++ ++ // release multilayer block temporarily buffer ++ BlockMultilayer.release(); + } + + /** +- * @param geoX +- * @param geoY +- * @return the region ++ * 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 IRegion getRegion(int geoX, int geoY) ++ private final boolean loadGeoBlocks(int regionX, int regionY) + { +- final int region = ((geoX / IRegion.REGION_CELLS_X) * GEO_REGIONS_Y) + (geoY / IRegion.REGION_CELLS_Y); +- if ((region < 0) || (region >= _regions.length())) ++ final String filename = String.format(GeoFormat.L2D.getFilename(), regionX, regionY); ++ ++ // standard load ++ try (RandomAccessFile raf = new RandomAccessFile(Config.GEODATA_PATH + filename, "r"); ++ FileChannel fc = raf.getChannel()) + { +- return null; ++ // initialize file buffer ++ final MappedByteBuffer buffer = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()).load(); ++ buffer.order(ByteOrder.LITTLE_ENDIAN); ++ ++ // 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++) ++ { ++ // get block type ++ final byte type = buffer.get(); ++ ++ // load block according to block type ++ switch (type) ++ { ++ case GeoStructure.TYPE_FLAT_L2D: ++ { ++ _blocks[blockX + ix][blockY + iy] = new BlockFlat(buffer, GeoFormat.L2D); ++ break; ++ } ++ case GeoStructure.TYPE_COMPLEX_L2D: ++ { ++ _blocks[blockX + ix][blockY + iy] = new BlockComplex(buffer, GeoFormat.L2D); ++ break; ++ } ++ case GeoStructure.TYPE_MULTILAYER_L2D: ++ { ++ _blocks[blockX + ix][blockY + iy] = new BlockMultilayer(buffer, GeoFormat.L2D); ++ break; ++ } ++ default: ++ { ++ throw new IllegalArgumentException("Unknown block type: " + type); ++ } ++ } ++ } ++ } ++ ++ // check data consistency ++ if (buffer.remaining() > 0) ++ { ++ LOGGER.warning("GeoEngine: Region file " + filename + " can be corrupted, remaining " + buffer.remaining() + " bytes to read."); ++ } ++ ++ // loading was successful ++ return true; + } +- return _regions.get(region); +- } +- +- /** +- * @param filePath +- * @param regionX +- * @param regionY +- * @throws IOException +- */ +- 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")) ++ catch (Exception e) + { +- _regions.set(regionOffset, new Region(raf.getChannel().map(MapMode.READ_ONLY, 0, raf.length()).load().order(ByteOrder.LITTLE_ENDIAN))); ++ // an error occured while loading, load null blocks ++ LOGGER.warning("GeoEngine: Error while loading " + filename + " region file."); ++ LOGGER.warning(e.getMessage()); ++ ++ // replace whole region file with null blocks ++ loadNullBlocks(regionX, regionY); ++ ++ // loading was not successful ++ return false; + } + } + + /** +- * @param regionX +- * @param regionY ++ * 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. + */ +- public void unloadRegion(int regionX, int regionY) ++ private final void loadNullBlocks(int regionX, int regionY) + { +- _regions.set((regionX * GEO_REGIONS_Y) + regionY, NullRegion.INSTANCE); +- } +- +- /** +- * @param geoX +- * @param geoY +- * @return if geodata exist +- */ +- public boolean hasGeoPos(int geoX, int geoY) +- { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) ++ // 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++) + { +- return false; ++ for (int iy = 0; iy < GeoStructure.REGION_BLOCKS_Y; iy++) ++ { ++ _blocks[blockX + ix][blockY + iy] = _nullBlock; ++ } + } +- return region.hasGeo(); + } + ++ // GEODATA - GENERAL ++ + /** +- * Checks the specified position for available geodata. +- * @param x the world x +- * @param y the world y +- * @return {@code true} if there is geodata for the given coordinates, {@code false} otherwise ++ * Converts world X to geodata X. ++ * @param worldX ++ * @return int : Geo X + */ +- public boolean hasGeo(int x, int y) ++ public static int getGeoX(int worldX) + { +- return hasGeoPos(getGeoX(x), getGeoY(y)); ++ return (MathUtil.limit(worldX, World.MAP_MIN_X, World.MAP_MAX_X) - World.MAP_MIN_X) >> 4; + } + + /** +- * @param geoX +- * @param geoY +- * @param worldZ +- * @param nswe +- * @return the nearest nswe check ++ * Converts world Y to geodata Y. ++ * @param worldY ++ * @return int : Geo Y + */ +- public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) ++ public static int getGeoY(int worldY) + { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) +- { +- return true; +- } +- return region.checkNearestNswe(geoX, geoY, worldZ, nswe); ++ return (MathUtil.limit(worldY, World.MAP_MIN_Y, World.MAP_MAX_Y) - World.MAP_MIN_Y) >> 4; + } + + /** ++ * Converts geodata X to world X. + * @param geoX +- * @param geoY +- * @param worldZ +- * @param nswe +- * @return the nearest nswe anti-corner cut ++ * @return int : World X + */ +- public boolean checkNearestNsweAntiCornerCut(int geoX, int geoY, int worldZ, int nswe) ++ public static int getWorldX(int geoX) + { +- boolean can = true; +- if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) +- { +- can = checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_EAST) && checkNearestNswe(geoX + 1, geoY, worldZ, Cell.NSWE_NORTH); +- } +- if (can && ((nswe & Cell.NSWE_NORTH_WEST) == Cell.NSWE_NORTH_WEST)) +- { +- 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 = 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 = checkNearestNswe(geoX, geoY + 1, worldZ, Cell.NSWE_WEST) && checkNearestNswe(geoX - 1, geoY, worldZ, Cell.NSWE_SOUTH); +- } +- return can && checkNearestNswe(geoX, geoY, worldZ, nswe); ++ return (MathUtil.limit(geoX, 0, GeoStructure.GEO_CELLS_X) << 4) + World.MAP_MIN_X + 8; + } + + /** +- * @param geoX ++ * Converts geodata Y to world Y. + * @param geoY +- * @param worldZ +- * @return the nearest Z value ++ * @return int : World Y + */ +- public int getNearestZ(int geoX, int geoY, int worldZ) ++ public static int getWorldY(int geoY) + { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) +- { +- return worldZ; +- } +- return region.getNearestZ(geoX, geoY, worldZ); ++ return (MathUtil.limit(geoY, 0, GeoStructure.GEO_CELLS_Y) << 4) + World.MAP_MIN_Y + 8; + } + + /** +- * @param geoX +- * @param geoY +- * @param worldZ +- * @return the next lower Z value ++ * Returns block of geodata on given coordinates. ++ * @param geoX : Geodata X ++ * @param geoY : Geodata Y ++ * @return {@link ABlock} : Block of geodata. + */ +- public int getNextLowerZ(int geoX, int geoY, int worldZ) ++ private final ABlock getBlock(int geoX, int geoY) + { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) ++ final int x = geoX / GeoStructure.BLOCK_CELLS_X; ++ final int y = geoY / GeoStructure.BLOCK_CELLS_Y; ++ ++ // if x or y is out of array return null ++ if ((x > -1) && (y > -1) && (x < GeoStructure.GEO_BLOCKS_X) && (y < GeoStructure.GEO_BLOCKS_Y)) + { +- return worldZ; ++ return _blocks[x][y]; + } +- return region.getNextLowerZ(geoX, geoY, worldZ); ++ return null; + } + + /** +- * @param geoX +- * @param geoY +- * @param worldZ +- * @return the next higher Z value ++ * Check if geo coordinates has geo. ++ * @param geoX : Geodata X ++ * @param geoY : Geodata Y ++ * @return boolean : True, if given geo coordinates have geodata + */ +- public int getNextHigherZ(int geoX, int geoY, int worldZ) ++ public boolean hasGeoPos(int geoX, int geoY) + { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) ++ final ABlock block = getBlock(geoX, geoY); ++ if (block == null) // null block check + { +- return worldZ; ++ // TODO: Find when this can be null. (Bad geodata? Check World getRegion method.) ++ // LOGGER.warning("Could not find geodata block at " + getWorldX(geoX) + ", " + getWorldY(geoY) + "."); ++ return false; + } +- return region.getNextHigherZ(geoX, geoY, worldZ); ++ return block.hasGeoPos(); + } + + /** +- * Gets the Z height. +- * @param x the world x +- * @param y the world y +- * @param z the world z +- * @return the nearest Z height ++ * 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 int getHeight(int x, int y, int z) ++ public short getHeightNearest(int geoX, int geoY, int worldZ) + { +- return getNearestZ(getGeoX(x), getGeoY(y), z); ++ final ABlock block = getBlock(geoX, geoY); ++ return block != null ? block.getHeightNearest(geoX, geoY, worldZ) : (short) worldZ; + } + + /** +- * Gets the next lower Z height. +- * @param x the world x +- * @param y the world y +- * @param z the world z +- * @return the nearest Z height ++ * 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 int getLowerHeight(int x, int y, int z) ++ public byte getNsweNearest(int geoX, int geoY, int worldZ) + { +- return getNextLowerZ(getGeoX(x), getGeoY(y), z); ++ final ABlock block = getBlock(geoX, geoY); ++ return block != null ? block.getNsweNearest(geoX, geoY, worldZ) : (byte) 0xFF; + } + + /** +- * Gets the next higher Z height. +- * @param x the world x +- * @param y the world y +- * @param z the world z +- * @return the nearest Z height ++ * Check if world coordinates has geo. ++ * @param worldX : World X ++ * @param worldY : World Y ++ * @return boolean : True, if given world coordinates have geodata + */ +- public int getHigherHeight(int x, int y, int z) ++ public boolean hasGeo(int worldX, int worldY) + { +- return getNextHigherZ(getGeoX(x), getGeoY(y), z); ++ return hasGeoPos(getGeoX(worldX), getGeoY(worldY)); + } + + /** +- * @param worldX +- * @return the geo X ++ * 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 int getGeoX(int worldX) ++ public short getHeight(int worldX, int worldY, int worldZ) + { +- return (worldX - WORLD_MIN_X) / 16; ++ return getHeightNearest(getGeoX(worldX), getGeoY(worldY), worldZ); + } + +- /** +- * @param worldY +- * @return the geo Y +- */ +- public int getGeoY(int worldY) +- { +- return (worldY - WORLD_MIN_Y) / 16; +- } ++ // PATHFINDING + + /** +- * @param worldZ +- * @return the geo Z ++ * Check line of sight from {@link WorldObject} to {@link WorldObject}. ++ * @param origin : The origin object. ++ * @param target : The target object. ++ * @return {@code boolean} : True if origin can see target + */ +- public int getGeoZ(int worldZ) ++ public boolean canSeeTarget(WorldObject origin, WorldObject target) + { +- return (worldZ - WORLD_MIN_Z) / 16; +- } +- +- /** +- * @param geoX +- * @return the world X +- */ +- public int getWorldX(int geoX) +- { +- return (geoX * 16) + WORLD_MIN_X + 8; +- } +- +- /** +- * @param geoY +- * @return the world Y +- */ +- public int getWorldY(int geoY) +- { +- return (geoY * 16) + WORLD_MIN_Y + 8; +- } +- +- /** +- * @param geoZ +- * @return the world Z +- */ +- public int getWorldZ(int geoZ) +- { +- return (geoZ * 16) + WORLD_MIN_Z + 8; +- } +- +- /** +- * 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) +- { +- if (target.isDoor()) ++ if (target.isDoor() || target.isArtefact() || (target.isCreature() && ((Creature) target).isFlying())) + { +- // Can always see doors. + return true; + } +- return 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(), cha.getInstanceWorld(), worldPosition.getX(), worldPosition.getY(), worldPosition.getZ()); +- } +- +- /** +- * Can see target. Checks doors between. +- * @param x the x coordinate +- * @param y the y coordinate +- * @param z the z coordinate +- * @param world +- * @param tx the target's x coordinate +- * @param ty the target's y coordinate +- * @param tz the target's z coordinate +- * @param tworld the target's instanceId +- * @return +- */ +- public boolean canSeeTarget(int x, int y, int z, Instance world, int tx, int ty, int tz, Instance tworld) +- { +- return (world != tworld) ? false : canSeeTarget(x, y, z, world, tx, ty, tz); +- } +- +- /** +- * 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 +- * @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, Instance instance, int tx, int ty, int tz) +- { +- if (DoorData.getInstance().checkIfDoorsBetween(x, y, z, tx, ty, tz, instance, true)) ++ ++ // get origin and target world coordinates ++ final int ox = origin.getX(); ++ final int oy = origin.getY(); ++ final int oz = origin.getZ(); ++ final int tx = target.getX(); ++ final int ty = target.getY(); ++ final int tz = target.getZ(); ++ ++ if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld(), true)) + { + return false; + } +- return canSeeTarget(x, y, z, tx, ty, tz); +- } +- +- /** +- * @param prevX +- * @param prevY +- * @param prevGeoZ +- * @param curX +- * @param curY +- * @param nswe +- * @return the LOS Z value +- */ +- 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))) ++ if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld())) + { +- throw new RuntimeException("Multiple directions!"); ++ return false; + } ++ if (WarpedSpaceManager.getInstance().checkForWarpedSpace(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld())) ++ { ++ return false; ++ } + +- if (checkNearestNsweAntiCornerCut(prevX, prevY, prevGeoZ, nswe)) ++ // get origin and check existing geo coordinates ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) + { +- return getNearestZ(curX, curY, prevGeoZ); ++ return true; + } + +- return getNextHigherZ(curX, curY, prevGeoZ); ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coordinates ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) ++ { ++ return true; ++ } ++ ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // origin and target coordinates are same ++ if ((gox == gtx) && (goy == gty)) ++ { ++ return goz == gtz; ++ } ++ ++ // get origin and target height, real height = collision height * 2 ++ double oheight = 0; ++ if (origin.isCreature()) ++ { ++ oheight = ((Creature) origin).getCollisionHeight() * 2; ++ } ++ ++ double theight = 0; ++ if (target.isCreature()) ++ { ++ theight = ((Creature) target).getCollisionHeight() * 2; ++ } ++ ++ // perform geodata check ++ return checkSee(gox, goy, goz, oheight, gtx, gty, gtz, theight, origin.getInstanceWorld()); + } + + /** +- * Can see target. Does not check doors between. +- * @param xValue the x coordinate +- * @param yValue the y coordinate +- * @param zValue the z coordinate +- * @param txValue the target's x coordinate +- * @param tyValue the target's y coordinate +- * @param tzValue the target's z coordinate +- * @return {@code true} if there is line of sight between the given coordinate sets, {@code false} otherwise ++ * Check line of sight from {@link WorldObject} to {@link Location}. ++ * @param origin : The origin object. ++ * @param position : The target position. ++ * @return {@code boolean} : True if object can see position + */ +- public boolean canSeeTarget(int xValue, int yValue, int zValue, int txValue, int tyValue, int tzValue) ++ public boolean canSeeTarget(WorldObject origin, Location position) + { +- int x = xValue; +- int y = yValue; +- int tx = txValue; +- int ty = tyValue; ++ // get origin and target world coordinates ++ final int ox = origin.getX(); ++ final int oy = origin.getY(); ++ final int oz = origin.getZ(); ++ final int tx = position.getX(); ++ final int ty = position.getY(); ++ final int tz = position.getZ(); + +- int geoX = getGeoX(x); +- int geoY = getGeoY(y); +- int tGeoX = getGeoX(tx); +- int tGeoY = getGeoY(ty); ++ if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld(), true)) ++ { ++ return false; ++ } ++ if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld())) ++ { ++ return false; ++ } ++ if (WarpedSpaceManager.getInstance().checkForWarpedSpace(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld())) ++ { ++ return false; ++ } + +- int z = getNearestZ(geoX, geoY, zValue); +- int tz = getNearestZ(tGeoX, tGeoY, tzValue); ++ // get origin and check existing geo coordinates ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) ++ { ++ return true; ++ } + +- // Fastpath. +- if ((geoX == tGeoX) && (geoY == tGeoY)) ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coordinates ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) + { +- if (hasGeoPos(tGeoX, tGeoY)) +- { +- return z == tz; +- } + return true; + } + +- if (tz > z) ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // origin and target coordinates are same ++ if ((gox == gtx) && (goy == gty)) + { +- int tmp = tx; +- tx = x; +- x = tmp; +- +- tmp = ty; +- ty = y; +- y = tmp; +- +- tmp = tz; +- tz = z; +- z = tmp; +- +- tmp = tGeoX; +- tGeoX = geoX; +- geoX = tmp; +- +- tmp = tGeoY; +- tGeoY = geoY; +- geoY = tmp; ++ return goz == gtz; + } + +- final LinePointIterator3D pointIter = new LinePointIterator3D(geoX, geoY, z, tGeoX, tGeoY, tz); +- // 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()) ++ // get origin and target height, real height = collision height * 2 ++ double oheight = 0; ++ if (origin.isCreature()) + { +- final int curX = pointIter.x(); +- final int curY = pointIter.y(); ++ oheight = ((Creature) origin).getTemplate().getCollisionHeight(); ++ } ++ ++ // perform geodata check ++ return checkSee(gox, goy, goz, oheight, gtx, gty, gtz, 0, origin.getInstanceWorld()); ++ } ++ ++ /** ++ * Simple check for origin to target visibility. ++ * @param goxValue : origin X geodata coordinate ++ * @param goyValue : origin Y geodata coordinate ++ * @param gozValue : origin Z geodata coordinate ++ * @param oheight : origin height (if instance of {@link Character}) ++ * @param gtxValue : target X geodata coordinate ++ * @param gtyValue : target Y geodata coordinate ++ * @param gtzValue : target Z geodata coordinate ++ * @param theight : target height (if instance of {@link Character}) ++ * @param instance ++ * @return {@code boolean} : True, when target can be seen. ++ */ ++ private final boolean checkSee(int goxValue, int goyValue, int gozValue, double oheight, int gtxValue, int gtyValue, int gtzValue, double theight, Instance instance) ++ { ++ int goz = gozValue; ++ int gtz = gtzValue; ++ int gox = goxValue; ++ int goy = goyValue; ++ int gtx = gtxValue; ++ int gty = gtyValue; ++ ++ // get line of sight Z coordinates ++ double losoz = goz + ((oheight * Config.PART_OF_CHARACTER_HEIGHT) / 100); ++ double lostz = gtz + ((theight * Config.PART_OF_CHARACTER_HEIGHT) / 100); ++ ++ // get X delta and signum ++ final int dx = Math.abs(gtx - gox); ++ final int sx = gox < gtx ? 1 : -1; ++ final byte dirox = sx > 0 ? GeoStructure.CELL_FLAG_E : GeoStructure.CELL_FLAG_W; ++ final byte dirtx = sx > 0 ? GeoStructure.CELL_FLAG_W : GeoStructure.CELL_FLAG_E; ++ ++ // get Y delta and signum ++ final int dy = Math.abs(gty - goy); ++ final int sy = goy < gty ? 1 : -1; ++ final byte diroy = sy > 0 ? GeoStructure.CELL_FLAG_S : GeoStructure.CELL_FLAG_N; ++ final byte dirty = sy > 0 ? GeoStructure.CELL_FLAG_N : GeoStructure.CELL_FLAG_S; ++ ++ // get Z delta ++ final int dm = Math.max(dx, dy); ++ final double dz = (lostz - losoz) / dm; ++ ++ // get direction flag for diagonal movement ++ final byte diroxy = getDirXY(dirox, diroy); ++ final byte dirtxy = getDirXY(dirtx, dirty); ++ ++ // delta, determines axis to move on (+..X axis, -..Y axis) ++ int d = dx - dy; ++ ++ // NSWE direction of movement ++ byte diro; ++ byte dirt; ++ ++ // initialize node values ++ int nox = gox; ++ int noy = goy; ++ int ntx = gtx; ++ int nty = gty; ++ byte nsweo = getNsweNearest(gox, goy, goz); ++ byte nswet = getNsweNearest(gtx, gty, gtz); ++ ++ // loop ++ ABlock block; ++ int index; ++ for (int i = 0; i < ((dm + 1) / 2); i++) ++ { ++ // reset direction flag ++ diro = 0; ++ dirt = 0; + +- if ((curX == prevX) && (curY == prevY)) ++ // calculate next point coordinates ++ final int e2 = 2 * d; ++ if ((e2 > -dy) && (e2 < dx)) + { +- continue; ++ // calculate next point XY coordinates ++ d -= dy; ++ d += dx; ++ nox += sx; ++ ntx -= sx; ++ noy += sy; ++ nty -= sy; ++ diro |= diroxy; ++ dirt |= dirtxy; + } ++ else if (e2 > -dy) ++ { ++ // calculate next point X coordinate ++ d -= dy; ++ nox += sx; ++ ntx -= sx; ++ diro |= dirox; ++ dirt |= dirtx; ++ } ++ else if (e2 < dx) ++ { ++ // calculate next point Y coordinate ++ d += dx; ++ noy += sy; ++ nty -= sy; ++ diro |= diroy; ++ dirt |= dirty; ++ } + +- final int beeCurZ = pointIter.z(); +- int curGeoZ = prevGeoZ; +- +- // Check if the position has geodata. +- if (hasGeoPos(curX, curY)) + { +- final int beeCurGeoZ = getNearestZ(curX, curY, beeCurZ); +- final int nswe = GeoUtils.computeNswe(prevX, prevY, curX, curY); // .computeDirection(prevX, prevY, curX, curY); +- curGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, curX, curY, nswe); +- int maxHeight; +- if (ptIndex < ELEVATED_SEE_OVER_DISTANCE) ++ // get block of the next cell ++ block = getBlock(nox, noy); ++ ++ // get index of particular layer, based on movement conditions ++ if ((nsweo & diro) == 0) + { +- maxHeight = z + MAX_SEE_OVER_HEIGHT; ++ index = block.getIndexAbove(nox, noy, goz - GeoStructure.CELL_IGNORE_HEIGHT); + } + else + { +- maxHeight = beeCurZ + MAX_SEE_OVER_HEIGHT; ++ index = block.getIndexBelow(nox, noy, goz + GeoStructure.CELL_IGNORE_HEIGHT); + } + +- boolean canSeeThrough = false; +- if ((curGeoZ <= maxHeight) && (curGeoZ <= beeCurGeoZ)) ++ // layer does not exist, return ++ if (index == -1) + { +- if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) +- { +- 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 +- { +- canSeeThrough = true; +- } ++ return false; + } + +- if (!canSeeThrough) ++ // get layer and next line of sight Z coordinate ++ goz = block.getHeight(index); ++ losoz += dz; ++ ++ // perform line of sight check, return when fails ++ if ((goz - losoz) > Config.MAX_OBSTACLE_HEIGHT) + { + return false; + } ++ ++ // get layer nswe ++ nsweo = block.getNswe(index); + } ++ { ++ // get block of the next cell ++ block = getBlock(ntx, nty); ++ ++ // get index of particular layer, based on movement conditions ++ if ((nswet & dirt) == 0) ++ { ++ index = block.getIndexAbove(ntx, nty, gtz - GeoStructure.CELL_IGNORE_HEIGHT); ++ } ++ else ++ { ++ index = block.getIndexBelow(ntx, nty, gtz + GeoStructure.CELL_IGNORE_HEIGHT); ++ } ++ ++ // layer does not exist, return ++ if (index == -1) ++ { ++ return false; ++ } ++ ++ // get layer and next line of sight Z coordinate ++ gtz = block.getHeight(index); ++ lostz -= dz; ++ ++ // perform line of sight check, return when fails ++ if ((gtz - lostz) > Config.MAX_OBSTACLE_HEIGHT) ++ { ++ return false; ++ } ++ ++ // get layer nswe ++ nswet = block.getNswe(index); ++ } + +- prevX = curX; +- prevY = curY; +- prevGeoZ = curGeoZ; +- ++ptIndex; ++ // update coords ++ gox = nox; ++ goy = noy; ++ gtx = ntx; ++ gty = nty; + } + +- return true; ++ // when iteration is completed, compare final Z coordinates ++ return Math.abs(goz - gtz) < (GeoStructure.CELL_HEIGHT * 4); + } + + /** +- * Move check. +- * @param x the x coordinate +- * @param y the y coordinate +- * @param zValue the z coordinate +- * @param tx the target's x coordinate +- * @param ty the target's y coordinate +- * @param tzValue the target's z coordinate +- * @param instance the instance +- * @return the last Location (x,y,z) where player can walk - just before wall ++ * Check movement from coordinates to 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 {code boolean} : True if target coordinates are reachable from origin coordinates + */ +- public Location canMoveToTargetLoc(int x, int y, int zValue, int tx, int ty, int tzValue, Instance instance) ++ public boolean canMoveToTarget(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + { +- final int geoX = getGeoX(x); +- final int geoY = getGeoY(y); +- final int z = getNearestZ(geoX, geoY, zValue); +- final int tGeoX = getGeoX(tx); +- final int tGeoY = getGeoY(ty); +- final int tz = getNearestZ(tGeoX, tGeoY, tzValue); ++ // get origin and check existing geo coordinates ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) ++ { ++ return true; ++ } + +- if (DoorData.getInstance().checkIfDoorsBetween(x, y, z, tx, ty, tz, instance, false)) ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coordinates ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) + { +- return new Location(x, y, getHeight(x, y, z)); ++ return true; + } +- if (FenceData.getInstance().checkIfFenceBetween(x, y, z, tx, ty, tz, instance)) ++ ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // target coordinates reached ++ if ((gox == gtx) && (goy == gty) && (goz == gtz)) + { +- return new Location(x, y, getHeight(x, y, z)); ++ return true; + } +- if (WarpedSpaceManager.getInstance().checkForWarpedSpace(x, y, z, tx, ty, tz, instance)) ++ ++ // perform geodata check ++ final GeoLocation loc = checkMove(gox, goy, goz, gtx, gty, gtz, instance); ++ return (loc.getGeoX() == gtx) && (loc.getGeoY() == gty); ++ } ++ ++ /** ++ * Check movement from origin to target. Returns last available point in the checked path. ++ * @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 {@link Location} : Last point where object can walk (just before wall) ++ */ ++ public Location canMoveToTargetLoc(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) ++ { ++ // Mobius: Double check for doors before normal checkMove to avoid exploiting key movement. ++ if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, instance, false)) + { +- return new Location(x, y, getHeight(x, y, z)); ++ return new Location(ox, oy, oz); + } ++ if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, instance)) ++ { ++ return new Location(ox, oy, oz); ++ } + +- 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 = z; ++ // get origin and check existing geo coordinates ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) ++ { ++ return new Location(tx, ty, tz); ++ } + +- while (pointIter.next()) ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coordinates ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) + { +- final int curX = pointIter.x(); +- final int curY = pointIter.y(); +- final int curZ = getNearestZ(curX, curY, prevZ); +- +- if (hasGeoPos(prevX, prevY)) +- { +- final int nswe = GeoUtils.computeNswe(prevX, prevY, curX, curY); +- if (!checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, nswe)) +- { +- // Can't move, return previous location. +- return new Location(getWorldX(prevX), getWorldY(prevY), prevZ); +- } +- } +- +- prevX = curX; +- prevY = curY; +- prevZ = curZ; ++ return new Location(tx, ty, tz); + } + +- if (hasGeoPos(prevX, prevY) && (prevZ != tz)) ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // target coordinates reached ++ if ((gox == gtx) && (goy == gty) && (goz == gtz)) + { +- // Different floors, return start location. +- return new Location(x, y, z); ++ return new Location(tx, ty, tz); + } + +- return new Location(tx, ty, tz); ++ // perform geodata check ++ return checkMove(gox, goy, goz, gtx, gty, gtz, instance); + } + + /** +- * 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 fromZvalue 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 toZvalue 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 ++ * With this method you can check if a position is visible or can be reached by beeline movement.
++ * 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 gox : origin X geodata coordinate ++ * @param goy : origin Y geodata coordinate ++ * @param goz : origin Z geodata coordinate ++ * @param gtx : target X geodata coordinate ++ * @param gty : target Y geodata coordinate ++ * @param gtz : target Z geodata coordinate ++ * @param instance ++ * @return {@link GeoLocation} : The last allowed point of movement. + */ +- public boolean canMoveToTarget(int fromX, int fromY, int fromZvalue, int toX, int toY, int toZvalue, Instance instance) ++ protected final GeoLocation checkMove(int gox, int goy, int goz, int gtx, int gty, int gtz, Instance instance) + { +- final int geoX = getGeoX(fromX); +- final int geoY = getGeoY(fromY); +- final int fromZ = getNearestZ(geoX, geoY, fromZvalue); +- final int tGeoX = getGeoX(toX); +- final int tGeoY = getGeoY(toY); +- final int toZ = getNearestZ(tGeoX, tGeoY, toZvalue); +- +- if (DoorData.getInstance().checkIfDoorsBetween(fromX, fromY, fromZ, toX, toY, toZ, instance, false)) ++ if (DoorData.getInstance().checkIfDoorsBetween(gox, goy, goz, gtx, gty, gtz, instance, false)) + { +- return false; ++ return new GeoLocation(gox, goy, goz); + } +- if (FenceData.getInstance().checkIfFenceBetween(fromX, fromY, fromZ, toX, toY, toZ, instance)) ++ if (FenceData.getInstance().checkIfFenceBetween(gox, goy, goz, gtx, gty, gtz, instance)) + { +- return false; ++ return new GeoLocation(gox, goy, goz); + } +- if (WarpedSpaceManager.getInstance().checkForWarpedSpace(fromX, fromY, fromZ, toX, toY, toZ, instance)) ++ if (WarpedSpaceManager.getInstance().checkForWarpedSpace(gox, goy, goz, gtx, gty, gtz, instance)) + { +- return false; ++ return new GeoLocation(gox, goy, goz); + } + +- 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 = fromZ; ++ // get X delta, signum and direction flag ++ final int dx = Math.abs(gtx - gox); ++ final int sx = gox < gtx ? 1 : -1; ++ final byte dirX = sx > 0 ? GeoStructure.CELL_FLAG_E : GeoStructure.CELL_FLAG_W; + +- while (pointIter.next()) ++ // get Y delta, signum and direction flag ++ final int dy = Math.abs(gty - goy); ++ final int sy = goy < gty ? 1 : -1; ++ final byte dirY = sy > 0 ? GeoStructure.CELL_FLAG_S : GeoStructure.CELL_FLAG_N; ++ ++ // get direction flag for diagonal movement ++ final byte dirXY = getDirXY(dirX, dirY); ++ ++ // delta, determines axis to move on (+..X axis, -..Y axis) ++ int d = dx - dy; ++ ++ // NSWE direction of movement ++ byte direction; ++ ++ // load pointer coordinates ++ int gpx = gox; ++ int gpy = goy; ++ int gpz = goz; ++ ++ // load next pointer ++ int nx = gpx; ++ int ny = gpy; ++ ++ // loop ++ int count = 0; ++ while (count++ < Config.MAX_ITERATIONS) + { +- final int curX = pointIter.x(); +- final int curY = pointIter.y(); +- final int curZ = getNearestZ(curX, curY, prevZ); ++ direction = 0; + +- if (hasGeoPos(prevX, prevY)) ++ // calculate next point coordinates ++ final int e2 = 2 * d; ++ if ((e2 > -dy) && (e2 < dx)) + { +- final int nswe = GeoUtils.computeNswe(prevX, prevY, curX, curY); +- if (!checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, nswe)) ++ d -= dy; ++ d += dx; ++ nx += sx; ++ ny += sy; ++ direction |= dirXY; ++ } ++ else if (e2 > -dy) ++ { ++ d -= dy; ++ nx += sx; ++ direction |= dirX; ++ } ++ else if (e2 < dx) ++ { ++ d += dx; ++ ny += sy; ++ direction |= dirY; ++ } ++ ++ // obstacle found, return ++ if ((getNsweNearest(gpx, gpy, gpz) & direction) == 0) ++ { ++ return new GeoLocation(gpx, gpy, gpz); ++ } ++ ++ // update pointer coordinates ++ gpx = nx; ++ gpy = ny; ++ gpz = getHeightNearest(nx, ny, gpz); ++ ++ // target coordinates reached ++ if ((gpx == gtx) && (gpy == gty)) ++ { ++ if (gpz == gtz) + { +- return false; ++ // path found, Z coordinates are okay, return target point ++ return new GeoLocation(gtx, gty, gtz); + } ++ ++ // path found, Z coordinates are not okay, return last good point ++ return new GeoLocation(gpx, gpy, gpz); + } ++ } ++ ++ return new GeoLocation(gox, goy, goz); ++ } ++ ++ /** ++ * Returns diagonal NSWE flag format of combined two NSWE flags. ++ * @param dirX : X direction NSWE flag ++ * @param dirY : Y direction NSWE flag ++ * @return byte : NSWE flag of combined direction ++ */ ++ private static byte getDirXY(byte dirX, byte dirY) ++ { ++ // check axis directions ++ if (dirY == GeoStructure.CELL_FLAG_N) ++ { ++ if (dirX == GeoStructure.CELL_FLAG_W) ++ { ++ return GeoStructure.CELL_FLAG_NW; ++ } + +- prevX = curX; +- prevY = curY; +- prevZ = curZ; ++ return GeoStructure.CELL_FLAG_NE; + } + +- if (hasGeoPos(prevX, prevY) && (prevZ != toZ)) ++ if (dirX == GeoStructure.CELL_FLAG_W) + { +- // Different floors. +- return false; ++ return GeoStructure.CELL_FLAG_SW; + } + +- return true; ++ return GeoStructure.CELL_FLAG_SE; + } + ++ /** ++ * 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 ++ */ ++ public List findPath(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) ++ { ++ return null; ++ } ++ ++ /** ++ * Returns the instance of the {@link GeoEngine}. ++ * @return {@link GeoEngine} : The instance. ++ */ + public static GeoEngine getInstance() + { + return SingletonHolder.INSTANCE; +@@ -752,6 +947,6 @@ + + private static class SingletonHolder + { +- protected static final GeoEngine INSTANCE = new GeoEngine(); ++ protected static final GeoEngine INSTANCE = Config.PATHFINDING ? new GeoEnginePathfinding() : new GeoEngine(); + } +-} ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/GeoEnginePathfinding.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/GeoEnginePathfinding.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/GeoEnginePathfinding.java (working copy) +@@ -20,88 +20,84 @@ + import java.util.LinkedList; + import java.util.List; + import java.util.ListIterator; +-import java.util.logging.Level; +-import java.util.logging.Logger; + + import org.l2jmobius.Config; +-import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; +-import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +-import org.l2jmobius.gameserver.geoengine.pathfinding.CellNode; +-import org.l2jmobius.gameserver.geoengine.pathfinding.CellNodeBuffer; +-import org.l2jmobius.gameserver.geoengine.pathfinding.NodeLoc; +-import org.l2jmobius.gameserver.model.World; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoLocation; ++import org.l2jmobius.gameserver.geoengine.pathfinding.Node; ++import org.l2jmobius.gameserver.geoengine.pathfinding.NodeBuffer; ++import org.l2jmobius.gameserver.model.Location; + import org.l2jmobius.gameserver.model.instancezone.Instance; + + /** +- * @author -Nemesiss- ++ * @author Hasha + */ +-public class GeoEnginePathfinding ++final class GeoEnginePathfinding extends GeoEngine + { +- private static final Logger LOGGER = Logger.getLogger(GeoEnginePathfinding.class.getName()); ++ // pre-allocated buffers ++ private final BufferHolder[] _buffers; + +- private BufferInfo[] _buffers; +- + protected GeoEnginePathfinding() + { +- try ++ super(); ++ ++ final String[] array = Config.PATHFIND_BUFFERS.split(";"); ++ _buffers = new BufferHolder[array.length]; ++ int count = 0; ++ for (int i = 0; i < array.length; i++) + { +- final String[] array = Config.PATHFIND_BUFFERS.split(";"); ++ final String buf = array[i]; ++ final String[] args = buf.split("x"); + +- _buffers = new BufferInfo[array.length]; +- +- String buf; +- String[] args; +- for (int i = 0; i < array.length; i++) ++ try + { +- buf = array[i]; +- args = buf.split("x"); +- if (args.length != 2) +- { +- throw new Exception("Invalid buffer definition: " + buf); +- } +- +- _buffers[i] = new BufferInfo(Integer.parseInt(args[0]), Integer.parseInt(args[1])); ++ final int size = Integer.parseInt(args[1]); ++ count += size; ++ _buffers[i] = new BufferHolder(Integer.parseInt(args[0]), size); + } ++ catch (Exception e) ++ { ++ LOGGER.warning("GeoEnginePathfinding: Can not load buffer setting: " + buf); ++ } + } +- catch (Exception e) +- { +- LOGGER.log(Level.WARNING, "CellPathFinding: Problem during buffer init: " + e.getMessage(), e); +- throw new Error("CellPathFinding: load aborted"); +- } ++ ++ LOGGER.info("GeoEnginePathfinding: Loaded " + count + " node buffers."); + } + +- public boolean pathNodesExist(short regionoffset) ++ @Override ++ public List findPath(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + { +- return false; +- } +- +- public List findPath(int x, int y, int z, int tx, int ty, int tz, Instance instance) +- { +- final int gx = GeoEngine.getInstance().getGeoX(x); +- final int gy = GeoEngine.getInstance().getGeoY(y); +- if (!GeoEngine.getInstance().hasGeo(x, y)) ++ // get origin and check existing geo coords ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) + { + 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)) ++ ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coords ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) + { + 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)))); ++ ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // Prepare buffer for pathfinding calculations ++ final NodeBuffer buffer = getBuffer(64 + (2 * Math.max(Math.abs(gox - gtx), Math.abs(goy - gty)))); + if (buffer == null) + { + return null; + } + +- List path = null; ++ // find path ++ List path = null; + try + { +- final CellNode result = buffer.findPath(gx, gy, gz, gtx, gty, gtz); +- ++ final Node result = buffer.findPath(gox, goy, goz, gtx, gty, gtz); + if (result == null) + { + return null; +@@ -125,33 +121,45 @@ + return path; + } + +- int currentX, currentY, currentZ; +- ListIterator middlePoint; ++ // get path list iterator ++ final ListIterator point = path.listIterator(); + +- middlePoint = path.listIterator(); +- currentX = x; +- currentY = y; +- currentZ = z; ++ // get node A (origin) ++ int nodeAx = gox; ++ int nodeAy = goy; ++ short nodeAz = goz; + +- while (middlePoint.hasNext()) ++ // get node B ++ GeoLocation nodeB = (GeoLocation) point.next(); ++ ++ // iterate thought the path to optimize it ++ int count = 0; ++ while (point.hasNext() && (count++ < Config.MAX_ITERATIONS)) + { +- final AbstractNodeLoc locMiddle = middlePoint.next(); +- if (!middlePoint.hasNext()) +- { +- break; +- } ++ // get node C ++ final GeoLocation nodeC = (GeoLocation) path.get(point.nextIndex()); + +- final AbstractNodeLoc locEnd = path.get(middlePoint.nextIndex()); +- if (GeoEngine.getInstance().canMoveToTarget(currentX, currentY, currentZ, locEnd.getX(), locEnd.getY(), locEnd.getZ(), instance)) ++ // check movement from node A to node C ++ final GeoLocation loc = checkMove(nodeAx, nodeAy, nodeAz, nodeC.getGeoX(), nodeC.getGeoY(), nodeC.getZ(), instance); ++ if ((loc.getGeoX() == nodeC.getGeoX()) && (loc.getGeoY() == nodeC.getGeoY())) + { +- middlePoint.remove(); ++ // can move from node A to node C ++ ++ // remove node B ++ point.remove(); + } + else + { +- currentX = locMiddle.getX(); +- currentY = locMiddle.getY(); +- currentZ = locMiddle.getZ(); ++ // can not move from node A to node C ++ ++ // set node A (node B is part of path, update A coordinates) ++ nodeAx = nodeB.getGeoX(); ++ nodeAy = nodeB.getGeoY(); ++ nodeAz = (short) nodeB.getZ(); + } ++ ++ // set node B ++ nodeB = (GeoLocation) point.next(); + } + + return path; +@@ -158,144 +166,102 @@ + } + + /** +- * Convert geodata position to pathnode position +- * @param geo_pos +- * @return pathnode position ++ * Create list of node locations as result of calculated buffer node tree. ++ * @param node : the entry point ++ * @return List : list of node location + */ +- public short getNodePos(int geo_pos) ++ private static List constructPath(Node node) + { +- return (short) (geo_pos >> 3); // OK? +- } +- +- /** +- * Convert node position to pathnode block position +- * @param node_pos +- * @return pathnode block position (0...255) +- */ +- public short getNodeBlock(int node_pos) +- { +- return (short) (node_pos % 256); +- } +- +- public byte getRegionX(int node_pos) +- { +- return (byte) ((node_pos >> 8) + World.TILE_X_MIN); +- } +- +- public byte getRegionY(int node_pos) +- { +- return (byte) ((node_pos >> 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 node_x rx +- * @return +- */ +- public int calculateWorldX(short node_x) +- { +- return World.MAP_MIN_X + (node_x * 128) + 48; +- } +- +- /** +- * Convert pathnode y to World y position +- * @param node_y +- * @return +- */ +- public int calculateWorldY(short node_y) +- { +- return World.MAP_MIN_Y + (node_y * 128) + 48; +- } +- +- private List constructPath(AbstractNode nodeValue) +- { +- final LinkedList path = new LinkedList<>(); +- int previousDirectionX = Integer.MIN_VALUE; +- int previousDirectionY = Integer.MIN_VALUE; +- int directionX, directionY; ++ // create empty list ++ final LinkedList list = new LinkedList<>(); + +- AbstractNode node = nodeValue; +- while (node.getParent() != null) ++ // set direction X/Y ++ int dx = 0; ++ int dy = 0; ++ ++ // get target parent ++ Node target = node; ++ Node parent = target.getParent(); ++ ++ // while parent exists ++ int count = 0; ++ while ((parent != null) && (count++ < Config.MAX_ITERATIONS)) + { +- directionX = node.getLoc().getNodeX() - node.getParent().getLoc().getNodeX(); +- directionY = node.getLoc().getNodeY() - node.getParent().getLoc().getNodeY(); ++ // get parent <> target direction X/Y ++ final int nx = parent.getLoc().getGeoX() - target.getLoc().getGeoX(); ++ final int ny = parent.getLoc().getGeoY() - target.getLoc().getGeoY(); + +- // only add a new route point if moving direction changes +- if ((directionX != previousDirectionX) || (directionY != previousDirectionY)) ++ // direction has changed? ++ if ((dx != nx) || (dy != ny)) + { +- previousDirectionX = directionX; +- previousDirectionY = directionY; ++ // add node to the beginning of the list ++ list.addFirst(target.getLoc()); + +- path.addFirst(node.getLoc()); +- node.setLoc(null); ++ // update direction X/Y ++ dx = nx; ++ dy = ny; + } + +- node = node.getParent(); ++ // move to next node, set target and get its parent ++ target = parent; ++ parent = target.getParent(); + } + +- return path; ++ // return list ++ return list; + } + +- private CellNodeBuffer alloc(int size) ++ /** ++ * Provides optimize selection of the buffer. When all pre-initialized buffer are locked, creates new buffer. ++ * @param size : pre-calculated minimal required size ++ * @return NodeBuffer : buffer ++ */ ++ private final NodeBuffer getBuffer(int size) + { +- CellNodeBuffer current = null; +- for (BufferInfo i : _buffers) ++ NodeBuffer current = null; ++ for (BufferHolder holder : _buffers) + { +- if (i.mapSize >= size) ++ // Find proper size of buffer ++ if (holder._size < size) + { +- for (CellNodeBuffer buf : i.buffer) ++ continue; ++ } ++ ++ // Find unlocked NodeBuffer ++ for (NodeBuffer buffer : holder._buffer) ++ { ++ if (!buffer.isLocked()) + { +- if (buf.lock()) +- { +- current = buf; +- break; +- } ++ continue; + } +- if (current != null) +- { +- break; +- } + +- // not found, allocate temporary buffer +- current = new CellNodeBuffer(i.mapSize); +- current.lock(); +- if (i.buffer.size() < i.count) +- { +- i.buffer.add(current); +- break; +- } ++ return buffer; + } ++ ++ // NodeBuffer not found, allocate temporary buffer ++ current = new NodeBuffer(holder._size); ++ current.isLocked(); + } + + return current; + } + +- private static final class BufferInfo ++ /** ++ * NodeBuffer container with specified size and count of separate buffers. ++ */ ++ private static final class BufferHolder + { +- final int mapSize; +- final int count; +- ArrayList buffer; ++ final int _size; ++ List _buffer; + +- public BufferInfo(int size, int cnt) ++ public BufferHolder(int size, int count) + { +- mapSize = size; +- count = cnt; +- buffer = new ArrayList<>(count); ++ _size = size; ++ _buffer = new ArrayList<>(count); ++ for (int i = 0; i < count; i++) ++ { ++ _buffer.add(new NodeBuffer(size)); ++ } + } + } +- +- public static GeoEnginePathfinding getInstance() +- { +- return SingletonHolder.INSTANCE; +- } +- +- private static class SingletonHolder +- { +- protected static final GeoEnginePathfinding INSTANCE = new GeoEnginePathfinding(); +- } +-} ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java (working copy) +@@ -0,0 +1,197 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++import java.io.IOException; ++ ++/** ++ * @author Hasha ++ */ ++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 height of cell, which is closest to given coordinates.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @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 getHeightNearestOriginal(int geoX, int geoY, int worldZ); ++ ++ /** ++ * Returns the height of cell, which is first above 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, above given coordinates. ++ */ ++ public abstract short getHeightAbove(int geoX, int geoY, int worldZ); ++ ++ /** ++ * Returns the height of cell, which is first below 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, below given coordinates. ++ */ ++ public abstract short getHeightBelow(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 the NSWE flag byte of cell, which is closest to given coordinates.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @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 getNsweNearestOriginal(int geoX, int geoY, int worldZ); ++ ++ /** ++ * Returns the NSWE flag byte of cell, which is first above 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 getNsweAbove(int geoX, int geoY, int worldZ); ++ ++ /** ++ * Returns the NSWE flag byte of cell, which is first below 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 getNsweBelow(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 above given coordinates.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @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 getIndexAboveOriginal(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 index to data of the cell, which is first layer below given coordinates.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @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 getIndexBelowOriginal(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 height of cell given by cell index.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @param index : Index of the cell. ++ * @return short : Cell geodata Z coordinate, below given coordinates. ++ */ ++ public abstract short getHeightOriginal(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); ++ ++ /** ++ * Returns the NSWE flag byte of cell given by cell index.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @param index : Index of the cell. ++ * @return short : Cell geodata Z coordinate, below given coordinates. ++ */ ++ public abstract byte getNsweOriginal(int index); ++ ++ /** ++ * Sets the NSWE flag byte of cell given by cell index. ++ * @param index : Index of the cell. ++ * @param nswe : New NSWE flag byte. ++ */ ++ public abstract void setNswe(int index, byte nswe); ++ ++ /** ++ * Saves the block in L2D format to {@link BufferedOutputStream}. Used only for L2D geodata conversion. ++ * @param stream : The stream. ++ * @throws IOException : Can't save the block to steam. ++ */ ++ public abstract void saveBlock(BufferedOutputStream stream) throws IOException; ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java (working copy) +@@ -0,0 +1,252 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++import java.io.IOException; ++import java.nio.ByteBuffer; ++ ++/** ++ * @author Hasha ++ */ ++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. ++ * @param format : GeoFormat specifying format of loaded data. ++ */ ++ public BlockComplex(ByteBuffer bb, GeoFormat format) ++ { ++ // initialize buffer ++ _buffer = new byte[GeoStructure.BLOCK_CELLS * 3]; ++ ++ // load data ++ for (int i = 0; i < GeoStructure.BLOCK_CELLS; i++) ++ { ++ if (format != GeoFormat.L2D) ++ { ++ // 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); ++ } ++ else ++ { ++ // get nswe ++ final byte nswe = bb.get(); ++ _buffer[i * 3] = nswe; ++ ++ // get height ++ final short height = bb.getShort(); ++ _buffer[(i * 3) + 1] = (byte) (height & 0x00FF); ++ _buffer[(i * 3) + 2] = (byte) (height >> 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 short getHeightNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getHeightNearest(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeightAbove(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 short height = (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ ++ // check and return height ++ return height > worldZ ? height : Short.MIN_VALUE; ++ } ++ ++ @Override ++ public short getHeightBelow(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 short height = (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ ++ // check and return height ++ return height < worldZ ? height : Short.MAX_VALUE; ++ } ++ ++ @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 byte getNsweNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getNsweNearest(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public byte getNsweAbove(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 ? _buffer[index] : 0; ++ } ++ ++ @Override ++ public byte getNsweBelow(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 ? _buffer[index] : 0; ++ } ++ ++ @Override ++ public 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 getIndexAboveOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexAbove(geoX, geoY, worldZ); ++ } ++ ++ @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 int getIndexBelowOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexBelow(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeight(int index) ++ { ++ return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ } ++ ++ @Override ++ public short getHeightOriginal(int index) ++ { ++ return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ } ++ ++ @Override ++ public byte getNswe(int index) ++ { ++ return _buffer[index]; ++ } ++ ++ @Override ++ public byte getNsweOriginal(int index) ++ { ++ return _buffer[index]; ++ } ++ ++ @Override ++ public void setNswe(int index, byte nswe) ++ { ++ _buffer[index] = nswe; ++ } ++ ++ @Override ++ public void saveBlock(BufferedOutputStream stream) throws IOException ++ { ++ // write block type ++ stream.write(GeoStructure.TYPE_COMPLEX_L2D); ++ ++ // write block data ++ stream.write(_buffer, 0, GeoStructure.BLOCK_CELLS * 3); ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java (working copy) +@@ -0,0 +1,176 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++import java.io.IOException; ++import java.nio.ByteBuffer; ++ ++/** ++ * @author Hasha ++ */ ++public class BlockFlat extends ABlock ++{ ++ protected final short _height; ++ protected byte _nswe; ++ ++ /** ++ * Creates FlatBlock. ++ * @param bb : Input byte buffer. ++ * @param format : GeoFormat specifying format of loaded data. ++ */ ++ public BlockFlat(ByteBuffer bb, GeoFormat format) ++ { ++ _height = bb.getShort(); ++ _nswe = format != GeoFormat.L2D ? 0x0F : (byte) (0xFF); ++ if (format == GeoFormat.L2OFF) ++ { ++ bb.getShort(); ++ } ++ } ++ ++ @Override ++ public boolean hasGeoPos() ++ { ++ return true; ++ } ++ ++ @Override ++ public short getHeightNearest(int geoX, int geoY, int worldZ) ++ { ++ return _height; ++ } ++ ++ @Override ++ public short getHeightNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return _height; ++ } ++ ++ @Override ++ public short getHeightAbove(int geoX, int geoY, int worldZ) ++ { ++ // check and return height ++ return _height > worldZ ? _height : Short.MIN_VALUE; ++ } ++ ++ @Override ++ public short getHeightBelow(int geoX, int geoY, int worldZ) ++ { ++ // check and return height ++ return _height < worldZ ? _height : Short.MAX_VALUE; ++ } ++ ++ @Override ++ public byte getNsweNearest(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweAbove(int geoX, int geoY, int worldZ) ++ { ++ // check height and return nswe ++ return _height > worldZ ? _nswe : 0; ++ } ++ ++ @Override ++ public byte getNsweBelow(int geoX, int geoY, int worldZ) ++ { ++ // check height and return nswe ++ return _height < worldZ ? _nswe : 0; ++ } ++ ++ @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 getIndexAboveOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexAbove(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public int getIndexBelow(int geoX, int geoY, int worldZ) ++ { ++ // check height and return index ++ return _height < worldZ ? 0 : -1; ++ } ++ ++ @Override ++ public int getIndexBelowOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexBelow(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeight(int index) ++ { ++ return _height; ++ } ++ ++ @Override ++ public short getHeightOriginal(int index) ++ { ++ return _height; ++ } ++ ++ @Override ++ public byte getNswe(int index) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweOriginal(int index) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public void setNswe(int index, byte nswe) ++ { ++ _nswe = nswe; ++ } ++ ++ @Override ++ public void saveBlock(BufferedOutputStream stream) throws IOException ++ { ++ // write block type ++ stream.write(GeoStructure.TYPE_FLAT_L2D); ++ ++ // write height ++ stream.write((byte) (_height & 0x00FF)); ++ stream.write((byte) (_height >> 8)); ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java (working copy) +@@ -0,0 +1,465 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++import java.io.IOException; ++import java.nio.ByteBuffer; ++import java.nio.ByteOrder; ++import java.util.Arrays; ++ ++/** ++ * @author Hasha ++ */ ++public class BlockMultilayer extends ABlock ++{ ++ private static final int MAX_LAYERS = Byte.MAX_VALUE; ++ ++ private static ByteBuffer _temp; ++ ++ /** ++ * Initializes the temporarily buffer. ++ */ ++ public static void initialize() ++ { ++ // initialize temporarily buffer and sorting mechanism ++ _temp = ByteBuffer.allocate(GeoStructure.BLOCK_CELLS * MAX_LAYERS * 3); ++ _temp.order(ByteOrder.LITTLE_ENDIAN); ++ } ++ ++ /** ++ * Releases temporarily buffer. ++ */ ++ public static void release() ++ { ++ _temp = null; ++ } ++ ++ protected byte[] _buffer; ++ ++ /** ++ * Implicit constructor for children class. ++ */ ++ protected BlockMultilayer() ++ { ++ _buffer = null; ++ } ++ ++ /** ++ * Creates MultilayerBlock. ++ * @param bb : Input byte buffer. ++ * @param format : GeoFormat specifying format of loaded data. ++ */ ++ public BlockMultilayer(ByteBuffer bb, GeoFormat format) ++ { ++ // 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 = format != GeoFormat.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++) ++ { ++ if (format != GeoFormat.L2D) ++ { ++ // get data ++ final short data = bb.getShort(); ++ ++ // add nswe and height ++ _temp.put((byte) (data & 0x000F)); ++ _temp.putShort((short) ((short) (data & 0xFFF0) >> 1)); ++ } ++ else ++ { ++ // add nswe ++ _temp.put(bb.get()); ++ ++ // add height ++ _temp.putShort(bb.getShort()); ++ } ++ } ++ } ++ ++ // 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 short getHeightNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getHeightNearest(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeightAbove(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 height ++ if (height > worldZ) ++ { ++ return (short) height; ++ } ++ ++ // move index to next layer ++ index -= 3; ++ } ++ ++ // none layer found, return minimum value ++ return Short.MIN_VALUE; ++ } ++ ++ @Override ++ public short getHeightBelow(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 height ++ if (height < worldZ) ++ { ++ return (short) height; ++ } ++ ++ // move index to next layer ++ index += 3; ++ } ++ ++ // none layer found, return maximum value ++ return Short.MAX_VALUE; ++ } ++ ++ @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 byte getNsweNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getNsweNearest(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public byte getNsweAbove(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 nswe ++ if (height > worldZ) ++ { ++ return _buffer[index]; ++ } ++ ++ // move index to next layer ++ index -= 3; ++ } ++ ++ // none layer found, block movement ++ return 0; ++ } ++ ++ @Override ++ public byte getNsweBelow(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 nswe ++ if (height < worldZ) ++ { ++ return _buffer[index]; ++ } ++ ++ // move index to next layer ++ index += 3; ++ } ++ ++ // none layer found, block movement ++ return 0; ++ } ++ ++ @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 first layer data (first from bottom) ++ byte layers = _buffer[index++]; ++ ++ // loop though all cell layers, find closest layer ++ 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; ++ } ++ ++ // none layer found ++ return -1; ++ } ++ ++ @Override ++ public int getIndexAboveOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexAbove(geoX, geoY, worldZ); ++ } ++ ++ @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; ++ } ++ ++ // none layer found ++ return -1; ++ } ++ ++ @Override ++ public int getIndexBelowOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexBelow(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeight(int index) ++ { ++ // get height ++ return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ } ++ ++ @Override ++ public short getHeightOriginal(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]; ++ } ++ ++ @Override ++ public byte getNsweOriginal(int index) ++ { ++ // get nswe ++ return _buffer[index]; ++ } ++ ++ @Override ++ public void setNswe(int index, byte nswe) ++ { ++ // set nswe ++ _buffer[index] = nswe; ++ } ++ ++ @Override ++ public void saveBlock(BufferedOutputStream stream) throws IOException ++ { ++ // write block type ++ stream.write(GeoStructure.TYPE_MULTILAYER_L2D); ++ ++ // for each cell ++ int index = 0; ++ for (int i = 0; i < GeoStructure.BLOCK_CELLS; i++) ++ { ++ // write layers count ++ final byte layers = _buffer[index++]; ++ stream.write(layers); ++ ++ // write cell data ++ stream.write(_buffer, index, layers * 3); ++ ++ // move index to next cell ++ index += layers * 3; ++ } ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java (working copy) +@@ -0,0 +1,150 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++ ++/** ++ * @author Hasha ++ */ ++public class BlockNull extends ABlock ++{ ++ private final byte _nswe; ++ ++ public BlockNull() ++ { ++ _nswe = (byte) 0xFF; ++ } ++ ++ @Override ++ public boolean hasGeoPos() ++ { ++ return false; ++ } ++ ++ @Override ++ public short getHeightNearest(int geoX, int geoY, int worldZ) ++ { ++ return (short) worldZ; ++ } ++ ++ @Override ++ public short getHeightNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return (short) worldZ; ++ } ++ ++ @Override ++ public short getHeightAbove(int geoX, int geoY, int worldZ) ++ { ++ return (short) worldZ; ++ } ++ ++ @Override ++ public short getHeightBelow(int geoX, int geoY, int worldZ) ++ { ++ return (short) worldZ; ++ } ++ ++ @Override ++ public byte getNsweNearest(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweAbove(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweBelow(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) ++ { ++ return 0; ++ } ++ ++ @Override ++ public int getIndexAboveOriginal(int geoX, int geoY, int worldZ) ++ { ++ return 0; ++ } ++ ++ @Override ++ public int getIndexBelow(int geoX, int geoY, int worldZ) ++ { ++ return 0; ++ } ++ ++ @Override ++ public int getIndexBelowOriginal(int geoX, int geoY, int worldZ) ++ { ++ return 0; ++ } ++ ++ @Override ++ public short getHeight(int index) ++ { ++ return 0; ++ } ++ ++ @Override ++ public short getHeightOriginal(int index) ++ { ++ return 0; ++ } ++ ++ @Override ++ public byte getNswe(int index) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweOriginal(int index) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public void setNswe(int index, byte nswe) ++ { ++ } ++ ++ @Override ++ public void saveBlock(BufferedOutputStream stream) ++ { ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java (nonexistent) +@@ -1,48 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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() +- { +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/ComplexBlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/ComplexBlock.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/ComplexBlock.java (nonexistent) +@@ -1,77 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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; +- +-/** +- * @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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/FlatBlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/FlatBlock.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/FlatBlock.java (nonexistent) +@@ -1,56 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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; +- +-/** +- * @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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/GeoFormat.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/GeoFormat.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/GeoFormat.java (working copy) +@@ -0,0 +1,39 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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 Hasha ++ */ ++public enum GeoFormat ++{ ++ L2J("%d_%d.l2j"), ++ L2OFF("%d_%d_conv.dat"), ++ L2D("%d_%d.l2d"); ++ ++ private final String _filename; ++ ++ private GeoFormat(String filename) ++ { ++ _filename = filename; ++ } ++ ++ public String getFilename() ++ { ++ return _filename; ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/GeoLocation.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/GeoLocation.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/GeoLocation.java (working copy) +@@ -0,0 +1,67 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.geoengine.GeoEngine; ++import org.l2jmobius.gameserver.model.Location; ++ ++/** ++ * @author Hasha ++ */ ++public class GeoLocation extends Location ++{ ++ private byte _nswe; ++ ++ public GeoLocation(int x, int y, int z) ++ { ++ super(x, y, GeoEngine.getInstance().getHeightNearest(x, y, z)); ++ _nswe = GeoEngine.getInstance().getNsweNearest(x, y, z); ++ } ++ ++ public void set(int x, int y, short z) ++ { ++ super.setXYZ(x, y, GeoEngine.getInstance().getHeightNearest(x, y, z)); ++ _nswe = GeoEngine.getInstance().getNsweNearest(x, y, z); ++ } ++ ++ public int getGeoX() ++ { ++ return _x; ++ } ++ ++ public int getGeoY() ++ { ++ return _y; ++ } ++ ++ @Override ++ public int getX() ++ { ++ return GeoEngine.getWorldX(_x); ++ } ++ ++ @Override ++ public int getY() ++ { ++ return GeoEngine.getWorldY(_y); ++ } ++ ++ public byte getNSWE() ++ { ++ return _nswe; ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java (working copy) +@@ -0,0 +1,70 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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; ++ ++/** ++ * @author Hasha ++ */ ++public class GeoStructure ++{ ++ // cells ++ public static final byte CELL_FLAG_E = 1 << 0; ++ public static final byte CELL_FLAG_W = 1 << 1; ++ public static final byte CELL_FLAG_S = 1 << 2; ++ public static final byte CELL_FLAG_N = 1 << 3; ++ public static final byte CELL_FLAG_SE = 1 << 4; ++ public static final byte CELL_FLAG_SW = 1 << 5; ++ public static final byte CELL_FLAG_NE = 1 << 6; ++ public static final byte CELL_FLAG_NW = (byte) (1 << 7); ++ ++ public static final int CELL_HEIGHT = 8; ++ public static final int CELL_IGNORE_HEIGHT = CELL_HEIGHT * 6; ++ ++ // blocks ++ public static final byte TYPE_FLAT_L2J_L2OFF = 0; ++ public static final byte TYPE_FLAT_L2D = (byte) 0xD0; ++ public static final byte TYPE_COMPLEX_L2J = 1; ++ public static final byte TYPE_COMPLEX_L2OFF = 0x40; ++ public static final byte TYPE_COMPLEX_L2D = (byte) 0xD1; ++ 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) ++ public static final byte TYPE_MULTILAYER_L2D = (byte) 0xD2; ++ ++ 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; ++ ++ // regions ++ 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; ++ ++ // global geodata ++ private static final int GEO_REGIONS_X = ((World.TILE_X_MAX - World.TILE_X_MIN) + 1); ++ private 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 +Index: java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java (nonexistent) +@@ -1,42 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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); +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/IGeoObject.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/IGeoObject.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/IGeoObject.java (working copy) +@@ -0,0 +1,53 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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 Hasha ++ */ ++public interface IGeoObject ++{ ++ /** ++ * Returns geodata X coordinate of the {@link IGeoObject}. ++ * @return int : Geodata X coordinate. ++ */ ++ int getGeoX(); ++ ++ /** ++ * Returns geodata Y coordinate of the {@link IGeoObject}. ++ * @return int : Geodata Y coordinate. ++ */ ++ int getGeoY(); ++ ++ /** ++ * Returns geodata Z coordinate of the {@link IGeoObject}. ++ * @return int : Geodata Z coordinate. ++ */ ++ int getGeoZ(); ++ ++ /** ++ * Returns height of the {@link IGeoObject}. ++ * @return int : Height. ++ */ ++ int getHeight(); ++ ++ /** ++ * Returns {@link IGeoObject} data. ++ * @return byte[][] : {@link IGeoObject} data. ++ */ ++ byte[][] getObjectGeoData(); ++} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java (nonexistent) +@@ -1,47 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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(); +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/MultilayerBlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/MultilayerBlock.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/MultilayerBlock.java (nonexistent) +@@ -1,183 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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; +- +-/** +- * @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); +- +- // 1 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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/NullRegion.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/NullRegion.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/NullRegion.java (nonexistent) +@@ -1,55 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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 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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/Region.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/Region.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/Region.java (nonexistent) +@@ -1,92 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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; +- +-/** +- * @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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java (nonexistent) +@@ -1,87 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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 T _loc; +- private AbstractNode _parent; +- +- public AbstractNode(T loc) +- { +- _loc = loc; +- } +- +- public void setParent(AbstractNode p) +- { +- _parent = p; +- } +- +- public AbstractNode getParent() +- { +- return _parent; +- } +- +- public T getLoc() +- { +- return _loc; +- } +- +- public void setLoc(T l) +- { +- _loc = l; +- } +- +- @Override +- public int hashCode() +- { +- final int prime = 31; +- int result = 1; +- result = (prime * result) + ((_loc == null) ? 0 : _loc.hashCode()); +- return result; +- } +- +- @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 +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java (nonexistent) +@@ -1,33 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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 int getNodeX(); +- +- public abstract int getNodeY(); +-} +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNode.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNode.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNode.java (nonexistent) +@@ -1,67 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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 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 +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNodeBuffer.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNodeBuffer.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNodeBuffer.java (nonexistent) +@@ -1,348 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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.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 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) +- { +- _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(); +- } +- +- 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 void getNeighbors() +- { +- if (!_current.getLoc().canGoAll()) +- { +- 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); +- } +- +- // SouthEast +- if ((nodeE != null) && (nodeS != null)) +- { +- if (nodeE.getLoc().canGoSouth() && nodeS.getLoc().canGoEast()) +- { +- addNode(x + 1, y + 1, z, true); +- } +- } +- +- // SouthWest +- if ((nodeS != null) && (nodeW != null)) +- { +- if (nodeW.getLoc().canGoSouth() && nodeS.getLoc().canGoWest()) +- { +- addNode(x - 1, y + 1, z, true); +- } +- } +- +- // NorthEast +- if ((nodeN != null) && (nodeE != null)) +- { +- if (nodeE.getLoc().canGoNorth() && nodeN.getLoc().canGoEast()) +- { +- addNode(x + 1, y - 1, z, true); +- } +- } +- +- // NorthWest +- if ((nodeN != null) && (nodeW != null)) +- { +- if (nodeW.getLoc().canGoNorth() && nodeN.getLoc().canGoWest()) +- { +- addNode(x - 1, y - 1, z, true); +- } +- } +- } +- +- private 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(); +- // reinit node if needed +- if (result.getLoc() != null) +- { +- result.getLoc().set(x, y, z); +- } +- else +- { +- result.setLoc(new NodeLoc(x, y, z)); +- } +- } +- +- return result; +- } +- +- private 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 boolean isHighWeight(int x, int y, int z) +- { +- final CellNode result = getNode(x, y, z); +- if (result == null) +- { +- return true; +- } +- +- if (!result.getLoc().canGoAll()) +- { +- return true; +- } +- if (Math.abs(result.getLoc().getZ() - z) > 16) +- { +- return true; +- } +- +- return false; +- } +- +- private 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 +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java (working copy) +@@ -0,0 +1,87 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.geodata.GeoLocation; ++ ++/** ++ * @author Hasha ++ */ ++public class Node ++{ ++ // node coords and nswe flag ++ private GeoLocation _loc; ++ ++ // node parent (for reverse path construction) ++ private Node _parent; ++ // node child (for moving over nodes during iteration) ++ private Node _child; ++ ++ // node G cost (movement cost = parent movement cost + current movement cost) ++ private double _cost = -1000; ++ ++ public void setLoc(int x, int y, int z) ++ { ++ _loc = new GeoLocation(x, y, z); ++ } ++ ++ public GeoLocation getLoc() ++ { ++ return _loc; ++ } ++ ++ public void setParent(Node parent) ++ { ++ _parent = parent; ++ } ++ ++ public Node getParent() ++ { ++ return _parent; ++ } ++ ++ public void setChild(Node child) ++ { ++ _child = child; ++ } ++ ++ public Node getChild() ++ { ++ return _child; ++ } ++ ++ public void setCost(double cost) ++ { ++ _cost = cost; ++ } ++ ++ public double getCost() ++ { ++ return _cost; ++ } ++ ++ public void free() ++ { ++ // reset node location ++ _loc = null; ++ ++ // reset node parent, child and cost ++ _parent = null; ++ _child = null; ++ _cost = -1000; ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java (working copy) +@@ -0,0 +1,306 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.concurrent.locks.ReentrantLock; ++ ++import org.l2jmobius.Config; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; ++ ++/** ++ * @author DS, Hasha; Credits to Diamond ++ */ ++public class NodeBuffer ++{ ++ private final ReentrantLock _lock = new ReentrantLock(); ++ private final int _size; ++ private final Node[][] _buffer; ++ ++ // center coordinates ++ private int _cx = 0; ++ private int _cy = 0; ++ ++ // target coordinates ++ private int _gtx = 0; ++ private int _gty = 0; ++ private short _gtz = 0; ++ ++ private Node _current = null; ++ ++ /** ++ * Constructor of NodeBuffer. ++ * @param size : one dimension size of buffer ++ */ ++ public NodeBuffer(int size) ++ { ++ // set size ++ _size = size; ++ ++ // initialize buffer ++ _buffer = new Node[size][size]; ++ for (int x = 0; x < size; x++) ++ { ++ for (int y = 0; y < size; y++) ++ { ++ _buffer[x][y] = 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 Node : first node of path ++ */ ++ public Node findPath(int gox, int goy, short goz, int gtx, int gty, short gtz) ++ { ++ // set coordinates (middle of the line (gox,goy) - (gtx,gty), will be in the center of the buffer) ++ _cx = gox + ((gtx - gox - _size) / 2); ++ _cy = goy + ((gty - goy - _size) / 2); ++ ++ _gtx = gtx; ++ _gty = gty; ++ _gtz = gtz; ++ ++ _current = getNode(gox, goy, goz); ++ _current.setCost(getCostH(gox, goy, goz)); ++ ++ int count = 0; ++ do ++ { ++ // reached target? ++ if ((_current.getLoc().getGeoX() == _gtx) && (_current.getLoc().getGeoY() == _gty) && (Math.abs(_current.getLoc().getZ() - _gtz) < 8)) ++ { ++ return _current; ++ } ++ ++ // expand current node ++ expand(); ++ ++ // move pointer ++ _current = _current.getChild(); ++ } ++ while ((_current != null) && (count++ < Config.MAX_ITERATIONS)); ++ ++ return null; ++ } ++ ++ public boolean isLocked() ++ { ++ return _lock.tryLock(); ++ } ++ ++ public void free() ++ { ++ _current = null; ++ ++ for (Node[] nodes : _buffer) ++ { ++ for (Node node : nodes) ++ { ++ if (node.getLoc() != null) ++ { ++ node.free(); ++ } ++ } ++ } ++ ++ _lock.unlock(); ++ } ++ ++ /** ++ * Check _current Node and add its neighbors to the buffer. ++ */ ++ private final void expand() ++ { ++ // can't move anywhere, don't expand ++ final byte nswe = _current.getLoc().getNSWE(); ++ if (nswe == 0) ++ { ++ return; ++ } ++ ++ // get geo coords of the node to be expanded ++ final int x = _current.getLoc().getGeoX(); ++ final int y = _current.getLoc().getGeoY(); ++ final short z = (short) _current.getLoc().getZ(); ++ ++ // can move north, expand ++ if ((nswe & GeoStructure.CELL_FLAG_N) != 0) ++ { ++ addNode(x, y - 1, z, Config.BASE_WEIGHT); ++ } ++ ++ // can move south, expand ++ if ((nswe & GeoStructure.CELL_FLAG_S) != 0) ++ { ++ addNode(x, y + 1, z, Config.BASE_WEIGHT); ++ } ++ ++ // can move west, expand ++ if ((nswe & GeoStructure.CELL_FLAG_W) != 0) ++ { ++ addNode(x - 1, y, z, Config.BASE_WEIGHT); ++ } ++ ++ // can move east, expand ++ if ((nswe & GeoStructure.CELL_FLAG_E) != 0) ++ { ++ addNode(x + 1, y, z, Config.BASE_WEIGHT); ++ } ++ ++ // can move north-west, expand ++ if ((nswe & GeoStructure.CELL_FLAG_NW) != 0) ++ { ++ addNode(x - 1, y - 1, z, Config.DIAGONAL_WEIGHT); ++ } ++ ++ // can move north-east, expand ++ if ((nswe & GeoStructure.CELL_FLAG_NE) != 0) ++ { ++ addNode(x + 1, y - 1, z, Config.DIAGONAL_WEIGHT); ++ } ++ ++ // can move south-west, expand ++ if ((nswe & GeoStructure.CELL_FLAG_SW) != 0) ++ { ++ addNode(x - 1, y + 1, z, Config.DIAGONAL_WEIGHT); ++ } ++ ++ // can move south-east, expand ++ if ((nswe & GeoStructure.CELL_FLAG_SE) != 0) ++ { ++ addNode(x + 1, y + 1, z, Config.DIAGONAL_WEIGHT); ++ } ++ } ++ ++ /** ++ * Returns node, if it exists in buffer. ++ * @param x : node X coord ++ * @param y : node Y coord ++ * @param z : node Z coord ++ * @return Node : node, if exits in buffer ++ */ ++ private final Node getNode(int x, int y, short z) ++ { ++ // check node X out of coordinates ++ final int ix = x - _cx; ++ if ((ix < 0) || (ix >= _size)) ++ { ++ return null; ++ } ++ ++ // check node Y out of coordinates ++ final int iy = y - _cy; ++ if ((iy < 0) || (iy >= _size)) ++ { ++ return null; ++ } ++ ++ // get node ++ final Node result = _buffer[ix][iy]; ++ ++ // check and update ++ if (result.getLoc() == null) ++ { ++ result.setLoc(x, y, z); ++ } ++ ++ // return node ++ return result; ++ } ++ ++ /** ++ * Add node given by coordinates to the buffer. ++ * @param x : geo X coord ++ * @param y : geo Y coord ++ * @param z : geo Z coord ++ * @param weight : weight of movement to new node ++ */ ++ private final void addNode(int x, int y, short z, int weight) ++ { ++ // get node to be expanded ++ final Node node = getNode(x, y, z); ++ if (node == null) ++ { ++ return; ++ } ++ ++ // Z distance between nearby cells is higher than cell size ++ if (node.getLoc().getZ() > (z + (2 * GeoStructure.CELL_HEIGHT))) ++ { ++ return; ++ } ++ ++ // node was already expanded, return ++ if (node.getCost() >= 0) ++ { ++ return; ++ } ++ ++ node.setParent(_current); ++ if (node.getLoc().getNSWE() != (byte) 0xFF) ++ { ++ node.setCost(getCostH(x, y, node.getLoc().getZ()) + (weight * Config.OBSTACLE_MULTIPLIER)); ++ } ++ else ++ { ++ node.setCost(getCostH(x, y, node.getLoc().getZ()) + weight); ++ } ++ ++ Node current = _current; ++ int count = 0; ++ while ((current.getChild() != null) && (count < (Config.MAX_ITERATIONS * 4))) ++ { ++ count++; ++ if (current.getChild().getCost() > node.getCost()) ++ { ++ node.setChild(current.getChild()); ++ break; ++ } ++ current = current.getChild(); ++ } ++ ++ if (count >= (Config.MAX_ITERATIONS * 4)) ++ { ++ System.err.println("Pathfinding: too long loop detected, cost:" + node.getCost()); ++ } ++ ++ current.setChild(node); ++ } ++ ++ /** ++ * @param x : node X coord ++ * @param y : node Y coord ++ * @param i : node Z coord ++ * @return double : node cost ++ */ ++ private final double getCostH(int x, int y, int i) ++ { ++ final int dX = x - _gtx; ++ final int dY = y - _gty; ++ final int dZ = (i - _gtz) / GeoStructure.CELL_HEIGHT; ++ ++ // return (Math.abs(dX) + Math.abs(dY) + Math.abs(dZ)) * Config.HEURISTIC_WEIGHT; // Manhattan distance ++ return Math.sqrt((dX * dX) + (dY * dY) + (dZ * dZ)) * Config.HEURISTIC_WEIGHT; // Direct distance ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeLoc.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeLoc.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeLoc.java (nonexistent) +@@ -1,183 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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.Cell; +- +-/** +- * @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 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 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; +- // return super.hashCode(); +- } +- +- @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; +- if (_x != other._x) +- { +- return false; +- } +- if (_y != other._y) +- { +- return false; +- } +- if (_goNorth != other._goNorth) +- { +- return false; +- } +- if (_goEast != other._goEast) +- { +- return false; +- } +- if (_goSouth != other._goSouth) +- { +- return false; +- } +- if (_goWest != other._goWest) +- { +- return false; +- } +- if (_geoHeight != other._geoHeight) +- { +- return false; +- } +- return true; +- } +-} +Index: java/org/l2jmobius/gameserver/model/actor/Creature.java +=================================================================== +--- java/org/l2jmobius/gameserver/model/actor/Creature.java (revision 8399) ++++ java/org/l2jmobius/gameserver/model/actor/Creature.java (working copy) +@@ -66,8 +66,6 @@ + import org.l2jmobius.gameserver.enums.TeleportWhereType; + import org.l2jmobius.gameserver.enums.UserInfoType; + import org.l2jmobius.gameserver.geoengine.GeoEngine; +-import org.l2jmobius.gameserver.geoengine.GeoEnginePathfinding; +-import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; + import org.l2jmobius.gameserver.instancemanager.IdManager; + import org.l2jmobius.gameserver.instancemanager.MapRegionManager; + import org.l2jmobius.gameserver.instancemanager.QuestManager; +@@ -2577,7 +2575,7 @@ + + public boolean disregardingGeodata; + public int onGeodataPathIndex; +- public List geoPath; ++ public List geoPath; + public int geoPathAccurateTx; + public int geoPathAccurateTy; + public int geoPathGtx; +@@ -3466,7 +3464,7 @@ + if (((originalDistance - distance) > 30) && !isControlBlocked() && !isInVehicle) + { + // Path calculation -- overrides previous movement check +- m.geoPath = GeoEnginePathfinding.getInstance().findPath(curX, curY, curZ, originalX, originalY, originalZ, getInstanceWorld()); ++ m.geoPath = GeoEngine.getInstance().findPath(curX, curY, curZ, originalX, originalY, originalZ, getInstanceWorld()); + if ((m.geoPath == null) || (m.geoPath.size() < 2)) // No path found + { + if (isPlayer() && !_isFlying && !isInWater) +Index: java/org/l2jmobius/gameserver/model/actor/instance/PlayerInstance.java +=================================================================== +--- java/org/l2jmobius/gameserver/model/actor/instance/PlayerInstance.java (revision 8397) ++++ java/org/l2jmobius/gameserver/model/actor/instance/PlayerInstance.java (working copy) +@@ -12704,7 +12704,7 @@ + { + if (Config.CORRECT_PLAYER_Z) + { +- final int nearestZ = GeoEngine.getInstance().getHigherHeight(getX(), getY(), getZ()); ++ final int nearestZ = GeoEngine.getInstance().getHeightNearest(getX(), getY(), getZ()); + if (getZ() < nearestZ) + { + teleToLocation(new Location(getX(), getY(), nearestZ)); +Index: java/org/l2jmobius/gameserver/util/GeoUtils.java +=================================================================== +--- java/org/l2jmobius/gameserver/util/GeoUtils.java (revision 8397) ++++ java/org/l2jmobius/gameserver/util/GeoUtils.java (working copy) +@@ -19,7 +19,7 @@ + import java.awt.Color; + + import org.l2jmobius.gameserver.geoengine.GeoEngine; +-import org.l2jmobius.gameserver.geoengine.geodata.Cell; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; + import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance; + import org.l2jmobius.gameserver.network.serverpackets.ExServerPrimitive; + +@@ -26,25 +26,25 @@ + /** + * @author HorridoJoho + */ +-public final class GeoUtils ++public class GeoUtils + { + public static void debug2DLine(PlayerInstance player, int x, int y, int tx, int ty, int z) + { +- final int gx = GeoEngine.getInstance().getGeoX(x); +- final int gy = GeoEngine.getInstance().getGeoY(y); ++ final int gx = GeoEngine.getGeoX(x); ++ final int gy = GeoEngine.getGeoY(y); + +- final int tgx = GeoEngine.getInstance().getGeoX(tx); +- final int tgy = GeoEngine.getInstance().getGeoY(ty); ++ final int tgx = GeoEngine.getGeoX(tx); ++ final int tgy = GeoEngine.getGeoY(ty); + + final ExServerPrimitive prim = new ExServerPrimitive("Debug2DLine", x, y, z); +- prim.addLine(Color.BLUE, GeoEngine.getInstance().getWorldX(gx), GeoEngine.getInstance().getWorldY(gy), z, GeoEngine.getInstance().getWorldX(tgx), GeoEngine.getInstance().getWorldY(tgy), z); ++ prim.addLine(Color.BLUE, GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), z, GeoEngine.getWorldX(tgx), GeoEngine.getWorldY(tgy), z); + + final LinePointIterator iter = new LinePointIterator(gx, gy, tgx, tgy); + + while (iter.next()) + { +- final int wx = GeoEngine.getInstance().getWorldX(iter.x()); +- final int wy = GeoEngine.getInstance().getWorldY(iter.y()); ++ final int wx = GeoEngine.getWorldX(iter.x()); ++ final int wy = GeoEngine.getWorldY(iter.y()); + + prim.addPoint(Color.RED, wx, wy, z); + } +@@ -53,21 +53,21 @@ + + public static void debug3DLine(PlayerInstance player, int x, int y, int z, int tx, int ty, int tz) + { +- final int gx = GeoEngine.getInstance().getGeoX(x); +- final int gy = GeoEngine.getInstance().getGeoY(y); ++ final int gx = GeoEngine.getGeoX(x); ++ final int gy = GeoEngine.getGeoY(y); + +- final int tgx = GeoEngine.getInstance().getGeoX(tx); +- final int tgy = GeoEngine.getInstance().getGeoY(ty); ++ final int tgx = GeoEngine.getGeoX(tx); ++ final int tgy = GeoEngine.getGeoY(ty); + + final ExServerPrimitive prim = new ExServerPrimitive("Debug3DLine", x, y, z); +- prim.addLine(Color.BLUE, GeoEngine.getInstance().getWorldX(gx), GeoEngine.getInstance().getWorldY(gy), z, GeoEngine.getInstance().getWorldX(tgx), GeoEngine.getInstance().getWorldY(tgy), tz); ++ prim.addLine(Color.BLUE, GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), z, GeoEngine.getWorldX(tgx), GeoEngine.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.getInstance().getWorldX(prevX); +- int wy = GeoEngine.getInstance().getWorldY(prevY); ++ int wx = GeoEngine.getWorldX(prevX); ++ int wy = GeoEngine.getWorldY(prevY); + int wz = iter.z(); + prim.addPoint(Color.RED, wx, wy, wz); + +@@ -78,8 +78,8 @@ + + if ((curX != prevX) || (curY != prevY)) + { +- wx = GeoEngine.getInstance().getWorldX(curX); +- wy = GeoEngine.getInstance().getWorldY(curY); ++ wx = GeoEngine.getWorldX(curX); ++ wy = GeoEngine.getWorldY(curY); + wz = iter.z(); + + prim.addPoint(Color.RED, wx, wy, wz); +@@ -93,7 +93,7 @@ + + private static Color getDirectionColor(int x, int y, int z, int nswe) + { +- if (GeoEngine.getInstance().checkNearestNswe(x, y, z, nswe)) ++ if ((GeoEngine.getInstance().getNsweNearest(x, y, z) & nswe) == nswe) + { + return Color.GREEN; + } +@@ -109,9 +109,8 @@ + int iPacket = 0; + + ExServerPrimitive exsp = null; +- final GeoEngine ge = GeoEngine.getInstance(); +- final int playerGx = ge.getGeoX(player.getX()); +- final int playerGy = ge.getGeoY(player.getY()); ++ final int playerGx = GeoEngine.getGeoX(player.getX()); ++ final int playerGy = GeoEngine.getGeoY(player.getY()); + for (int dx = -geoRadius; dx <= geoRadius; ++dx) + { + for (int dy = -geoRadius; dy <= geoRadius; ++dy) +@@ -135,12 +134,12 @@ + final int gx = playerGx + dx; + final int gy = playerGy + dy; + +- final int x = ge.getWorldX(gx); +- final int y = ge.getWorldY(gy); +- final int z = ge.getNearestZ(gx, gy, player.getZ()); ++ final int x = GeoEngine.getWorldX(gx); ++ final int y = GeoEngine.getWorldY(gy); ++ final int z = GeoEngine.getInstance().getHeightNearest(gx, gy, player.getZ()); + + // north arrow +- Color col = getDirectionColor(gx, gy, z, Cell.NSWE_NORTH); ++ Color col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_N); + 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); +@@ -147,7 +146,7 @@ + exsp.addLine(col, x - 4, y - 4, z, x + 4, y - 4, z); + + // east arrow +- col = getDirectionColor(gx, gy, z, Cell.NSWE_EAST); ++ col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_E); + 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); +@@ -154,13 +153,13 @@ + exsp.addLine(col, x + 4, y - 4, z, x + 4, y + 4, z); + + // south arrow +- col = getDirectionColor(gx, gy, z, Cell.NSWE_SOUTH); ++ col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_S); + 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, Cell.NSWE_WEST); ++ col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_W); + 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); +@@ -188,15 +187,15 @@ + { + if (y > lastY) + { +- return Cell.NSWE_SOUTH_EAST; ++ return GeoStructure.CELL_FLAG_SE; // Direction.SOUTH_EAST; + } + else if (y < lastY) + { +- return Cell.NSWE_NORTH_EAST; ++ return GeoStructure.CELL_FLAG_NE; // Direction.NORTH_EAST; + } + else + { +- return Cell.NSWE_EAST; ++ return GeoStructure.CELL_FLAG_E; // Direction.EAST; + } + } + else if (x < lastX) // west +@@ -203,26 +202,27 @@ + { + if (y > lastY) + { +- return Cell.NSWE_SOUTH_WEST; ++ return GeoStructure.CELL_FLAG_SW; // Direction.SOUTH_WEST; + } + else if (y < lastY) + { +- return Cell.NSWE_NORTH_WEST; ++ return GeoStructure.CELL_FLAG_NW; // Direction.NORTH_WEST; + } + else + { +- return Cell.NSWE_WEST; ++ return GeoStructure.CELL_FLAG_W; // Direction.WEST; + } + } +- else // unchanged x ++ else ++ // unchanged x + { + if (y > lastY) + { +- return Cell.NSWE_SOUTH; ++ return GeoStructure.CELL_FLAG_S; // Direction.SOUTH; + } + else if (y < lastY) + { +- return Cell.NSWE_NORTH; ++ return GeoStructure.CELL_FLAG_N; // Direction.NORTH; + } + else + { +Index: java/org/l2jmobius/tools/geodataconverter/GeoDataConverter.java +=================================================================== +--- java/org/l2jmobius/tools/geodataconverter/GeoDataConverter.java (nonexistent) ++++ java/org/l2jmobius/tools/geodataconverter/GeoDataConverter.java (working copy) +@@ -0,0 +1,386 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ */ ++package org.l2jmobius.tools.geodataconverter; ++ ++import java.io.BufferedOutputStream; ++import java.io.File; ++import java.io.FileOutputStream; ++import java.io.RandomAccessFile; ++import java.nio.ByteOrder; ++import java.nio.MappedByteBuffer; ++import java.nio.channels.FileChannel; ++import java.util.Scanner; ++ ++import org.l2jmobius.Config; ++import org.l2jmobius.commons.util.PropertiesParser; ++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.GeoFormat; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; ++import org.l2jmobius.gameserver.model.World; ++ ++/** ++ * @author Hasha ++ */ ++public class GeoDataConverter ++{ ++ private static GeoFormat _format; ++ private static ABlock[][] _blocks; ++ ++ public static void main(String[] args) ++ { ++ // initialize config ++ loadGeoengineConfigs(); ++ ++ // get geodata format ++ String type = ""; ++ try (Scanner scn = new Scanner(System.in)) ++ { ++ while (!(type.equalsIgnoreCase("J") || type.equalsIgnoreCase("O") || type.equalsIgnoreCase("E"))) ++ { ++ System.out.println("GeoDataConverter: Select source geodata type:"); ++ System.out.println(" J: L2J (e.g. 23_22.l2j)"); ++ System.out.println(" O: L2OFF (e.g. 23_22_conv.dat)"); ++ System.out.println(" E: Exit"); ++ System.out.print("Choice: "); ++ type = scn.next(); ++ } ++ } ++ if (type.equalsIgnoreCase("E")) ++ { ++ System.exit(0); ++ } ++ ++ _format = type.equalsIgnoreCase("J") ? GeoFormat.L2J : GeoFormat.L2OFF; ++ ++ // start conversion ++ System.out.println("GeoDataConverter: Converting all " + _format + " files."); ++ ++ // initialize geodata container ++ _blocks = new ABlock[GeoStructure.REGION_BLOCKS_X][GeoStructure.REGION_BLOCKS_Y]; ++ ++ // initialize multilayer temporarily buffer ++ BlockMultilayer.initialize(); ++ ++ // load geo files ++ int converted = 0; ++ for (int rx = World.TILE_X_MIN; rx <= World.TILE_X_MAX; rx++) ++ { ++ for (int ry = World.TILE_Y_MIN; ry <= World.TILE_Y_MAX; ry++) ++ { ++ final String input = String.format(_format.getFilename(), rx, ry); ++ final String filepath = Config.GEODATA_PATH; ++ final File f = new File(filepath + input); ++ if (f.exists() && !f.isDirectory()) ++ { ++ // load geodata ++ if (!loadGeoBlocks(input)) ++ { ++ System.out.println("GeoDataConverter: Unable to load " + input + " region file."); ++ continue; ++ } ++ ++ // recalculate nswe ++ if (!recalculateNswe()) ++ { ++ System.out.println("GeoDataConverter: Unable to convert " + input + " region file."); ++ continue; ++ } ++ ++ // save geodata ++ final String output = String.format(GeoFormat.L2D.getFilename(), rx, ry); ++ if (!saveGeoBlocks(output)) ++ { ++ System.out.println("GeoDataConverter: Unable to save " + output + " region file."); ++ continue; ++ } ++ ++ converted++; ++ System.out.println("GeoDataConverter: Created " + output + " region file."); ++ } ++ } ++ } ++ System.out.println("GeoDataConverter: Converted " + converted + " " + _format + " to L2D region file(s)."); ++ ++ // release multilayer block temporarily buffer ++ BlockMultilayer.release(); ++ } ++ ++ /** ++ * Loads geo blocks from buffer of the region file. ++ * @param filename : The name of the to load. ++ * @return boolean : True when successful. ++ */ ++ private static boolean loadGeoBlocks(String filename) ++ { ++ // region file is load-able, try to load it ++ try (RandomAccessFile raf = new RandomAccessFile(Config.GEODATA_PATH + filename, "r"); ++ FileChannel fc = raf.getChannel()) ++ { ++ final MappedByteBuffer buffer = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()).load(); ++ buffer.order(ByteOrder.LITTLE_ENDIAN); ++ ++ // load 18B header for L2off geodata (1st and 2nd byte...region X and Y) ++ if (_format == GeoFormat.L2OFF) ++ { ++ for (int i = 0; i < 18; i++) ++ { ++ buffer.get(); ++ } ++ } ++ ++ // 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 (_format == GeoFormat.L2J) ++ { ++ // get block type ++ final byte type = buffer.get(); ++ ++ // load block according to block type ++ switch (type) ++ { ++ case GeoStructure.TYPE_FLAT_L2J_L2OFF: ++ { ++ _blocks[ix][iy] = new BlockFlat(buffer, _format); ++ break; ++ } ++ case GeoStructure.TYPE_COMPLEX_L2J: ++ { ++ _blocks[ix][iy] = new BlockComplex(buffer, _format); ++ break; ++ } ++ case GeoStructure.TYPE_MULTILAYER_L2J: ++ { ++ _blocks[ix][iy] = new BlockMultilayer(buffer, _format); ++ break; ++ } ++ default: ++ { ++ throw new IllegalArgumentException("Unknown block type: " + type); ++ } ++ } ++ } ++ else ++ { ++ // get block type ++ final short type = buffer.getShort(); ++ ++ // load block according to block type ++ switch (type) ++ { ++ case GeoStructure.TYPE_FLAT_L2J_L2OFF: ++ { ++ _blocks[ix][iy] = new BlockFlat(buffer, _format); ++ break; ++ } ++ case GeoStructure.TYPE_COMPLEX_L2OFF: ++ { ++ _blocks[ix][iy] = new BlockComplex(buffer, _format); ++ break; ++ } ++ default: ++ { ++ _blocks[ix][iy] = new BlockMultilayer(buffer, _format); ++ break; ++ } ++ } ++ } ++ } ++ } ++ ++ if (buffer.remaining() > 0) ++ { ++ System.out.println("GeoDataConverter: Region file " + filename + " can be corrupted, remaining " + buffer.remaining() + " bytes to read."); ++ return false; ++ } ++ ++ return true; ++ } ++ catch (Exception e) ++ { ++ System.out.println("GeoDataConverter: Error while loading " + filename + " region file."); ++ return false; ++ } ++ } ++ ++ /** ++ * Recalculate diagonal flags for the region file. ++ * @return boolean : True when successful. ++ */ ++ private static boolean recalculateNswe() ++ { ++ try ++ { ++ for (int x = 0; x < GeoStructure.REGION_CELLS_X; x++) ++ { ++ for (int y = 0; y < GeoStructure.REGION_CELLS_Y; y++) ++ { ++ // get block ++ final ABlock block = _blocks[x / GeoStructure.BLOCK_CELLS_X][y / GeoStructure.BLOCK_CELLS_Y]; ++ ++ // skip flat blocks ++ if (block instanceof BlockFlat) ++ { ++ continue; ++ } ++ ++ // for complex and multilayer blocks go though all layers ++ short height = Short.MAX_VALUE; ++ int index; ++ while ((index = block.getIndexBelow(x, y, height)) != -1) ++ { ++ // get height and nswe ++ height = block.getHeight(index); ++ byte nswe = block.getNswe(index); ++ ++ // update nswe with diagonal flags ++ nswe = updateNsweBelow(x, y, height, nswe); ++ ++ // set nswe of the cell ++ block.setNswe(index, nswe); ++ } ++ } ++ } ++ ++ return true; ++ } ++ catch (Exception e) ++ { ++ return false; ++ } ++ } ++ ++ /** ++ * Updates the NSWE flag with diagonal flags. ++ * @param x : Geodata X coordinate. ++ * @param y : Geodata Y coordinate. ++ * @param z : Geodata Z coordinate. ++ * @param nsweValue : NSWE flag to be updated. ++ * @return byte : Updated NSWE flag. ++ */ ++ private static byte updateNsweBelow(int x, int y, short z, byte nsweValue) ++ { ++ byte nswe = nsweValue; ++ ++ // calculate virtual layer height ++ final short height = (short) (z + GeoStructure.CELL_IGNORE_HEIGHT); ++ ++ // get NSWE of neighbor cells below virtual layer (NPC/PC can fall down of clif, but can not climb it -> NSWE of cell below) ++ final byte nsweN = getNsweBelow(x, y - 1, height); ++ final byte nsweS = getNsweBelow(x, y + 1, height); ++ final byte nsweW = getNsweBelow(x - 1, y, height); ++ final byte nsweE = getNsweBelow(x + 1, y, height); ++ ++ // north-west ++ if ((((nswe & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) || (((nswe & GeoStructure.CELL_FLAG_W) != 0) && ((nsweW & GeoStructure.CELL_FLAG_N) != 0))) ++ { ++ nswe |= GeoStructure.CELL_FLAG_NW; ++ } ++ ++ // north-east ++ if ((((nswe & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) || (((nswe & GeoStructure.CELL_FLAG_E) != 0) && ((nsweE & GeoStructure.CELL_FLAG_N) != 0))) ++ { ++ nswe |= GeoStructure.CELL_FLAG_NE; ++ } ++ ++ // south-west ++ if ((((nswe & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) || (((nswe & GeoStructure.CELL_FLAG_W) != 0) && ((nsweW & GeoStructure.CELL_FLAG_S) != 0))) ++ { ++ nswe |= GeoStructure.CELL_FLAG_SW; ++ } ++ ++ // south-east ++ if ((((nswe & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) || (((nswe & GeoStructure.CELL_FLAG_E) != 0) && ((nsweE & GeoStructure.CELL_FLAG_S) != 0))) ++ { ++ nswe |= GeoStructure.CELL_FLAG_SE; ++ } ++ ++ return nswe; ++ } ++ ++ private static byte getNsweBelow(int geoX, int geoY, short worldZ) ++ { ++ // out of geo coordinates ++ if ((geoX < 0) || (geoX >= GeoStructure.REGION_CELLS_X)) ++ { ++ return 0; ++ } ++ ++ // out of geo coordinates ++ if ((geoY < 0) || (geoY >= GeoStructure.REGION_CELLS_Y)) ++ { ++ return 0; ++ } ++ ++ // get block ++ final ABlock block = _blocks[geoX / GeoStructure.BLOCK_CELLS_X][geoY / GeoStructure.BLOCK_CELLS_Y]; ++ ++ // get index, when valid, return nswe ++ final int index = block.getIndexBelow(geoX, geoY, worldZ); ++ return index == -1 ? 0 : block.getNswe(index); ++ } ++ ++ /** ++ * Save region file to file. ++ * @param filename : The name of file to save. ++ * @return boolean : True when successful. ++ */ ++ private static boolean saveGeoBlocks(String filename) ++ { ++ try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(Config.GEODATA_PATH + filename), GeoStructure.REGION_BLOCKS * GeoStructure.BLOCK_CELLS * 3)) ++ { ++ // loop over region blocks and save each block ++ for (int ix = 0; ix < GeoStructure.REGION_BLOCKS_X; ix++) ++ { ++ for (int iy = 0; iy < GeoStructure.REGION_BLOCKS_Y; iy++) ++ { ++ _blocks[ix][iy].saveBlock(bos); ++ } ++ } ++ ++ // flush data to file ++ bos.flush(); ++ ++ return true; ++ } ++ catch (Exception e) ++ { ++ return false; ++ } ++ } ++ ++ private static void loadGeoengineConfigs() ++ { ++ final PropertiesParser geoData = new PropertiesParser(Config.GEOENGINE_CONFIG_FILE); ++ Config.GEODATA_PATH = geoData.getString("GeoDataPath", "./data/geodata/"); ++ Config.COORD_SYNCHRONIZE = geoData.getInt("CoordSynchronize", -1); ++ Config.PART_OF_CHARACTER_HEIGHT = geoData.getInt("PartOfCharacterHeight", 75); ++ Config.MAX_OBSTACLE_HEIGHT = geoData.getInt("MaxObstacleHeight", 32); ++ Config.PATHFINDING = geoData.getBoolean("PathFinding", true); ++ Config.PATHFIND_BUFFERS = geoData.getString("PathFindBuffers", "100x6;128x6;192x6;256x4;320x4;384x4;500x2"); ++ Config.BASE_WEIGHT = geoData.getInt("BaseWeight", 10); ++ Config.DIAGONAL_WEIGHT = geoData.getInt("DiagonalWeight", 14); ++ Config.OBSTACLE_MULTIPLIER = geoData.getInt("ObstacleMultiplier", 10); ++ Config.HEURISTIC_WEIGHT = geoData.getInt("HeuristicWeight", 20); ++ Config.MAX_ITERATIONS = geoData.getInt("MaxIterations", 3500); ++ } ++} +\ No newline at end of file diff --git a/L2J_Mobius_Classic_Interlude/dist/game/data/geodata/L2D_GeoEngine.patch b/L2J_Mobius_Classic_Interlude/dist/game/data/geodata/L2D_GeoEngine.patch new file mode 100644 index 0000000000..63e2faf0c9 --- /dev/null +++ b/L2J_Mobius_Classic_Interlude/dist/game/data/geodata/L2D_GeoEngine.patch @@ -0,0 +1,6052 @@ +Index: dist/game/GeoDataConverter.bat +=================================================================== +--- dist/game/GeoDataConverter.bat (nonexistent) ++++ dist/game/GeoDataConverter.bat (working copy) +@@ -0,0 +1,6 @@ ++@echo off ++title L2D geodata converter ++ ++java -Xmx512m -cp ./../libs/* org.l2jmobius.tools.geodataconverter.GeoDataConverter ++ ++pause +Index: dist/game/GeoDataConverter.sh +=================================================================== +--- dist/game/GeoDataConverter.sh (nonexistent) ++++ dist/game/GeoDataConverter.sh (working copy) +@@ -0,0 +1,4 @@ ++#! /bin/sh ++ ++java -Xmx512m -cp ../libs/*: org.l2jmobius.tools.geodataconverter.GeoDataConverter > log/stdout.log 2>&1 ++ +Index: dist/game/config/GeoEngine.ini +=================================================================== +--- dist/game/config/GeoEngine.ini (revision 8397) ++++ dist/game/config/GeoEngine.ini (working copy) +@@ -1,6 +1,10 @@ + # ================================================================= + # Geodata + # ================================================================= ++# Because of real-time performance we are using geodata files only in ++# diagonal L2D format now (using filename e.g. 22_16.l2d). ++# L2D geodata can be obtained by conversion of existing L2J or L2OFF geodata. ++# Launch "GeoDataConverter.bat/sh" and follow instructions to start the conversion. + + # 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/ +@@ -13,6 +17,16 @@ + CoordSynchronize = 2 + + # ================================================================= ++# Path checking ++# ================================================================= ++ ++# 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 finding + # ================================================================= + +@@ -23,19 +37,23 @@ + # Pathfinding array buffers configuration, default: 100x6;128x6;192x6;256x4;320x4;384x4;500x2 + PathFindBuffers = 100x6;128x6;192x6;256x4;320x4;384x4;500x2 + +-# Weight for nodes without obstacles far from walls +-LowWeight = 0.5 ++# Base path weight, when moving from one node to another on axis direction, default: 10 ++BaseWeight = 10 + +-# Weight for nodes near walls +-MediumWeight = 2 ++# Path weight, when moving from one node to another on diagonal direction, default: BaseWeight * sqrt(2) = 14 ++DiagonalWeight = 14 + +-# Weight for nodes with obstacles +-HighWeight = 3 ++# When movement flags of target node is blocked to any direction, multiply movement weight by this multiplier. ++# This causes pathfinding algorithm to avoid path construction exactly near an obstacle, default: 10 ++ObstacleMultiplier = 10 + +-# Weight for diagonal movement. +-# Default: LowWeight * sqrt(2) +-DiagonalWeight = 0.707 ++# Weight of the heuristic algorithm, which is giving estimated cost from node to target, default: 20 ++# For proper function must be higher than BaseWeight and/or DiagonalWeight. ++HeuristicWeight = 20 + ++# Maximum number of generated nodes per one path-finding process, default 3500 ++MaxIterations = 3500 ++ + # ================================================================= + # Other + # ================================================================= +Index: dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java +=================================================================== +--- dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java (revision 8397) ++++ dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java (working copy) +@@ -55,9 +55,8 @@ + final int worldX = activeChar.getX(); + final int worldY = activeChar.getY(); + final int worldZ = activeChar.getZ(); +- final int geoX = GeoEngine.getInstance().getGeoX(worldX); +- final int geoY = GeoEngine.getInstance().getGeoY(worldY); +- ++ final int geoX = GeoEngine.getGeoX(worldX); ++ final int geoY = GeoEngine.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)); +@@ -73,9 +72,8 @@ + final int worldX = activeChar.getX(); + final int worldY = activeChar.getY(); + final int worldZ = activeChar.getZ(); +- final int geoX = GeoEngine.getInstance().getGeoX(worldX); +- final int geoY = GeoEngine.getInstance().getGeoY(worldY); +- ++ final int geoX = GeoEngine.getGeoX(worldX); ++ final int geoY = GeoEngine.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)); +Index: dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java +=================================================================== +--- dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java (revision 8397) ++++ dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java (working copy) +@@ -18,11 +18,11 @@ + + import java.util.List; + +-import org.l2jmobius.Config; +-import org.l2jmobius.gameserver.geoengine.GeoEnginePathfinding; +-import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; ++import org.l2jmobius.gameserver.geoengine.GeoEngine; + import org.l2jmobius.gameserver.handler.IAdminCommandHandler; ++import org.l2jmobius.gameserver.model.Location; + import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance; ++import org.l2jmobius.gameserver.network.SystemMessageId; + import org.l2jmobius.gameserver.util.BuilderUtil; + + public class AdminPathNode implements IAdminCommandHandler +@@ -37,29 +37,30 @@ + { + if (command.equals("admin_path_find")) + { +- if (!Config.PATHFINDING) +- { +- BuilderUtil.sendSysMessage(activeChar, "PathFinding is disabled."); +- return true; +- } + if (activeChar.getTarget() != null) + { +- final List path = GeoEnginePathfinding.getInstance().findPath(activeChar.getX(), activeChar.getY(), (short) activeChar.getZ(), activeChar.getTarget().getX(), activeChar.getTarget().getY(), (short) activeChar.getTarget().getZ(), activeChar.getInstanceWorld()); ++ 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()); + if (path == null) + { +- BuilderUtil.sendSysMessage(activeChar, "No Route!"); +- return true; ++ BuilderUtil.sendSysMessage(activeChar, "No route found or pathfinding disabled."); + } +- for (AbstractNodeLoc a : path) ++ else + { +- BuilderUtil.sendSysMessage(activeChar, "x:" + a.getX() + " y:" + a.getY() + " z:" + a.getZ()); ++ for (Location point : path) ++ { ++ BuilderUtil.sendSysMessage(activeChar, "x:" + point.getX() + " y:" + point.getY() + " z:" + point.getZ()); ++ } + } + } + else + { +- BuilderUtil.sendSysMessage(activeChar, "No Target!"); ++ activeChar.sendPacket(SystemMessageId.INVALID_TARGET); + } + } ++ else ++ { ++ return false; ++ } + return true; + } + +Index: java/org/l2jmobius/Config.java +=================================================================== +--- java/org/l2jmobius/Config.java (revision 8397) ++++ java/org/l2jmobius/Config.java (working copy) +@@ -32,7 +32,6 @@ + import java.net.UnknownHostException; + import java.nio.charset.StandardCharsets; + import java.nio.file.Files; +-import java.nio.file.Path; + import java.nio.file.Paths; + import java.time.Duration; + import java.util.ArrayList; +@@ -927,14 +926,17 @@ + // -------------------------------------------------- + // GeoEngine + // -------------------------------------------------- +- public static Path GEODATA_PATH; ++ public static String GEODATA_PATH; ++ public static int COORD_SYNCHRONIZE; ++ public static int PART_OF_CHARACTER_HEIGHT; ++ public static int MAX_OBSTACLE_HEIGHT; + public static boolean PATHFINDING; + public static String PATHFIND_BUFFERS; +- public static float LOW_WEIGHT; +- public static float MEDIUM_WEIGHT; +- public static float HIGH_WEIGHT; +- public static float DIAGONAL_WEIGHT; +- public static int COORD_SYNCHRONIZE; ++ public static int BASE_WEIGHT; ++ public static int DIAGONAL_WEIGHT; ++ public static int HEURISTIC_WEIGHT; ++ public static int OBSTACLE_MULTIPLIER; ++ public static int MAX_ITERATIONS; + public static boolean CORRECT_PLAYER_Z; + + /** Attribute System */ +@@ -2468,14 +2470,17 @@ + + // Load GeoEngine config file (if exists) + final PropertiesParser GeoEngine = new PropertiesParser(GEOENGINE_CONFIG_FILE); +- GEODATA_PATH = Paths.get(GeoEngine.getString("GeoDataPath", "./data/geodata")); ++ GEODATA_PATH = GeoEngine.getString("GeoDataPath", "./data/geodata/"); ++ COORD_SYNCHRONIZE = GeoEngine.getInt("CoordSynchronize", -1); ++ PART_OF_CHARACTER_HEIGHT = GeoEngine.getInt("PartOfCharacterHeight", 75); ++ MAX_OBSTACLE_HEIGHT = GeoEngine.getInt("MaxObstacleHeight", 32); + PATHFINDING = GeoEngine.getBoolean("PathFinding", true); + PATHFIND_BUFFERS = GeoEngine.getString("PathFindBuffers", "100x6;128x6;192x6;256x4;320x4;384x4;500x2"); +- LOW_WEIGHT = GeoEngine.getFloat("LowWeight", 0.5f); +- MEDIUM_WEIGHT = GeoEngine.getFloat("MediumWeight", 2); +- HIGH_WEIGHT = GeoEngine.getFloat("HighWeight", 3); +- DIAGONAL_WEIGHT = GeoEngine.getFloat("DiagonalWeight", 0.707f); +- COORD_SYNCHRONIZE = GeoEngine.getInt("CoordSynchronize", -1); ++ BASE_WEIGHT = GeoEngine.getInt("BaseWeight", 10); ++ DIAGONAL_WEIGHT = GeoEngine.getInt("DiagonalWeight", 14); ++ OBSTACLE_MULTIPLIER = GeoEngine.getInt("ObstacleMultiplier", 10); ++ HEURISTIC_WEIGHT = GeoEngine.getInt("HeuristicWeight", 20); ++ MAX_ITERATIONS = GeoEngine.getInt("MaxIterations", 3500); + CORRECT_PLAYER_Z = GeoEngine.getBoolean("CorrectPlayerZ", false); + + // Load AllowedPlayerRaces config file (if exists) +Index: java/org/l2jmobius/gameserver/geoengine/GeoEngine.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/GeoEngine.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/GeoEngine.java (working copy) +@@ -16,101 +16,91 @@ + */ + package org.l2jmobius.gameserver.geoengine; + +-import java.io.IOException; ++import java.io.File; + import java.io.RandomAccessFile; + import java.nio.ByteOrder; +-import java.nio.channels.FileChannel.MapMode; +-import java.nio.file.Files; +-import java.nio.file.Path; +-import java.util.concurrent.atomic.AtomicReferenceArray; +-import java.util.logging.Level; ++import java.nio.MappedByteBuffer; ++import java.nio.channels.FileChannel; ++import java.util.List; + 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.geoengine.geodata.Cell; +-import org.l2jmobius.gameserver.geoengine.geodata.IRegion; +-import org.l2jmobius.gameserver.geoengine.geodata.NullRegion; +-import org.l2jmobius.gameserver.geoengine.geodata.Region; ++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.GeoFormat; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoLocation; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; + import org.l2jmobius.gameserver.instancemanager.WarpedSpaceManager; + 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; ++import org.l2jmobius.gameserver.util.MathUtil; + + /** +- * @author -Nemesiss-, HorridoJoho ++ * @author Hasha + */ + public class GeoEngine + { +- private static final Logger LOGGER = Logger.getLogger(GeoEngine.class.getName()); ++ protected static final Logger LOGGER = Logger.getLogger(GeoEngine.class.getName()); + +- private static final int WORLD_MIN_X = -655360; +- private static final int WORLD_MIN_Y = -589824; +- private static final int WORLD_MIN_Z = -16384; ++ private final ABlock[][] _blocks; ++ private final BlockNull _nullBlock; + +- /** 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; +- +- /** The regions array */ +- private final AtomicReferenceArray _regions = new AtomicReferenceArray<>(GEO_REGIONS); +- +- 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; +- +- protected GeoEngine() ++ /** ++ * GeoEngine constructor. Loads all geodata files of chosen geodata format. ++ */ ++ public GeoEngine() + { + LOGGER.info("GeoEngine: Initializing..."); +- for (int i = 0; i < _regions.length(); i++) +- { +- _regions.set(i, NullRegion.INSTANCE); +- } + ++ // 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; +- try ++ long fileSize = 0; ++ for (int rx = World.TILE_X_MIN; rx <= World.TILE_X_MAX; rx++) + { +- for (int regionX = World.TILE_X_MIN; regionX <= World.TILE_X_MAX; regionX++) ++ for (int ry = World.TILE_Y_MIN; ry <= World.TILE_Y_MAX; ry++) + { +- for (int regionY = World.TILE_Y_MIN; regionY <= World.TILE_Y_MAX; regionY++) ++ final File f = new File(Config.GEODATA_PATH + String.format(GeoFormat.L2D.getFilename(), rx, ry)); ++ if (f.exists() && !f.isDirectory()) + { +- 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(rx, ry)) + { +- try (RandomAccessFile raf = new RandomAccessFile(geoFilePath.toFile(), "r")) +- { +- _regions.set((regionX * GEO_REGIONS_Y) + regionY, new Region(raf.getChannel().map(MapMode.READ_ONLY, 0, raf.length()).load().order(ByteOrder.LITTLE_ENDIAN))); +- loaded++; +- } ++ loaded++; ++ fileSize += f.length(); + } + } ++ else ++ { ++ // region file is not load-able, load null blocks ++ loadNullBlocks(rx, ry); ++ } + } + } +- catch (Exception e) ++ ++ LOGGER.info("GeoEngine: Loaded " + loaded + " geodata files."); ++ if (loaded > 0) + { +- LOGGER.log(Level.SEVERE, "GeoEngine: Failed to load geodata!", e); +- System.exit(1); ++ LOGGER.info("GeoEngine: Total geodata file size " + (fileSize / 1024 / 1024) + " MB."); + } + +- LOGGER.info("GeoEngine: Loaded " + loaded + " geodata files."); +- +- // Avoid wrong configs when no files are loaded. ++ // avoid wrong configs when no files are loaded + if (loaded == 0) + { + if (Config.PATHFINDING) +@@ -124,627 +114,832 @@ + LOGGER.info("GeoEngine: Forcing CoordSynchronize setting to -1."); + } + } ++ ++ // release multilayer block temporarily buffer ++ BlockMultilayer.release(); + } + + /** +- * @param geoX +- * @param geoY +- * @return the region ++ * 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 IRegion getRegion(int geoX, int geoY) ++ private final boolean loadGeoBlocks(int regionX, int regionY) + { +- final int region = ((geoX / IRegion.REGION_CELLS_X) * GEO_REGIONS_Y) + (geoY / IRegion.REGION_CELLS_Y); +- if ((region < 0) || (region >= _regions.length())) ++ final String filename = String.format(GeoFormat.L2D.getFilename(), regionX, regionY); ++ ++ // standard load ++ try (RandomAccessFile raf = new RandomAccessFile(Config.GEODATA_PATH + filename, "r"); ++ FileChannel fc = raf.getChannel()) + { +- return null; ++ // initialize file buffer ++ final MappedByteBuffer buffer = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()).load(); ++ buffer.order(ByteOrder.LITTLE_ENDIAN); ++ ++ // 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++) ++ { ++ // get block type ++ final byte type = buffer.get(); ++ ++ // load block according to block type ++ switch (type) ++ { ++ case GeoStructure.TYPE_FLAT_L2D: ++ { ++ _blocks[blockX + ix][blockY + iy] = new BlockFlat(buffer, GeoFormat.L2D); ++ break; ++ } ++ case GeoStructure.TYPE_COMPLEX_L2D: ++ { ++ _blocks[blockX + ix][blockY + iy] = new BlockComplex(buffer, GeoFormat.L2D); ++ break; ++ } ++ case GeoStructure.TYPE_MULTILAYER_L2D: ++ { ++ _blocks[blockX + ix][blockY + iy] = new BlockMultilayer(buffer, GeoFormat.L2D); ++ break; ++ } ++ default: ++ { ++ throw new IllegalArgumentException("Unknown block type: " + type); ++ } ++ } ++ } ++ } ++ ++ // check data consistency ++ if (buffer.remaining() > 0) ++ { ++ LOGGER.warning("GeoEngine: Region file " + filename + " can be corrupted, remaining " + buffer.remaining() + " bytes to read."); ++ } ++ ++ // loading was successful ++ return true; + } +- return _regions.get(region); +- } +- +- /** +- * @param filePath +- * @param regionX +- * @param regionY +- * @throws IOException +- */ +- 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")) ++ catch (Exception e) + { +- _regions.set(regionOffset, new Region(raf.getChannel().map(MapMode.READ_ONLY, 0, raf.length()).load().order(ByteOrder.LITTLE_ENDIAN))); ++ // an error occured while loading, load null blocks ++ LOGGER.warning("GeoEngine: Error while loading " + filename + " region file."); ++ LOGGER.warning(e.getMessage()); ++ ++ // replace whole region file with null blocks ++ loadNullBlocks(regionX, regionY); ++ ++ // loading was not successful ++ return false; + } + } + + /** +- * @param regionX +- * @param regionY ++ * 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. + */ +- public void unloadRegion(int regionX, int regionY) ++ private final void loadNullBlocks(int regionX, int regionY) + { +- _regions.set((regionX * GEO_REGIONS_Y) + regionY, NullRegion.INSTANCE); +- } +- +- /** +- * @param geoX +- * @param geoY +- * @return if geodata exist +- */ +- public boolean hasGeoPos(int geoX, int geoY) +- { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) ++ // 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++) + { +- return false; ++ for (int iy = 0; iy < GeoStructure.REGION_BLOCKS_Y; iy++) ++ { ++ _blocks[blockX + ix][blockY + iy] = _nullBlock; ++ } + } +- return region.hasGeo(); + } + ++ // GEODATA - GENERAL ++ + /** +- * Checks the specified position for available geodata. +- * @param x the world x +- * @param y the world y +- * @return {@code true} if there is geodata for the given coordinates, {@code false} otherwise ++ * Converts world X to geodata X. ++ * @param worldX ++ * @return int : Geo X + */ +- public boolean hasGeo(int x, int y) ++ public static int getGeoX(int worldX) + { +- return hasGeoPos(getGeoX(x), getGeoY(y)); ++ return (MathUtil.limit(worldX, World.MAP_MIN_X, World.MAP_MAX_X) - World.MAP_MIN_X) >> 4; + } + + /** +- * @param geoX +- * @param geoY +- * @param worldZ +- * @param nswe +- * @return the nearest nswe check ++ * Converts world Y to geodata Y. ++ * @param worldY ++ * @return int : Geo Y + */ +- public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) ++ public static int getGeoY(int worldY) + { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) +- { +- return true; +- } +- return region.checkNearestNswe(geoX, geoY, worldZ, nswe); ++ return (MathUtil.limit(worldY, World.MAP_MIN_Y, World.MAP_MAX_Y) - World.MAP_MIN_Y) >> 4; + } + + /** ++ * Converts geodata X to world X. + * @param geoX +- * @param geoY +- * @param worldZ +- * @param nswe +- * @return the nearest nswe anti-corner cut ++ * @return int : World X + */ +- public boolean checkNearestNsweAntiCornerCut(int geoX, int geoY, int worldZ, int nswe) ++ public static int getWorldX(int geoX) + { +- boolean can = true; +- if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) +- { +- can = checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_EAST) && checkNearestNswe(geoX + 1, geoY, worldZ, Cell.NSWE_NORTH); +- } +- if (can && ((nswe & Cell.NSWE_NORTH_WEST) == Cell.NSWE_NORTH_WEST)) +- { +- 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 = 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 = checkNearestNswe(geoX, geoY + 1, worldZ, Cell.NSWE_WEST) && checkNearestNswe(geoX - 1, geoY, worldZ, Cell.NSWE_SOUTH); +- } +- return can && checkNearestNswe(geoX, geoY, worldZ, nswe); ++ return (MathUtil.limit(geoX, 0, GeoStructure.GEO_CELLS_X) << 4) + World.MAP_MIN_X + 8; + } + + /** +- * @param geoX ++ * Converts geodata Y to world Y. + * @param geoY +- * @param worldZ +- * @return the nearest Z value ++ * @return int : World Y + */ +- public int getNearestZ(int geoX, int geoY, int worldZ) ++ public static int getWorldY(int geoY) + { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) +- { +- return worldZ; +- } +- return region.getNearestZ(geoX, geoY, worldZ); ++ return (MathUtil.limit(geoY, 0, GeoStructure.GEO_CELLS_Y) << 4) + World.MAP_MIN_Y + 8; + } + + /** +- * @param geoX +- * @param geoY +- * @param worldZ +- * @return the next lower Z value ++ * Returns block of geodata on given coordinates. ++ * @param geoX : Geodata X ++ * @param geoY : Geodata Y ++ * @return {@link ABlock} : Block of geodata. + */ +- public int getNextLowerZ(int geoX, int geoY, int worldZ) ++ private final ABlock getBlock(int geoX, int geoY) + { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) ++ final int x = geoX / GeoStructure.BLOCK_CELLS_X; ++ final int y = geoY / GeoStructure.BLOCK_CELLS_Y; ++ ++ // if x or y is out of array return null ++ if ((x > -1) && (y > -1) && (x < GeoStructure.GEO_BLOCKS_X) && (y < GeoStructure.GEO_BLOCKS_Y)) + { +- return worldZ; ++ return _blocks[x][y]; + } +- return region.getNextLowerZ(geoX, geoY, worldZ); ++ return null; + } + + /** +- * @param geoX +- * @param geoY +- * @param worldZ +- * @return the next higher Z value ++ * Check if geo coordinates has geo. ++ * @param geoX : Geodata X ++ * @param geoY : Geodata Y ++ * @return boolean : True, if given geo coordinates have geodata + */ +- public int getNextHigherZ(int geoX, int geoY, int worldZ) ++ public boolean hasGeoPos(int geoX, int geoY) + { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) ++ final ABlock block = getBlock(geoX, geoY); ++ if (block == null) // null block check + { +- return worldZ; ++ // TODO: Find when this can be null. (Bad geodata? Check World getRegion method.) ++ // LOGGER.warning("Could not find geodata block at " + getWorldX(geoX) + ", " + getWorldY(geoY) + "."); ++ return false; + } +- return region.getNextHigherZ(geoX, geoY, worldZ); ++ return block.hasGeoPos(); + } + + /** +- * Gets the Z height. +- * @param x the world x +- * @param y the world y +- * @param z the world z +- * @return the nearest Z height ++ * 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 int getHeight(int x, int y, int z) ++ public short getHeightNearest(int geoX, int geoY, int worldZ) + { +- return getNearestZ(getGeoX(x), getGeoY(y), z); ++ final ABlock block = getBlock(geoX, geoY); ++ return block != null ? block.getHeightNearest(geoX, geoY, worldZ) : (short) worldZ; + } + + /** +- * Gets the next lower Z height. +- * @param x the world x +- * @param y the world y +- * @param z the world z +- * @return the nearest Z height ++ * 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 int getLowerHeight(int x, int y, int z) ++ public byte getNsweNearest(int geoX, int geoY, int worldZ) + { +- return getNextLowerZ(getGeoX(x), getGeoY(y), z); ++ final ABlock block = getBlock(geoX, geoY); ++ return block != null ? block.getNsweNearest(geoX, geoY, worldZ) : (byte) 0xFF; + } + + /** +- * Gets the next higher Z height. +- * @param x the world x +- * @param y the world y +- * @param z the world z +- * @return the nearest Z height ++ * Check if world coordinates has geo. ++ * @param worldX : World X ++ * @param worldY : World Y ++ * @return boolean : True, if given world coordinates have geodata + */ +- public int getHigherHeight(int x, int y, int z) ++ public boolean hasGeo(int worldX, int worldY) + { +- return getNextHigherZ(getGeoX(x), getGeoY(y), z); ++ return hasGeoPos(getGeoX(worldX), getGeoY(worldY)); + } + + /** +- * @param worldX +- * @return the geo X ++ * 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 int getGeoX(int worldX) ++ public short getHeight(int worldX, int worldY, int worldZ) + { +- return (worldX - WORLD_MIN_X) / 16; ++ return getHeightNearest(getGeoX(worldX), getGeoY(worldY), worldZ); + } + +- /** +- * @param worldY +- * @return the geo Y +- */ +- public int getGeoY(int worldY) +- { +- return (worldY - WORLD_MIN_Y) / 16; +- } ++ // PATHFINDING + + /** +- * @param worldZ +- * @return the geo Z ++ * Check line of sight from {@link WorldObject} to {@link WorldObject}. ++ * @param origin : The origin object. ++ * @param target : The target object. ++ * @return {@code boolean} : True if origin can see target + */ +- public int getGeoZ(int worldZ) ++ public boolean canSeeTarget(WorldObject origin, WorldObject target) + { +- return (worldZ - WORLD_MIN_Z) / 16; +- } +- +- /** +- * @param geoX +- * @return the world X +- */ +- public int getWorldX(int geoX) +- { +- return (geoX * 16) + WORLD_MIN_X + 8; +- } +- +- /** +- * @param geoY +- * @return the world Y +- */ +- public int getWorldY(int geoY) +- { +- return (geoY * 16) + WORLD_MIN_Y + 8; +- } +- +- /** +- * @param geoZ +- * @return the world Z +- */ +- public int getWorldZ(int geoZ) +- { +- return (geoZ * 16) + WORLD_MIN_Z + 8; +- } +- +- /** +- * 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) +- { +- if (target.isDoor()) ++ if (target.isDoor() || target.isArtefact() || (target.isCreature() && ((Creature) target).isFlying())) + { +- // Can always see doors. + return true; + } +- return 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(), cha.getInstanceWorld(), worldPosition.getX(), worldPosition.getY(), worldPosition.getZ()); +- } +- +- /** +- * Can see target. Checks doors between. +- * @param x the x coordinate +- * @param y the y coordinate +- * @param z the z coordinate +- * @param world +- * @param tx the target's x coordinate +- * @param ty the target's y coordinate +- * @param tz the target's z coordinate +- * @param tworld the target's instanceId +- * @return +- */ +- public boolean canSeeTarget(int x, int y, int z, Instance world, int tx, int ty, int tz, Instance tworld) +- { +- return (world != tworld) ? false : canSeeTarget(x, y, z, world, tx, ty, tz); +- } +- +- /** +- * 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 +- * @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, Instance instance, int tx, int ty, int tz) +- { +- if (DoorData.getInstance().checkIfDoorsBetween(x, y, z, tx, ty, tz, instance, true)) ++ ++ // get origin and target world coordinates ++ final int ox = origin.getX(); ++ final int oy = origin.getY(); ++ final int oz = origin.getZ(); ++ final int tx = target.getX(); ++ final int ty = target.getY(); ++ final int tz = target.getZ(); ++ ++ if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld(), true)) + { + return false; + } +- return canSeeTarget(x, y, z, tx, ty, tz); +- } +- +- /** +- * @param prevX +- * @param prevY +- * @param prevGeoZ +- * @param curX +- * @param curY +- * @param nswe +- * @return the LOS Z value +- */ +- 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))) ++ if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld())) + { +- throw new RuntimeException("Multiple directions!"); ++ return false; + } ++ if (WarpedSpaceManager.getInstance().checkForWarpedSpace(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld())) ++ { ++ return false; ++ } + +- if (checkNearestNsweAntiCornerCut(prevX, prevY, prevGeoZ, nswe)) ++ // get origin and check existing geo coordinates ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) + { +- return getNearestZ(curX, curY, prevGeoZ); ++ return true; + } + +- return getNextHigherZ(curX, curY, prevGeoZ); ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coordinates ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) ++ { ++ return true; ++ } ++ ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // origin and target coordinates are same ++ if ((gox == gtx) && (goy == gty)) ++ { ++ return goz == gtz; ++ } ++ ++ // get origin and target height, real height = collision height * 2 ++ double oheight = 0; ++ if (origin.isCreature()) ++ { ++ oheight = ((Creature) origin).getCollisionHeight() * 2; ++ } ++ ++ double theight = 0; ++ if (target.isCreature()) ++ { ++ theight = ((Creature) target).getCollisionHeight() * 2; ++ } ++ ++ // perform geodata check ++ return checkSee(gox, goy, goz, oheight, gtx, gty, gtz, theight, origin.getInstanceWorld()); + } + + /** +- * Can see target. Does not check doors between. +- * @param xValue the x coordinate +- * @param yValue the y coordinate +- * @param zValue the z coordinate +- * @param txValue the target's x coordinate +- * @param tyValue the target's y coordinate +- * @param tzValue the target's z coordinate +- * @return {@code true} if there is line of sight between the given coordinate sets, {@code false} otherwise ++ * Check line of sight from {@link WorldObject} to {@link Location}. ++ * @param origin : The origin object. ++ * @param position : The target position. ++ * @return {@code boolean} : True if object can see position + */ +- public boolean canSeeTarget(int xValue, int yValue, int zValue, int txValue, int tyValue, int tzValue) ++ public boolean canSeeTarget(WorldObject origin, Location position) + { +- int x = xValue; +- int y = yValue; +- int tx = txValue; +- int ty = tyValue; ++ // get origin and target world coordinates ++ final int ox = origin.getX(); ++ final int oy = origin.getY(); ++ final int oz = origin.getZ(); ++ final int tx = position.getX(); ++ final int ty = position.getY(); ++ final int tz = position.getZ(); + +- int geoX = getGeoX(x); +- int geoY = getGeoY(y); +- int tGeoX = getGeoX(tx); +- int tGeoY = getGeoY(ty); ++ if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld(), true)) ++ { ++ return false; ++ } ++ if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld())) ++ { ++ return false; ++ } ++ if (WarpedSpaceManager.getInstance().checkForWarpedSpace(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld())) ++ { ++ return false; ++ } + +- int z = getNearestZ(geoX, geoY, zValue); +- int tz = getNearestZ(tGeoX, tGeoY, tzValue); ++ // get origin and check existing geo coordinates ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) ++ { ++ return true; ++ } + +- // Fastpath. +- if ((geoX == tGeoX) && (geoY == tGeoY)) ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coordinates ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) + { +- if (hasGeoPos(tGeoX, tGeoY)) +- { +- return z == tz; +- } + return true; + } + +- if (tz > z) ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // origin and target coordinates are same ++ if ((gox == gtx) && (goy == gty)) + { +- int tmp = tx; +- tx = x; +- x = tmp; +- +- tmp = ty; +- ty = y; +- y = tmp; +- +- tmp = tz; +- tz = z; +- z = tmp; +- +- tmp = tGeoX; +- tGeoX = geoX; +- geoX = tmp; +- +- tmp = tGeoY; +- tGeoY = geoY; +- geoY = tmp; ++ return goz == gtz; + } + +- final LinePointIterator3D pointIter = new LinePointIterator3D(geoX, geoY, z, tGeoX, tGeoY, tz); +- // 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()) ++ // get origin and target height, real height = collision height * 2 ++ double oheight = 0; ++ if (origin.isCreature()) + { +- final int curX = pointIter.x(); +- final int curY = pointIter.y(); ++ oheight = ((Creature) origin).getTemplate().getCollisionHeight(); ++ } ++ ++ // perform geodata check ++ return checkSee(gox, goy, goz, oheight, gtx, gty, gtz, 0, origin.getInstanceWorld()); ++ } ++ ++ /** ++ * Simple check for origin to target visibility. ++ * @param goxValue : origin X geodata coordinate ++ * @param goyValue : origin Y geodata coordinate ++ * @param gozValue : origin Z geodata coordinate ++ * @param oheight : origin height (if instance of {@link Character}) ++ * @param gtxValue : target X geodata coordinate ++ * @param gtyValue : target Y geodata coordinate ++ * @param gtzValue : target Z geodata coordinate ++ * @param theight : target height (if instance of {@link Character}) ++ * @param instance ++ * @return {@code boolean} : True, when target can be seen. ++ */ ++ private final boolean checkSee(int goxValue, int goyValue, int gozValue, double oheight, int gtxValue, int gtyValue, int gtzValue, double theight, Instance instance) ++ { ++ int goz = gozValue; ++ int gtz = gtzValue; ++ int gox = goxValue; ++ int goy = goyValue; ++ int gtx = gtxValue; ++ int gty = gtyValue; ++ ++ // get line of sight Z coordinates ++ double losoz = goz + ((oheight * Config.PART_OF_CHARACTER_HEIGHT) / 100); ++ double lostz = gtz + ((theight * Config.PART_OF_CHARACTER_HEIGHT) / 100); ++ ++ // get X delta and signum ++ final int dx = Math.abs(gtx - gox); ++ final int sx = gox < gtx ? 1 : -1; ++ final byte dirox = sx > 0 ? GeoStructure.CELL_FLAG_E : GeoStructure.CELL_FLAG_W; ++ final byte dirtx = sx > 0 ? GeoStructure.CELL_FLAG_W : GeoStructure.CELL_FLAG_E; ++ ++ // get Y delta and signum ++ final int dy = Math.abs(gty - goy); ++ final int sy = goy < gty ? 1 : -1; ++ final byte diroy = sy > 0 ? GeoStructure.CELL_FLAG_S : GeoStructure.CELL_FLAG_N; ++ final byte dirty = sy > 0 ? GeoStructure.CELL_FLAG_N : GeoStructure.CELL_FLAG_S; ++ ++ // get Z delta ++ final int dm = Math.max(dx, dy); ++ final double dz = (lostz - losoz) / dm; ++ ++ // get direction flag for diagonal movement ++ final byte diroxy = getDirXY(dirox, diroy); ++ final byte dirtxy = getDirXY(dirtx, dirty); ++ ++ // delta, determines axis to move on (+..X axis, -..Y axis) ++ int d = dx - dy; ++ ++ // NSWE direction of movement ++ byte diro; ++ byte dirt; ++ ++ // initialize node values ++ int nox = gox; ++ int noy = goy; ++ int ntx = gtx; ++ int nty = gty; ++ byte nsweo = getNsweNearest(gox, goy, goz); ++ byte nswet = getNsweNearest(gtx, gty, gtz); ++ ++ // loop ++ ABlock block; ++ int index; ++ for (int i = 0; i < ((dm + 1) / 2); i++) ++ { ++ // reset direction flag ++ diro = 0; ++ dirt = 0; + +- if ((curX == prevX) && (curY == prevY)) ++ // calculate next point coordinates ++ final int e2 = 2 * d; ++ if ((e2 > -dy) && (e2 < dx)) + { +- continue; ++ // calculate next point XY coordinates ++ d -= dy; ++ d += dx; ++ nox += sx; ++ ntx -= sx; ++ noy += sy; ++ nty -= sy; ++ diro |= diroxy; ++ dirt |= dirtxy; + } ++ else if (e2 > -dy) ++ { ++ // calculate next point X coordinate ++ d -= dy; ++ nox += sx; ++ ntx -= sx; ++ diro |= dirox; ++ dirt |= dirtx; ++ } ++ else if (e2 < dx) ++ { ++ // calculate next point Y coordinate ++ d += dx; ++ noy += sy; ++ nty -= sy; ++ diro |= diroy; ++ dirt |= dirty; ++ } + +- final int beeCurZ = pointIter.z(); +- int curGeoZ = prevGeoZ; +- +- // Check if the position has geodata. +- if (hasGeoPos(curX, curY)) + { +- final int beeCurGeoZ = getNearestZ(curX, curY, beeCurZ); +- final int nswe = GeoUtils.computeNswe(prevX, prevY, curX, curY); // .computeDirection(prevX, prevY, curX, curY); +- curGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, curX, curY, nswe); +- int maxHeight; +- if (ptIndex < ELEVATED_SEE_OVER_DISTANCE) ++ // get block of the next cell ++ block = getBlock(nox, noy); ++ ++ // get index of particular layer, based on movement conditions ++ if ((nsweo & diro) == 0) + { +- maxHeight = z + MAX_SEE_OVER_HEIGHT; ++ index = block.getIndexAbove(nox, noy, goz - GeoStructure.CELL_IGNORE_HEIGHT); + } + else + { +- maxHeight = beeCurZ + MAX_SEE_OVER_HEIGHT; ++ index = block.getIndexBelow(nox, noy, goz + GeoStructure.CELL_IGNORE_HEIGHT); + } + +- boolean canSeeThrough = false; +- if ((curGeoZ <= maxHeight) && (curGeoZ <= beeCurGeoZ)) ++ // layer does not exist, return ++ if (index == -1) + { +- if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) +- { +- 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 +- { +- canSeeThrough = true; +- } ++ return false; + } + +- if (!canSeeThrough) ++ // get layer and next line of sight Z coordinate ++ goz = block.getHeight(index); ++ losoz += dz; ++ ++ // perform line of sight check, return when fails ++ if ((goz - losoz) > Config.MAX_OBSTACLE_HEIGHT) + { + return false; + } ++ ++ // get layer nswe ++ nsweo = block.getNswe(index); + } ++ { ++ // get block of the next cell ++ block = getBlock(ntx, nty); ++ ++ // get index of particular layer, based on movement conditions ++ if ((nswet & dirt) == 0) ++ { ++ index = block.getIndexAbove(ntx, nty, gtz - GeoStructure.CELL_IGNORE_HEIGHT); ++ } ++ else ++ { ++ index = block.getIndexBelow(ntx, nty, gtz + GeoStructure.CELL_IGNORE_HEIGHT); ++ } ++ ++ // layer does not exist, return ++ if (index == -1) ++ { ++ return false; ++ } ++ ++ // get layer and next line of sight Z coordinate ++ gtz = block.getHeight(index); ++ lostz -= dz; ++ ++ // perform line of sight check, return when fails ++ if ((gtz - lostz) > Config.MAX_OBSTACLE_HEIGHT) ++ { ++ return false; ++ } ++ ++ // get layer nswe ++ nswet = block.getNswe(index); ++ } + +- prevX = curX; +- prevY = curY; +- prevGeoZ = curGeoZ; +- ++ptIndex; ++ // update coords ++ gox = nox; ++ goy = noy; ++ gtx = ntx; ++ gty = nty; + } + +- return true; ++ // when iteration is completed, compare final Z coordinates ++ return Math.abs(goz - gtz) < (GeoStructure.CELL_HEIGHT * 4); + } + + /** +- * Move check. +- * @param x the x coordinate +- * @param y the y coordinate +- * @param zValue the z coordinate +- * @param tx the target's x coordinate +- * @param ty the target's y coordinate +- * @param tzValue the target's z coordinate +- * @param instance the instance +- * @return the last Location (x,y,z) where player can walk - just before wall ++ * Check movement from coordinates to 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 {code boolean} : True if target coordinates are reachable from origin coordinates + */ +- public Location canMoveToTargetLoc(int x, int y, int zValue, int tx, int ty, int tzValue, Instance instance) ++ public boolean canMoveToTarget(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + { +- final int geoX = getGeoX(x); +- final int geoY = getGeoY(y); +- final int z = getNearestZ(geoX, geoY, zValue); +- final int tGeoX = getGeoX(tx); +- final int tGeoY = getGeoY(ty); +- final int tz = getNearestZ(tGeoX, tGeoY, tzValue); ++ // get origin and check existing geo coordinates ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) ++ { ++ return true; ++ } + +- if (DoorData.getInstance().checkIfDoorsBetween(x, y, z, tx, ty, tz, instance, false)) ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coordinates ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) + { +- return new Location(x, y, getHeight(x, y, z)); ++ return true; + } +- if (FenceData.getInstance().checkIfFenceBetween(x, y, z, tx, ty, tz, instance)) ++ ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // target coordinates reached ++ if ((gox == gtx) && (goy == gty) && (goz == gtz)) + { +- return new Location(x, y, getHeight(x, y, z)); ++ return true; + } +- if (WarpedSpaceManager.getInstance().checkForWarpedSpace(x, y, z, tx, ty, tz, instance)) ++ ++ // perform geodata check ++ final GeoLocation loc = checkMove(gox, goy, goz, gtx, gty, gtz, instance); ++ return (loc.getGeoX() == gtx) && (loc.getGeoY() == gty); ++ } ++ ++ /** ++ * Check movement from origin to target. Returns last available point in the checked path. ++ * @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 {@link Location} : Last point where object can walk (just before wall) ++ */ ++ public Location canMoveToTargetLoc(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) ++ { ++ // Mobius: Double check for doors before normal checkMove to avoid exploiting key movement. ++ if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, instance, false)) + { +- return new Location(x, y, getHeight(x, y, z)); ++ return new Location(ox, oy, oz); + } ++ if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, instance)) ++ { ++ return new Location(ox, oy, oz); ++ } + +- 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 = z; ++ // get origin and check existing geo coordinates ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) ++ { ++ return new Location(tx, ty, tz); ++ } + +- while (pointIter.next()) ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coordinates ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) + { +- final int curX = pointIter.x(); +- final int curY = pointIter.y(); +- final int curZ = getNearestZ(curX, curY, prevZ); +- +- if (hasGeoPos(prevX, prevY)) +- { +- final int nswe = GeoUtils.computeNswe(prevX, prevY, curX, curY); +- if (!checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, nswe)) +- { +- // Can't move, return previous location. +- return new Location(getWorldX(prevX), getWorldY(prevY), prevZ); +- } +- } +- +- prevX = curX; +- prevY = curY; +- prevZ = curZ; ++ return new Location(tx, ty, tz); + } + +- if (hasGeoPos(prevX, prevY) && (prevZ != tz)) ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // target coordinates reached ++ if ((gox == gtx) && (goy == gty) && (goz == gtz)) + { +- // Different floors, return start location. +- return new Location(x, y, z); ++ return new Location(tx, ty, tz); + } + +- return new Location(tx, ty, tz); ++ // perform geodata check ++ return checkMove(gox, goy, goz, gtx, gty, gtz, instance); + } + + /** +- * 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 fromZvalue 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 toZvalue 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 ++ * With this method you can check if a position is visible or can be reached by beeline movement.
++ * 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 gox : origin X geodata coordinate ++ * @param goy : origin Y geodata coordinate ++ * @param goz : origin Z geodata coordinate ++ * @param gtx : target X geodata coordinate ++ * @param gty : target Y geodata coordinate ++ * @param gtz : target Z geodata coordinate ++ * @param instance ++ * @return {@link GeoLocation} : The last allowed point of movement. + */ +- public boolean canMoveToTarget(int fromX, int fromY, int fromZvalue, int toX, int toY, int toZvalue, Instance instance) ++ protected final GeoLocation checkMove(int gox, int goy, int goz, int gtx, int gty, int gtz, Instance instance) + { +- final int geoX = getGeoX(fromX); +- final int geoY = getGeoY(fromY); +- final int fromZ = getNearestZ(geoX, geoY, fromZvalue); +- final int tGeoX = getGeoX(toX); +- final int tGeoY = getGeoY(toY); +- final int toZ = getNearestZ(tGeoX, tGeoY, toZvalue); +- +- if (DoorData.getInstance().checkIfDoorsBetween(fromX, fromY, fromZ, toX, toY, toZ, instance, false)) ++ if (DoorData.getInstance().checkIfDoorsBetween(gox, goy, goz, gtx, gty, gtz, instance, false)) + { +- return false; ++ return new GeoLocation(gox, goy, goz); + } +- if (FenceData.getInstance().checkIfFenceBetween(fromX, fromY, fromZ, toX, toY, toZ, instance)) ++ if (FenceData.getInstance().checkIfFenceBetween(gox, goy, goz, gtx, gty, gtz, instance)) + { +- return false; ++ return new GeoLocation(gox, goy, goz); + } +- if (WarpedSpaceManager.getInstance().checkForWarpedSpace(fromX, fromY, fromZ, toX, toY, toZ, instance)) ++ if (WarpedSpaceManager.getInstance().checkForWarpedSpace(gox, goy, goz, gtx, gty, gtz, instance)) + { +- return false; ++ return new GeoLocation(gox, goy, goz); + } + +- 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 = fromZ; ++ // get X delta, signum and direction flag ++ final int dx = Math.abs(gtx - gox); ++ final int sx = gox < gtx ? 1 : -1; ++ final byte dirX = sx > 0 ? GeoStructure.CELL_FLAG_E : GeoStructure.CELL_FLAG_W; + +- while (pointIter.next()) ++ // get Y delta, signum and direction flag ++ final int dy = Math.abs(gty - goy); ++ final int sy = goy < gty ? 1 : -1; ++ final byte dirY = sy > 0 ? GeoStructure.CELL_FLAG_S : GeoStructure.CELL_FLAG_N; ++ ++ // get direction flag for diagonal movement ++ final byte dirXY = getDirXY(dirX, dirY); ++ ++ // delta, determines axis to move on (+..X axis, -..Y axis) ++ int d = dx - dy; ++ ++ // NSWE direction of movement ++ byte direction; ++ ++ // load pointer coordinates ++ int gpx = gox; ++ int gpy = goy; ++ int gpz = goz; ++ ++ // load next pointer ++ int nx = gpx; ++ int ny = gpy; ++ ++ // loop ++ int count = 0; ++ while (count++ < Config.MAX_ITERATIONS) + { +- final int curX = pointIter.x(); +- final int curY = pointIter.y(); +- final int curZ = getNearestZ(curX, curY, prevZ); ++ direction = 0; + +- if (hasGeoPos(prevX, prevY)) ++ // calculate next point coordinates ++ final int e2 = 2 * d; ++ if ((e2 > -dy) && (e2 < dx)) + { +- final int nswe = GeoUtils.computeNswe(prevX, prevY, curX, curY); +- if (!checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, nswe)) ++ d -= dy; ++ d += dx; ++ nx += sx; ++ ny += sy; ++ direction |= dirXY; ++ } ++ else if (e2 > -dy) ++ { ++ d -= dy; ++ nx += sx; ++ direction |= dirX; ++ } ++ else if (e2 < dx) ++ { ++ d += dx; ++ ny += sy; ++ direction |= dirY; ++ } ++ ++ // obstacle found, return ++ if ((getNsweNearest(gpx, gpy, gpz) & direction) == 0) ++ { ++ return new GeoLocation(gpx, gpy, gpz); ++ } ++ ++ // update pointer coordinates ++ gpx = nx; ++ gpy = ny; ++ gpz = getHeightNearest(nx, ny, gpz); ++ ++ // target coordinates reached ++ if ((gpx == gtx) && (gpy == gty)) ++ { ++ if (gpz == gtz) + { +- return false; ++ // path found, Z coordinates are okay, return target point ++ return new GeoLocation(gtx, gty, gtz); + } ++ ++ // path found, Z coordinates are not okay, return last good point ++ return new GeoLocation(gpx, gpy, gpz); + } ++ } ++ ++ return new GeoLocation(gox, goy, goz); ++ } ++ ++ /** ++ * Returns diagonal NSWE flag format of combined two NSWE flags. ++ * @param dirX : X direction NSWE flag ++ * @param dirY : Y direction NSWE flag ++ * @return byte : NSWE flag of combined direction ++ */ ++ private static byte getDirXY(byte dirX, byte dirY) ++ { ++ // check axis directions ++ if (dirY == GeoStructure.CELL_FLAG_N) ++ { ++ if (dirX == GeoStructure.CELL_FLAG_W) ++ { ++ return GeoStructure.CELL_FLAG_NW; ++ } + +- prevX = curX; +- prevY = curY; +- prevZ = curZ; ++ return GeoStructure.CELL_FLAG_NE; + } + +- if (hasGeoPos(prevX, prevY) && (prevZ != toZ)) ++ if (dirX == GeoStructure.CELL_FLAG_W) + { +- // Different floors. +- return false; ++ return GeoStructure.CELL_FLAG_SW; + } + +- return true; ++ return GeoStructure.CELL_FLAG_SE; + } + ++ /** ++ * 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 ++ */ ++ public List findPath(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) ++ { ++ return null; ++ } ++ ++ /** ++ * Returns the instance of the {@link GeoEngine}. ++ * @return {@link GeoEngine} : The instance. ++ */ + public static GeoEngine getInstance() + { + return SingletonHolder.INSTANCE; +@@ -752,6 +947,6 @@ + + private static class SingletonHolder + { +- protected static final GeoEngine INSTANCE = new GeoEngine(); ++ protected static final GeoEngine INSTANCE = Config.PATHFINDING ? new GeoEnginePathfinding() : new GeoEngine(); + } +-} ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/GeoEnginePathfinding.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/GeoEnginePathfinding.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/GeoEnginePathfinding.java (working copy) +@@ -20,88 +20,84 @@ + import java.util.LinkedList; + import java.util.List; + import java.util.ListIterator; +-import java.util.logging.Level; +-import java.util.logging.Logger; + + import org.l2jmobius.Config; +-import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; +-import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +-import org.l2jmobius.gameserver.geoengine.pathfinding.CellNode; +-import org.l2jmobius.gameserver.geoengine.pathfinding.CellNodeBuffer; +-import org.l2jmobius.gameserver.geoengine.pathfinding.NodeLoc; +-import org.l2jmobius.gameserver.model.World; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoLocation; ++import org.l2jmobius.gameserver.geoengine.pathfinding.Node; ++import org.l2jmobius.gameserver.geoengine.pathfinding.NodeBuffer; ++import org.l2jmobius.gameserver.model.Location; + import org.l2jmobius.gameserver.model.instancezone.Instance; + + /** +- * @author -Nemesiss- ++ * @author Hasha + */ +-public class GeoEnginePathfinding ++final class GeoEnginePathfinding extends GeoEngine + { +- private static final Logger LOGGER = Logger.getLogger(GeoEnginePathfinding.class.getName()); ++ // pre-allocated buffers ++ private final BufferHolder[] _buffers; + +- private BufferInfo[] _buffers; +- + protected GeoEnginePathfinding() + { +- try ++ super(); ++ ++ final String[] array = Config.PATHFIND_BUFFERS.split(";"); ++ _buffers = new BufferHolder[array.length]; ++ int count = 0; ++ for (int i = 0; i < array.length; i++) + { +- final String[] array = Config.PATHFIND_BUFFERS.split(";"); ++ final String buf = array[i]; ++ final String[] args = buf.split("x"); + +- _buffers = new BufferInfo[array.length]; +- +- String buf; +- String[] args; +- for (int i = 0; i < array.length; i++) ++ try + { +- buf = array[i]; +- args = buf.split("x"); +- if (args.length != 2) +- { +- throw new Exception("Invalid buffer definition: " + buf); +- } +- +- _buffers[i] = new BufferInfo(Integer.parseInt(args[0]), Integer.parseInt(args[1])); ++ final int size = Integer.parseInt(args[1]); ++ count += size; ++ _buffers[i] = new BufferHolder(Integer.parseInt(args[0]), size); + } ++ catch (Exception e) ++ { ++ LOGGER.warning("GeoEnginePathfinding: Can not load buffer setting: " + buf); ++ } + } +- catch (Exception e) +- { +- LOGGER.log(Level.WARNING, "CellPathFinding: Problem during buffer init: " + e.getMessage(), e); +- throw new Error("CellPathFinding: load aborted"); +- } ++ ++ LOGGER.info("GeoEnginePathfinding: Loaded " + count + " node buffers."); + } + +- public boolean pathNodesExist(short regionoffset) ++ @Override ++ public List findPath(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + { +- return false; +- } +- +- public List findPath(int x, int y, int z, int tx, int ty, int tz, Instance instance) +- { +- final int gx = GeoEngine.getInstance().getGeoX(x); +- final int gy = GeoEngine.getInstance().getGeoY(y); +- if (!GeoEngine.getInstance().hasGeo(x, y)) ++ // get origin and check existing geo coords ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) + { + 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)) ++ ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coords ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) + { + 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)))); ++ ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // Prepare buffer for pathfinding calculations ++ final NodeBuffer buffer = getBuffer(64 + (2 * Math.max(Math.abs(gox - gtx), Math.abs(goy - gty)))); + if (buffer == null) + { + return null; + } + +- List path = null; ++ // find path ++ List path = null; + try + { +- final CellNode result = buffer.findPath(gx, gy, gz, gtx, gty, gtz); +- ++ final Node result = buffer.findPath(gox, goy, goz, gtx, gty, gtz); + if (result == null) + { + return null; +@@ -125,33 +121,45 @@ + return path; + } + +- int currentX, currentY, currentZ; +- ListIterator middlePoint; ++ // get path list iterator ++ final ListIterator point = path.listIterator(); + +- middlePoint = path.listIterator(); +- currentX = x; +- currentY = y; +- currentZ = z; ++ // get node A (origin) ++ int nodeAx = gox; ++ int nodeAy = goy; ++ short nodeAz = goz; + +- while (middlePoint.hasNext()) ++ // get node B ++ GeoLocation nodeB = (GeoLocation) point.next(); ++ ++ // iterate thought the path to optimize it ++ int count = 0; ++ while (point.hasNext() && (count++ < Config.MAX_ITERATIONS)) + { +- final AbstractNodeLoc locMiddle = middlePoint.next(); +- if (!middlePoint.hasNext()) +- { +- break; +- } ++ // get node C ++ final GeoLocation nodeC = (GeoLocation) path.get(point.nextIndex()); + +- final AbstractNodeLoc locEnd = path.get(middlePoint.nextIndex()); +- if (GeoEngine.getInstance().canMoveToTarget(currentX, currentY, currentZ, locEnd.getX(), locEnd.getY(), locEnd.getZ(), instance)) ++ // check movement from node A to node C ++ final GeoLocation loc = checkMove(nodeAx, nodeAy, nodeAz, nodeC.getGeoX(), nodeC.getGeoY(), nodeC.getZ(), instance); ++ if ((loc.getGeoX() == nodeC.getGeoX()) && (loc.getGeoY() == nodeC.getGeoY())) + { +- middlePoint.remove(); ++ // can move from node A to node C ++ ++ // remove node B ++ point.remove(); + } + else + { +- currentX = locMiddle.getX(); +- currentY = locMiddle.getY(); +- currentZ = locMiddle.getZ(); ++ // can not move from node A to node C ++ ++ // set node A (node B is part of path, update A coordinates) ++ nodeAx = nodeB.getGeoX(); ++ nodeAy = nodeB.getGeoY(); ++ nodeAz = (short) nodeB.getZ(); + } ++ ++ // set node B ++ nodeB = (GeoLocation) point.next(); + } + + return path; +@@ -158,144 +166,102 @@ + } + + /** +- * Convert geodata position to pathnode position +- * @param geo_pos +- * @return pathnode position ++ * Create list of node locations as result of calculated buffer node tree. ++ * @param node : the entry point ++ * @return List : list of node location + */ +- public short getNodePos(int geo_pos) ++ private static List constructPath(Node node) + { +- return (short) (geo_pos >> 3); // OK? +- } +- +- /** +- * Convert node position to pathnode block position +- * @param node_pos +- * @return pathnode block position (0...255) +- */ +- public short getNodeBlock(int node_pos) +- { +- return (short) (node_pos % 256); +- } +- +- public byte getRegionX(int node_pos) +- { +- return (byte) ((node_pos >> 8) + World.TILE_X_MIN); +- } +- +- public byte getRegionY(int node_pos) +- { +- return (byte) ((node_pos >> 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 node_x rx +- * @return +- */ +- public int calculateWorldX(short node_x) +- { +- return World.MAP_MIN_X + (node_x * 128) + 48; +- } +- +- /** +- * Convert pathnode y to World y position +- * @param node_y +- * @return +- */ +- public int calculateWorldY(short node_y) +- { +- return World.MAP_MIN_Y + (node_y * 128) + 48; +- } +- +- private List constructPath(AbstractNode nodeValue) +- { +- final LinkedList path = new LinkedList<>(); +- int previousDirectionX = Integer.MIN_VALUE; +- int previousDirectionY = Integer.MIN_VALUE; +- int directionX, directionY; ++ // create empty list ++ final LinkedList list = new LinkedList<>(); + +- AbstractNode node = nodeValue; +- while (node.getParent() != null) ++ // set direction X/Y ++ int dx = 0; ++ int dy = 0; ++ ++ // get target parent ++ Node target = node; ++ Node parent = target.getParent(); ++ ++ // while parent exists ++ int count = 0; ++ while ((parent != null) && (count++ < Config.MAX_ITERATIONS)) + { +- directionX = node.getLoc().getNodeX() - node.getParent().getLoc().getNodeX(); +- directionY = node.getLoc().getNodeY() - node.getParent().getLoc().getNodeY(); ++ // get parent <> target direction X/Y ++ final int nx = parent.getLoc().getGeoX() - target.getLoc().getGeoX(); ++ final int ny = parent.getLoc().getGeoY() - target.getLoc().getGeoY(); + +- // only add a new route point if moving direction changes +- if ((directionX != previousDirectionX) || (directionY != previousDirectionY)) ++ // direction has changed? ++ if ((dx != nx) || (dy != ny)) + { +- previousDirectionX = directionX; +- previousDirectionY = directionY; ++ // add node to the beginning of the list ++ list.addFirst(target.getLoc()); + +- path.addFirst(node.getLoc()); +- node.setLoc(null); ++ // update direction X/Y ++ dx = nx; ++ dy = ny; + } + +- node = node.getParent(); ++ // move to next node, set target and get its parent ++ target = parent; ++ parent = target.getParent(); + } + +- return path; ++ // return list ++ return list; + } + +- private CellNodeBuffer alloc(int size) ++ /** ++ * Provides optimize selection of the buffer. When all pre-initialized buffer are locked, creates new buffer. ++ * @param size : pre-calculated minimal required size ++ * @return NodeBuffer : buffer ++ */ ++ private final NodeBuffer getBuffer(int size) + { +- CellNodeBuffer current = null; +- for (BufferInfo i : _buffers) ++ NodeBuffer current = null; ++ for (BufferHolder holder : _buffers) + { +- if (i.mapSize >= size) ++ // Find proper size of buffer ++ if (holder._size < size) + { +- for (CellNodeBuffer buf : i.buffer) ++ continue; ++ } ++ ++ // Find unlocked NodeBuffer ++ for (NodeBuffer buffer : holder._buffer) ++ { ++ if (!buffer.isLocked()) + { +- if (buf.lock()) +- { +- current = buf; +- break; +- } ++ continue; + } +- if (current != null) +- { +- break; +- } + +- // not found, allocate temporary buffer +- current = new CellNodeBuffer(i.mapSize); +- current.lock(); +- if (i.buffer.size() < i.count) +- { +- i.buffer.add(current); +- break; +- } ++ return buffer; + } ++ ++ // NodeBuffer not found, allocate temporary buffer ++ current = new NodeBuffer(holder._size); ++ current.isLocked(); + } + + return current; + } + +- private static final class BufferInfo ++ /** ++ * NodeBuffer container with specified size and count of separate buffers. ++ */ ++ private static final class BufferHolder + { +- final int mapSize; +- final int count; +- ArrayList buffer; ++ final int _size; ++ List _buffer; + +- public BufferInfo(int size, int cnt) ++ public BufferHolder(int size, int count) + { +- mapSize = size; +- count = cnt; +- buffer = new ArrayList<>(count); ++ _size = size; ++ _buffer = new ArrayList<>(count); ++ for (int i = 0; i < count; i++) ++ { ++ _buffer.add(new NodeBuffer(size)); ++ } + } + } +- +- public static GeoEnginePathfinding getInstance() +- { +- return SingletonHolder.INSTANCE; +- } +- +- private static class SingletonHolder +- { +- protected static final GeoEnginePathfinding INSTANCE = new GeoEnginePathfinding(); +- } +-} ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java (working copy) +@@ -0,0 +1,197 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++import java.io.IOException; ++ ++/** ++ * @author Hasha ++ */ ++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 height of cell, which is closest to given coordinates.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @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 getHeightNearestOriginal(int geoX, int geoY, int worldZ); ++ ++ /** ++ * Returns the height of cell, which is first above 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, above given coordinates. ++ */ ++ public abstract short getHeightAbove(int geoX, int geoY, int worldZ); ++ ++ /** ++ * Returns the height of cell, which is first below 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, below given coordinates. ++ */ ++ public abstract short getHeightBelow(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 the NSWE flag byte of cell, which is closest to given coordinates.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @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 getNsweNearestOriginal(int geoX, int geoY, int worldZ); ++ ++ /** ++ * Returns the NSWE flag byte of cell, which is first above 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 getNsweAbove(int geoX, int geoY, int worldZ); ++ ++ /** ++ * Returns the NSWE flag byte of cell, which is first below 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 getNsweBelow(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 above given coordinates.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @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 getIndexAboveOriginal(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 index to data of the cell, which is first layer below given coordinates.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @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 getIndexBelowOriginal(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 height of cell given by cell index.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @param index : Index of the cell. ++ * @return short : Cell geodata Z coordinate, below given coordinates. ++ */ ++ public abstract short getHeightOriginal(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); ++ ++ /** ++ * Returns the NSWE flag byte of cell given by cell index.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @param index : Index of the cell. ++ * @return short : Cell geodata Z coordinate, below given coordinates. ++ */ ++ public abstract byte getNsweOriginal(int index); ++ ++ /** ++ * Sets the NSWE flag byte of cell given by cell index. ++ * @param index : Index of the cell. ++ * @param nswe : New NSWE flag byte. ++ */ ++ public abstract void setNswe(int index, byte nswe); ++ ++ /** ++ * Saves the block in L2D format to {@link BufferedOutputStream}. Used only for L2D geodata conversion. ++ * @param stream : The stream. ++ * @throws IOException : Can't save the block to steam. ++ */ ++ public abstract void saveBlock(BufferedOutputStream stream) throws IOException; ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java (working copy) +@@ -0,0 +1,252 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++import java.io.IOException; ++import java.nio.ByteBuffer; ++ ++/** ++ * @author Hasha ++ */ ++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. ++ * @param format : GeoFormat specifying format of loaded data. ++ */ ++ public BlockComplex(ByteBuffer bb, GeoFormat format) ++ { ++ // initialize buffer ++ _buffer = new byte[GeoStructure.BLOCK_CELLS * 3]; ++ ++ // load data ++ for (int i = 0; i < GeoStructure.BLOCK_CELLS; i++) ++ { ++ if (format != GeoFormat.L2D) ++ { ++ // 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); ++ } ++ else ++ { ++ // get nswe ++ final byte nswe = bb.get(); ++ _buffer[i * 3] = nswe; ++ ++ // get height ++ final short height = bb.getShort(); ++ _buffer[(i * 3) + 1] = (byte) (height & 0x00FF); ++ _buffer[(i * 3) + 2] = (byte) (height >> 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 short getHeightNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getHeightNearest(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeightAbove(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 short height = (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ ++ // check and return height ++ return height > worldZ ? height : Short.MIN_VALUE; ++ } ++ ++ @Override ++ public short getHeightBelow(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 short height = (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ ++ // check and return height ++ return height < worldZ ? height : Short.MAX_VALUE; ++ } ++ ++ @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 byte getNsweNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getNsweNearest(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public byte getNsweAbove(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 ? _buffer[index] : 0; ++ } ++ ++ @Override ++ public byte getNsweBelow(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 ? _buffer[index] : 0; ++ } ++ ++ @Override ++ public 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 getIndexAboveOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexAbove(geoX, geoY, worldZ); ++ } ++ ++ @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 int getIndexBelowOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexBelow(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeight(int index) ++ { ++ return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ } ++ ++ @Override ++ public short getHeightOriginal(int index) ++ { ++ return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ } ++ ++ @Override ++ public byte getNswe(int index) ++ { ++ return _buffer[index]; ++ } ++ ++ @Override ++ public byte getNsweOriginal(int index) ++ { ++ return _buffer[index]; ++ } ++ ++ @Override ++ public void setNswe(int index, byte nswe) ++ { ++ _buffer[index] = nswe; ++ } ++ ++ @Override ++ public void saveBlock(BufferedOutputStream stream) throws IOException ++ { ++ // write block type ++ stream.write(GeoStructure.TYPE_COMPLEX_L2D); ++ ++ // write block data ++ stream.write(_buffer, 0, GeoStructure.BLOCK_CELLS * 3); ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java (working copy) +@@ -0,0 +1,176 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++import java.io.IOException; ++import java.nio.ByteBuffer; ++ ++/** ++ * @author Hasha ++ */ ++public class BlockFlat extends ABlock ++{ ++ protected final short _height; ++ protected byte _nswe; ++ ++ /** ++ * Creates FlatBlock. ++ * @param bb : Input byte buffer. ++ * @param format : GeoFormat specifying format of loaded data. ++ */ ++ public BlockFlat(ByteBuffer bb, GeoFormat format) ++ { ++ _height = bb.getShort(); ++ _nswe = format != GeoFormat.L2D ? 0x0F : (byte) (0xFF); ++ if (format == GeoFormat.L2OFF) ++ { ++ bb.getShort(); ++ } ++ } ++ ++ @Override ++ public boolean hasGeoPos() ++ { ++ return true; ++ } ++ ++ @Override ++ public short getHeightNearest(int geoX, int geoY, int worldZ) ++ { ++ return _height; ++ } ++ ++ @Override ++ public short getHeightNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return _height; ++ } ++ ++ @Override ++ public short getHeightAbove(int geoX, int geoY, int worldZ) ++ { ++ // check and return height ++ return _height > worldZ ? _height : Short.MIN_VALUE; ++ } ++ ++ @Override ++ public short getHeightBelow(int geoX, int geoY, int worldZ) ++ { ++ // check and return height ++ return _height < worldZ ? _height : Short.MAX_VALUE; ++ } ++ ++ @Override ++ public byte getNsweNearest(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweAbove(int geoX, int geoY, int worldZ) ++ { ++ // check height and return nswe ++ return _height > worldZ ? _nswe : 0; ++ } ++ ++ @Override ++ public byte getNsweBelow(int geoX, int geoY, int worldZ) ++ { ++ // check height and return nswe ++ return _height < worldZ ? _nswe : 0; ++ } ++ ++ @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 getIndexAboveOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexAbove(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public int getIndexBelow(int geoX, int geoY, int worldZ) ++ { ++ // check height and return index ++ return _height < worldZ ? 0 : -1; ++ } ++ ++ @Override ++ public int getIndexBelowOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexBelow(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeight(int index) ++ { ++ return _height; ++ } ++ ++ @Override ++ public short getHeightOriginal(int index) ++ { ++ return _height; ++ } ++ ++ @Override ++ public byte getNswe(int index) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweOriginal(int index) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public void setNswe(int index, byte nswe) ++ { ++ _nswe = nswe; ++ } ++ ++ @Override ++ public void saveBlock(BufferedOutputStream stream) throws IOException ++ { ++ // write block type ++ stream.write(GeoStructure.TYPE_FLAT_L2D); ++ ++ // write height ++ stream.write((byte) (_height & 0x00FF)); ++ stream.write((byte) (_height >> 8)); ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java (working copy) +@@ -0,0 +1,465 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++import java.io.IOException; ++import java.nio.ByteBuffer; ++import java.nio.ByteOrder; ++import java.util.Arrays; ++ ++/** ++ * @author Hasha ++ */ ++public class BlockMultilayer extends ABlock ++{ ++ private static final int MAX_LAYERS = Byte.MAX_VALUE; ++ ++ private static ByteBuffer _temp; ++ ++ /** ++ * Initializes the temporarily buffer. ++ */ ++ public static void initialize() ++ { ++ // initialize temporarily buffer and sorting mechanism ++ _temp = ByteBuffer.allocate(GeoStructure.BLOCK_CELLS * MAX_LAYERS * 3); ++ _temp.order(ByteOrder.LITTLE_ENDIAN); ++ } ++ ++ /** ++ * Releases temporarily buffer. ++ */ ++ public static void release() ++ { ++ _temp = null; ++ } ++ ++ protected byte[] _buffer; ++ ++ /** ++ * Implicit constructor for children class. ++ */ ++ protected BlockMultilayer() ++ { ++ _buffer = null; ++ } ++ ++ /** ++ * Creates MultilayerBlock. ++ * @param bb : Input byte buffer. ++ * @param format : GeoFormat specifying format of loaded data. ++ */ ++ public BlockMultilayer(ByteBuffer bb, GeoFormat format) ++ { ++ // 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 = format != GeoFormat.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++) ++ { ++ if (format != GeoFormat.L2D) ++ { ++ // get data ++ final short data = bb.getShort(); ++ ++ // add nswe and height ++ _temp.put((byte) (data & 0x000F)); ++ _temp.putShort((short) ((short) (data & 0xFFF0) >> 1)); ++ } ++ else ++ { ++ // add nswe ++ _temp.put(bb.get()); ++ ++ // add height ++ _temp.putShort(bb.getShort()); ++ } ++ } ++ } ++ ++ // 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 short getHeightNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getHeightNearest(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeightAbove(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 height ++ if (height > worldZ) ++ { ++ return (short) height; ++ } ++ ++ // move index to next layer ++ index -= 3; ++ } ++ ++ // none layer found, return minimum value ++ return Short.MIN_VALUE; ++ } ++ ++ @Override ++ public short getHeightBelow(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 height ++ if (height < worldZ) ++ { ++ return (short) height; ++ } ++ ++ // move index to next layer ++ index += 3; ++ } ++ ++ // none layer found, return maximum value ++ return Short.MAX_VALUE; ++ } ++ ++ @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 byte getNsweNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getNsweNearest(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public byte getNsweAbove(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 nswe ++ if (height > worldZ) ++ { ++ return _buffer[index]; ++ } ++ ++ // move index to next layer ++ index -= 3; ++ } ++ ++ // none layer found, block movement ++ return 0; ++ } ++ ++ @Override ++ public byte getNsweBelow(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 nswe ++ if (height < worldZ) ++ { ++ return _buffer[index]; ++ } ++ ++ // move index to next layer ++ index += 3; ++ } ++ ++ // none layer found, block movement ++ return 0; ++ } ++ ++ @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 first layer data (first from bottom) ++ byte layers = _buffer[index++]; ++ ++ // loop though all cell layers, find closest layer ++ 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; ++ } ++ ++ // none layer found ++ return -1; ++ } ++ ++ @Override ++ public int getIndexAboveOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexAbove(geoX, geoY, worldZ); ++ } ++ ++ @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; ++ } ++ ++ // none layer found ++ return -1; ++ } ++ ++ @Override ++ public int getIndexBelowOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexBelow(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeight(int index) ++ { ++ // get height ++ return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ } ++ ++ @Override ++ public short getHeightOriginal(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]; ++ } ++ ++ @Override ++ public byte getNsweOriginal(int index) ++ { ++ // get nswe ++ return _buffer[index]; ++ } ++ ++ @Override ++ public void setNswe(int index, byte nswe) ++ { ++ // set nswe ++ _buffer[index] = nswe; ++ } ++ ++ @Override ++ public void saveBlock(BufferedOutputStream stream) throws IOException ++ { ++ // write block type ++ stream.write(GeoStructure.TYPE_MULTILAYER_L2D); ++ ++ // for each cell ++ int index = 0; ++ for (int i = 0; i < GeoStructure.BLOCK_CELLS; i++) ++ { ++ // write layers count ++ final byte layers = _buffer[index++]; ++ stream.write(layers); ++ ++ // write cell data ++ stream.write(_buffer, index, layers * 3); ++ ++ // move index to next cell ++ index += layers * 3; ++ } ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java (working copy) +@@ -0,0 +1,150 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++ ++/** ++ * @author Hasha ++ */ ++public class BlockNull extends ABlock ++{ ++ private final byte _nswe; ++ ++ public BlockNull() ++ { ++ _nswe = (byte) 0xFF; ++ } ++ ++ @Override ++ public boolean hasGeoPos() ++ { ++ return false; ++ } ++ ++ @Override ++ public short getHeightNearest(int geoX, int geoY, int worldZ) ++ { ++ return (short) worldZ; ++ } ++ ++ @Override ++ public short getHeightNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return (short) worldZ; ++ } ++ ++ @Override ++ public short getHeightAbove(int geoX, int geoY, int worldZ) ++ { ++ return (short) worldZ; ++ } ++ ++ @Override ++ public short getHeightBelow(int geoX, int geoY, int worldZ) ++ { ++ return (short) worldZ; ++ } ++ ++ @Override ++ public byte getNsweNearest(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweAbove(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweBelow(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) ++ { ++ return 0; ++ } ++ ++ @Override ++ public int getIndexAboveOriginal(int geoX, int geoY, int worldZ) ++ { ++ return 0; ++ } ++ ++ @Override ++ public int getIndexBelow(int geoX, int geoY, int worldZ) ++ { ++ return 0; ++ } ++ ++ @Override ++ public int getIndexBelowOriginal(int geoX, int geoY, int worldZ) ++ { ++ return 0; ++ } ++ ++ @Override ++ public short getHeight(int index) ++ { ++ return 0; ++ } ++ ++ @Override ++ public short getHeightOriginal(int index) ++ { ++ return 0; ++ } ++ ++ @Override ++ public byte getNswe(int index) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweOriginal(int index) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public void setNswe(int index, byte nswe) ++ { ++ } ++ ++ @Override ++ public void saveBlock(BufferedOutputStream stream) ++ { ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java (nonexistent) +@@ -1,48 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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() +- { +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/ComplexBlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/ComplexBlock.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/ComplexBlock.java (nonexistent) +@@ -1,77 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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; +- +-/** +- * @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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/FlatBlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/FlatBlock.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/FlatBlock.java (nonexistent) +@@ -1,56 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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; +- +-/** +- * @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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/GeoFormat.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/GeoFormat.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/GeoFormat.java (working copy) +@@ -0,0 +1,39 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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 Hasha ++ */ ++public enum GeoFormat ++{ ++ L2J("%d_%d.l2j"), ++ L2OFF("%d_%d_conv.dat"), ++ L2D("%d_%d.l2d"); ++ ++ private final String _filename; ++ ++ private GeoFormat(String filename) ++ { ++ _filename = filename; ++ } ++ ++ public String getFilename() ++ { ++ return _filename; ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/GeoLocation.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/GeoLocation.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/GeoLocation.java (working copy) +@@ -0,0 +1,67 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.geoengine.GeoEngine; ++import org.l2jmobius.gameserver.model.Location; ++ ++/** ++ * @author Hasha ++ */ ++public class GeoLocation extends Location ++{ ++ private byte _nswe; ++ ++ public GeoLocation(int x, int y, int z) ++ { ++ super(x, y, GeoEngine.getInstance().getHeightNearest(x, y, z)); ++ _nswe = GeoEngine.getInstance().getNsweNearest(x, y, z); ++ } ++ ++ public void set(int x, int y, short z) ++ { ++ super.setXYZ(x, y, GeoEngine.getInstance().getHeightNearest(x, y, z)); ++ _nswe = GeoEngine.getInstance().getNsweNearest(x, y, z); ++ } ++ ++ public int getGeoX() ++ { ++ return _x; ++ } ++ ++ public int getGeoY() ++ { ++ return _y; ++ } ++ ++ @Override ++ public int getX() ++ { ++ return GeoEngine.getWorldX(_x); ++ } ++ ++ @Override ++ public int getY() ++ { ++ return GeoEngine.getWorldY(_y); ++ } ++ ++ public byte getNSWE() ++ { ++ return _nswe; ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java (working copy) +@@ -0,0 +1,70 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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; ++ ++/** ++ * @author Hasha ++ */ ++public class GeoStructure ++{ ++ // cells ++ public static final byte CELL_FLAG_E = 1 << 0; ++ public static final byte CELL_FLAG_W = 1 << 1; ++ public static final byte CELL_FLAG_S = 1 << 2; ++ public static final byte CELL_FLAG_N = 1 << 3; ++ public static final byte CELL_FLAG_SE = 1 << 4; ++ public static final byte CELL_FLAG_SW = 1 << 5; ++ public static final byte CELL_FLAG_NE = 1 << 6; ++ public static final byte CELL_FLAG_NW = (byte) (1 << 7); ++ ++ public static final int CELL_HEIGHT = 8; ++ public static final int CELL_IGNORE_HEIGHT = CELL_HEIGHT * 6; ++ ++ // blocks ++ public static final byte TYPE_FLAT_L2J_L2OFF = 0; ++ public static final byte TYPE_FLAT_L2D = (byte) 0xD0; ++ public static final byte TYPE_COMPLEX_L2J = 1; ++ public static final byte TYPE_COMPLEX_L2OFF = 0x40; ++ public static final byte TYPE_COMPLEX_L2D = (byte) 0xD1; ++ 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) ++ public static final byte TYPE_MULTILAYER_L2D = (byte) 0xD2; ++ ++ 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; ++ ++ // regions ++ 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; ++ ++ // global geodata ++ private static final int GEO_REGIONS_X = ((World.TILE_X_MAX - World.TILE_X_MIN) + 1); ++ private 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 +Index: java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java (nonexistent) +@@ -1,42 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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); +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/IGeoObject.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/IGeoObject.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/IGeoObject.java (working copy) +@@ -0,0 +1,53 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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 Hasha ++ */ ++public interface IGeoObject ++{ ++ /** ++ * Returns geodata X coordinate of the {@link IGeoObject}. ++ * @return int : Geodata X coordinate. ++ */ ++ int getGeoX(); ++ ++ /** ++ * Returns geodata Y coordinate of the {@link IGeoObject}. ++ * @return int : Geodata Y coordinate. ++ */ ++ int getGeoY(); ++ ++ /** ++ * Returns geodata Z coordinate of the {@link IGeoObject}. ++ * @return int : Geodata Z coordinate. ++ */ ++ int getGeoZ(); ++ ++ /** ++ * Returns height of the {@link IGeoObject}. ++ * @return int : Height. ++ */ ++ int getHeight(); ++ ++ /** ++ * Returns {@link IGeoObject} data. ++ * @return byte[][] : {@link IGeoObject} data. ++ */ ++ byte[][] getObjectGeoData(); ++} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java (nonexistent) +@@ -1,47 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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(); +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/MultilayerBlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/MultilayerBlock.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/MultilayerBlock.java (nonexistent) +@@ -1,183 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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; +- +-/** +- * @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); +- +- // 1 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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/NullRegion.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/NullRegion.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/NullRegion.java (nonexistent) +@@ -1,55 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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 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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/Region.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/Region.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/Region.java (nonexistent) +@@ -1,92 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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; +- +-/** +- * @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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java (nonexistent) +@@ -1,87 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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 T _loc; +- private AbstractNode _parent; +- +- public AbstractNode(T loc) +- { +- _loc = loc; +- } +- +- public void setParent(AbstractNode p) +- { +- _parent = p; +- } +- +- public AbstractNode getParent() +- { +- return _parent; +- } +- +- public T getLoc() +- { +- return _loc; +- } +- +- public void setLoc(T l) +- { +- _loc = l; +- } +- +- @Override +- public int hashCode() +- { +- final int prime = 31; +- int result = 1; +- result = (prime * result) + ((_loc == null) ? 0 : _loc.hashCode()); +- return result; +- } +- +- @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 +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java (nonexistent) +@@ -1,33 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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 int getNodeX(); +- +- public abstract int getNodeY(); +-} +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNode.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNode.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNode.java (nonexistent) +@@ -1,67 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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 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 +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNodeBuffer.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNodeBuffer.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNodeBuffer.java (nonexistent) +@@ -1,348 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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.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 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) +- { +- _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(); +- } +- +- 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 void getNeighbors() +- { +- if (!_current.getLoc().canGoAll()) +- { +- 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); +- } +- +- // SouthEast +- if ((nodeE != null) && (nodeS != null)) +- { +- if (nodeE.getLoc().canGoSouth() && nodeS.getLoc().canGoEast()) +- { +- addNode(x + 1, y + 1, z, true); +- } +- } +- +- // SouthWest +- if ((nodeS != null) && (nodeW != null)) +- { +- if (nodeW.getLoc().canGoSouth() && nodeS.getLoc().canGoWest()) +- { +- addNode(x - 1, y + 1, z, true); +- } +- } +- +- // NorthEast +- if ((nodeN != null) && (nodeE != null)) +- { +- if (nodeE.getLoc().canGoNorth() && nodeN.getLoc().canGoEast()) +- { +- addNode(x + 1, y - 1, z, true); +- } +- } +- +- // NorthWest +- if ((nodeN != null) && (nodeW != null)) +- { +- if (nodeW.getLoc().canGoNorth() && nodeN.getLoc().canGoWest()) +- { +- addNode(x - 1, y - 1, z, true); +- } +- } +- } +- +- private 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(); +- // reinit node if needed +- if (result.getLoc() != null) +- { +- result.getLoc().set(x, y, z); +- } +- else +- { +- result.setLoc(new NodeLoc(x, y, z)); +- } +- } +- +- return result; +- } +- +- private 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 boolean isHighWeight(int x, int y, int z) +- { +- final CellNode result = getNode(x, y, z); +- if (result == null) +- { +- return true; +- } +- +- if (!result.getLoc().canGoAll()) +- { +- return true; +- } +- if (Math.abs(result.getLoc().getZ() - z) > 16) +- { +- return true; +- } +- +- return false; +- } +- +- private 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 +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java (working copy) +@@ -0,0 +1,87 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.geodata.GeoLocation; ++ ++/** ++ * @author Hasha ++ */ ++public class Node ++{ ++ // node coords and nswe flag ++ private GeoLocation _loc; ++ ++ // node parent (for reverse path construction) ++ private Node _parent; ++ // node child (for moving over nodes during iteration) ++ private Node _child; ++ ++ // node G cost (movement cost = parent movement cost + current movement cost) ++ private double _cost = -1000; ++ ++ public void setLoc(int x, int y, int z) ++ { ++ _loc = new GeoLocation(x, y, z); ++ } ++ ++ public GeoLocation getLoc() ++ { ++ return _loc; ++ } ++ ++ public void setParent(Node parent) ++ { ++ _parent = parent; ++ } ++ ++ public Node getParent() ++ { ++ return _parent; ++ } ++ ++ public void setChild(Node child) ++ { ++ _child = child; ++ } ++ ++ public Node getChild() ++ { ++ return _child; ++ } ++ ++ public void setCost(double cost) ++ { ++ _cost = cost; ++ } ++ ++ public double getCost() ++ { ++ return _cost; ++ } ++ ++ public void free() ++ { ++ // reset node location ++ _loc = null; ++ ++ // reset node parent, child and cost ++ _parent = null; ++ _child = null; ++ _cost = -1000; ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java (working copy) +@@ -0,0 +1,306 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.concurrent.locks.ReentrantLock; ++ ++import org.l2jmobius.Config; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; ++ ++/** ++ * @author DS, Hasha; Credits to Diamond ++ */ ++public class NodeBuffer ++{ ++ private final ReentrantLock _lock = new ReentrantLock(); ++ private final int _size; ++ private final Node[][] _buffer; ++ ++ // center coordinates ++ private int _cx = 0; ++ private int _cy = 0; ++ ++ // target coordinates ++ private int _gtx = 0; ++ private int _gty = 0; ++ private short _gtz = 0; ++ ++ private Node _current = null; ++ ++ /** ++ * Constructor of NodeBuffer. ++ * @param size : one dimension size of buffer ++ */ ++ public NodeBuffer(int size) ++ { ++ // set size ++ _size = size; ++ ++ // initialize buffer ++ _buffer = new Node[size][size]; ++ for (int x = 0; x < size; x++) ++ { ++ for (int y = 0; y < size; y++) ++ { ++ _buffer[x][y] = 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 Node : first node of path ++ */ ++ public Node findPath(int gox, int goy, short goz, int gtx, int gty, short gtz) ++ { ++ // set coordinates (middle of the line (gox,goy) - (gtx,gty), will be in the center of the buffer) ++ _cx = gox + ((gtx - gox - _size) / 2); ++ _cy = goy + ((gty - goy - _size) / 2); ++ ++ _gtx = gtx; ++ _gty = gty; ++ _gtz = gtz; ++ ++ _current = getNode(gox, goy, goz); ++ _current.setCost(getCostH(gox, goy, goz)); ++ ++ int count = 0; ++ do ++ { ++ // reached target? ++ if ((_current.getLoc().getGeoX() == _gtx) && (_current.getLoc().getGeoY() == _gty) && (Math.abs(_current.getLoc().getZ() - _gtz) < 8)) ++ { ++ return _current; ++ } ++ ++ // expand current node ++ expand(); ++ ++ // move pointer ++ _current = _current.getChild(); ++ } ++ while ((_current != null) && (count++ < Config.MAX_ITERATIONS)); ++ ++ return null; ++ } ++ ++ public boolean isLocked() ++ { ++ return _lock.tryLock(); ++ } ++ ++ public void free() ++ { ++ _current = null; ++ ++ for (Node[] nodes : _buffer) ++ { ++ for (Node node : nodes) ++ { ++ if (node.getLoc() != null) ++ { ++ node.free(); ++ } ++ } ++ } ++ ++ _lock.unlock(); ++ } ++ ++ /** ++ * Check _current Node and add its neighbors to the buffer. ++ */ ++ private final void expand() ++ { ++ // can't move anywhere, don't expand ++ final byte nswe = _current.getLoc().getNSWE(); ++ if (nswe == 0) ++ { ++ return; ++ } ++ ++ // get geo coords of the node to be expanded ++ final int x = _current.getLoc().getGeoX(); ++ final int y = _current.getLoc().getGeoY(); ++ final short z = (short) _current.getLoc().getZ(); ++ ++ // can move north, expand ++ if ((nswe & GeoStructure.CELL_FLAG_N) != 0) ++ { ++ addNode(x, y - 1, z, Config.BASE_WEIGHT); ++ } ++ ++ // can move south, expand ++ if ((nswe & GeoStructure.CELL_FLAG_S) != 0) ++ { ++ addNode(x, y + 1, z, Config.BASE_WEIGHT); ++ } ++ ++ // can move west, expand ++ if ((nswe & GeoStructure.CELL_FLAG_W) != 0) ++ { ++ addNode(x - 1, y, z, Config.BASE_WEIGHT); ++ } ++ ++ // can move east, expand ++ if ((nswe & GeoStructure.CELL_FLAG_E) != 0) ++ { ++ addNode(x + 1, y, z, Config.BASE_WEIGHT); ++ } ++ ++ // can move north-west, expand ++ if ((nswe & GeoStructure.CELL_FLAG_NW) != 0) ++ { ++ addNode(x - 1, y - 1, z, Config.DIAGONAL_WEIGHT); ++ } ++ ++ // can move north-east, expand ++ if ((nswe & GeoStructure.CELL_FLAG_NE) != 0) ++ { ++ addNode(x + 1, y - 1, z, Config.DIAGONAL_WEIGHT); ++ } ++ ++ // can move south-west, expand ++ if ((nswe & GeoStructure.CELL_FLAG_SW) != 0) ++ { ++ addNode(x - 1, y + 1, z, Config.DIAGONAL_WEIGHT); ++ } ++ ++ // can move south-east, expand ++ if ((nswe & GeoStructure.CELL_FLAG_SE) != 0) ++ { ++ addNode(x + 1, y + 1, z, Config.DIAGONAL_WEIGHT); ++ } ++ } ++ ++ /** ++ * Returns node, if it exists in buffer. ++ * @param x : node X coord ++ * @param y : node Y coord ++ * @param z : node Z coord ++ * @return Node : node, if exits in buffer ++ */ ++ private final Node getNode(int x, int y, short z) ++ { ++ // check node X out of coordinates ++ final int ix = x - _cx; ++ if ((ix < 0) || (ix >= _size)) ++ { ++ return null; ++ } ++ ++ // check node Y out of coordinates ++ final int iy = y - _cy; ++ if ((iy < 0) || (iy >= _size)) ++ { ++ return null; ++ } ++ ++ // get node ++ final Node result = _buffer[ix][iy]; ++ ++ // check and update ++ if (result.getLoc() == null) ++ { ++ result.setLoc(x, y, z); ++ } ++ ++ // return node ++ return result; ++ } ++ ++ /** ++ * Add node given by coordinates to the buffer. ++ * @param x : geo X coord ++ * @param y : geo Y coord ++ * @param z : geo Z coord ++ * @param weight : weight of movement to new node ++ */ ++ private final void addNode(int x, int y, short z, int weight) ++ { ++ // get node to be expanded ++ final Node node = getNode(x, y, z); ++ if (node == null) ++ { ++ return; ++ } ++ ++ // Z distance between nearby cells is higher than cell size ++ if (node.getLoc().getZ() > (z + (2 * GeoStructure.CELL_HEIGHT))) ++ { ++ return; ++ } ++ ++ // node was already expanded, return ++ if (node.getCost() >= 0) ++ { ++ return; ++ } ++ ++ node.setParent(_current); ++ if (node.getLoc().getNSWE() != (byte) 0xFF) ++ { ++ node.setCost(getCostH(x, y, node.getLoc().getZ()) + (weight * Config.OBSTACLE_MULTIPLIER)); ++ } ++ else ++ { ++ node.setCost(getCostH(x, y, node.getLoc().getZ()) + weight); ++ } ++ ++ Node current = _current; ++ int count = 0; ++ while ((current.getChild() != null) && (count < (Config.MAX_ITERATIONS * 4))) ++ { ++ count++; ++ if (current.getChild().getCost() > node.getCost()) ++ { ++ node.setChild(current.getChild()); ++ break; ++ } ++ current = current.getChild(); ++ } ++ ++ if (count >= (Config.MAX_ITERATIONS * 4)) ++ { ++ System.err.println("Pathfinding: too long loop detected, cost:" + node.getCost()); ++ } ++ ++ current.setChild(node); ++ } ++ ++ /** ++ * @param x : node X coord ++ * @param y : node Y coord ++ * @param i : node Z coord ++ * @return double : node cost ++ */ ++ private final double getCostH(int x, int y, int i) ++ { ++ final int dX = x - _gtx; ++ final int dY = y - _gty; ++ final int dZ = (i - _gtz) / GeoStructure.CELL_HEIGHT; ++ ++ // return (Math.abs(dX) + Math.abs(dY) + Math.abs(dZ)) * Config.HEURISTIC_WEIGHT; // Manhattan distance ++ return Math.sqrt((dX * dX) + (dY * dY) + (dZ * dZ)) * Config.HEURISTIC_WEIGHT; // Direct distance ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeLoc.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeLoc.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeLoc.java (nonexistent) +@@ -1,183 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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.Cell; +- +-/** +- * @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 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 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; +- // return super.hashCode(); +- } +- +- @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; +- if (_x != other._x) +- { +- return false; +- } +- if (_y != other._y) +- { +- return false; +- } +- if (_goNorth != other._goNorth) +- { +- return false; +- } +- if (_goEast != other._goEast) +- { +- return false; +- } +- if (_goSouth != other._goSouth) +- { +- return false; +- } +- if (_goWest != other._goWest) +- { +- return false; +- } +- if (_geoHeight != other._geoHeight) +- { +- return false; +- } +- return true; +- } +-} +Index: java/org/l2jmobius/gameserver/model/actor/Creature.java +=================================================================== +--- java/org/l2jmobius/gameserver/model/actor/Creature.java (revision 8399) ++++ java/org/l2jmobius/gameserver/model/actor/Creature.java (working copy) +@@ -66,8 +66,6 @@ + import org.l2jmobius.gameserver.enums.TeleportWhereType; + import org.l2jmobius.gameserver.enums.UserInfoType; + import org.l2jmobius.gameserver.geoengine.GeoEngine; +-import org.l2jmobius.gameserver.geoengine.GeoEnginePathfinding; +-import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; + import org.l2jmobius.gameserver.instancemanager.IdManager; + import org.l2jmobius.gameserver.instancemanager.MapRegionManager; + import org.l2jmobius.gameserver.instancemanager.QuestManager; +@@ -2577,7 +2575,7 @@ + + public boolean disregardingGeodata; + public int onGeodataPathIndex; +- public List geoPath; ++ public List geoPath; + public int geoPathAccurateTx; + public int geoPathAccurateTy; + public int geoPathGtx; +@@ -3466,7 +3464,7 @@ + if (((originalDistance - distance) > 30) && !isControlBlocked() && !isInVehicle) + { + // Path calculation -- overrides previous movement check +- m.geoPath = GeoEnginePathfinding.getInstance().findPath(curX, curY, curZ, originalX, originalY, originalZ, getInstanceWorld()); ++ m.geoPath = GeoEngine.getInstance().findPath(curX, curY, curZ, originalX, originalY, originalZ, getInstanceWorld()); + if ((m.geoPath == null) || (m.geoPath.size() < 2)) // No path found + { + if (isPlayer() && !_isFlying && !isInWater) +Index: java/org/l2jmobius/gameserver/model/actor/instance/PlayerInstance.java +=================================================================== +--- java/org/l2jmobius/gameserver/model/actor/instance/PlayerInstance.java (revision 8397) ++++ java/org/l2jmobius/gameserver/model/actor/instance/PlayerInstance.java (working copy) +@@ -12704,7 +12704,7 @@ + { + if (Config.CORRECT_PLAYER_Z) + { +- final int nearestZ = GeoEngine.getInstance().getHigherHeight(getX(), getY(), getZ()); ++ final int nearestZ = GeoEngine.getInstance().getHeightNearest(getX(), getY(), getZ()); + if (getZ() < nearestZ) + { + teleToLocation(new Location(getX(), getY(), nearestZ)); +Index: java/org/l2jmobius/gameserver/util/GeoUtils.java +=================================================================== +--- java/org/l2jmobius/gameserver/util/GeoUtils.java (revision 8397) ++++ java/org/l2jmobius/gameserver/util/GeoUtils.java (working copy) +@@ -19,7 +19,7 @@ + import java.awt.Color; + + import org.l2jmobius.gameserver.geoengine.GeoEngine; +-import org.l2jmobius.gameserver.geoengine.geodata.Cell; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; + import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance; + import org.l2jmobius.gameserver.network.serverpackets.ExServerPrimitive; + +@@ -26,25 +26,25 @@ + /** + * @author HorridoJoho + */ +-public final class GeoUtils ++public class GeoUtils + { + public static void debug2DLine(PlayerInstance player, int x, int y, int tx, int ty, int z) + { +- final int gx = GeoEngine.getInstance().getGeoX(x); +- final int gy = GeoEngine.getInstance().getGeoY(y); ++ final int gx = GeoEngine.getGeoX(x); ++ final int gy = GeoEngine.getGeoY(y); + +- final int tgx = GeoEngine.getInstance().getGeoX(tx); +- final int tgy = GeoEngine.getInstance().getGeoY(ty); ++ final int tgx = GeoEngine.getGeoX(tx); ++ final int tgy = GeoEngine.getGeoY(ty); + + final ExServerPrimitive prim = new ExServerPrimitive("Debug2DLine", x, y, z); +- prim.addLine(Color.BLUE, GeoEngine.getInstance().getWorldX(gx), GeoEngine.getInstance().getWorldY(gy), z, GeoEngine.getInstance().getWorldX(tgx), GeoEngine.getInstance().getWorldY(tgy), z); ++ prim.addLine(Color.BLUE, GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), z, GeoEngine.getWorldX(tgx), GeoEngine.getWorldY(tgy), z); + + final LinePointIterator iter = new LinePointIterator(gx, gy, tgx, tgy); + + while (iter.next()) + { +- final int wx = GeoEngine.getInstance().getWorldX(iter.x()); +- final int wy = GeoEngine.getInstance().getWorldY(iter.y()); ++ final int wx = GeoEngine.getWorldX(iter.x()); ++ final int wy = GeoEngine.getWorldY(iter.y()); + + prim.addPoint(Color.RED, wx, wy, z); + } +@@ -53,21 +53,21 @@ + + public static void debug3DLine(PlayerInstance player, int x, int y, int z, int tx, int ty, int tz) + { +- final int gx = GeoEngine.getInstance().getGeoX(x); +- final int gy = GeoEngine.getInstance().getGeoY(y); ++ final int gx = GeoEngine.getGeoX(x); ++ final int gy = GeoEngine.getGeoY(y); + +- final int tgx = GeoEngine.getInstance().getGeoX(tx); +- final int tgy = GeoEngine.getInstance().getGeoY(ty); ++ final int tgx = GeoEngine.getGeoX(tx); ++ final int tgy = GeoEngine.getGeoY(ty); + + final ExServerPrimitive prim = new ExServerPrimitive("Debug3DLine", x, y, z); +- prim.addLine(Color.BLUE, GeoEngine.getInstance().getWorldX(gx), GeoEngine.getInstance().getWorldY(gy), z, GeoEngine.getInstance().getWorldX(tgx), GeoEngine.getInstance().getWorldY(tgy), tz); ++ prim.addLine(Color.BLUE, GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), z, GeoEngine.getWorldX(tgx), GeoEngine.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.getInstance().getWorldX(prevX); +- int wy = GeoEngine.getInstance().getWorldY(prevY); ++ int wx = GeoEngine.getWorldX(prevX); ++ int wy = GeoEngine.getWorldY(prevY); + int wz = iter.z(); + prim.addPoint(Color.RED, wx, wy, wz); + +@@ -78,8 +78,8 @@ + + if ((curX != prevX) || (curY != prevY)) + { +- wx = GeoEngine.getInstance().getWorldX(curX); +- wy = GeoEngine.getInstance().getWorldY(curY); ++ wx = GeoEngine.getWorldX(curX); ++ wy = GeoEngine.getWorldY(curY); + wz = iter.z(); + + prim.addPoint(Color.RED, wx, wy, wz); +@@ -93,7 +93,7 @@ + + private static Color getDirectionColor(int x, int y, int z, int nswe) + { +- if (GeoEngine.getInstance().checkNearestNswe(x, y, z, nswe)) ++ if ((GeoEngine.getInstance().getNsweNearest(x, y, z) & nswe) == nswe) + { + return Color.GREEN; + } +@@ -109,9 +109,8 @@ + int iPacket = 0; + + ExServerPrimitive exsp = null; +- final GeoEngine ge = GeoEngine.getInstance(); +- final int playerGx = ge.getGeoX(player.getX()); +- final int playerGy = ge.getGeoY(player.getY()); ++ final int playerGx = GeoEngine.getGeoX(player.getX()); ++ final int playerGy = GeoEngine.getGeoY(player.getY()); + for (int dx = -geoRadius; dx <= geoRadius; ++dx) + { + for (int dy = -geoRadius; dy <= geoRadius; ++dy) +@@ -135,12 +134,12 @@ + final int gx = playerGx + dx; + final int gy = playerGy + dy; + +- final int x = ge.getWorldX(gx); +- final int y = ge.getWorldY(gy); +- final int z = ge.getNearestZ(gx, gy, player.getZ()); ++ final int x = GeoEngine.getWorldX(gx); ++ final int y = GeoEngine.getWorldY(gy); ++ final int z = GeoEngine.getInstance().getHeightNearest(gx, gy, player.getZ()); + + // north arrow +- Color col = getDirectionColor(gx, gy, z, Cell.NSWE_NORTH); ++ Color col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_N); + 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); +@@ -147,7 +146,7 @@ + exsp.addLine(col, x - 4, y - 4, z, x + 4, y - 4, z); + + // east arrow +- col = getDirectionColor(gx, gy, z, Cell.NSWE_EAST); ++ col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_E); + 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); +@@ -154,13 +153,13 @@ + exsp.addLine(col, x + 4, y - 4, z, x + 4, y + 4, z); + + // south arrow +- col = getDirectionColor(gx, gy, z, Cell.NSWE_SOUTH); ++ col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_S); + 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, Cell.NSWE_WEST); ++ col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_W); + 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); +@@ -188,15 +187,15 @@ + { + if (y > lastY) + { +- return Cell.NSWE_SOUTH_EAST; ++ return GeoStructure.CELL_FLAG_SE; // Direction.SOUTH_EAST; + } + else if (y < lastY) + { +- return Cell.NSWE_NORTH_EAST; ++ return GeoStructure.CELL_FLAG_NE; // Direction.NORTH_EAST; + } + else + { +- return Cell.NSWE_EAST; ++ return GeoStructure.CELL_FLAG_E; // Direction.EAST; + } + } + else if (x < lastX) // west +@@ -203,26 +202,27 @@ + { + if (y > lastY) + { +- return Cell.NSWE_SOUTH_WEST; ++ return GeoStructure.CELL_FLAG_SW; // Direction.SOUTH_WEST; + } + else if (y < lastY) + { +- return Cell.NSWE_NORTH_WEST; ++ return GeoStructure.CELL_FLAG_NW; // Direction.NORTH_WEST; + } + else + { +- return Cell.NSWE_WEST; ++ return GeoStructure.CELL_FLAG_W; // Direction.WEST; + } + } +- else // unchanged x ++ else ++ // unchanged x + { + if (y > lastY) + { +- return Cell.NSWE_SOUTH; ++ return GeoStructure.CELL_FLAG_S; // Direction.SOUTH; + } + else if (y < lastY) + { +- return Cell.NSWE_NORTH; ++ return GeoStructure.CELL_FLAG_N; // Direction.NORTH; + } + else + { +Index: java/org/l2jmobius/tools/geodataconverter/GeoDataConverter.java +=================================================================== +--- java/org/l2jmobius/tools/geodataconverter/GeoDataConverter.java (nonexistent) ++++ java/org/l2jmobius/tools/geodataconverter/GeoDataConverter.java (working copy) +@@ -0,0 +1,386 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ */ ++package org.l2jmobius.tools.geodataconverter; ++ ++import java.io.BufferedOutputStream; ++import java.io.File; ++import java.io.FileOutputStream; ++import java.io.RandomAccessFile; ++import java.nio.ByteOrder; ++import java.nio.MappedByteBuffer; ++import java.nio.channels.FileChannel; ++import java.util.Scanner; ++ ++import org.l2jmobius.Config; ++import org.l2jmobius.commons.util.PropertiesParser; ++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.GeoFormat; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; ++import org.l2jmobius.gameserver.model.World; ++ ++/** ++ * @author Hasha ++ */ ++public class GeoDataConverter ++{ ++ private static GeoFormat _format; ++ private static ABlock[][] _blocks; ++ ++ public static void main(String[] args) ++ { ++ // initialize config ++ loadGeoengineConfigs(); ++ ++ // get geodata format ++ String type = ""; ++ try (Scanner scn = new Scanner(System.in)) ++ { ++ while (!(type.equalsIgnoreCase("J") || type.equalsIgnoreCase("O") || type.equalsIgnoreCase("E"))) ++ { ++ System.out.println("GeoDataConverter: Select source geodata type:"); ++ System.out.println(" J: L2J (e.g. 23_22.l2j)"); ++ System.out.println(" O: L2OFF (e.g. 23_22_conv.dat)"); ++ System.out.println(" E: Exit"); ++ System.out.print("Choice: "); ++ type = scn.next(); ++ } ++ } ++ if (type.equalsIgnoreCase("E")) ++ { ++ System.exit(0); ++ } ++ ++ _format = type.equalsIgnoreCase("J") ? GeoFormat.L2J : GeoFormat.L2OFF; ++ ++ // start conversion ++ System.out.println("GeoDataConverter: Converting all " + _format + " files."); ++ ++ // initialize geodata container ++ _blocks = new ABlock[GeoStructure.REGION_BLOCKS_X][GeoStructure.REGION_BLOCKS_Y]; ++ ++ // initialize multilayer temporarily buffer ++ BlockMultilayer.initialize(); ++ ++ // load geo files ++ int converted = 0; ++ for (int rx = World.TILE_X_MIN; rx <= World.TILE_X_MAX; rx++) ++ { ++ for (int ry = World.TILE_Y_MIN; ry <= World.TILE_Y_MAX; ry++) ++ { ++ final String input = String.format(_format.getFilename(), rx, ry); ++ final String filepath = Config.GEODATA_PATH; ++ final File f = new File(filepath + input); ++ if (f.exists() && !f.isDirectory()) ++ { ++ // load geodata ++ if (!loadGeoBlocks(input)) ++ { ++ System.out.println("GeoDataConverter: Unable to load " + input + " region file."); ++ continue; ++ } ++ ++ // recalculate nswe ++ if (!recalculateNswe()) ++ { ++ System.out.println("GeoDataConverter: Unable to convert " + input + " region file."); ++ continue; ++ } ++ ++ // save geodata ++ final String output = String.format(GeoFormat.L2D.getFilename(), rx, ry); ++ if (!saveGeoBlocks(output)) ++ { ++ System.out.println("GeoDataConverter: Unable to save " + output + " region file."); ++ continue; ++ } ++ ++ converted++; ++ System.out.println("GeoDataConverter: Created " + output + " region file."); ++ } ++ } ++ } ++ System.out.println("GeoDataConverter: Converted " + converted + " " + _format + " to L2D region file(s)."); ++ ++ // release multilayer block temporarily buffer ++ BlockMultilayer.release(); ++ } ++ ++ /** ++ * Loads geo blocks from buffer of the region file. ++ * @param filename : The name of the to load. ++ * @return boolean : True when successful. ++ */ ++ private static boolean loadGeoBlocks(String filename) ++ { ++ // region file is load-able, try to load it ++ try (RandomAccessFile raf = new RandomAccessFile(Config.GEODATA_PATH + filename, "r"); ++ FileChannel fc = raf.getChannel()) ++ { ++ final MappedByteBuffer buffer = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()).load(); ++ buffer.order(ByteOrder.LITTLE_ENDIAN); ++ ++ // load 18B header for L2off geodata (1st and 2nd byte...region X and Y) ++ if (_format == GeoFormat.L2OFF) ++ { ++ for (int i = 0; i < 18; i++) ++ { ++ buffer.get(); ++ } ++ } ++ ++ // 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 (_format == GeoFormat.L2J) ++ { ++ // get block type ++ final byte type = buffer.get(); ++ ++ // load block according to block type ++ switch (type) ++ { ++ case GeoStructure.TYPE_FLAT_L2J_L2OFF: ++ { ++ _blocks[ix][iy] = new BlockFlat(buffer, _format); ++ break; ++ } ++ case GeoStructure.TYPE_COMPLEX_L2J: ++ { ++ _blocks[ix][iy] = new BlockComplex(buffer, _format); ++ break; ++ } ++ case GeoStructure.TYPE_MULTILAYER_L2J: ++ { ++ _blocks[ix][iy] = new BlockMultilayer(buffer, _format); ++ break; ++ } ++ default: ++ { ++ throw new IllegalArgumentException("Unknown block type: " + type); ++ } ++ } ++ } ++ else ++ { ++ // get block type ++ final short type = buffer.getShort(); ++ ++ // load block according to block type ++ switch (type) ++ { ++ case GeoStructure.TYPE_FLAT_L2J_L2OFF: ++ { ++ _blocks[ix][iy] = new BlockFlat(buffer, _format); ++ break; ++ } ++ case GeoStructure.TYPE_COMPLEX_L2OFF: ++ { ++ _blocks[ix][iy] = new BlockComplex(buffer, _format); ++ break; ++ } ++ default: ++ { ++ _blocks[ix][iy] = new BlockMultilayer(buffer, _format); ++ break; ++ } ++ } ++ } ++ } ++ } ++ ++ if (buffer.remaining() > 0) ++ { ++ System.out.println("GeoDataConverter: Region file " + filename + " can be corrupted, remaining " + buffer.remaining() + " bytes to read."); ++ return false; ++ } ++ ++ return true; ++ } ++ catch (Exception e) ++ { ++ System.out.println("GeoDataConverter: Error while loading " + filename + " region file."); ++ return false; ++ } ++ } ++ ++ /** ++ * Recalculate diagonal flags for the region file. ++ * @return boolean : True when successful. ++ */ ++ private static boolean recalculateNswe() ++ { ++ try ++ { ++ for (int x = 0; x < GeoStructure.REGION_CELLS_X; x++) ++ { ++ for (int y = 0; y < GeoStructure.REGION_CELLS_Y; y++) ++ { ++ // get block ++ final ABlock block = _blocks[x / GeoStructure.BLOCK_CELLS_X][y / GeoStructure.BLOCK_CELLS_Y]; ++ ++ // skip flat blocks ++ if (block instanceof BlockFlat) ++ { ++ continue; ++ } ++ ++ // for complex and multilayer blocks go though all layers ++ short height = Short.MAX_VALUE; ++ int index; ++ while ((index = block.getIndexBelow(x, y, height)) != -1) ++ { ++ // get height and nswe ++ height = block.getHeight(index); ++ byte nswe = block.getNswe(index); ++ ++ // update nswe with diagonal flags ++ nswe = updateNsweBelow(x, y, height, nswe); ++ ++ // set nswe of the cell ++ block.setNswe(index, nswe); ++ } ++ } ++ } ++ ++ return true; ++ } ++ catch (Exception e) ++ { ++ return false; ++ } ++ } ++ ++ /** ++ * Updates the NSWE flag with diagonal flags. ++ * @param x : Geodata X coordinate. ++ * @param y : Geodata Y coordinate. ++ * @param z : Geodata Z coordinate. ++ * @param nsweValue : NSWE flag to be updated. ++ * @return byte : Updated NSWE flag. ++ */ ++ private static byte updateNsweBelow(int x, int y, short z, byte nsweValue) ++ { ++ byte nswe = nsweValue; ++ ++ // calculate virtual layer height ++ final short height = (short) (z + GeoStructure.CELL_IGNORE_HEIGHT); ++ ++ // get NSWE of neighbor cells below virtual layer (NPC/PC can fall down of clif, but can not climb it -> NSWE of cell below) ++ final byte nsweN = getNsweBelow(x, y - 1, height); ++ final byte nsweS = getNsweBelow(x, y + 1, height); ++ final byte nsweW = getNsweBelow(x - 1, y, height); ++ final byte nsweE = getNsweBelow(x + 1, y, height); ++ ++ // north-west ++ if ((((nswe & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) || (((nswe & GeoStructure.CELL_FLAG_W) != 0) && ((nsweW & GeoStructure.CELL_FLAG_N) != 0))) ++ { ++ nswe |= GeoStructure.CELL_FLAG_NW; ++ } ++ ++ // north-east ++ if ((((nswe & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) || (((nswe & GeoStructure.CELL_FLAG_E) != 0) && ((nsweE & GeoStructure.CELL_FLAG_N) != 0))) ++ { ++ nswe |= GeoStructure.CELL_FLAG_NE; ++ } ++ ++ // south-west ++ if ((((nswe & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) || (((nswe & GeoStructure.CELL_FLAG_W) != 0) && ((nsweW & GeoStructure.CELL_FLAG_S) != 0))) ++ { ++ nswe |= GeoStructure.CELL_FLAG_SW; ++ } ++ ++ // south-east ++ if ((((nswe & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) || (((nswe & GeoStructure.CELL_FLAG_E) != 0) && ((nsweE & GeoStructure.CELL_FLAG_S) != 0))) ++ { ++ nswe |= GeoStructure.CELL_FLAG_SE; ++ } ++ ++ return nswe; ++ } ++ ++ private static byte getNsweBelow(int geoX, int geoY, short worldZ) ++ { ++ // out of geo coordinates ++ if ((geoX < 0) || (geoX >= GeoStructure.REGION_CELLS_X)) ++ { ++ return 0; ++ } ++ ++ // out of geo coordinates ++ if ((geoY < 0) || (geoY >= GeoStructure.REGION_CELLS_Y)) ++ { ++ return 0; ++ } ++ ++ // get block ++ final ABlock block = _blocks[geoX / GeoStructure.BLOCK_CELLS_X][geoY / GeoStructure.BLOCK_CELLS_Y]; ++ ++ // get index, when valid, return nswe ++ final int index = block.getIndexBelow(geoX, geoY, worldZ); ++ return index == -1 ? 0 : block.getNswe(index); ++ } ++ ++ /** ++ * Save region file to file. ++ * @param filename : The name of file to save. ++ * @return boolean : True when successful. ++ */ ++ private static boolean saveGeoBlocks(String filename) ++ { ++ try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(Config.GEODATA_PATH + filename), GeoStructure.REGION_BLOCKS * GeoStructure.BLOCK_CELLS * 3)) ++ { ++ // loop over region blocks and save each block ++ for (int ix = 0; ix < GeoStructure.REGION_BLOCKS_X; ix++) ++ { ++ for (int iy = 0; iy < GeoStructure.REGION_BLOCKS_Y; iy++) ++ { ++ _blocks[ix][iy].saveBlock(bos); ++ } ++ } ++ ++ // flush data to file ++ bos.flush(); ++ ++ return true; ++ } ++ catch (Exception e) ++ { ++ return false; ++ } ++ } ++ ++ private static void loadGeoengineConfigs() ++ { ++ final PropertiesParser geoData = new PropertiesParser(Config.GEOENGINE_CONFIG_FILE); ++ Config.GEODATA_PATH = geoData.getString("GeoDataPath", "./data/geodata/"); ++ Config.COORD_SYNCHRONIZE = geoData.getInt("CoordSynchronize", -1); ++ Config.PART_OF_CHARACTER_HEIGHT = geoData.getInt("PartOfCharacterHeight", 75); ++ Config.MAX_OBSTACLE_HEIGHT = geoData.getInt("MaxObstacleHeight", 32); ++ Config.PATHFINDING = geoData.getBoolean("PathFinding", true); ++ Config.PATHFIND_BUFFERS = geoData.getString("PathFindBuffers", "100x6;128x6;192x6;256x4;320x4;384x4;500x2"); ++ Config.BASE_WEIGHT = geoData.getInt("BaseWeight", 10); ++ Config.DIAGONAL_WEIGHT = geoData.getInt("DiagonalWeight", 14); ++ Config.OBSTACLE_MULTIPLIER = geoData.getInt("ObstacleMultiplier", 10); ++ Config.HEURISTIC_WEIGHT = geoData.getInt("HeuristicWeight", 20); ++ Config.MAX_ITERATIONS = geoData.getInt("MaxIterations", 3500); ++ } ++} +\ No newline at end of file diff --git a/L2J_Mobius_Essence_4.0_DwellingOfSpirits/dist/game/data/geodata/L2D_GeoEngine.patch b/L2J_Mobius_Essence_4.0_DwellingOfSpirits/dist/game/data/geodata/L2D_GeoEngine.patch new file mode 100644 index 0000000000..63e2faf0c9 --- /dev/null +++ b/L2J_Mobius_Essence_4.0_DwellingOfSpirits/dist/game/data/geodata/L2D_GeoEngine.patch @@ -0,0 +1,6052 @@ +Index: dist/game/GeoDataConverter.bat +=================================================================== +--- dist/game/GeoDataConverter.bat (nonexistent) ++++ dist/game/GeoDataConverter.bat (working copy) +@@ -0,0 +1,6 @@ ++@echo off ++title L2D geodata converter ++ ++java -Xmx512m -cp ./../libs/* org.l2jmobius.tools.geodataconverter.GeoDataConverter ++ ++pause +Index: dist/game/GeoDataConverter.sh +=================================================================== +--- dist/game/GeoDataConverter.sh (nonexistent) ++++ dist/game/GeoDataConverter.sh (working copy) +@@ -0,0 +1,4 @@ ++#! /bin/sh ++ ++java -Xmx512m -cp ../libs/*: org.l2jmobius.tools.geodataconverter.GeoDataConverter > log/stdout.log 2>&1 ++ +Index: dist/game/config/GeoEngine.ini +=================================================================== +--- dist/game/config/GeoEngine.ini (revision 8397) ++++ dist/game/config/GeoEngine.ini (working copy) +@@ -1,6 +1,10 @@ + # ================================================================= + # Geodata + # ================================================================= ++# Because of real-time performance we are using geodata files only in ++# diagonal L2D format now (using filename e.g. 22_16.l2d). ++# L2D geodata can be obtained by conversion of existing L2J or L2OFF geodata. ++# Launch "GeoDataConverter.bat/sh" and follow instructions to start the conversion. + + # 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/ +@@ -13,6 +17,16 @@ + CoordSynchronize = 2 + + # ================================================================= ++# Path checking ++# ================================================================= ++ ++# 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 finding + # ================================================================= + +@@ -23,19 +37,23 @@ + # Pathfinding array buffers configuration, default: 100x6;128x6;192x6;256x4;320x4;384x4;500x2 + PathFindBuffers = 100x6;128x6;192x6;256x4;320x4;384x4;500x2 + +-# Weight for nodes without obstacles far from walls +-LowWeight = 0.5 ++# Base path weight, when moving from one node to another on axis direction, default: 10 ++BaseWeight = 10 + +-# Weight for nodes near walls +-MediumWeight = 2 ++# Path weight, when moving from one node to another on diagonal direction, default: BaseWeight * sqrt(2) = 14 ++DiagonalWeight = 14 + +-# Weight for nodes with obstacles +-HighWeight = 3 ++# When movement flags of target node is blocked to any direction, multiply movement weight by this multiplier. ++# This causes pathfinding algorithm to avoid path construction exactly near an obstacle, default: 10 ++ObstacleMultiplier = 10 + +-# Weight for diagonal movement. +-# Default: LowWeight * sqrt(2) +-DiagonalWeight = 0.707 ++# Weight of the heuristic algorithm, which is giving estimated cost from node to target, default: 20 ++# For proper function must be higher than BaseWeight and/or DiagonalWeight. ++HeuristicWeight = 20 + ++# Maximum number of generated nodes per one path-finding process, default 3500 ++MaxIterations = 3500 ++ + # ================================================================= + # Other + # ================================================================= +Index: dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java +=================================================================== +--- dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java (revision 8397) ++++ dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java (working copy) +@@ -55,9 +55,8 @@ + final int worldX = activeChar.getX(); + final int worldY = activeChar.getY(); + final int worldZ = activeChar.getZ(); +- final int geoX = GeoEngine.getInstance().getGeoX(worldX); +- final int geoY = GeoEngine.getInstance().getGeoY(worldY); +- ++ final int geoX = GeoEngine.getGeoX(worldX); ++ final int geoY = GeoEngine.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)); +@@ -73,9 +72,8 @@ + final int worldX = activeChar.getX(); + final int worldY = activeChar.getY(); + final int worldZ = activeChar.getZ(); +- final int geoX = GeoEngine.getInstance().getGeoX(worldX); +- final int geoY = GeoEngine.getInstance().getGeoY(worldY); +- ++ final int geoX = GeoEngine.getGeoX(worldX); ++ final int geoY = GeoEngine.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)); +Index: dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java +=================================================================== +--- dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java (revision 8397) ++++ dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java (working copy) +@@ -18,11 +18,11 @@ + + import java.util.List; + +-import org.l2jmobius.Config; +-import org.l2jmobius.gameserver.geoengine.GeoEnginePathfinding; +-import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; ++import org.l2jmobius.gameserver.geoengine.GeoEngine; + import org.l2jmobius.gameserver.handler.IAdminCommandHandler; ++import org.l2jmobius.gameserver.model.Location; + import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance; ++import org.l2jmobius.gameserver.network.SystemMessageId; + import org.l2jmobius.gameserver.util.BuilderUtil; + + public class AdminPathNode implements IAdminCommandHandler +@@ -37,29 +37,30 @@ + { + if (command.equals("admin_path_find")) + { +- if (!Config.PATHFINDING) +- { +- BuilderUtil.sendSysMessage(activeChar, "PathFinding is disabled."); +- return true; +- } + if (activeChar.getTarget() != null) + { +- final List path = GeoEnginePathfinding.getInstance().findPath(activeChar.getX(), activeChar.getY(), (short) activeChar.getZ(), activeChar.getTarget().getX(), activeChar.getTarget().getY(), (short) activeChar.getTarget().getZ(), activeChar.getInstanceWorld()); ++ 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()); + if (path == null) + { +- BuilderUtil.sendSysMessage(activeChar, "No Route!"); +- return true; ++ BuilderUtil.sendSysMessage(activeChar, "No route found or pathfinding disabled."); + } +- for (AbstractNodeLoc a : path) ++ else + { +- BuilderUtil.sendSysMessage(activeChar, "x:" + a.getX() + " y:" + a.getY() + " z:" + a.getZ()); ++ for (Location point : path) ++ { ++ BuilderUtil.sendSysMessage(activeChar, "x:" + point.getX() + " y:" + point.getY() + " z:" + point.getZ()); ++ } + } + } + else + { +- BuilderUtil.sendSysMessage(activeChar, "No Target!"); ++ activeChar.sendPacket(SystemMessageId.INVALID_TARGET); + } + } ++ else ++ { ++ return false; ++ } + return true; + } + +Index: java/org/l2jmobius/Config.java +=================================================================== +--- java/org/l2jmobius/Config.java (revision 8397) ++++ java/org/l2jmobius/Config.java (working copy) +@@ -32,7 +32,6 @@ + import java.net.UnknownHostException; + import java.nio.charset.StandardCharsets; + import java.nio.file.Files; +-import java.nio.file.Path; + import java.nio.file.Paths; + import java.time.Duration; + import java.util.ArrayList; +@@ -927,14 +926,17 @@ + // -------------------------------------------------- + // GeoEngine + // -------------------------------------------------- +- public static Path GEODATA_PATH; ++ public static String GEODATA_PATH; ++ public static int COORD_SYNCHRONIZE; ++ public static int PART_OF_CHARACTER_HEIGHT; ++ public static int MAX_OBSTACLE_HEIGHT; + public static boolean PATHFINDING; + public static String PATHFIND_BUFFERS; +- public static float LOW_WEIGHT; +- public static float MEDIUM_WEIGHT; +- public static float HIGH_WEIGHT; +- public static float DIAGONAL_WEIGHT; +- public static int COORD_SYNCHRONIZE; ++ public static int BASE_WEIGHT; ++ public static int DIAGONAL_WEIGHT; ++ public static int HEURISTIC_WEIGHT; ++ public static int OBSTACLE_MULTIPLIER; ++ public static int MAX_ITERATIONS; + public static boolean CORRECT_PLAYER_Z; + + /** Attribute System */ +@@ -2468,14 +2470,17 @@ + + // Load GeoEngine config file (if exists) + final PropertiesParser GeoEngine = new PropertiesParser(GEOENGINE_CONFIG_FILE); +- GEODATA_PATH = Paths.get(GeoEngine.getString("GeoDataPath", "./data/geodata")); ++ GEODATA_PATH = GeoEngine.getString("GeoDataPath", "./data/geodata/"); ++ COORD_SYNCHRONIZE = GeoEngine.getInt("CoordSynchronize", -1); ++ PART_OF_CHARACTER_HEIGHT = GeoEngine.getInt("PartOfCharacterHeight", 75); ++ MAX_OBSTACLE_HEIGHT = GeoEngine.getInt("MaxObstacleHeight", 32); + PATHFINDING = GeoEngine.getBoolean("PathFinding", true); + PATHFIND_BUFFERS = GeoEngine.getString("PathFindBuffers", "100x6;128x6;192x6;256x4;320x4;384x4;500x2"); +- LOW_WEIGHT = GeoEngine.getFloat("LowWeight", 0.5f); +- MEDIUM_WEIGHT = GeoEngine.getFloat("MediumWeight", 2); +- HIGH_WEIGHT = GeoEngine.getFloat("HighWeight", 3); +- DIAGONAL_WEIGHT = GeoEngine.getFloat("DiagonalWeight", 0.707f); +- COORD_SYNCHRONIZE = GeoEngine.getInt("CoordSynchronize", -1); ++ BASE_WEIGHT = GeoEngine.getInt("BaseWeight", 10); ++ DIAGONAL_WEIGHT = GeoEngine.getInt("DiagonalWeight", 14); ++ OBSTACLE_MULTIPLIER = GeoEngine.getInt("ObstacleMultiplier", 10); ++ HEURISTIC_WEIGHT = GeoEngine.getInt("HeuristicWeight", 20); ++ MAX_ITERATIONS = GeoEngine.getInt("MaxIterations", 3500); + CORRECT_PLAYER_Z = GeoEngine.getBoolean("CorrectPlayerZ", false); + + // Load AllowedPlayerRaces config file (if exists) +Index: java/org/l2jmobius/gameserver/geoengine/GeoEngine.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/GeoEngine.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/GeoEngine.java (working copy) +@@ -16,101 +16,91 @@ + */ + package org.l2jmobius.gameserver.geoengine; + +-import java.io.IOException; ++import java.io.File; + import java.io.RandomAccessFile; + import java.nio.ByteOrder; +-import java.nio.channels.FileChannel.MapMode; +-import java.nio.file.Files; +-import java.nio.file.Path; +-import java.util.concurrent.atomic.AtomicReferenceArray; +-import java.util.logging.Level; ++import java.nio.MappedByteBuffer; ++import java.nio.channels.FileChannel; ++import java.util.List; + 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.geoengine.geodata.Cell; +-import org.l2jmobius.gameserver.geoengine.geodata.IRegion; +-import org.l2jmobius.gameserver.geoengine.geodata.NullRegion; +-import org.l2jmobius.gameserver.geoengine.geodata.Region; ++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.GeoFormat; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoLocation; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; + import org.l2jmobius.gameserver.instancemanager.WarpedSpaceManager; + 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; ++import org.l2jmobius.gameserver.util.MathUtil; + + /** +- * @author -Nemesiss-, HorridoJoho ++ * @author Hasha + */ + public class GeoEngine + { +- private static final Logger LOGGER = Logger.getLogger(GeoEngine.class.getName()); ++ protected static final Logger LOGGER = Logger.getLogger(GeoEngine.class.getName()); + +- private static final int WORLD_MIN_X = -655360; +- private static final int WORLD_MIN_Y = -589824; +- private static final int WORLD_MIN_Z = -16384; ++ private final ABlock[][] _blocks; ++ private final BlockNull _nullBlock; + +- /** 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; +- +- /** The regions array */ +- private final AtomicReferenceArray _regions = new AtomicReferenceArray<>(GEO_REGIONS); +- +- 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; +- +- protected GeoEngine() ++ /** ++ * GeoEngine constructor. Loads all geodata files of chosen geodata format. ++ */ ++ public GeoEngine() + { + LOGGER.info("GeoEngine: Initializing..."); +- for (int i = 0; i < _regions.length(); i++) +- { +- _regions.set(i, NullRegion.INSTANCE); +- } + ++ // 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; +- try ++ long fileSize = 0; ++ for (int rx = World.TILE_X_MIN; rx <= World.TILE_X_MAX; rx++) + { +- for (int regionX = World.TILE_X_MIN; regionX <= World.TILE_X_MAX; regionX++) ++ for (int ry = World.TILE_Y_MIN; ry <= World.TILE_Y_MAX; ry++) + { +- for (int regionY = World.TILE_Y_MIN; regionY <= World.TILE_Y_MAX; regionY++) ++ final File f = new File(Config.GEODATA_PATH + String.format(GeoFormat.L2D.getFilename(), rx, ry)); ++ if (f.exists() && !f.isDirectory()) + { +- 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(rx, ry)) + { +- try (RandomAccessFile raf = new RandomAccessFile(geoFilePath.toFile(), "r")) +- { +- _regions.set((regionX * GEO_REGIONS_Y) + regionY, new Region(raf.getChannel().map(MapMode.READ_ONLY, 0, raf.length()).load().order(ByteOrder.LITTLE_ENDIAN))); +- loaded++; +- } ++ loaded++; ++ fileSize += f.length(); + } + } ++ else ++ { ++ // region file is not load-able, load null blocks ++ loadNullBlocks(rx, ry); ++ } + } + } +- catch (Exception e) ++ ++ LOGGER.info("GeoEngine: Loaded " + loaded + " geodata files."); ++ if (loaded > 0) + { +- LOGGER.log(Level.SEVERE, "GeoEngine: Failed to load geodata!", e); +- System.exit(1); ++ LOGGER.info("GeoEngine: Total geodata file size " + (fileSize / 1024 / 1024) + " MB."); + } + +- LOGGER.info("GeoEngine: Loaded " + loaded + " geodata files."); +- +- // Avoid wrong configs when no files are loaded. ++ // avoid wrong configs when no files are loaded + if (loaded == 0) + { + if (Config.PATHFINDING) +@@ -124,627 +114,832 @@ + LOGGER.info("GeoEngine: Forcing CoordSynchronize setting to -1."); + } + } ++ ++ // release multilayer block temporarily buffer ++ BlockMultilayer.release(); + } + + /** +- * @param geoX +- * @param geoY +- * @return the region ++ * 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 IRegion getRegion(int geoX, int geoY) ++ private final boolean loadGeoBlocks(int regionX, int regionY) + { +- final int region = ((geoX / IRegion.REGION_CELLS_X) * GEO_REGIONS_Y) + (geoY / IRegion.REGION_CELLS_Y); +- if ((region < 0) || (region >= _regions.length())) ++ final String filename = String.format(GeoFormat.L2D.getFilename(), regionX, regionY); ++ ++ // standard load ++ try (RandomAccessFile raf = new RandomAccessFile(Config.GEODATA_PATH + filename, "r"); ++ FileChannel fc = raf.getChannel()) + { +- return null; ++ // initialize file buffer ++ final MappedByteBuffer buffer = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()).load(); ++ buffer.order(ByteOrder.LITTLE_ENDIAN); ++ ++ // 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++) ++ { ++ // get block type ++ final byte type = buffer.get(); ++ ++ // load block according to block type ++ switch (type) ++ { ++ case GeoStructure.TYPE_FLAT_L2D: ++ { ++ _blocks[blockX + ix][blockY + iy] = new BlockFlat(buffer, GeoFormat.L2D); ++ break; ++ } ++ case GeoStructure.TYPE_COMPLEX_L2D: ++ { ++ _blocks[blockX + ix][blockY + iy] = new BlockComplex(buffer, GeoFormat.L2D); ++ break; ++ } ++ case GeoStructure.TYPE_MULTILAYER_L2D: ++ { ++ _blocks[blockX + ix][blockY + iy] = new BlockMultilayer(buffer, GeoFormat.L2D); ++ break; ++ } ++ default: ++ { ++ throw new IllegalArgumentException("Unknown block type: " + type); ++ } ++ } ++ } ++ } ++ ++ // check data consistency ++ if (buffer.remaining() > 0) ++ { ++ LOGGER.warning("GeoEngine: Region file " + filename + " can be corrupted, remaining " + buffer.remaining() + " bytes to read."); ++ } ++ ++ // loading was successful ++ return true; + } +- return _regions.get(region); +- } +- +- /** +- * @param filePath +- * @param regionX +- * @param regionY +- * @throws IOException +- */ +- 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")) ++ catch (Exception e) + { +- _regions.set(regionOffset, new Region(raf.getChannel().map(MapMode.READ_ONLY, 0, raf.length()).load().order(ByteOrder.LITTLE_ENDIAN))); ++ // an error occured while loading, load null blocks ++ LOGGER.warning("GeoEngine: Error while loading " + filename + " region file."); ++ LOGGER.warning(e.getMessage()); ++ ++ // replace whole region file with null blocks ++ loadNullBlocks(regionX, regionY); ++ ++ // loading was not successful ++ return false; + } + } + + /** +- * @param regionX +- * @param regionY ++ * 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. + */ +- public void unloadRegion(int regionX, int regionY) ++ private final void loadNullBlocks(int regionX, int regionY) + { +- _regions.set((regionX * GEO_REGIONS_Y) + regionY, NullRegion.INSTANCE); +- } +- +- /** +- * @param geoX +- * @param geoY +- * @return if geodata exist +- */ +- public boolean hasGeoPos(int geoX, int geoY) +- { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) ++ // 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++) + { +- return false; ++ for (int iy = 0; iy < GeoStructure.REGION_BLOCKS_Y; iy++) ++ { ++ _blocks[blockX + ix][blockY + iy] = _nullBlock; ++ } + } +- return region.hasGeo(); + } + ++ // GEODATA - GENERAL ++ + /** +- * Checks the specified position for available geodata. +- * @param x the world x +- * @param y the world y +- * @return {@code true} if there is geodata for the given coordinates, {@code false} otherwise ++ * Converts world X to geodata X. ++ * @param worldX ++ * @return int : Geo X + */ +- public boolean hasGeo(int x, int y) ++ public static int getGeoX(int worldX) + { +- return hasGeoPos(getGeoX(x), getGeoY(y)); ++ return (MathUtil.limit(worldX, World.MAP_MIN_X, World.MAP_MAX_X) - World.MAP_MIN_X) >> 4; + } + + /** +- * @param geoX +- * @param geoY +- * @param worldZ +- * @param nswe +- * @return the nearest nswe check ++ * Converts world Y to geodata Y. ++ * @param worldY ++ * @return int : Geo Y + */ +- public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) ++ public static int getGeoY(int worldY) + { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) +- { +- return true; +- } +- return region.checkNearestNswe(geoX, geoY, worldZ, nswe); ++ return (MathUtil.limit(worldY, World.MAP_MIN_Y, World.MAP_MAX_Y) - World.MAP_MIN_Y) >> 4; + } + + /** ++ * Converts geodata X to world X. + * @param geoX +- * @param geoY +- * @param worldZ +- * @param nswe +- * @return the nearest nswe anti-corner cut ++ * @return int : World X + */ +- public boolean checkNearestNsweAntiCornerCut(int geoX, int geoY, int worldZ, int nswe) ++ public static int getWorldX(int geoX) + { +- boolean can = true; +- if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) +- { +- can = checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_EAST) && checkNearestNswe(geoX + 1, geoY, worldZ, Cell.NSWE_NORTH); +- } +- if (can && ((nswe & Cell.NSWE_NORTH_WEST) == Cell.NSWE_NORTH_WEST)) +- { +- 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 = 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 = checkNearestNswe(geoX, geoY + 1, worldZ, Cell.NSWE_WEST) && checkNearestNswe(geoX - 1, geoY, worldZ, Cell.NSWE_SOUTH); +- } +- return can && checkNearestNswe(geoX, geoY, worldZ, nswe); ++ return (MathUtil.limit(geoX, 0, GeoStructure.GEO_CELLS_X) << 4) + World.MAP_MIN_X + 8; + } + + /** +- * @param geoX ++ * Converts geodata Y to world Y. + * @param geoY +- * @param worldZ +- * @return the nearest Z value ++ * @return int : World Y + */ +- public int getNearestZ(int geoX, int geoY, int worldZ) ++ public static int getWorldY(int geoY) + { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) +- { +- return worldZ; +- } +- return region.getNearestZ(geoX, geoY, worldZ); ++ return (MathUtil.limit(geoY, 0, GeoStructure.GEO_CELLS_Y) << 4) + World.MAP_MIN_Y + 8; + } + + /** +- * @param geoX +- * @param geoY +- * @param worldZ +- * @return the next lower Z value ++ * Returns block of geodata on given coordinates. ++ * @param geoX : Geodata X ++ * @param geoY : Geodata Y ++ * @return {@link ABlock} : Block of geodata. + */ +- public int getNextLowerZ(int geoX, int geoY, int worldZ) ++ private final ABlock getBlock(int geoX, int geoY) + { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) ++ final int x = geoX / GeoStructure.BLOCK_CELLS_X; ++ final int y = geoY / GeoStructure.BLOCK_CELLS_Y; ++ ++ // if x or y is out of array return null ++ if ((x > -1) && (y > -1) && (x < GeoStructure.GEO_BLOCKS_X) && (y < GeoStructure.GEO_BLOCKS_Y)) + { +- return worldZ; ++ return _blocks[x][y]; + } +- return region.getNextLowerZ(geoX, geoY, worldZ); ++ return null; + } + + /** +- * @param geoX +- * @param geoY +- * @param worldZ +- * @return the next higher Z value ++ * Check if geo coordinates has geo. ++ * @param geoX : Geodata X ++ * @param geoY : Geodata Y ++ * @return boolean : True, if given geo coordinates have geodata + */ +- public int getNextHigherZ(int geoX, int geoY, int worldZ) ++ public boolean hasGeoPos(int geoX, int geoY) + { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) ++ final ABlock block = getBlock(geoX, geoY); ++ if (block == null) // null block check + { +- return worldZ; ++ // TODO: Find when this can be null. (Bad geodata? Check World getRegion method.) ++ // LOGGER.warning("Could not find geodata block at " + getWorldX(geoX) + ", " + getWorldY(geoY) + "."); ++ return false; + } +- return region.getNextHigherZ(geoX, geoY, worldZ); ++ return block.hasGeoPos(); + } + + /** +- * Gets the Z height. +- * @param x the world x +- * @param y the world y +- * @param z the world z +- * @return the nearest Z height ++ * 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 int getHeight(int x, int y, int z) ++ public short getHeightNearest(int geoX, int geoY, int worldZ) + { +- return getNearestZ(getGeoX(x), getGeoY(y), z); ++ final ABlock block = getBlock(geoX, geoY); ++ return block != null ? block.getHeightNearest(geoX, geoY, worldZ) : (short) worldZ; + } + + /** +- * Gets the next lower Z height. +- * @param x the world x +- * @param y the world y +- * @param z the world z +- * @return the nearest Z height ++ * 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 int getLowerHeight(int x, int y, int z) ++ public byte getNsweNearest(int geoX, int geoY, int worldZ) + { +- return getNextLowerZ(getGeoX(x), getGeoY(y), z); ++ final ABlock block = getBlock(geoX, geoY); ++ return block != null ? block.getNsweNearest(geoX, geoY, worldZ) : (byte) 0xFF; + } + + /** +- * Gets the next higher Z height. +- * @param x the world x +- * @param y the world y +- * @param z the world z +- * @return the nearest Z height ++ * Check if world coordinates has geo. ++ * @param worldX : World X ++ * @param worldY : World Y ++ * @return boolean : True, if given world coordinates have geodata + */ +- public int getHigherHeight(int x, int y, int z) ++ public boolean hasGeo(int worldX, int worldY) + { +- return getNextHigherZ(getGeoX(x), getGeoY(y), z); ++ return hasGeoPos(getGeoX(worldX), getGeoY(worldY)); + } + + /** +- * @param worldX +- * @return the geo X ++ * 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 int getGeoX(int worldX) ++ public short getHeight(int worldX, int worldY, int worldZ) + { +- return (worldX - WORLD_MIN_X) / 16; ++ return getHeightNearest(getGeoX(worldX), getGeoY(worldY), worldZ); + } + +- /** +- * @param worldY +- * @return the geo Y +- */ +- public int getGeoY(int worldY) +- { +- return (worldY - WORLD_MIN_Y) / 16; +- } ++ // PATHFINDING + + /** +- * @param worldZ +- * @return the geo Z ++ * Check line of sight from {@link WorldObject} to {@link WorldObject}. ++ * @param origin : The origin object. ++ * @param target : The target object. ++ * @return {@code boolean} : True if origin can see target + */ +- public int getGeoZ(int worldZ) ++ public boolean canSeeTarget(WorldObject origin, WorldObject target) + { +- return (worldZ - WORLD_MIN_Z) / 16; +- } +- +- /** +- * @param geoX +- * @return the world X +- */ +- public int getWorldX(int geoX) +- { +- return (geoX * 16) + WORLD_MIN_X + 8; +- } +- +- /** +- * @param geoY +- * @return the world Y +- */ +- public int getWorldY(int geoY) +- { +- return (geoY * 16) + WORLD_MIN_Y + 8; +- } +- +- /** +- * @param geoZ +- * @return the world Z +- */ +- public int getWorldZ(int geoZ) +- { +- return (geoZ * 16) + WORLD_MIN_Z + 8; +- } +- +- /** +- * 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) +- { +- if (target.isDoor()) ++ if (target.isDoor() || target.isArtefact() || (target.isCreature() && ((Creature) target).isFlying())) + { +- // Can always see doors. + return true; + } +- return 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(), cha.getInstanceWorld(), worldPosition.getX(), worldPosition.getY(), worldPosition.getZ()); +- } +- +- /** +- * Can see target. Checks doors between. +- * @param x the x coordinate +- * @param y the y coordinate +- * @param z the z coordinate +- * @param world +- * @param tx the target's x coordinate +- * @param ty the target's y coordinate +- * @param tz the target's z coordinate +- * @param tworld the target's instanceId +- * @return +- */ +- public boolean canSeeTarget(int x, int y, int z, Instance world, int tx, int ty, int tz, Instance tworld) +- { +- return (world != tworld) ? false : canSeeTarget(x, y, z, world, tx, ty, tz); +- } +- +- /** +- * 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 +- * @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, Instance instance, int tx, int ty, int tz) +- { +- if (DoorData.getInstance().checkIfDoorsBetween(x, y, z, tx, ty, tz, instance, true)) ++ ++ // get origin and target world coordinates ++ final int ox = origin.getX(); ++ final int oy = origin.getY(); ++ final int oz = origin.getZ(); ++ final int tx = target.getX(); ++ final int ty = target.getY(); ++ final int tz = target.getZ(); ++ ++ if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld(), true)) + { + return false; + } +- return canSeeTarget(x, y, z, tx, ty, tz); +- } +- +- /** +- * @param prevX +- * @param prevY +- * @param prevGeoZ +- * @param curX +- * @param curY +- * @param nswe +- * @return the LOS Z value +- */ +- 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))) ++ if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld())) + { +- throw new RuntimeException("Multiple directions!"); ++ return false; + } ++ if (WarpedSpaceManager.getInstance().checkForWarpedSpace(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld())) ++ { ++ return false; ++ } + +- if (checkNearestNsweAntiCornerCut(prevX, prevY, prevGeoZ, nswe)) ++ // get origin and check existing geo coordinates ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) + { +- return getNearestZ(curX, curY, prevGeoZ); ++ return true; + } + +- return getNextHigherZ(curX, curY, prevGeoZ); ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coordinates ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) ++ { ++ return true; ++ } ++ ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // origin and target coordinates are same ++ if ((gox == gtx) && (goy == gty)) ++ { ++ return goz == gtz; ++ } ++ ++ // get origin and target height, real height = collision height * 2 ++ double oheight = 0; ++ if (origin.isCreature()) ++ { ++ oheight = ((Creature) origin).getCollisionHeight() * 2; ++ } ++ ++ double theight = 0; ++ if (target.isCreature()) ++ { ++ theight = ((Creature) target).getCollisionHeight() * 2; ++ } ++ ++ // perform geodata check ++ return checkSee(gox, goy, goz, oheight, gtx, gty, gtz, theight, origin.getInstanceWorld()); + } + + /** +- * Can see target. Does not check doors between. +- * @param xValue the x coordinate +- * @param yValue the y coordinate +- * @param zValue the z coordinate +- * @param txValue the target's x coordinate +- * @param tyValue the target's y coordinate +- * @param tzValue the target's z coordinate +- * @return {@code true} if there is line of sight between the given coordinate sets, {@code false} otherwise ++ * Check line of sight from {@link WorldObject} to {@link Location}. ++ * @param origin : The origin object. ++ * @param position : The target position. ++ * @return {@code boolean} : True if object can see position + */ +- public boolean canSeeTarget(int xValue, int yValue, int zValue, int txValue, int tyValue, int tzValue) ++ public boolean canSeeTarget(WorldObject origin, Location position) + { +- int x = xValue; +- int y = yValue; +- int tx = txValue; +- int ty = tyValue; ++ // get origin and target world coordinates ++ final int ox = origin.getX(); ++ final int oy = origin.getY(); ++ final int oz = origin.getZ(); ++ final int tx = position.getX(); ++ final int ty = position.getY(); ++ final int tz = position.getZ(); + +- int geoX = getGeoX(x); +- int geoY = getGeoY(y); +- int tGeoX = getGeoX(tx); +- int tGeoY = getGeoY(ty); ++ if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld(), true)) ++ { ++ return false; ++ } ++ if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld())) ++ { ++ return false; ++ } ++ if (WarpedSpaceManager.getInstance().checkForWarpedSpace(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld())) ++ { ++ return false; ++ } + +- int z = getNearestZ(geoX, geoY, zValue); +- int tz = getNearestZ(tGeoX, tGeoY, tzValue); ++ // get origin and check existing geo coordinates ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) ++ { ++ return true; ++ } + +- // Fastpath. +- if ((geoX == tGeoX) && (geoY == tGeoY)) ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coordinates ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) + { +- if (hasGeoPos(tGeoX, tGeoY)) +- { +- return z == tz; +- } + return true; + } + +- if (tz > z) ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // origin and target coordinates are same ++ if ((gox == gtx) && (goy == gty)) + { +- int tmp = tx; +- tx = x; +- x = tmp; +- +- tmp = ty; +- ty = y; +- y = tmp; +- +- tmp = tz; +- tz = z; +- z = tmp; +- +- tmp = tGeoX; +- tGeoX = geoX; +- geoX = tmp; +- +- tmp = tGeoY; +- tGeoY = geoY; +- geoY = tmp; ++ return goz == gtz; + } + +- final LinePointIterator3D pointIter = new LinePointIterator3D(geoX, geoY, z, tGeoX, tGeoY, tz); +- // 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()) ++ // get origin and target height, real height = collision height * 2 ++ double oheight = 0; ++ if (origin.isCreature()) + { +- final int curX = pointIter.x(); +- final int curY = pointIter.y(); ++ oheight = ((Creature) origin).getTemplate().getCollisionHeight(); ++ } ++ ++ // perform geodata check ++ return checkSee(gox, goy, goz, oheight, gtx, gty, gtz, 0, origin.getInstanceWorld()); ++ } ++ ++ /** ++ * Simple check for origin to target visibility. ++ * @param goxValue : origin X geodata coordinate ++ * @param goyValue : origin Y geodata coordinate ++ * @param gozValue : origin Z geodata coordinate ++ * @param oheight : origin height (if instance of {@link Character}) ++ * @param gtxValue : target X geodata coordinate ++ * @param gtyValue : target Y geodata coordinate ++ * @param gtzValue : target Z geodata coordinate ++ * @param theight : target height (if instance of {@link Character}) ++ * @param instance ++ * @return {@code boolean} : True, when target can be seen. ++ */ ++ private final boolean checkSee(int goxValue, int goyValue, int gozValue, double oheight, int gtxValue, int gtyValue, int gtzValue, double theight, Instance instance) ++ { ++ int goz = gozValue; ++ int gtz = gtzValue; ++ int gox = goxValue; ++ int goy = goyValue; ++ int gtx = gtxValue; ++ int gty = gtyValue; ++ ++ // get line of sight Z coordinates ++ double losoz = goz + ((oheight * Config.PART_OF_CHARACTER_HEIGHT) / 100); ++ double lostz = gtz + ((theight * Config.PART_OF_CHARACTER_HEIGHT) / 100); ++ ++ // get X delta and signum ++ final int dx = Math.abs(gtx - gox); ++ final int sx = gox < gtx ? 1 : -1; ++ final byte dirox = sx > 0 ? GeoStructure.CELL_FLAG_E : GeoStructure.CELL_FLAG_W; ++ final byte dirtx = sx > 0 ? GeoStructure.CELL_FLAG_W : GeoStructure.CELL_FLAG_E; ++ ++ // get Y delta and signum ++ final int dy = Math.abs(gty - goy); ++ final int sy = goy < gty ? 1 : -1; ++ final byte diroy = sy > 0 ? GeoStructure.CELL_FLAG_S : GeoStructure.CELL_FLAG_N; ++ final byte dirty = sy > 0 ? GeoStructure.CELL_FLAG_N : GeoStructure.CELL_FLAG_S; ++ ++ // get Z delta ++ final int dm = Math.max(dx, dy); ++ final double dz = (lostz - losoz) / dm; ++ ++ // get direction flag for diagonal movement ++ final byte diroxy = getDirXY(dirox, diroy); ++ final byte dirtxy = getDirXY(dirtx, dirty); ++ ++ // delta, determines axis to move on (+..X axis, -..Y axis) ++ int d = dx - dy; ++ ++ // NSWE direction of movement ++ byte diro; ++ byte dirt; ++ ++ // initialize node values ++ int nox = gox; ++ int noy = goy; ++ int ntx = gtx; ++ int nty = gty; ++ byte nsweo = getNsweNearest(gox, goy, goz); ++ byte nswet = getNsweNearest(gtx, gty, gtz); ++ ++ // loop ++ ABlock block; ++ int index; ++ for (int i = 0; i < ((dm + 1) / 2); i++) ++ { ++ // reset direction flag ++ diro = 0; ++ dirt = 0; + +- if ((curX == prevX) && (curY == prevY)) ++ // calculate next point coordinates ++ final int e2 = 2 * d; ++ if ((e2 > -dy) && (e2 < dx)) + { +- continue; ++ // calculate next point XY coordinates ++ d -= dy; ++ d += dx; ++ nox += sx; ++ ntx -= sx; ++ noy += sy; ++ nty -= sy; ++ diro |= diroxy; ++ dirt |= dirtxy; + } ++ else if (e2 > -dy) ++ { ++ // calculate next point X coordinate ++ d -= dy; ++ nox += sx; ++ ntx -= sx; ++ diro |= dirox; ++ dirt |= dirtx; ++ } ++ else if (e2 < dx) ++ { ++ // calculate next point Y coordinate ++ d += dx; ++ noy += sy; ++ nty -= sy; ++ diro |= diroy; ++ dirt |= dirty; ++ } + +- final int beeCurZ = pointIter.z(); +- int curGeoZ = prevGeoZ; +- +- // Check if the position has geodata. +- if (hasGeoPos(curX, curY)) + { +- final int beeCurGeoZ = getNearestZ(curX, curY, beeCurZ); +- final int nswe = GeoUtils.computeNswe(prevX, prevY, curX, curY); // .computeDirection(prevX, prevY, curX, curY); +- curGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, curX, curY, nswe); +- int maxHeight; +- if (ptIndex < ELEVATED_SEE_OVER_DISTANCE) ++ // get block of the next cell ++ block = getBlock(nox, noy); ++ ++ // get index of particular layer, based on movement conditions ++ if ((nsweo & diro) == 0) + { +- maxHeight = z + MAX_SEE_OVER_HEIGHT; ++ index = block.getIndexAbove(nox, noy, goz - GeoStructure.CELL_IGNORE_HEIGHT); + } + else + { +- maxHeight = beeCurZ + MAX_SEE_OVER_HEIGHT; ++ index = block.getIndexBelow(nox, noy, goz + GeoStructure.CELL_IGNORE_HEIGHT); + } + +- boolean canSeeThrough = false; +- if ((curGeoZ <= maxHeight) && (curGeoZ <= beeCurGeoZ)) ++ // layer does not exist, return ++ if (index == -1) + { +- if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) +- { +- 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 +- { +- canSeeThrough = true; +- } ++ return false; + } + +- if (!canSeeThrough) ++ // get layer and next line of sight Z coordinate ++ goz = block.getHeight(index); ++ losoz += dz; ++ ++ // perform line of sight check, return when fails ++ if ((goz - losoz) > Config.MAX_OBSTACLE_HEIGHT) + { + return false; + } ++ ++ // get layer nswe ++ nsweo = block.getNswe(index); + } ++ { ++ // get block of the next cell ++ block = getBlock(ntx, nty); ++ ++ // get index of particular layer, based on movement conditions ++ if ((nswet & dirt) == 0) ++ { ++ index = block.getIndexAbove(ntx, nty, gtz - GeoStructure.CELL_IGNORE_HEIGHT); ++ } ++ else ++ { ++ index = block.getIndexBelow(ntx, nty, gtz + GeoStructure.CELL_IGNORE_HEIGHT); ++ } ++ ++ // layer does not exist, return ++ if (index == -1) ++ { ++ return false; ++ } ++ ++ // get layer and next line of sight Z coordinate ++ gtz = block.getHeight(index); ++ lostz -= dz; ++ ++ // perform line of sight check, return when fails ++ if ((gtz - lostz) > Config.MAX_OBSTACLE_HEIGHT) ++ { ++ return false; ++ } ++ ++ // get layer nswe ++ nswet = block.getNswe(index); ++ } + +- prevX = curX; +- prevY = curY; +- prevGeoZ = curGeoZ; +- ++ptIndex; ++ // update coords ++ gox = nox; ++ goy = noy; ++ gtx = ntx; ++ gty = nty; + } + +- return true; ++ // when iteration is completed, compare final Z coordinates ++ return Math.abs(goz - gtz) < (GeoStructure.CELL_HEIGHT * 4); + } + + /** +- * Move check. +- * @param x the x coordinate +- * @param y the y coordinate +- * @param zValue the z coordinate +- * @param tx the target's x coordinate +- * @param ty the target's y coordinate +- * @param tzValue the target's z coordinate +- * @param instance the instance +- * @return the last Location (x,y,z) where player can walk - just before wall ++ * Check movement from coordinates to 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 {code boolean} : True if target coordinates are reachable from origin coordinates + */ +- public Location canMoveToTargetLoc(int x, int y, int zValue, int tx, int ty, int tzValue, Instance instance) ++ public boolean canMoveToTarget(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + { +- final int geoX = getGeoX(x); +- final int geoY = getGeoY(y); +- final int z = getNearestZ(geoX, geoY, zValue); +- final int tGeoX = getGeoX(tx); +- final int tGeoY = getGeoY(ty); +- final int tz = getNearestZ(tGeoX, tGeoY, tzValue); ++ // get origin and check existing geo coordinates ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) ++ { ++ return true; ++ } + +- if (DoorData.getInstance().checkIfDoorsBetween(x, y, z, tx, ty, tz, instance, false)) ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coordinates ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) + { +- return new Location(x, y, getHeight(x, y, z)); ++ return true; + } +- if (FenceData.getInstance().checkIfFenceBetween(x, y, z, tx, ty, tz, instance)) ++ ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // target coordinates reached ++ if ((gox == gtx) && (goy == gty) && (goz == gtz)) + { +- return new Location(x, y, getHeight(x, y, z)); ++ return true; + } +- if (WarpedSpaceManager.getInstance().checkForWarpedSpace(x, y, z, tx, ty, tz, instance)) ++ ++ // perform geodata check ++ final GeoLocation loc = checkMove(gox, goy, goz, gtx, gty, gtz, instance); ++ return (loc.getGeoX() == gtx) && (loc.getGeoY() == gty); ++ } ++ ++ /** ++ * Check movement from origin to target. Returns last available point in the checked path. ++ * @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 {@link Location} : Last point where object can walk (just before wall) ++ */ ++ public Location canMoveToTargetLoc(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) ++ { ++ // Mobius: Double check for doors before normal checkMove to avoid exploiting key movement. ++ if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, instance, false)) + { +- return new Location(x, y, getHeight(x, y, z)); ++ return new Location(ox, oy, oz); + } ++ if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, instance)) ++ { ++ return new Location(ox, oy, oz); ++ } + +- 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 = z; ++ // get origin and check existing geo coordinates ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) ++ { ++ return new Location(tx, ty, tz); ++ } + +- while (pointIter.next()) ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coordinates ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) + { +- final int curX = pointIter.x(); +- final int curY = pointIter.y(); +- final int curZ = getNearestZ(curX, curY, prevZ); +- +- if (hasGeoPos(prevX, prevY)) +- { +- final int nswe = GeoUtils.computeNswe(prevX, prevY, curX, curY); +- if (!checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, nswe)) +- { +- // Can't move, return previous location. +- return new Location(getWorldX(prevX), getWorldY(prevY), prevZ); +- } +- } +- +- prevX = curX; +- prevY = curY; +- prevZ = curZ; ++ return new Location(tx, ty, tz); + } + +- if (hasGeoPos(prevX, prevY) && (prevZ != tz)) ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // target coordinates reached ++ if ((gox == gtx) && (goy == gty) && (goz == gtz)) + { +- // Different floors, return start location. +- return new Location(x, y, z); ++ return new Location(tx, ty, tz); + } + +- return new Location(tx, ty, tz); ++ // perform geodata check ++ return checkMove(gox, goy, goz, gtx, gty, gtz, instance); + } + + /** +- * 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 fromZvalue 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 toZvalue 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 ++ * With this method you can check if a position is visible or can be reached by beeline movement.
++ * 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 gox : origin X geodata coordinate ++ * @param goy : origin Y geodata coordinate ++ * @param goz : origin Z geodata coordinate ++ * @param gtx : target X geodata coordinate ++ * @param gty : target Y geodata coordinate ++ * @param gtz : target Z geodata coordinate ++ * @param instance ++ * @return {@link GeoLocation} : The last allowed point of movement. + */ +- public boolean canMoveToTarget(int fromX, int fromY, int fromZvalue, int toX, int toY, int toZvalue, Instance instance) ++ protected final GeoLocation checkMove(int gox, int goy, int goz, int gtx, int gty, int gtz, Instance instance) + { +- final int geoX = getGeoX(fromX); +- final int geoY = getGeoY(fromY); +- final int fromZ = getNearestZ(geoX, geoY, fromZvalue); +- final int tGeoX = getGeoX(toX); +- final int tGeoY = getGeoY(toY); +- final int toZ = getNearestZ(tGeoX, tGeoY, toZvalue); +- +- if (DoorData.getInstance().checkIfDoorsBetween(fromX, fromY, fromZ, toX, toY, toZ, instance, false)) ++ if (DoorData.getInstance().checkIfDoorsBetween(gox, goy, goz, gtx, gty, gtz, instance, false)) + { +- return false; ++ return new GeoLocation(gox, goy, goz); + } +- if (FenceData.getInstance().checkIfFenceBetween(fromX, fromY, fromZ, toX, toY, toZ, instance)) ++ if (FenceData.getInstance().checkIfFenceBetween(gox, goy, goz, gtx, gty, gtz, instance)) + { +- return false; ++ return new GeoLocation(gox, goy, goz); + } +- if (WarpedSpaceManager.getInstance().checkForWarpedSpace(fromX, fromY, fromZ, toX, toY, toZ, instance)) ++ if (WarpedSpaceManager.getInstance().checkForWarpedSpace(gox, goy, goz, gtx, gty, gtz, instance)) + { +- return false; ++ return new GeoLocation(gox, goy, goz); + } + +- 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 = fromZ; ++ // get X delta, signum and direction flag ++ final int dx = Math.abs(gtx - gox); ++ final int sx = gox < gtx ? 1 : -1; ++ final byte dirX = sx > 0 ? GeoStructure.CELL_FLAG_E : GeoStructure.CELL_FLAG_W; + +- while (pointIter.next()) ++ // get Y delta, signum and direction flag ++ final int dy = Math.abs(gty - goy); ++ final int sy = goy < gty ? 1 : -1; ++ final byte dirY = sy > 0 ? GeoStructure.CELL_FLAG_S : GeoStructure.CELL_FLAG_N; ++ ++ // get direction flag for diagonal movement ++ final byte dirXY = getDirXY(dirX, dirY); ++ ++ // delta, determines axis to move on (+..X axis, -..Y axis) ++ int d = dx - dy; ++ ++ // NSWE direction of movement ++ byte direction; ++ ++ // load pointer coordinates ++ int gpx = gox; ++ int gpy = goy; ++ int gpz = goz; ++ ++ // load next pointer ++ int nx = gpx; ++ int ny = gpy; ++ ++ // loop ++ int count = 0; ++ while (count++ < Config.MAX_ITERATIONS) + { +- final int curX = pointIter.x(); +- final int curY = pointIter.y(); +- final int curZ = getNearestZ(curX, curY, prevZ); ++ direction = 0; + +- if (hasGeoPos(prevX, prevY)) ++ // calculate next point coordinates ++ final int e2 = 2 * d; ++ if ((e2 > -dy) && (e2 < dx)) + { +- final int nswe = GeoUtils.computeNswe(prevX, prevY, curX, curY); +- if (!checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, nswe)) ++ d -= dy; ++ d += dx; ++ nx += sx; ++ ny += sy; ++ direction |= dirXY; ++ } ++ else if (e2 > -dy) ++ { ++ d -= dy; ++ nx += sx; ++ direction |= dirX; ++ } ++ else if (e2 < dx) ++ { ++ d += dx; ++ ny += sy; ++ direction |= dirY; ++ } ++ ++ // obstacle found, return ++ if ((getNsweNearest(gpx, gpy, gpz) & direction) == 0) ++ { ++ return new GeoLocation(gpx, gpy, gpz); ++ } ++ ++ // update pointer coordinates ++ gpx = nx; ++ gpy = ny; ++ gpz = getHeightNearest(nx, ny, gpz); ++ ++ // target coordinates reached ++ if ((gpx == gtx) && (gpy == gty)) ++ { ++ if (gpz == gtz) + { +- return false; ++ // path found, Z coordinates are okay, return target point ++ return new GeoLocation(gtx, gty, gtz); + } ++ ++ // path found, Z coordinates are not okay, return last good point ++ return new GeoLocation(gpx, gpy, gpz); + } ++ } ++ ++ return new GeoLocation(gox, goy, goz); ++ } ++ ++ /** ++ * Returns diagonal NSWE flag format of combined two NSWE flags. ++ * @param dirX : X direction NSWE flag ++ * @param dirY : Y direction NSWE flag ++ * @return byte : NSWE flag of combined direction ++ */ ++ private static byte getDirXY(byte dirX, byte dirY) ++ { ++ // check axis directions ++ if (dirY == GeoStructure.CELL_FLAG_N) ++ { ++ if (dirX == GeoStructure.CELL_FLAG_W) ++ { ++ return GeoStructure.CELL_FLAG_NW; ++ } + +- prevX = curX; +- prevY = curY; +- prevZ = curZ; ++ return GeoStructure.CELL_FLAG_NE; + } + +- if (hasGeoPos(prevX, prevY) && (prevZ != toZ)) ++ if (dirX == GeoStructure.CELL_FLAG_W) + { +- // Different floors. +- return false; ++ return GeoStructure.CELL_FLAG_SW; + } + +- return true; ++ return GeoStructure.CELL_FLAG_SE; + } + ++ /** ++ * 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 ++ */ ++ public List findPath(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) ++ { ++ return null; ++ } ++ ++ /** ++ * Returns the instance of the {@link GeoEngine}. ++ * @return {@link GeoEngine} : The instance. ++ */ + public static GeoEngine getInstance() + { + return SingletonHolder.INSTANCE; +@@ -752,6 +947,6 @@ + + private static class SingletonHolder + { +- protected static final GeoEngine INSTANCE = new GeoEngine(); ++ protected static final GeoEngine INSTANCE = Config.PATHFINDING ? new GeoEnginePathfinding() : new GeoEngine(); + } +-} ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/GeoEnginePathfinding.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/GeoEnginePathfinding.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/GeoEnginePathfinding.java (working copy) +@@ -20,88 +20,84 @@ + import java.util.LinkedList; + import java.util.List; + import java.util.ListIterator; +-import java.util.logging.Level; +-import java.util.logging.Logger; + + import org.l2jmobius.Config; +-import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; +-import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +-import org.l2jmobius.gameserver.geoengine.pathfinding.CellNode; +-import org.l2jmobius.gameserver.geoengine.pathfinding.CellNodeBuffer; +-import org.l2jmobius.gameserver.geoengine.pathfinding.NodeLoc; +-import org.l2jmobius.gameserver.model.World; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoLocation; ++import org.l2jmobius.gameserver.geoengine.pathfinding.Node; ++import org.l2jmobius.gameserver.geoengine.pathfinding.NodeBuffer; ++import org.l2jmobius.gameserver.model.Location; + import org.l2jmobius.gameserver.model.instancezone.Instance; + + /** +- * @author -Nemesiss- ++ * @author Hasha + */ +-public class GeoEnginePathfinding ++final class GeoEnginePathfinding extends GeoEngine + { +- private static final Logger LOGGER = Logger.getLogger(GeoEnginePathfinding.class.getName()); ++ // pre-allocated buffers ++ private final BufferHolder[] _buffers; + +- private BufferInfo[] _buffers; +- + protected GeoEnginePathfinding() + { +- try ++ super(); ++ ++ final String[] array = Config.PATHFIND_BUFFERS.split(";"); ++ _buffers = new BufferHolder[array.length]; ++ int count = 0; ++ for (int i = 0; i < array.length; i++) + { +- final String[] array = Config.PATHFIND_BUFFERS.split(";"); ++ final String buf = array[i]; ++ final String[] args = buf.split("x"); + +- _buffers = new BufferInfo[array.length]; +- +- String buf; +- String[] args; +- for (int i = 0; i < array.length; i++) ++ try + { +- buf = array[i]; +- args = buf.split("x"); +- if (args.length != 2) +- { +- throw new Exception("Invalid buffer definition: " + buf); +- } +- +- _buffers[i] = new BufferInfo(Integer.parseInt(args[0]), Integer.parseInt(args[1])); ++ final int size = Integer.parseInt(args[1]); ++ count += size; ++ _buffers[i] = new BufferHolder(Integer.parseInt(args[0]), size); + } ++ catch (Exception e) ++ { ++ LOGGER.warning("GeoEnginePathfinding: Can not load buffer setting: " + buf); ++ } + } +- catch (Exception e) +- { +- LOGGER.log(Level.WARNING, "CellPathFinding: Problem during buffer init: " + e.getMessage(), e); +- throw new Error("CellPathFinding: load aborted"); +- } ++ ++ LOGGER.info("GeoEnginePathfinding: Loaded " + count + " node buffers."); + } + +- public boolean pathNodesExist(short regionoffset) ++ @Override ++ public List findPath(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + { +- return false; +- } +- +- public List findPath(int x, int y, int z, int tx, int ty, int tz, Instance instance) +- { +- final int gx = GeoEngine.getInstance().getGeoX(x); +- final int gy = GeoEngine.getInstance().getGeoY(y); +- if (!GeoEngine.getInstance().hasGeo(x, y)) ++ // get origin and check existing geo coords ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) + { + 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)) ++ ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coords ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) + { + 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)))); ++ ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // Prepare buffer for pathfinding calculations ++ final NodeBuffer buffer = getBuffer(64 + (2 * Math.max(Math.abs(gox - gtx), Math.abs(goy - gty)))); + if (buffer == null) + { + return null; + } + +- List path = null; ++ // find path ++ List path = null; + try + { +- final CellNode result = buffer.findPath(gx, gy, gz, gtx, gty, gtz); +- ++ final Node result = buffer.findPath(gox, goy, goz, gtx, gty, gtz); + if (result == null) + { + return null; +@@ -125,33 +121,45 @@ + return path; + } + +- int currentX, currentY, currentZ; +- ListIterator middlePoint; ++ // get path list iterator ++ final ListIterator point = path.listIterator(); + +- middlePoint = path.listIterator(); +- currentX = x; +- currentY = y; +- currentZ = z; ++ // get node A (origin) ++ int nodeAx = gox; ++ int nodeAy = goy; ++ short nodeAz = goz; + +- while (middlePoint.hasNext()) ++ // get node B ++ GeoLocation nodeB = (GeoLocation) point.next(); ++ ++ // iterate thought the path to optimize it ++ int count = 0; ++ while (point.hasNext() && (count++ < Config.MAX_ITERATIONS)) + { +- final AbstractNodeLoc locMiddle = middlePoint.next(); +- if (!middlePoint.hasNext()) +- { +- break; +- } ++ // get node C ++ final GeoLocation nodeC = (GeoLocation) path.get(point.nextIndex()); + +- final AbstractNodeLoc locEnd = path.get(middlePoint.nextIndex()); +- if (GeoEngine.getInstance().canMoveToTarget(currentX, currentY, currentZ, locEnd.getX(), locEnd.getY(), locEnd.getZ(), instance)) ++ // check movement from node A to node C ++ final GeoLocation loc = checkMove(nodeAx, nodeAy, nodeAz, nodeC.getGeoX(), nodeC.getGeoY(), nodeC.getZ(), instance); ++ if ((loc.getGeoX() == nodeC.getGeoX()) && (loc.getGeoY() == nodeC.getGeoY())) + { +- middlePoint.remove(); ++ // can move from node A to node C ++ ++ // remove node B ++ point.remove(); + } + else + { +- currentX = locMiddle.getX(); +- currentY = locMiddle.getY(); +- currentZ = locMiddle.getZ(); ++ // can not move from node A to node C ++ ++ // set node A (node B is part of path, update A coordinates) ++ nodeAx = nodeB.getGeoX(); ++ nodeAy = nodeB.getGeoY(); ++ nodeAz = (short) nodeB.getZ(); + } ++ ++ // set node B ++ nodeB = (GeoLocation) point.next(); + } + + return path; +@@ -158,144 +166,102 @@ + } + + /** +- * Convert geodata position to pathnode position +- * @param geo_pos +- * @return pathnode position ++ * Create list of node locations as result of calculated buffer node tree. ++ * @param node : the entry point ++ * @return List : list of node location + */ +- public short getNodePos(int geo_pos) ++ private static List constructPath(Node node) + { +- return (short) (geo_pos >> 3); // OK? +- } +- +- /** +- * Convert node position to pathnode block position +- * @param node_pos +- * @return pathnode block position (0...255) +- */ +- public short getNodeBlock(int node_pos) +- { +- return (short) (node_pos % 256); +- } +- +- public byte getRegionX(int node_pos) +- { +- return (byte) ((node_pos >> 8) + World.TILE_X_MIN); +- } +- +- public byte getRegionY(int node_pos) +- { +- return (byte) ((node_pos >> 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 node_x rx +- * @return +- */ +- public int calculateWorldX(short node_x) +- { +- return World.MAP_MIN_X + (node_x * 128) + 48; +- } +- +- /** +- * Convert pathnode y to World y position +- * @param node_y +- * @return +- */ +- public int calculateWorldY(short node_y) +- { +- return World.MAP_MIN_Y + (node_y * 128) + 48; +- } +- +- private List constructPath(AbstractNode nodeValue) +- { +- final LinkedList path = new LinkedList<>(); +- int previousDirectionX = Integer.MIN_VALUE; +- int previousDirectionY = Integer.MIN_VALUE; +- int directionX, directionY; ++ // create empty list ++ final LinkedList list = new LinkedList<>(); + +- AbstractNode node = nodeValue; +- while (node.getParent() != null) ++ // set direction X/Y ++ int dx = 0; ++ int dy = 0; ++ ++ // get target parent ++ Node target = node; ++ Node parent = target.getParent(); ++ ++ // while parent exists ++ int count = 0; ++ while ((parent != null) && (count++ < Config.MAX_ITERATIONS)) + { +- directionX = node.getLoc().getNodeX() - node.getParent().getLoc().getNodeX(); +- directionY = node.getLoc().getNodeY() - node.getParent().getLoc().getNodeY(); ++ // get parent <> target direction X/Y ++ final int nx = parent.getLoc().getGeoX() - target.getLoc().getGeoX(); ++ final int ny = parent.getLoc().getGeoY() - target.getLoc().getGeoY(); + +- // only add a new route point if moving direction changes +- if ((directionX != previousDirectionX) || (directionY != previousDirectionY)) ++ // direction has changed? ++ if ((dx != nx) || (dy != ny)) + { +- previousDirectionX = directionX; +- previousDirectionY = directionY; ++ // add node to the beginning of the list ++ list.addFirst(target.getLoc()); + +- path.addFirst(node.getLoc()); +- node.setLoc(null); ++ // update direction X/Y ++ dx = nx; ++ dy = ny; + } + +- node = node.getParent(); ++ // move to next node, set target and get its parent ++ target = parent; ++ parent = target.getParent(); + } + +- return path; ++ // return list ++ return list; + } + +- private CellNodeBuffer alloc(int size) ++ /** ++ * Provides optimize selection of the buffer. When all pre-initialized buffer are locked, creates new buffer. ++ * @param size : pre-calculated minimal required size ++ * @return NodeBuffer : buffer ++ */ ++ private final NodeBuffer getBuffer(int size) + { +- CellNodeBuffer current = null; +- for (BufferInfo i : _buffers) ++ NodeBuffer current = null; ++ for (BufferHolder holder : _buffers) + { +- if (i.mapSize >= size) ++ // Find proper size of buffer ++ if (holder._size < size) + { +- for (CellNodeBuffer buf : i.buffer) ++ continue; ++ } ++ ++ // Find unlocked NodeBuffer ++ for (NodeBuffer buffer : holder._buffer) ++ { ++ if (!buffer.isLocked()) + { +- if (buf.lock()) +- { +- current = buf; +- break; +- } ++ continue; + } +- if (current != null) +- { +- break; +- } + +- // not found, allocate temporary buffer +- current = new CellNodeBuffer(i.mapSize); +- current.lock(); +- if (i.buffer.size() < i.count) +- { +- i.buffer.add(current); +- break; +- } ++ return buffer; + } ++ ++ // NodeBuffer not found, allocate temporary buffer ++ current = new NodeBuffer(holder._size); ++ current.isLocked(); + } + + return current; + } + +- private static final class BufferInfo ++ /** ++ * NodeBuffer container with specified size and count of separate buffers. ++ */ ++ private static final class BufferHolder + { +- final int mapSize; +- final int count; +- ArrayList buffer; ++ final int _size; ++ List _buffer; + +- public BufferInfo(int size, int cnt) ++ public BufferHolder(int size, int count) + { +- mapSize = size; +- count = cnt; +- buffer = new ArrayList<>(count); ++ _size = size; ++ _buffer = new ArrayList<>(count); ++ for (int i = 0; i < count; i++) ++ { ++ _buffer.add(new NodeBuffer(size)); ++ } + } + } +- +- public static GeoEnginePathfinding getInstance() +- { +- return SingletonHolder.INSTANCE; +- } +- +- private static class SingletonHolder +- { +- protected static final GeoEnginePathfinding INSTANCE = new GeoEnginePathfinding(); +- } +-} ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java (working copy) +@@ -0,0 +1,197 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++import java.io.IOException; ++ ++/** ++ * @author Hasha ++ */ ++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 height of cell, which is closest to given coordinates.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @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 getHeightNearestOriginal(int geoX, int geoY, int worldZ); ++ ++ /** ++ * Returns the height of cell, which is first above 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, above given coordinates. ++ */ ++ public abstract short getHeightAbove(int geoX, int geoY, int worldZ); ++ ++ /** ++ * Returns the height of cell, which is first below 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, below given coordinates. ++ */ ++ public abstract short getHeightBelow(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 the NSWE flag byte of cell, which is closest to given coordinates.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @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 getNsweNearestOriginal(int geoX, int geoY, int worldZ); ++ ++ /** ++ * Returns the NSWE flag byte of cell, which is first above 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 getNsweAbove(int geoX, int geoY, int worldZ); ++ ++ /** ++ * Returns the NSWE flag byte of cell, which is first below 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 getNsweBelow(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 above given coordinates.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @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 getIndexAboveOriginal(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 index to data of the cell, which is first layer below given coordinates.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @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 getIndexBelowOriginal(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 height of cell given by cell index.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @param index : Index of the cell. ++ * @return short : Cell geodata Z coordinate, below given coordinates. ++ */ ++ public abstract short getHeightOriginal(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); ++ ++ /** ++ * Returns the NSWE flag byte of cell given by cell index.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @param index : Index of the cell. ++ * @return short : Cell geodata Z coordinate, below given coordinates. ++ */ ++ public abstract byte getNsweOriginal(int index); ++ ++ /** ++ * Sets the NSWE flag byte of cell given by cell index. ++ * @param index : Index of the cell. ++ * @param nswe : New NSWE flag byte. ++ */ ++ public abstract void setNswe(int index, byte nswe); ++ ++ /** ++ * Saves the block in L2D format to {@link BufferedOutputStream}. Used only for L2D geodata conversion. ++ * @param stream : The stream. ++ * @throws IOException : Can't save the block to steam. ++ */ ++ public abstract void saveBlock(BufferedOutputStream stream) throws IOException; ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java (working copy) +@@ -0,0 +1,252 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++import java.io.IOException; ++import java.nio.ByteBuffer; ++ ++/** ++ * @author Hasha ++ */ ++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. ++ * @param format : GeoFormat specifying format of loaded data. ++ */ ++ public BlockComplex(ByteBuffer bb, GeoFormat format) ++ { ++ // initialize buffer ++ _buffer = new byte[GeoStructure.BLOCK_CELLS * 3]; ++ ++ // load data ++ for (int i = 0; i < GeoStructure.BLOCK_CELLS; i++) ++ { ++ if (format != GeoFormat.L2D) ++ { ++ // 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); ++ } ++ else ++ { ++ // get nswe ++ final byte nswe = bb.get(); ++ _buffer[i * 3] = nswe; ++ ++ // get height ++ final short height = bb.getShort(); ++ _buffer[(i * 3) + 1] = (byte) (height & 0x00FF); ++ _buffer[(i * 3) + 2] = (byte) (height >> 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 short getHeightNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getHeightNearest(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeightAbove(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 short height = (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ ++ // check and return height ++ return height > worldZ ? height : Short.MIN_VALUE; ++ } ++ ++ @Override ++ public short getHeightBelow(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 short height = (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ ++ // check and return height ++ return height < worldZ ? height : Short.MAX_VALUE; ++ } ++ ++ @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 byte getNsweNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getNsweNearest(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public byte getNsweAbove(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 ? _buffer[index] : 0; ++ } ++ ++ @Override ++ public byte getNsweBelow(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 ? _buffer[index] : 0; ++ } ++ ++ @Override ++ public 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 getIndexAboveOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexAbove(geoX, geoY, worldZ); ++ } ++ ++ @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 int getIndexBelowOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexBelow(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeight(int index) ++ { ++ return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ } ++ ++ @Override ++ public short getHeightOriginal(int index) ++ { ++ return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ } ++ ++ @Override ++ public byte getNswe(int index) ++ { ++ return _buffer[index]; ++ } ++ ++ @Override ++ public byte getNsweOriginal(int index) ++ { ++ return _buffer[index]; ++ } ++ ++ @Override ++ public void setNswe(int index, byte nswe) ++ { ++ _buffer[index] = nswe; ++ } ++ ++ @Override ++ public void saveBlock(BufferedOutputStream stream) throws IOException ++ { ++ // write block type ++ stream.write(GeoStructure.TYPE_COMPLEX_L2D); ++ ++ // write block data ++ stream.write(_buffer, 0, GeoStructure.BLOCK_CELLS * 3); ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java (working copy) +@@ -0,0 +1,176 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++import java.io.IOException; ++import java.nio.ByteBuffer; ++ ++/** ++ * @author Hasha ++ */ ++public class BlockFlat extends ABlock ++{ ++ protected final short _height; ++ protected byte _nswe; ++ ++ /** ++ * Creates FlatBlock. ++ * @param bb : Input byte buffer. ++ * @param format : GeoFormat specifying format of loaded data. ++ */ ++ public BlockFlat(ByteBuffer bb, GeoFormat format) ++ { ++ _height = bb.getShort(); ++ _nswe = format != GeoFormat.L2D ? 0x0F : (byte) (0xFF); ++ if (format == GeoFormat.L2OFF) ++ { ++ bb.getShort(); ++ } ++ } ++ ++ @Override ++ public boolean hasGeoPos() ++ { ++ return true; ++ } ++ ++ @Override ++ public short getHeightNearest(int geoX, int geoY, int worldZ) ++ { ++ return _height; ++ } ++ ++ @Override ++ public short getHeightNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return _height; ++ } ++ ++ @Override ++ public short getHeightAbove(int geoX, int geoY, int worldZ) ++ { ++ // check and return height ++ return _height > worldZ ? _height : Short.MIN_VALUE; ++ } ++ ++ @Override ++ public short getHeightBelow(int geoX, int geoY, int worldZ) ++ { ++ // check and return height ++ return _height < worldZ ? _height : Short.MAX_VALUE; ++ } ++ ++ @Override ++ public byte getNsweNearest(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweAbove(int geoX, int geoY, int worldZ) ++ { ++ // check height and return nswe ++ return _height > worldZ ? _nswe : 0; ++ } ++ ++ @Override ++ public byte getNsweBelow(int geoX, int geoY, int worldZ) ++ { ++ // check height and return nswe ++ return _height < worldZ ? _nswe : 0; ++ } ++ ++ @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 getIndexAboveOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexAbove(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public int getIndexBelow(int geoX, int geoY, int worldZ) ++ { ++ // check height and return index ++ return _height < worldZ ? 0 : -1; ++ } ++ ++ @Override ++ public int getIndexBelowOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexBelow(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeight(int index) ++ { ++ return _height; ++ } ++ ++ @Override ++ public short getHeightOriginal(int index) ++ { ++ return _height; ++ } ++ ++ @Override ++ public byte getNswe(int index) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweOriginal(int index) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public void setNswe(int index, byte nswe) ++ { ++ _nswe = nswe; ++ } ++ ++ @Override ++ public void saveBlock(BufferedOutputStream stream) throws IOException ++ { ++ // write block type ++ stream.write(GeoStructure.TYPE_FLAT_L2D); ++ ++ // write height ++ stream.write((byte) (_height & 0x00FF)); ++ stream.write((byte) (_height >> 8)); ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java (working copy) +@@ -0,0 +1,465 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++import java.io.IOException; ++import java.nio.ByteBuffer; ++import java.nio.ByteOrder; ++import java.util.Arrays; ++ ++/** ++ * @author Hasha ++ */ ++public class BlockMultilayer extends ABlock ++{ ++ private static final int MAX_LAYERS = Byte.MAX_VALUE; ++ ++ private static ByteBuffer _temp; ++ ++ /** ++ * Initializes the temporarily buffer. ++ */ ++ public static void initialize() ++ { ++ // initialize temporarily buffer and sorting mechanism ++ _temp = ByteBuffer.allocate(GeoStructure.BLOCK_CELLS * MAX_LAYERS * 3); ++ _temp.order(ByteOrder.LITTLE_ENDIAN); ++ } ++ ++ /** ++ * Releases temporarily buffer. ++ */ ++ public static void release() ++ { ++ _temp = null; ++ } ++ ++ protected byte[] _buffer; ++ ++ /** ++ * Implicit constructor for children class. ++ */ ++ protected BlockMultilayer() ++ { ++ _buffer = null; ++ } ++ ++ /** ++ * Creates MultilayerBlock. ++ * @param bb : Input byte buffer. ++ * @param format : GeoFormat specifying format of loaded data. ++ */ ++ public BlockMultilayer(ByteBuffer bb, GeoFormat format) ++ { ++ // 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 = format != GeoFormat.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++) ++ { ++ if (format != GeoFormat.L2D) ++ { ++ // get data ++ final short data = bb.getShort(); ++ ++ // add nswe and height ++ _temp.put((byte) (data & 0x000F)); ++ _temp.putShort((short) ((short) (data & 0xFFF0) >> 1)); ++ } ++ else ++ { ++ // add nswe ++ _temp.put(bb.get()); ++ ++ // add height ++ _temp.putShort(bb.getShort()); ++ } ++ } ++ } ++ ++ // 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 short getHeightNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getHeightNearest(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeightAbove(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 height ++ if (height > worldZ) ++ { ++ return (short) height; ++ } ++ ++ // move index to next layer ++ index -= 3; ++ } ++ ++ // none layer found, return minimum value ++ return Short.MIN_VALUE; ++ } ++ ++ @Override ++ public short getHeightBelow(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 height ++ if (height < worldZ) ++ { ++ return (short) height; ++ } ++ ++ // move index to next layer ++ index += 3; ++ } ++ ++ // none layer found, return maximum value ++ return Short.MAX_VALUE; ++ } ++ ++ @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 byte getNsweNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getNsweNearest(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public byte getNsweAbove(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 nswe ++ if (height > worldZ) ++ { ++ return _buffer[index]; ++ } ++ ++ // move index to next layer ++ index -= 3; ++ } ++ ++ // none layer found, block movement ++ return 0; ++ } ++ ++ @Override ++ public byte getNsweBelow(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 nswe ++ if (height < worldZ) ++ { ++ return _buffer[index]; ++ } ++ ++ // move index to next layer ++ index += 3; ++ } ++ ++ // none layer found, block movement ++ return 0; ++ } ++ ++ @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 first layer data (first from bottom) ++ byte layers = _buffer[index++]; ++ ++ // loop though all cell layers, find closest layer ++ 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; ++ } ++ ++ // none layer found ++ return -1; ++ } ++ ++ @Override ++ public int getIndexAboveOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexAbove(geoX, geoY, worldZ); ++ } ++ ++ @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; ++ } ++ ++ // none layer found ++ return -1; ++ } ++ ++ @Override ++ public int getIndexBelowOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexBelow(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeight(int index) ++ { ++ // get height ++ return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ } ++ ++ @Override ++ public short getHeightOriginal(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]; ++ } ++ ++ @Override ++ public byte getNsweOriginal(int index) ++ { ++ // get nswe ++ return _buffer[index]; ++ } ++ ++ @Override ++ public void setNswe(int index, byte nswe) ++ { ++ // set nswe ++ _buffer[index] = nswe; ++ } ++ ++ @Override ++ public void saveBlock(BufferedOutputStream stream) throws IOException ++ { ++ // write block type ++ stream.write(GeoStructure.TYPE_MULTILAYER_L2D); ++ ++ // for each cell ++ int index = 0; ++ for (int i = 0; i < GeoStructure.BLOCK_CELLS; i++) ++ { ++ // write layers count ++ final byte layers = _buffer[index++]; ++ stream.write(layers); ++ ++ // write cell data ++ stream.write(_buffer, index, layers * 3); ++ ++ // move index to next cell ++ index += layers * 3; ++ } ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java (working copy) +@@ -0,0 +1,150 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++ ++/** ++ * @author Hasha ++ */ ++public class BlockNull extends ABlock ++{ ++ private final byte _nswe; ++ ++ public BlockNull() ++ { ++ _nswe = (byte) 0xFF; ++ } ++ ++ @Override ++ public boolean hasGeoPos() ++ { ++ return false; ++ } ++ ++ @Override ++ public short getHeightNearest(int geoX, int geoY, int worldZ) ++ { ++ return (short) worldZ; ++ } ++ ++ @Override ++ public short getHeightNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return (short) worldZ; ++ } ++ ++ @Override ++ public short getHeightAbove(int geoX, int geoY, int worldZ) ++ { ++ return (short) worldZ; ++ } ++ ++ @Override ++ public short getHeightBelow(int geoX, int geoY, int worldZ) ++ { ++ return (short) worldZ; ++ } ++ ++ @Override ++ public byte getNsweNearest(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweAbove(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweBelow(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) ++ { ++ return 0; ++ } ++ ++ @Override ++ public int getIndexAboveOriginal(int geoX, int geoY, int worldZ) ++ { ++ return 0; ++ } ++ ++ @Override ++ public int getIndexBelow(int geoX, int geoY, int worldZ) ++ { ++ return 0; ++ } ++ ++ @Override ++ public int getIndexBelowOriginal(int geoX, int geoY, int worldZ) ++ { ++ return 0; ++ } ++ ++ @Override ++ public short getHeight(int index) ++ { ++ return 0; ++ } ++ ++ @Override ++ public short getHeightOriginal(int index) ++ { ++ return 0; ++ } ++ ++ @Override ++ public byte getNswe(int index) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweOriginal(int index) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public void setNswe(int index, byte nswe) ++ { ++ } ++ ++ @Override ++ public void saveBlock(BufferedOutputStream stream) ++ { ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java (nonexistent) +@@ -1,48 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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() +- { +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/ComplexBlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/ComplexBlock.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/ComplexBlock.java (nonexistent) +@@ -1,77 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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; +- +-/** +- * @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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/FlatBlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/FlatBlock.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/FlatBlock.java (nonexistent) +@@ -1,56 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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; +- +-/** +- * @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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/GeoFormat.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/GeoFormat.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/GeoFormat.java (working copy) +@@ -0,0 +1,39 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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 Hasha ++ */ ++public enum GeoFormat ++{ ++ L2J("%d_%d.l2j"), ++ L2OFF("%d_%d_conv.dat"), ++ L2D("%d_%d.l2d"); ++ ++ private final String _filename; ++ ++ private GeoFormat(String filename) ++ { ++ _filename = filename; ++ } ++ ++ public String getFilename() ++ { ++ return _filename; ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/GeoLocation.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/GeoLocation.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/GeoLocation.java (working copy) +@@ -0,0 +1,67 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.geoengine.GeoEngine; ++import org.l2jmobius.gameserver.model.Location; ++ ++/** ++ * @author Hasha ++ */ ++public class GeoLocation extends Location ++{ ++ private byte _nswe; ++ ++ public GeoLocation(int x, int y, int z) ++ { ++ super(x, y, GeoEngine.getInstance().getHeightNearest(x, y, z)); ++ _nswe = GeoEngine.getInstance().getNsweNearest(x, y, z); ++ } ++ ++ public void set(int x, int y, short z) ++ { ++ super.setXYZ(x, y, GeoEngine.getInstance().getHeightNearest(x, y, z)); ++ _nswe = GeoEngine.getInstance().getNsweNearest(x, y, z); ++ } ++ ++ public int getGeoX() ++ { ++ return _x; ++ } ++ ++ public int getGeoY() ++ { ++ return _y; ++ } ++ ++ @Override ++ public int getX() ++ { ++ return GeoEngine.getWorldX(_x); ++ } ++ ++ @Override ++ public int getY() ++ { ++ return GeoEngine.getWorldY(_y); ++ } ++ ++ public byte getNSWE() ++ { ++ return _nswe; ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java (working copy) +@@ -0,0 +1,70 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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; ++ ++/** ++ * @author Hasha ++ */ ++public class GeoStructure ++{ ++ // cells ++ public static final byte CELL_FLAG_E = 1 << 0; ++ public static final byte CELL_FLAG_W = 1 << 1; ++ public static final byte CELL_FLAG_S = 1 << 2; ++ public static final byte CELL_FLAG_N = 1 << 3; ++ public static final byte CELL_FLAG_SE = 1 << 4; ++ public static final byte CELL_FLAG_SW = 1 << 5; ++ public static final byte CELL_FLAG_NE = 1 << 6; ++ public static final byte CELL_FLAG_NW = (byte) (1 << 7); ++ ++ public static final int CELL_HEIGHT = 8; ++ public static final int CELL_IGNORE_HEIGHT = CELL_HEIGHT * 6; ++ ++ // blocks ++ public static final byte TYPE_FLAT_L2J_L2OFF = 0; ++ public static final byte TYPE_FLAT_L2D = (byte) 0xD0; ++ public static final byte TYPE_COMPLEX_L2J = 1; ++ public static final byte TYPE_COMPLEX_L2OFF = 0x40; ++ public static final byte TYPE_COMPLEX_L2D = (byte) 0xD1; ++ 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) ++ public static final byte TYPE_MULTILAYER_L2D = (byte) 0xD2; ++ ++ 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; ++ ++ // regions ++ 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; ++ ++ // global geodata ++ private static final int GEO_REGIONS_X = ((World.TILE_X_MAX - World.TILE_X_MIN) + 1); ++ private 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 +Index: java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java (nonexistent) +@@ -1,42 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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); +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/IGeoObject.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/IGeoObject.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/IGeoObject.java (working copy) +@@ -0,0 +1,53 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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 Hasha ++ */ ++public interface IGeoObject ++{ ++ /** ++ * Returns geodata X coordinate of the {@link IGeoObject}. ++ * @return int : Geodata X coordinate. ++ */ ++ int getGeoX(); ++ ++ /** ++ * Returns geodata Y coordinate of the {@link IGeoObject}. ++ * @return int : Geodata Y coordinate. ++ */ ++ int getGeoY(); ++ ++ /** ++ * Returns geodata Z coordinate of the {@link IGeoObject}. ++ * @return int : Geodata Z coordinate. ++ */ ++ int getGeoZ(); ++ ++ /** ++ * Returns height of the {@link IGeoObject}. ++ * @return int : Height. ++ */ ++ int getHeight(); ++ ++ /** ++ * Returns {@link IGeoObject} data. ++ * @return byte[][] : {@link IGeoObject} data. ++ */ ++ byte[][] getObjectGeoData(); ++} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java (nonexistent) +@@ -1,47 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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(); +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/MultilayerBlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/MultilayerBlock.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/MultilayerBlock.java (nonexistent) +@@ -1,183 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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; +- +-/** +- * @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); +- +- // 1 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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/NullRegion.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/NullRegion.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/NullRegion.java (nonexistent) +@@ -1,55 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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 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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/Region.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/Region.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/Region.java (nonexistent) +@@ -1,92 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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; +- +-/** +- * @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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java (nonexistent) +@@ -1,87 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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 T _loc; +- private AbstractNode _parent; +- +- public AbstractNode(T loc) +- { +- _loc = loc; +- } +- +- public void setParent(AbstractNode p) +- { +- _parent = p; +- } +- +- public AbstractNode getParent() +- { +- return _parent; +- } +- +- public T getLoc() +- { +- return _loc; +- } +- +- public void setLoc(T l) +- { +- _loc = l; +- } +- +- @Override +- public int hashCode() +- { +- final int prime = 31; +- int result = 1; +- result = (prime * result) + ((_loc == null) ? 0 : _loc.hashCode()); +- return result; +- } +- +- @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 +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java (nonexistent) +@@ -1,33 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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 int getNodeX(); +- +- public abstract int getNodeY(); +-} +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNode.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNode.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNode.java (nonexistent) +@@ -1,67 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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 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 +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNodeBuffer.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNodeBuffer.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNodeBuffer.java (nonexistent) +@@ -1,348 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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.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 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) +- { +- _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(); +- } +- +- 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 void getNeighbors() +- { +- if (!_current.getLoc().canGoAll()) +- { +- 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); +- } +- +- // SouthEast +- if ((nodeE != null) && (nodeS != null)) +- { +- if (nodeE.getLoc().canGoSouth() && nodeS.getLoc().canGoEast()) +- { +- addNode(x + 1, y + 1, z, true); +- } +- } +- +- // SouthWest +- if ((nodeS != null) && (nodeW != null)) +- { +- if (nodeW.getLoc().canGoSouth() && nodeS.getLoc().canGoWest()) +- { +- addNode(x - 1, y + 1, z, true); +- } +- } +- +- // NorthEast +- if ((nodeN != null) && (nodeE != null)) +- { +- if (nodeE.getLoc().canGoNorth() && nodeN.getLoc().canGoEast()) +- { +- addNode(x + 1, y - 1, z, true); +- } +- } +- +- // NorthWest +- if ((nodeN != null) && (nodeW != null)) +- { +- if (nodeW.getLoc().canGoNorth() && nodeN.getLoc().canGoWest()) +- { +- addNode(x - 1, y - 1, z, true); +- } +- } +- } +- +- private 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(); +- // reinit node if needed +- if (result.getLoc() != null) +- { +- result.getLoc().set(x, y, z); +- } +- else +- { +- result.setLoc(new NodeLoc(x, y, z)); +- } +- } +- +- return result; +- } +- +- private 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 boolean isHighWeight(int x, int y, int z) +- { +- final CellNode result = getNode(x, y, z); +- if (result == null) +- { +- return true; +- } +- +- if (!result.getLoc().canGoAll()) +- { +- return true; +- } +- if (Math.abs(result.getLoc().getZ() - z) > 16) +- { +- return true; +- } +- +- return false; +- } +- +- private 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 +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java (working copy) +@@ -0,0 +1,87 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.geodata.GeoLocation; ++ ++/** ++ * @author Hasha ++ */ ++public class Node ++{ ++ // node coords and nswe flag ++ private GeoLocation _loc; ++ ++ // node parent (for reverse path construction) ++ private Node _parent; ++ // node child (for moving over nodes during iteration) ++ private Node _child; ++ ++ // node G cost (movement cost = parent movement cost + current movement cost) ++ private double _cost = -1000; ++ ++ public void setLoc(int x, int y, int z) ++ { ++ _loc = new GeoLocation(x, y, z); ++ } ++ ++ public GeoLocation getLoc() ++ { ++ return _loc; ++ } ++ ++ public void setParent(Node parent) ++ { ++ _parent = parent; ++ } ++ ++ public Node getParent() ++ { ++ return _parent; ++ } ++ ++ public void setChild(Node child) ++ { ++ _child = child; ++ } ++ ++ public Node getChild() ++ { ++ return _child; ++ } ++ ++ public void setCost(double cost) ++ { ++ _cost = cost; ++ } ++ ++ public double getCost() ++ { ++ return _cost; ++ } ++ ++ public void free() ++ { ++ // reset node location ++ _loc = null; ++ ++ // reset node parent, child and cost ++ _parent = null; ++ _child = null; ++ _cost = -1000; ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java (working copy) +@@ -0,0 +1,306 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.concurrent.locks.ReentrantLock; ++ ++import org.l2jmobius.Config; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; ++ ++/** ++ * @author DS, Hasha; Credits to Diamond ++ */ ++public class NodeBuffer ++{ ++ private final ReentrantLock _lock = new ReentrantLock(); ++ private final int _size; ++ private final Node[][] _buffer; ++ ++ // center coordinates ++ private int _cx = 0; ++ private int _cy = 0; ++ ++ // target coordinates ++ private int _gtx = 0; ++ private int _gty = 0; ++ private short _gtz = 0; ++ ++ private Node _current = null; ++ ++ /** ++ * Constructor of NodeBuffer. ++ * @param size : one dimension size of buffer ++ */ ++ public NodeBuffer(int size) ++ { ++ // set size ++ _size = size; ++ ++ // initialize buffer ++ _buffer = new Node[size][size]; ++ for (int x = 0; x < size; x++) ++ { ++ for (int y = 0; y < size; y++) ++ { ++ _buffer[x][y] = 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 Node : first node of path ++ */ ++ public Node findPath(int gox, int goy, short goz, int gtx, int gty, short gtz) ++ { ++ // set coordinates (middle of the line (gox,goy) - (gtx,gty), will be in the center of the buffer) ++ _cx = gox + ((gtx - gox - _size) / 2); ++ _cy = goy + ((gty - goy - _size) / 2); ++ ++ _gtx = gtx; ++ _gty = gty; ++ _gtz = gtz; ++ ++ _current = getNode(gox, goy, goz); ++ _current.setCost(getCostH(gox, goy, goz)); ++ ++ int count = 0; ++ do ++ { ++ // reached target? ++ if ((_current.getLoc().getGeoX() == _gtx) && (_current.getLoc().getGeoY() == _gty) && (Math.abs(_current.getLoc().getZ() - _gtz) < 8)) ++ { ++ return _current; ++ } ++ ++ // expand current node ++ expand(); ++ ++ // move pointer ++ _current = _current.getChild(); ++ } ++ while ((_current != null) && (count++ < Config.MAX_ITERATIONS)); ++ ++ return null; ++ } ++ ++ public boolean isLocked() ++ { ++ return _lock.tryLock(); ++ } ++ ++ public void free() ++ { ++ _current = null; ++ ++ for (Node[] nodes : _buffer) ++ { ++ for (Node node : nodes) ++ { ++ if (node.getLoc() != null) ++ { ++ node.free(); ++ } ++ } ++ } ++ ++ _lock.unlock(); ++ } ++ ++ /** ++ * Check _current Node and add its neighbors to the buffer. ++ */ ++ private final void expand() ++ { ++ // can't move anywhere, don't expand ++ final byte nswe = _current.getLoc().getNSWE(); ++ if (nswe == 0) ++ { ++ return; ++ } ++ ++ // get geo coords of the node to be expanded ++ final int x = _current.getLoc().getGeoX(); ++ final int y = _current.getLoc().getGeoY(); ++ final short z = (short) _current.getLoc().getZ(); ++ ++ // can move north, expand ++ if ((nswe & GeoStructure.CELL_FLAG_N) != 0) ++ { ++ addNode(x, y - 1, z, Config.BASE_WEIGHT); ++ } ++ ++ // can move south, expand ++ if ((nswe & GeoStructure.CELL_FLAG_S) != 0) ++ { ++ addNode(x, y + 1, z, Config.BASE_WEIGHT); ++ } ++ ++ // can move west, expand ++ if ((nswe & GeoStructure.CELL_FLAG_W) != 0) ++ { ++ addNode(x - 1, y, z, Config.BASE_WEIGHT); ++ } ++ ++ // can move east, expand ++ if ((nswe & GeoStructure.CELL_FLAG_E) != 0) ++ { ++ addNode(x + 1, y, z, Config.BASE_WEIGHT); ++ } ++ ++ // can move north-west, expand ++ if ((nswe & GeoStructure.CELL_FLAG_NW) != 0) ++ { ++ addNode(x - 1, y - 1, z, Config.DIAGONAL_WEIGHT); ++ } ++ ++ // can move north-east, expand ++ if ((nswe & GeoStructure.CELL_FLAG_NE) != 0) ++ { ++ addNode(x + 1, y - 1, z, Config.DIAGONAL_WEIGHT); ++ } ++ ++ // can move south-west, expand ++ if ((nswe & GeoStructure.CELL_FLAG_SW) != 0) ++ { ++ addNode(x - 1, y + 1, z, Config.DIAGONAL_WEIGHT); ++ } ++ ++ // can move south-east, expand ++ if ((nswe & GeoStructure.CELL_FLAG_SE) != 0) ++ { ++ addNode(x + 1, y + 1, z, Config.DIAGONAL_WEIGHT); ++ } ++ } ++ ++ /** ++ * Returns node, if it exists in buffer. ++ * @param x : node X coord ++ * @param y : node Y coord ++ * @param z : node Z coord ++ * @return Node : node, if exits in buffer ++ */ ++ private final Node getNode(int x, int y, short z) ++ { ++ // check node X out of coordinates ++ final int ix = x - _cx; ++ if ((ix < 0) || (ix >= _size)) ++ { ++ return null; ++ } ++ ++ // check node Y out of coordinates ++ final int iy = y - _cy; ++ if ((iy < 0) || (iy >= _size)) ++ { ++ return null; ++ } ++ ++ // get node ++ final Node result = _buffer[ix][iy]; ++ ++ // check and update ++ if (result.getLoc() == null) ++ { ++ result.setLoc(x, y, z); ++ } ++ ++ // return node ++ return result; ++ } ++ ++ /** ++ * Add node given by coordinates to the buffer. ++ * @param x : geo X coord ++ * @param y : geo Y coord ++ * @param z : geo Z coord ++ * @param weight : weight of movement to new node ++ */ ++ private final void addNode(int x, int y, short z, int weight) ++ { ++ // get node to be expanded ++ final Node node = getNode(x, y, z); ++ if (node == null) ++ { ++ return; ++ } ++ ++ // Z distance between nearby cells is higher than cell size ++ if (node.getLoc().getZ() > (z + (2 * GeoStructure.CELL_HEIGHT))) ++ { ++ return; ++ } ++ ++ // node was already expanded, return ++ if (node.getCost() >= 0) ++ { ++ return; ++ } ++ ++ node.setParent(_current); ++ if (node.getLoc().getNSWE() != (byte) 0xFF) ++ { ++ node.setCost(getCostH(x, y, node.getLoc().getZ()) + (weight * Config.OBSTACLE_MULTIPLIER)); ++ } ++ else ++ { ++ node.setCost(getCostH(x, y, node.getLoc().getZ()) + weight); ++ } ++ ++ Node current = _current; ++ int count = 0; ++ while ((current.getChild() != null) && (count < (Config.MAX_ITERATIONS * 4))) ++ { ++ count++; ++ if (current.getChild().getCost() > node.getCost()) ++ { ++ node.setChild(current.getChild()); ++ break; ++ } ++ current = current.getChild(); ++ } ++ ++ if (count >= (Config.MAX_ITERATIONS * 4)) ++ { ++ System.err.println("Pathfinding: too long loop detected, cost:" + node.getCost()); ++ } ++ ++ current.setChild(node); ++ } ++ ++ /** ++ * @param x : node X coord ++ * @param y : node Y coord ++ * @param i : node Z coord ++ * @return double : node cost ++ */ ++ private final double getCostH(int x, int y, int i) ++ { ++ final int dX = x - _gtx; ++ final int dY = y - _gty; ++ final int dZ = (i - _gtz) / GeoStructure.CELL_HEIGHT; ++ ++ // return (Math.abs(dX) + Math.abs(dY) + Math.abs(dZ)) * Config.HEURISTIC_WEIGHT; // Manhattan distance ++ return Math.sqrt((dX * dX) + (dY * dY) + (dZ * dZ)) * Config.HEURISTIC_WEIGHT; // Direct distance ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeLoc.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeLoc.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeLoc.java (nonexistent) +@@ -1,183 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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.Cell; +- +-/** +- * @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 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 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; +- // return super.hashCode(); +- } +- +- @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; +- if (_x != other._x) +- { +- return false; +- } +- if (_y != other._y) +- { +- return false; +- } +- if (_goNorth != other._goNorth) +- { +- return false; +- } +- if (_goEast != other._goEast) +- { +- return false; +- } +- if (_goSouth != other._goSouth) +- { +- return false; +- } +- if (_goWest != other._goWest) +- { +- return false; +- } +- if (_geoHeight != other._geoHeight) +- { +- return false; +- } +- return true; +- } +-} +Index: java/org/l2jmobius/gameserver/model/actor/Creature.java +=================================================================== +--- java/org/l2jmobius/gameserver/model/actor/Creature.java (revision 8399) ++++ java/org/l2jmobius/gameserver/model/actor/Creature.java (working copy) +@@ -66,8 +66,6 @@ + import org.l2jmobius.gameserver.enums.TeleportWhereType; + import org.l2jmobius.gameserver.enums.UserInfoType; + import org.l2jmobius.gameserver.geoengine.GeoEngine; +-import org.l2jmobius.gameserver.geoengine.GeoEnginePathfinding; +-import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; + import org.l2jmobius.gameserver.instancemanager.IdManager; + import org.l2jmobius.gameserver.instancemanager.MapRegionManager; + import org.l2jmobius.gameserver.instancemanager.QuestManager; +@@ -2577,7 +2575,7 @@ + + public boolean disregardingGeodata; + public int onGeodataPathIndex; +- public List geoPath; ++ public List geoPath; + public int geoPathAccurateTx; + public int geoPathAccurateTy; + public int geoPathGtx; +@@ -3466,7 +3464,7 @@ + if (((originalDistance - distance) > 30) && !isControlBlocked() && !isInVehicle) + { + // Path calculation -- overrides previous movement check +- m.geoPath = GeoEnginePathfinding.getInstance().findPath(curX, curY, curZ, originalX, originalY, originalZ, getInstanceWorld()); ++ m.geoPath = GeoEngine.getInstance().findPath(curX, curY, curZ, originalX, originalY, originalZ, getInstanceWorld()); + if ((m.geoPath == null) || (m.geoPath.size() < 2)) // No path found + { + if (isPlayer() && !_isFlying && !isInWater) +Index: java/org/l2jmobius/gameserver/model/actor/instance/PlayerInstance.java +=================================================================== +--- java/org/l2jmobius/gameserver/model/actor/instance/PlayerInstance.java (revision 8397) ++++ java/org/l2jmobius/gameserver/model/actor/instance/PlayerInstance.java (working copy) +@@ -12704,7 +12704,7 @@ + { + if (Config.CORRECT_PLAYER_Z) + { +- final int nearestZ = GeoEngine.getInstance().getHigherHeight(getX(), getY(), getZ()); ++ final int nearestZ = GeoEngine.getInstance().getHeightNearest(getX(), getY(), getZ()); + if (getZ() < nearestZ) + { + teleToLocation(new Location(getX(), getY(), nearestZ)); +Index: java/org/l2jmobius/gameserver/util/GeoUtils.java +=================================================================== +--- java/org/l2jmobius/gameserver/util/GeoUtils.java (revision 8397) ++++ java/org/l2jmobius/gameserver/util/GeoUtils.java (working copy) +@@ -19,7 +19,7 @@ + import java.awt.Color; + + import org.l2jmobius.gameserver.geoengine.GeoEngine; +-import org.l2jmobius.gameserver.geoengine.geodata.Cell; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; + import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance; + import org.l2jmobius.gameserver.network.serverpackets.ExServerPrimitive; + +@@ -26,25 +26,25 @@ + /** + * @author HorridoJoho + */ +-public final class GeoUtils ++public class GeoUtils + { + public static void debug2DLine(PlayerInstance player, int x, int y, int tx, int ty, int z) + { +- final int gx = GeoEngine.getInstance().getGeoX(x); +- final int gy = GeoEngine.getInstance().getGeoY(y); ++ final int gx = GeoEngine.getGeoX(x); ++ final int gy = GeoEngine.getGeoY(y); + +- final int tgx = GeoEngine.getInstance().getGeoX(tx); +- final int tgy = GeoEngine.getInstance().getGeoY(ty); ++ final int tgx = GeoEngine.getGeoX(tx); ++ final int tgy = GeoEngine.getGeoY(ty); + + final ExServerPrimitive prim = new ExServerPrimitive("Debug2DLine", x, y, z); +- prim.addLine(Color.BLUE, GeoEngine.getInstance().getWorldX(gx), GeoEngine.getInstance().getWorldY(gy), z, GeoEngine.getInstance().getWorldX(tgx), GeoEngine.getInstance().getWorldY(tgy), z); ++ prim.addLine(Color.BLUE, GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), z, GeoEngine.getWorldX(tgx), GeoEngine.getWorldY(tgy), z); + + final LinePointIterator iter = new LinePointIterator(gx, gy, tgx, tgy); + + while (iter.next()) + { +- final int wx = GeoEngine.getInstance().getWorldX(iter.x()); +- final int wy = GeoEngine.getInstance().getWorldY(iter.y()); ++ final int wx = GeoEngine.getWorldX(iter.x()); ++ final int wy = GeoEngine.getWorldY(iter.y()); + + prim.addPoint(Color.RED, wx, wy, z); + } +@@ -53,21 +53,21 @@ + + public static void debug3DLine(PlayerInstance player, int x, int y, int z, int tx, int ty, int tz) + { +- final int gx = GeoEngine.getInstance().getGeoX(x); +- final int gy = GeoEngine.getInstance().getGeoY(y); ++ final int gx = GeoEngine.getGeoX(x); ++ final int gy = GeoEngine.getGeoY(y); + +- final int tgx = GeoEngine.getInstance().getGeoX(tx); +- final int tgy = GeoEngine.getInstance().getGeoY(ty); ++ final int tgx = GeoEngine.getGeoX(tx); ++ final int tgy = GeoEngine.getGeoY(ty); + + final ExServerPrimitive prim = new ExServerPrimitive("Debug3DLine", x, y, z); +- prim.addLine(Color.BLUE, GeoEngine.getInstance().getWorldX(gx), GeoEngine.getInstance().getWorldY(gy), z, GeoEngine.getInstance().getWorldX(tgx), GeoEngine.getInstance().getWorldY(tgy), tz); ++ prim.addLine(Color.BLUE, GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), z, GeoEngine.getWorldX(tgx), GeoEngine.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.getInstance().getWorldX(prevX); +- int wy = GeoEngine.getInstance().getWorldY(prevY); ++ int wx = GeoEngine.getWorldX(prevX); ++ int wy = GeoEngine.getWorldY(prevY); + int wz = iter.z(); + prim.addPoint(Color.RED, wx, wy, wz); + +@@ -78,8 +78,8 @@ + + if ((curX != prevX) || (curY != prevY)) + { +- wx = GeoEngine.getInstance().getWorldX(curX); +- wy = GeoEngine.getInstance().getWorldY(curY); ++ wx = GeoEngine.getWorldX(curX); ++ wy = GeoEngine.getWorldY(curY); + wz = iter.z(); + + prim.addPoint(Color.RED, wx, wy, wz); +@@ -93,7 +93,7 @@ + + private static Color getDirectionColor(int x, int y, int z, int nswe) + { +- if (GeoEngine.getInstance().checkNearestNswe(x, y, z, nswe)) ++ if ((GeoEngine.getInstance().getNsweNearest(x, y, z) & nswe) == nswe) + { + return Color.GREEN; + } +@@ -109,9 +109,8 @@ + int iPacket = 0; + + ExServerPrimitive exsp = null; +- final GeoEngine ge = GeoEngine.getInstance(); +- final int playerGx = ge.getGeoX(player.getX()); +- final int playerGy = ge.getGeoY(player.getY()); ++ final int playerGx = GeoEngine.getGeoX(player.getX()); ++ final int playerGy = GeoEngine.getGeoY(player.getY()); + for (int dx = -geoRadius; dx <= geoRadius; ++dx) + { + for (int dy = -geoRadius; dy <= geoRadius; ++dy) +@@ -135,12 +134,12 @@ + final int gx = playerGx + dx; + final int gy = playerGy + dy; + +- final int x = ge.getWorldX(gx); +- final int y = ge.getWorldY(gy); +- final int z = ge.getNearestZ(gx, gy, player.getZ()); ++ final int x = GeoEngine.getWorldX(gx); ++ final int y = GeoEngine.getWorldY(gy); ++ final int z = GeoEngine.getInstance().getHeightNearest(gx, gy, player.getZ()); + + // north arrow +- Color col = getDirectionColor(gx, gy, z, Cell.NSWE_NORTH); ++ Color col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_N); + 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); +@@ -147,7 +146,7 @@ + exsp.addLine(col, x - 4, y - 4, z, x + 4, y - 4, z); + + // east arrow +- col = getDirectionColor(gx, gy, z, Cell.NSWE_EAST); ++ col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_E); + 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); +@@ -154,13 +153,13 @@ + exsp.addLine(col, x + 4, y - 4, z, x + 4, y + 4, z); + + // south arrow +- col = getDirectionColor(gx, gy, z, Cell.NSWE_SOUTH); ++ col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_S); + 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, Cell.NSWE_WEST); ++ col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_W); + 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); +@@ -188,15 +187,15 @@ + { + if (y > lastY) + { +- return Cell.NSWE_SOUTH_EAST; ++ return GeoStructure.CELL_FLAG_SE; // Direction.SOUTH_EAST; + } + else if (y < lastY) + { +- return Cell.NSWE_NORTH_EAST; ++ return GeoStructure.CELL_FLAG_NE; // Direction.NORTH_EAST; + } + else + { +- return Cell.NSWE_EAST; ++ return GeoStructure.CELL_FLAG_E; // Direction.EAST; + } + } + else if (x < lastX) // west +@@ -203,26 +202,27 @@ + { + if (y > lastY) + { +- return Cell.NSWE_SOUTH_WEST; ++ return GeoStructure.CELL_FLAG_SW; // Direction.SOUTH_WEST; + } + else if (y < lastY) + { +- return Cell.NSWE_NORTH_WEST; ++ return GeoStructure.CELL_FLAG_NW; // Direction.NORTH_WEST; + } + else + { +- return Cell.NSWE_WEST; ++ return GeoStructure.CELL_FLAG_W; // Direction.WEST; + } + } +- else // unchanged x ++ else ++ // unchanged x + { + if (y > lastY) + { +- return Cell.NSWE_SOUTH; ++ return GeoStructure.CELL_FLAG_S; // Direction.SOUTH; + } + else if (y < lastY) + { +- return Cell.NSWE_NORTH; ++ return GeoStructure.CELL_FLAG_N; // Direction.NORTH; + } + else + { +Index: java/org/l2jmobius/tools/geodataconverter/GeoDataConverter.java +=================================================================== +--- java/org/l2jmobius/tools/geodataconverter/GeoDataConverter.java (nonexistent) ++++ java/org/l2jmobius/tools/geodataconverter/GeoDataConverter.java (working copy) +@@ -0,0 +1,386 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ */ ++package org.l2jmobius.tools.geodataconverter; ++ ++import java.io.BufferedOutputStream; ++import java.io.File; ++import java.io.FileOutputStream; ++import java.io.RandomAccessFile; ++import java.nio.ByteOrder; ++import java.nio.MappedByteBuffer; ++import java.nio.channels.FileChannel; ++import java.util.Scanner; ++ ++import org.l2jmobius.Config; ++import org.l2jmobius.commons.util.PropertiesParser; ++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.GeoFormat; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; ++import org.l2jmobius.gameserver.model.World; ++ ++/** ++ * @author Hasha ++ */ ++public class GeoDataConverter ++{ ++ private static GeoFormat _format; ++ private static ABlock[][] _blocks; ++ ++ public static void main(String[] args) ++ { ++ // initialize config ++ loadGeoengineConfigs(); ++ ++ // get geodata format ++ String type = ""; ++ try (Scanner scn = new Scanner(System.in)) ++ { ++ while (!(type.equalsIgnoreCase("J") || type.equalsIgnoreCase("O") || type.equalsIgnoreCase("E"))) ++ { ++ System.out.println("GeoDataConverter: Select source geodata type:"); ++ System.out.println(" J: L2J (e.g. 23_22.l2j)"); ++ System.out.println(" O: L2OFF (e.g. 23_22_conv.dat)"); ++ System.out.println(" E: Exit"); ++ System.out.print("Choice: "); ++ type = scn.next(); ++ } ++ } ++ if (type.equalsIgnoreCase("E")) ++ { ++ System.exit(0); ++ } ++ ++ _format = type.equalsIgnoreCase("J") ? GeoFormat.L2J : GeoFormat.L2OFF; ++ ++ // start conversion ++ System.out.println("GeoDataConverter: Converting all " + _format + " files."); ++ ++ // initialize geodata container ++ _blocks = new ABlock[GeoStructure.REGION_BLOCKS_X][GeoStructure.REGION_BLOCKS_Y]; ++ ++ // initialize multilayer temporarily buffer ++ BlockMultilayer.initialize(); ++ ++ // load geo files ++ int converted = 0; ++ for (int rx = World.TILE_X_MIN; rx <= World.TILE_X_MAX; rx++) ++ { ++ for (int ry = World.TILE_Y_MIN; ry <= World.TILE_Y_MAX; ry++) ++ { ++ final String input = String.format(_format.getFilename(), rx, ry); ++ final String filepath = Config.GEODATA_PATH; ++ final File f = new File(filepath + input); ++ if (f.exists() && !f.isDirectory()) ++ { ++ // load geodata ++ if (!loadGeoBlocks(input)) ++ { ++ System.out.println("GeoDataConverter: Unable to load " + input + " region file."); ++ continue; ++ } ++ ++ // recalculate nswe ++ if (!recalculateNswe()) ++ { ++ System.out.println("GeoDataConverter: Unable to convert " + input + " region file."); ++ continue; ++ } ++ ++ // save geodata ++ final String output = String.format(GeoFormat.L2D.getFilename(), rx, ry); ++ if (!saveGeoBlocks(output)) ++ { ++ System.out.println("GeoDataConverter: Unable to save " + output + " region file."); ++ continue; ++ } ++ ++ converted++; ++ System.out.println("GeoDataConverter: Created " + output + " region file."); ++ } ++ } ++ } ++ System.out.println("GeoDataConverter: Converted " + converted + " " + _format + " to L2D region file(s)."); ++ ++ // release multilayer block temporarily buffer ++ BlockMultilayer.release(); ++ } ++ ++ /** ++ * Loads geo blocks from buffer of the region file. ++ * @param filename : The name of the to load. ++ * @return boolean : True when successful. ++ */ ++ private static boolean loadGeoBlocks(String filename) ++ { ++ // region file is load-able, try to load it ++ try (RandomAccessFile raf = new RandomAccessFile(Config.GEODATA_PATH + filename, "r"); ++ FileChannel fc = raf.getChannel()) ++ { ++ final MappedByteBuffer buffer = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()).load(); ++ buffer.order(ByteOrder.LITTLE_ENDIAN); ++ ++ // load 18B header for L2off geodata (1st and 2nd byte...region X and Y) ++ if (_format == GeoFormat.L2OFF) ++ { ++ for (int i = 0; i < 18; i++) ++ { ++ buffer.get(); ++ } ++ } ++ ++ // 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 (_format == GeoFormat.L2J) ++ { ++ // get block type ++ final byte type = buffer.get(); ++ ++ // load block according to block type ++ switch (type) ++ { ++ case GeoStructure.TYPE_FLAT_L2J_L2OFF: ++ { ++ _blocks[ix][iy] = new BlockFlat(buffer, _format); ++ break; ++ } ++ case GeoStructure.TYPE_COMPLEX_L2J: ++ { ++ _blocks[ix][iy] = new BlockComplex(buffer, _format); ++ break; ++ } ++ case GeoStructure.TYPE_MULTILAYER_L2J: ++ { ++ _blocks[ix][iy] = new BlockMultilayer(buffer, _format); ++ break; ++ } ++ default: ++ { ++ throw new IllegalArgumentException("Unknown block type: " + type); ++ } ++ } ++ } ++ else ++ { ++ // get block type ++ final short type = buffer.getShort(); ++ ++ // load block according to block type ++ switch (type) ++ { ++ case GeoStructure.TYPE_FLAT_L2J_L2OFF: ++ { ++ _blocks[ix][iy] = new BlockFlat(buffer, _format); ++ break; ++ } ++ case GeoStructure.TYPE_COMPLEX_L2OFF: ++ { ++ _blocks[ix][iy] = new BlockComplex(buffer, _format); ++ break; ++ } ++ default: ++ { ++ _blocks[ix][iy] = new BlockMultilayer(buffer, _format); ++ break; ++ } ++ } ++ } ++ } ++ } ++ ++ if (buffer.remaining() > 0) ++ { ++ System.out.println("GeoDataConverter: Region file " + filename + " can be corrupted, remaining " + buffer.remaining() + " bytes to read."); ++ return false; ++ } ++ ++ return true; ++ } ++ catch (Exception e) ++ { ++ System.out.println("GeoDataConverter: Error while loading " + filename + " region file."); ++ return false; ++ } ++ } ++ ++ /** ++ * Recalculate diagonal flags for the region file. ++ * @return boolean : True when successful. ++ */ ++ private static boolean recalculateNswe() ++ { ++ try ++ { ++ for (int x = 0; x < GeoStructure.REGION_CELLS_X; x++) ++ { ++ for (int y = 0; y < GeoStructure.REGION_CELLS_Y; y++) ++ { ++ // get block ++ final ABlock block = _blocks[x / GeoStructure.BLOCK_CELLS_X][y / GeoStructure.BLOCK_CELLS_Y]; ++ ++ // skip flat blocks ++ if (block instanceof BlockFlat) ++ { ++ continue; ++ } ++ ++ // for complex and multilayer blocks go though all layers ++ short height = Short.MAX_VALUE; ++ int index; ++ while ((index = block.getIndexBelow(x, y, height)) != -1) ++ { ++ // get height and nswe ++ height = block.getHeight(index); ++ byte nswe = block.getNswe(index); ++ ++ // update nswe with diagonal flags ++ nswe = updateNsweBelow(x, y, height, nswe); ++ ++ // set nswe of the cell ++ block.setNswe(index, nswe); ++ } ++ } ++ } ++ ++ return true; ++ } ++ catch (Exception e) ++ { ++ return false; ++ } ++ } ++ ++ /** ++ * Updates the NSWE flag with diagonal flags. ++ * @param x : Geodata X coordinate. ++ * @param y : Geodata Y coordinate. ++ * @param z : Geodata Z coordinate. ++ * @param nsweValue : NSWE flag to be updated. ++ * @return byte : Updated NSWE flag. ++ */ ++ private static byte updateNsweBelow(int x, int y, short z, byte nsweValue) ++ { ++ byte nswe = nsweValue; ++ ++ // calculate virtual layer height ++ final short height = (short) (z + GeoStructure.CELL_IGNORE_HEIGHT); ++ ++ // get NSWE of neighbor cells below virtual layer (NPC/PC can fall down of clif, but can not climb it -> NSWE of cell below) ++ final byte nsweN = getNsweBelow(x, y - 1, height); ++ final byte nsweS = getNsweBelow(x, y + 1, height); ++ final byte nsweW = getNsweBelow(x - 1, y, height); ++ final byte nsweE = getNsweBelow(x + 1, y, height); ++ ++ // north-west ++ if ((((nswe & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) || (((nswe & GeoStructure.CELL_FLAG_W) != 0) && ((nsweW & GeoStructure.CELL_FLAG_N) != 0))) ++ { ++ nswe |= GeoStructure.CELL_FLAG_NW; ++ } ++ ++ // north-east ++ if ((((nswe & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) || (((nswe & GeoStructure.CELL_FLAG_E) != 0) && ((nsweE & GeoStructure.CELL_FLAG_N) != 0))) ++ { ++ nswe |= GeoStructure.CELL_FLAG_NE; ++ } ++ ++ // south-west ++ if ((((nswe & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) || (((nswe & GeoStructure.CELL_FLAG_W) != 0) && ((nsweW & GeoStructure.CELL_FLAG_S) != 0))) ++ { ++ nswe |= GeoStructure.CELL_FLAG_SW; ++ } ++ ++ // south-east ++ if ((((nswe & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) || (((nswe & GeoStructure.CELL_FLAG_E) != 0) && ((nsweE & GeoStructure.CELL_FLAG_S) != 0))) ++ { ++ nswe |= GeoStructure.CELL_FLAG_SE; ++ } ++ ++ return nswe; ++ } ++ ++ private static byte getNsweBelow(int geoX, int geoY, short worldZ) ++ { ++ // out of geo coordinates ++ if ((geoX < 0) || (geoX >= GeoStructure.REGION_CELLS_X)) ++ { ++ return 0; ++ } ++ ++ // out of geo coordinates ++ if ((geoY < 0) || (geoY >= GeoStructure.REGION_CELLS_Y)) ++ { ++ return 0; ++ } ++ ++ // get block ++ final ABlock block = _blocks[geoX / GeoStructure.BLOCK_CELLS_X][geoY / GeoStructure.BLOCK_CELLS_Y]; ++ ++ // get index, when valid, return nswe ++ final int index = block.getIndexBelow(geoX, geoY, worldZ); ++ return index == -1 ? 0 : block.getNswe(index); ++ } ++ ++ /** ++ * Save region file to file. ++ * @param filename : The name of file to save. ++ * @return boolean : True when successful. ++ */ ++ private static boolean saveGeoBlocks(String filename) ++ { ++ try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(Config.GEODATA_PATH + filename), GeoStructure.REGION_BLOCKS * GeoStructure.BLOCK_CELLS * 3)) ++ { ++ // loop over region blocks and save each block ++ for (int ix = 0; ix < GeoStructure.REGION_BLOCKS_X; ix++) ++ { ++ for (int iy = 0; iy < GeoStructure.REGION_BLOCKS_Y; iy++) ++ { ++ _blocks[ix][iy].saveBlock(bos); ++ } ++ } ++ ++ // flush data to file ++ bos.flush(); ++ ++ return true; ++ } ++ catch (Exception e) ++ { ++ return false; ++ } ++ } ++ ++ private static void loadGeoengineConfigs() ++ { ++ final PropertiesParser geoData = new PropertiesParser(Config.GEOENGINE_CONFIG_FILE); ++ Config.GEODATA_PATH = geoData.getString("GeoDataPath", "./data/geodata/"); ++ Config.COORD_SYNCHRONIZE = geoData.getInt("CoordSynchronize", -1); ++ Config.PART_OF_CHARACTER_HEIGHT = geoData.getInt("PartOfCharacterHeight", 75); ++ Config.MAX_OBSTACLE_HEIGHT = geoData.getInt("MaxObstacleHeight", 32); ++ Config.PATHFINDING = geoData.getBoolean("PathFinding", true); ++ Config.PATHFIND_BUFFERS = geoData.getString("PathFindBuffers", "100x6;128x6;192x6;256x4;320x4;384x4;500x2"); ++ Config.BASE_WEIGHT = geoData.getInt("BaseWeight", 10); ++ Config.DIAGONAL_WEIGHT = geoData.getInt("DiagonalWeight", 14); ++ Config.OBSTACLE_MULTIPLIER = geoData.getInt("ObstacleMultiplier", 10); ++ Config.HEURISTIC_WEIGHT = geoData.getInt("HeuristicWeight", 20); ++ Config.MAX_ITERATIONS = geoData.getInt("MaxIterations", 3500); ++ } ++} +\ No newline at end of file diff --git a/L2J_Mobius_Essence_5.0_Sylph/dist/game/data/geodata/L2D_GeoEngine.patch b/L2J_Mobius_Essence_5.0_Sylph/dist/game/data/geodata/L2D_GeoEngine.patch new file mode 100644 index 0000000000..63e2faf0c9 --- /dev/null +++ b/L2J_Mobius_Essence_5.0_Sylph/dist/game/data/geodata/L2D_GeoEngine.patch @@ -0,0 +1,6052 @@ +Index: dist/game/GeoDataConverter.bat +=================================================================== +--- dist/game/GeoDataConverter.bat (nonexistent) ++++ dist/game/GeoDataConverter.bat (working copy) +@@ -0,0 +1,6 @@ ++@echo off ++title L2D geodata converter ++ ++java -Xmx512m -cp ./../libs/* org.l2jmobius.tools.geodataconverter.GeoDataConverter ++ ++pause +Index: dist/game/GeoDataConverter.sh +=================================================================== +--- dist/game/GeoDataConverter.sh (nonexistent) ++++ dist/game/GeoDataConverter.sh (working copy) +@@ -0,0 +1,4 @@ ++#! /bin/sh ++ ++java -Xmx512m -cp ../libs/*: org.l2jmobius.tools.geodataconverter.GeoDataConverter > log/stdout.log 2>&1 ++ +Index: dist/game/config/GeoEngine.ini +=================================================================== +--- dist/game/config/GeoEngine.ini (revision 8397) ++++ dist/game/config/GeoEngine.ini (working copy) +@@ -1,6 +1,10 @@ + # ================================================================= + # Geodata + # ================================================================= ++# Because of real-time performance we are using geodata files only in ++# diagonal L2D format now (using filename e.g. 22_16.l2d). ++# L2D geodata can be obtained by conversion of existing L2J or L2OFF geodata. ++# Launch "GeoDataConverter.bat/sh" and follow instructions to start the conversion. + + # 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/ +@@ -13,6 +17,16 @@ + CoordSynchronize = 2 + + # ================================================================= ++# Path checking ++# ================================================================= ++ ++# 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 finding + # ================================================================= + +@@ -23,19 +37,23 @@ + # Pathfinding array buffers configuration, default: 100x6;128x6;192x6;256x4;320x4;384x4;500x2 + PathFindBuffers = 100x6;128x6;192x6;256x4;320x4;384x4;500x2 + +-# Weight for nodes without obstacles far from walls +-LowWeight = 0.5 ++# Base path weight, when moving from one node to another on axis direction, default: 10 ++BaseWeight = 10 + +-# Weight for nodes near walls +-MediumWeight = 2 ++# Path weight, when moving from one node to another on diagonal direction, default: BaseWeight * sqrt(2) = 14 ++DiagonalWeight = 14 + +-# Weight for nodes with obstacles +-HighWeight = 3 ++# When movement flags of target node is blocked to any direction, multiply movement weight by this multiplier. ++# This causes pathfinding algorithm to avoid path construction exactly near an obstacle, default: 10 ++ObstacleMultiplier = 10 + +-# Weight for diagonal movement. +-# Default: LowWeight * sqrt(2) +-DiagonalWeight = 0.707 ++# Weight of the heuristic algorithm, which is giving estimated cost from node to target, default: 20 ++# For proper function must be higher than BaseWeight and/or DiagonalWeight. ++HeuristicWeight = 20 + ++# Maximum number of generated nodes per one path-finding process, default 3500 ++MaxIterations = 3500 ++ + # ================================================================= + # Other + # ================================================================= +Index: dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java +=================================================================== +--- dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java (revision 8397) ++++ dist/game/data/scripts/handlers/admincommandhandlers/AdminGeodata.java (working copy) +@@ -55,9 +55,8 @@ + final int worldX = activeChar.getX(); + final int worldY = activeChar.getY(); + final int worldZ = activeChar.getZ(); +- final int geoX = GeoEngine.getInstance().getGeoX(worldX); +- final int geoY = GeoEngine.getInstance().getGeoY(worldY); +- ++ final int geoX = GeoEngine.getGeoX(worldX); ++ final int geoY = GeoEngine.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)); +@@ -73,9 +72,8 @@ + final int worldX = activeChar.getX(); + final int worldY = activeChar.getY(); + final int worldZ = activeChar.getZ(); +- final int geoX = GeoEngine.getInstance().getGeoX(worldX); +- final int geoY = GeoEngine.getInstance().getGeoY(worldY); +- ++ final int geoX = GeoEngine.getGeoX(worldX); ++ final int geoY = GeoEngine.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)); +Index: dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java +=================================================================== +--- dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java (revision 8397) ++++ dist/game/data/scripts/handlers/admincommandhandlers/AdminPathNode.java (working copy) +@@ -18,11 +18,11 @@ + + import java.util.List; + +-import org.l2jmobius.Config; +-import org.l2jmobius.gameserver.geoengine.GeoEnginePathfinding; +-import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; ++import org.l2jmobius.gameserver.geoengine.GeoEngine; + import org.l2jmobius.gameserver.handler.IAdminCommandHandler; ++import org.l2jmobius.gameserver.model.Location; + import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance; ++import org.l2jmobius.gameserver.network.SystemMessageId; + import org.l2jmobius.gameserver.util.BuilderUtil; + + public class AdminPathNode implements IAdminCommandHandler +@@ -37,29 +37,30 @@ + { + if (command.equals("admin_path_find")) + { +- if (!Config.PATHFINDING) +- { +- BuilderUtil.sendSysMessage(activeChar, "PathFinding is disabled."); +- return true; +- } + if (activeChar.getTarget() != null) + { +- final List path = GeoEnginePathfinding.getInstance().findPath(activeChar.getX(), activeChar.getY(), (short) activeChar.getZ(), activeChar.getTarget().getX(), activeChar.getTarget().getY(), (short) activeChar.getTarget().getZ(), activeChar.getInstanceWorld()); ++ 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()); + if (path == null) + { +- BuilderUtil.sendSysMessage(activeChar, "No Route!"); +- return true; ++ BuilderUtil.sendSysMessage(activeChar, "No route found or pathfinding disabled."); + } +- for (AbstractNodeLoc a : path) ++ else + { +- BuilderUtil.sendSysMessage(activeChar, "x:" + a.getX() + " y:" + a.getY() + " z:" + a.getZ()); ++ for (Location point : path) ++ { ++ BuilderUtil.sendSysMessage(activeChar, "x:" + point.getX() + " y:" + point.getY() + " z:" + point.getZ()); ++ } + } + } + else + { +- BuilderUtil.sendSysMessage(activeChar, "No Target!"); ++ activeChar.sendPacket(SystemMessageId.INVALID_TARGET); + } + } ++ else ++ { ++ return false; ++ } + return true; + } + +Index: java/org/l2jmobius/Config.java +=================================================================== +--- java/org/l2jmobius/Config.java (revision 8397) ++++ java/org/l2jmobius/Config.java (working copy) +@@ -32,7 +32,6 @@ + import java.net.UnknownHostException; + import java.nio.charset.StandardCharsets; + import java.nio.file.Files; +-import java.nio.file.Path; + import java.nio.file.Paths; + import java.time.Duration; + import java.util.ArrayList; +@@ -927,14 +926,17 @@ + // -------------------------------------------------- + // GeoEngine + // -------------------------------------------------- +- public static Path GEODATA_PATH; ++ public static String GEODATA_PATH; ++ public static int COORD_SYNCHRONIZE; ++ public static int PART_OF_CHARACTER_HEIGHT; ++ public static int MAX_OBSTACLE_HEIGHT; + public static boolean PATHFINDING; + public static String PATHFIND_BUFFERS; +- public static float LOW_WEIGHT; +- public static float MEDIUM_WEIGHT; +- public static float HIGH_WEIGHT; +- public static float DIAGONAL_WEIGHT; +- public static int COORD_SYNCHRONIZE; ++ public static int BASE_WEIGHT; ++ public static int DIAGONAL_WEIGHT; ++ public static int HEURISTIC_WEIGHT; ++ public static int OBSTACLE_MULTIPLIER; ++ public static int MAX_ITERATIONS; + public static boolean CORRECT_PLAYER_Z; + + /** Attribute System */ +@@ -2468,14 +2470,17 @@ + + // Load GeoEngine config file (if exists) + final PropertiesParser GeoEngine = new PropertiesParser(GEOENGINE_CONFIG_FILE); +- GEODATA_PATH = Paths.get(GeoEngine.getString("GeoDataPath", "./data/geodata")); ++ GEODATA_PATH = GeoEngine.getString("GeoDataPath", "./data/geodata/"); ++ COORD_SYNCHRONIZE = GeoEngine.getInt("CoordSynchronize", -1); ++ PART_OF_CHARACTER_HEIGHT = GeoEngine.getInt("PartOfCharacterHeight", 75); ++ MAX_OBSTACLE_HEIGHT = GeoEngine.getInt("MaxObstacleHeight", 32); + PATHFINDING = GeoEngine.getBoolean("PathFinding", true); + PATHFIND_BUFFERS = GeoEngine.getString("PathFindBuffers", "100x6;128x6;192x6;256x4;320x4;384x4;500x2"); +- LOW_WEIGHT = GeoEngine.getFloat("LowWeight", 0.5f); +- MEDIUM_WEIGHT = GeoEngine.getFloat("MediumWeight", 2); +- HIGH_WEIGHT = GeoEngine.getFloat("HighWeight", 3); +- DIAGONAL_WEIGHT = GeoEngine.getFloat("DiagonalWeight", 0.707f); +- COORD_SYNCHRONIZE = GeoEngine.getInt("CoordSynchronize", -1); ++ BASE_WEIGHT = GeoEngine.getInt("BaseWeight", 10); ++ DIAGONAL_WEIGHT = GeoEngine.getInt("DiagonalWeight", 14); ++ OBSTACLE_MULTIPLIER = GeoEngine.getInt("ObstacleMultiplier", 10); ++ HEURISTIC_WEIGHT = GeoEngine.getInt("HeuristicWeight", 20); ++ MAX_ITERATIONS = GeoEngine.getInt("MaxIterations", 3500); + CORRECT_PLAYER_Z = GeoEngine.getBoolean("CorrectPlayerZ", false); + + // Load AllowedPlayerRaces config file (if exists) +Index: java/org/l2jmobius/gameserver/geoengine/GeoEngine.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/GeoEngine.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/GeoEngine.java (working copy) +@@ -16,101 +16,91 @@ + */ + package org.l2jmobius.gameserver.geoengine; + +-import java.io.IOException; ++import java.io.File; + import java.io.RandomAccessFile; + import java.nio.ByteOrder; +-import java.nio.channels.FileChannel.MapMode; +-import java.nio.file.Files; +-import java.nio.file.Path; +-import java.util.concurrent.atomic.AtomicReferenceArray; +-import java.util.logging.Level; ++import java.nio.MappedByteBuffer; ++import java.nio.channels.FileChannel; ++import java.util.List; + 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.geoengine.geodata.Cell; +-import org.l2jmobius.gameserver.geoengine.geodata.IRegion; +-import org.l2jmobius.gameserver.geoengine.geodata.NullRegion; +-import org.l2jmobius.gameserver.geoengine.geodata.Region; ++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.GeoFormat; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoLocation; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; + import org.l2jmobius.gameserver.instancemanager.WarpedSpaceManager; + 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; ++import org.l2jmobius.gameserver.util.MathUtil; + + /** +- * @author -Nemesiss-, HorridoJoho ++ * @author Hasha + */ + public class GeoEngine + { +- private static final Logger LOGGER = Logger.getLogger(GeoEngine.class.getName()); ++ protected static final Logger LOGGER = Logger.getLogger(GeoEngine.class.getName()); + +- private static final int WORLD_MIN_X = -655360; +- private static final int WORLD_MIN_Y = -589824; +- private static final int WORLD_MIN_Z = -16384; ++ private final ABlock[][] _blocks; ++ private final BlockNull _nullBlock; + +- /** 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; +- +- /** The regions array */ +- private final AtomicReferenceArray _regions = new AtomicReferenceArray<>(GEO_REGIONS); +- +- 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; +- +- protected GeoEngine() ++ /** ++ * GeoEngine constructor. Loads all geodata files of chosen geodata format. ++ */ ++ public GeoEngine() + { + LOGGER.info("GeoEngine: Initializing..."); +- for (int i = 0; i < _regions.length(); i++) +- { +- _regions.set(i, NullRegion.INSTANCE); +- } + ++ // 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; +- try ++ long fileSize = 0; ++ for (int rx = World.TILE_X_MIN; rx <= World.TILE_X_MAX; rx++) + { +- for (int regionX = World.TILE_X_MIN; regionX <= World.TILE_X_MAX; regionX++) ++ for (int ry = World.TILE_Y_MIN; ry <= World.TILE_Y_MAX; ry++) + { +- for (int regionY = World.TILE_Y_MIN; regionY <= World.TILE_Y_MAX; regionY++) ++ final File f = new File(Config.GEODATA_PATH + String.format(GeoFormat.L2D.getFilename(), rx, ry)); ++ if (f.exists() && !f.isDirectory()) + { +- 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(rx, ry)) + { +- try (RandomAccessFile raf = new RandomAccessFile(geoFilePath.toFile(), "r")) +- { +- _regions.set((regionX * GEO_REGIONS_Y) + regionY, new Region(raf.getChannel().map(MapMode.READ_ONLY, 0, raf.length()).load().order(ByteOrder.LITTLE_ENDIAN))); +- loaded++; +- } ++ loaded++; ++ fileSize += f.length(); + } + } ++ else ++ { ++ // region file is not load-able, load null blocks ++ loadNullBlocks(rx, ry); ++ } + } + } +- catch (Exception e) ++ ++ LOGGER.info("GeoEngine: Loaded " + loaded + " geodata files."); ++ if (loaded > 0) + { +- LOGGER.log(Level.SEVERE, "GeoEngine: Failed to load geodata!", e); +- System.exit(1); ++ LOGGER.info("GeoEngine: Total geodata file size " + (fileSize / 1024 / 1024) + " MB."); + } + +- LOGGER.info("GeoEngine: Loaded " + loaded + " geodata files."); +- +- // Avoid wrong configs when no files are loaded. ++ // avoid wrong configs when no files are loaded + if (loaded == 0) + { + if (Config.PATHFINDING) +@@ -124,627 +114,832 @@ + LOGGER.info("GeoEngine: Forcing CoordSynchronize setting to -1."); + } + } ++ ++ // release multilayer block temporarily buffer ++ BlockMultilayer.release(); + } + + /** +- * @param geoX +- * @param geoY +- * @return the region ++ * 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 IRegion getRegion(int geoX, int geoY) ++ private final boolean loadGeoBlocks(int regionX, int regionY) + { +- final int region = ((geoX / IRegion.REGION_CELLS_X) * GEO_REGIONS_Y) + (geoY / IRegion.REGION_CELLS_Y); +- if ((region < 0) || (region >= _regions.length())) ++ final String filename = String.format(GeoFormat.L2D.getFilename(), regionX, regionY); ++ ++ // standard load ++ try (RandomAccessFile raf = new RandomAccessFile(Config.GEODATA_PATH + filename, "r"); ++ FileChannel fc = raf.getChannel()) + { +- return null; ++ // initialize file buffer ++ final MappedByteBuffer buffer = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()).load(); ++ buffer.order(ByteOrder.LITTLE_ENDIAN); ++ ++ // 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++) ++ { ++ // get block type ++ final byte type = buffer.get(); ++ ++ // load block according to block type ++ switch (type) ++ { ++ case GeoStructure.TYPE_FLAT_L2D: ++ { ++ _blocks[blockX + ix][blockY + iy] = new BlockFlat(buffer, GeoFormat.L2D); ++ break; ++ } ++ case GeoStructure.TYPE_COMPLEX_L2D: ++ { ++ _blocks[blockX + ix][blockY + iy] = new BlockComplex(buffer, GeoFormat.L2D); ++ break; ++ } ++ case GeoStructure.TYPE_MULTILAYER_L2D: ++ { ++ _blocks[blockX + ix][blockY + iy] = new BlockMultilayer(buffer, GeoFormat.L2D); ++ break; ++ } ++ default: ++ { ++ throw new IllegalArgumentException("Unknown block type: " + type); ++ } ++ } ++ } ++ } ++ ++ // check data consistency ++ if (buffer.remaining() > 0) ++ { ++ LOGGER.warning("GeoEngine: Region file " + filename + " can be corrupted, remaining " + buffer.remaining() + " bytes to read."); ++ } ++ ++ // loading was successful ++ return true; + } +- return _regions.get(region); +- } +- +- /** +- * @param filePath +- * @param regionX +- * @param regionY +- * @throws IOException +- */ +- 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")) ++ catch (Exception e) + { +- _regions.set(regionOffset, new Region(raf.getChannel().map(MapMode.READ_ONLY, 0, raf.length()).load().order(ByteOrder.LITTLE_ENDIAN))); ++ // an error occured while loading, load null blocks ++ LOGGER.warning("GeoEngine: Error while loading " + filename + " region file."); ++ LOGGER.warning(e.getMessage()); ++ ++ // replace whole region file with null blocks ++ loadNullBlocks(regionX, regionY); ++ ++ // loading was not successful ++ return false; + } + } + + /** +- * @param regionX +- * @param regionY ++ * 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. + */ +- public void unloadRegion(int regionX, int regionY) ++ private final void loadNullBlocks(int regionX, int regionY) + { +- _regions.set((regionX * GEO_REGIONS_Y) + regionY, NullRegion.INSTANCE); +- } +- +- /** +- * @param geoX +- * @param geoY +- * @return if geodata exist +- */ +- public boolean hasGeoPos(int geoX, int geoY) +- { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) ++ // 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++) + { +- return false; ++ for (int iy = 0; iy < GeoStructure.REGION_BLOCKS_Y; iy++) ++ { ++ _blocks[blockX + ix][blockY + iy] = _nullBlock; ++ } + } +- return region.hasGeo(); + } + ++ // GEODATA - GENERAL ++ + /** +- * Checks the specified position for available geodata. +- * @param x the world x +- * @param y the world y +- * @return {@code true} if there is geodata for the given coordinates, {@code false} otherwise ++ * Converts world X to geodata X. ++ * @param worldX ++ * @return int : Geo X + */ +- public boolean hasGeo(int x, int y) ++ public static int getGeoX(int worldX) + { +- return hasGeoPos(getGeoX(x), getGeoY(y)); ++ return (MathUtil.limit(worldX, World.MAP_MIN_X, World.MAP_MAX_X) - World.MAP_MIN_X) >> 4; + } + + /** +- * @param geoX +- * @param geoY +- * @param worldZ +- * @param nswe +- * @return the nearest nswe check ++ * Converts world Y to geodata Y. ++ * @param worldY ++ * @return int : Geo Y + */ +- public boolean checkNearestNswe(int geoX, int geoY, int worldZ, int nswe) ++ public static int getGeoY(int worldY) + { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) +- { +- return true; +- } +- return region.checkNearestNswe(geoX, geoY, worldZ, nswe); ++ return (MathUtil.limit(worldY, World.MAP_MIN_Y, World.MAP_MAX_Y) - World.MAP_MIN_Y) >> 4; + } + + /** ++ * Converts geodata X to world X. + * @param geoX +- * @param geoY +- * @param worldZ +- * @param nswe +- * @return the nearest nswe anti-corner cut ++ * @return int : World X + */ +- public boolean checkNearestNsweAntiCornerCut(int geoX, int geoY, int worldZ, int nswe) ++ public static int getWorldX(int geoX) + { +- boolean can = true; +- if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) +- { +- can = checkNearestNswe(geoX, geoY - 1, worldZ, Cell.NSWE_EAST) && checkNearestNswe(geoX + 1, geoY, worldZ, Cell.NSWE_NORTH); +- } +- if (can && ((nswe & Cell.NSWE_NORTH_WEST) == Cell.NSWE_NORTH_WEST)) +- { +- 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 = 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 = checkNearestNswe(geoX, geoY + 1, worldZ, Cell.NSWE_WEST) && checkNearestNswe(geoX - 1, geoY, worldZ, Cell.NSWE_SOUTH); +- } +- return can && checkNearestNswe(geoX, geoY, worldZ, nswe); ++ return (MathUtil.limit(geoX, 0, GeoStructure.GEO_CELLS_X) << 4) + World.MAP_MIN_X + 8; + } + + /** +- * @param geoX ++ * Converts geodata Y to world Y. + * @param geoY +- * @param worldZ +- * @return the nearest Z value ++ * @return int : World Y + */ +- public int getNearestZ(int geoX, int geoY, int worldZ) ++ public static int getWorldY(int geoY) + { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) +- { +- return worldZ; +- } +- return region.getNearestZ(geoX, geoY, worldZ); ++ return (MathUtil.limit(geoY, 0, GeoStructure.GEO_CELLS_Y) << 4) + World.MAP_MIN_Y + 8; + } + + /** +- * @param geoX +- * @param geoY +- * @param worldZ +- * @return the next lower Z value ++ * Returns block of geodata on given coordinates. ++ * @param geoX : Geodata X ++ * @param geoY : Geodata Y ++ * @return {@link ABlock} : Block of geodata. + */ +- public int getNextLowerZ(int geoX, int geoY, int worldZ) ++ private final ABlock getBlock(int geoX, int geoY) + { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) ++ final int x = geoX / GeoStructure.BLOCK_CELLS_X; ++ final int y = geoY / GeoStructure.BLOCK_CELLS_Y; ++ ++ // if x or y is out of array return null ++ if ((x > -1) && (y > -1) && (x < GeoStructure.GEO_BLOCKS_X) && (y < GeoStructure.GEO_BLOCKS_Y)) + { +- return worldZ; ++ return _blocks[x][y]; + } +- return region.getNextLowerZ(geoX, geoY, worldZ); ++ return null; + } + + /** +- * @param geoX +- * @param geoY +- * @param worldZ +- * @return the next higher Z value ++ * Check if geo coordinates has geo. ++ * @param geoX : Geodata X ++ * @param geoY : Geodata Y ++ * @return boolean : True, if given geo coordinates have geodata + */ +- public int getNextHigherZ(int geoX, int geoY, int worldZ) ++ public boolean hasGeoPos(int geoX, int geoY) + { +- final IRegion region = getRegion(geoX, geoY); +- if (region == null) ++ final ABlock block = getBlock(geoX, geoY); ++ if (block == null) // null block check + { +- return worldZ; ++ // TODO: Find when this can be null. (Bad geodata? Check World getRegion method.) ++ // LOGGER.warning("Could not find geodata block at " + getWorldX(geoX) + ", " + getWorldY(geoY) + "."); ++ return false; + } +- return region.getNextHigherZ(geoX, geoY, worldZ); ++ return block.hasGeoPos(); + } + + /** +- * Gets the Z height. +- * @param x the world x +- * @param y the world y +- * @param z the world z +- * @return the nearest Z height ++ * 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 int getHeight(int x, int y, int z) ++ public short getHeightNearest(int geoX, int geoY, int worldZ) + { +- return getNearestZ(getGeoX(x), getGeoY(y), z); ++ final ABlock block = getBlock(geoX, geoY); ++ return block != null ? block.getHeightNearest(geoX, geoY, worldZ) : (short) worldZ; + } + + /** +- * Gets the next lower Z height. +- * @param x the world x +- * @param y the world y +- * @param z the world z +- * @return the nearest Z height ++ * 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 int getLowerHeight(int x, int y, int z) ++ public byte getNsweNearest(int geoX, int geoY, int worldZ) + { +- return getNextLowerZ(getGeoX(x), getGeoY(y), z); ++ final ABlock block = getBlock(geoX, geoY); ++ return block != null ? block.getNsweNearest(geoX, geoY, worldZ) : (byte) 0xFF; + } + + /** +- * Gets the next higher Z height. +- * @param x the world x +- * @param y the world y +- * @param z the world z +- * @return the nearest Z height ++ * Check if world coordinates has geo. ++ * @param worldX : World X ++ * @param worldY : World Y ++ * @return boolean : True, if given world coordinates have geodata + */ +- public int getHigherHeight(int x, int y, int z) ++ public boolean hasGeo(int worldX, int worldY) + { +- return getNextHigherZ(getGeoX(x), getGeoY(y), z); ++ return hasGeoPos(getGeoX(worldX), getGeoY(worldY)); + } + + /** +- * @param worldX +- * @return the geo X ++ * 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 int getGeoX(int worldX) ++ public short getHeight(int worldX, int worldY, int worldZ) + { +- return (worldX - WORLD_MIN_X) / 16; ++ return getHeightNearest(getGeoX(worldX), getGeoY(worldY), worldZ); + } + +- /** +- * @param worldY +- * @return the geo Y +- */ +- public int getGeoY(int worldY) +- { +- return (worldY - WORLD_MIN_Y) / 16; +- } ++ // PATHFINDING + + /** +- * @param worldZ +- * @return the geo Z ++ * Check line of sight from {@link WorldObject} to {@link WorldObject}. ++ * @param origin : The origin object. ++ * @param target : The target object. ++ * @return {@code boolean} : True if origin can see target + */ +- public int getGeoZ(int worldZ) ++ public boolean canSeeTarget(WorldObject origin, WorldObject target) + { +- return (worldZ - WORLD_MIN_Z) / 16; +- } +- +- /** +- * @param geoX +- * @return the world X +- */ +- public int getWorldX(int geoX) +- { +- return (geoX * 16) + WORLD_MIN_X + 8; +- } +- +- /** +- * @param geoY +- * @return the world Y +- */ +- public int getWorldY(int geoY) +- { +- return (geoY * 16) + WORLD_MIN_Y + 8; +- } +- +- /** +- * @param geoZ +- * @return the world Z +- */ +- public int getWorldZ(int geoZ) +- { +- return (geoZ * 16) + WORLD_MIN_Z + 8; +- } +- +- /** +- * 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) +- { +- if (target.isDoor()) ++ if (target.isDoor() || target.isArtefact() || (target.isCreature() && ((Creature) target).isFlying())) + { +- // Can always see doors. + return true; + } +- return 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(), cha.getInstanceWorld(), worldPosition.getX(), worldPosition.getY(), worldPosition.getZ()); +- } +- +- /** +- * Can see target. Checks doors between. +- * @param x the x coordinate +- * @param y the y coordinate +- * @param z the z coordinate +- * @param world +- * @param tx the target's x coordinate +- * @param ty the target's y coordinate +- * @param tz the target's z coordinate +- * @param tworld the target's instanceId +- * @return +- */ +- public boolean canSeeTarget(int x, int y, int z, Instance world, int tx, int ty, int tz, Instance tworld) +- { +- return (world != tworld) ? false : canSeeTarget(x, y, z, world, tx, ty, tz); +- } +- +- /** +- * 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 +- * @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, Instance instance, int tx, int ty, int tz) +- { +- if (DoorData.getInstance().checkIfDoorsBetween(x, y, z, tx, ty, tz, instance, true)) ++ ++ // get origin and target world coordinates ++ final int ox = origin.getX(); ++ final int oy = origin.getY(); ++ final int oz = origin.getZ(); ++ final int tx = target.getX(); ++ final int ty = target.getY(); ++ final int tz = target.getZ(); ++ ++ if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld(), true)) + { + return false; + } +- return canSeeTarget(x, y, z, tx, ty, tz); +- } +- +- /** +- * @param prevX +- * @param prevY +- * @param prevGeoZ +- * @param curX +- * @param curY +- * @param nswe +- * @return the LOS Z value +- */ +- 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))) ++ if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld())) + { +- throw new RuntimeException("Multiple directions!"); ++ return false; + } ++ if (WarpedSpaceManager.getInstance().checkForWarpedSpace(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld())) ++ { ++ return false; ++ } + +- if (checkNearestNsweAntiCornerCut(prevX, prevY, prevGeoZ, nswe)) ++ // get origin and check existing geo coordinates ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) + { +- return getNearestZ(curX, curY, prevGeoZ); ++ return true; + } + +- return getNextHigherZ(curX, curY, prevGeoZ); ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coordinates ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) ++ { ++ return true; ++ } ++ ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // origin and target coordinates are same ++ if ((gox == gtx) && (goy == gty)) ++ { ++ return goz == gtz; ++ } ++ ++ // get origin and target height, real height = collision height * 2 ++ double oheight = 0; ++ if (origin.isCreature()) ++ { ++ oheight = ((Creature) origin).getCollisionHeight() * 2; ++ } ++ ++ double theight = 0; ++ if (target.isCreature()) ++ { ++ theight = ((Creature) target).getCollisionHeight() * 2; ++ } ++ ++ // perform geodata check ++ return checkSee(gox, goy, goz, oheight, gtx, gty, gtz, theight, origin.getInstanceWorld()); + } + + /** +- * Can see target. Does not check doors between. +- * @param xValue the x coordinate +- * @param yValue the y coordinate +- * @param zValue the z coordinate +- * @param txValue the target's x coordinate +- * @param tyValue the target's y coordinate +- * @param tzValue the target's z coordinate +- * @return {@code true} if there is line of sight between the given coordinate sets, {@code false} otherwise ++ * Check line of sight from {@link WorldObject} to {@link Location}. ++ * @param origin : The origin object. ++ * @param position : The target position. ++ * @return {@code boolean} : True if object can see position + */ +- public boolean canSeeTarget(int xValue, int yValue, int zValue, int txValue, int tyValue, int tzValue) ++ public boolean canSeeTarget(WorldObject origin, Location position) + { +- int x = xValue; +- int y = yValue; +- int tx = txValue; +- int ty = tyValue; ++ // get origin and target world coordinates ++ final int ox = origin.getX(); ++ final int oy = origin.getY(); ++ final int oz = origin.getZ(); ++ final int tx = position.getX(); ++ final int ty = position.getY(); ++ final int tz = position.getZ(); + +- int geoX = getGeoX(x); +- int geoY = getGeoY(y); +- int tGeoX = getGeoX(tx); +- int tGeoY = getGeoY(ty); ++ if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld(), true)) ++ { ++ return false; ++ } ++ if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld())) ++ { ++ return false; ++ } ++ if (WarpedSpaceManager.getInstance().checkForWarpedSpace(ox, oy, oz, tx, ty, tz, origin.getInstanceWorld())) ++ { ++ return false; ++ } + +- int z = getNearestZ(geoX, geoY, zValue); +- int tz = getNearestZ(tGeoX, tGeoY, tzValue); ++ // get origin and check existing geo coordinates ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) ++ { ++ return true; ++ } + +- // Fastpath. +- if ((geoX == tGeoX) && (geoY == tGeoY)) ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coordinates ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) + { +- if (hasGeoPos(tGeoX, tGeoY)) +- { +- return z == tz; +- } + return true; + } + +- if (tz > z) ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // origin and target coordinates are same ++ if ((gox == gtx) && (goy == gty)) + { +- int tmp = tx; +- tx = x; +- x = tmp; +- +- tmp = ty; +- ty = y; +- y = tmp; +- +- tmp = tz; +- tz = z; +- z = tmp; +- +- tmp = tGeoX; +- tGeoX = geoX; +- geoX = tmp; +- +- tmp = tGeoY; +- tGeoY = geoY; +- geoY = tmp; ++ return goz == gtz; + } + +- final LinePointIterator3D pointIter = new LinePointIterator3D(geoX, geoY, z, tGeoX, tGeoY, tz); +- // 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()) ++ // get origin and target height, real height = collision height * 2 ++ double oheight = 0; ++ if (origin.isCreature()) + { +- final int curX = pointIter.x(); +- final int curY = pointIter.y(); ++ oheight = ((Creature) origin).getTemplate().getCollisionHeight(); ++ } ++ ++ // perform geodata check ++ return checkSee(gox, goy, goz, oheight, gtx, gty, gtz, 0, origin.getInstanceWorld()); ++ } ++ ++ /** ++ * Simple check for origin to target visibility. ++ * @param goxValue : origin X geodata coordinate ++ * @param goyValue : origin Y geodata coordinate ++ * @param gozValue : origin Z geodata coordinate ++ * @param oheight : origin height (if instance of {@link Character}) ++ * @param gtxValue : target X geodata coordinate ++ * @param gtyValue : target Y geodata coordinate ++ * @param gtzValue : target Z geodata coordinate ++ * @param theight : target height (if instance of {@link Character}) ++ * @param instance ++ * @return {@code boolean} : True, when target can be seen. ++ */ ++ private final boolean checkSee(int goxValue, int goyValue, int gozValue, double oheight, int gtxValue, int gtyValue, int gtzValue, double theight, Instance instance) ++ { ++ int goz = gozValue; ++ int gtz = gtzValue; ++ int gox = goxValue; ++ int goy = goyValue; ++ int gtx = gtxValue; ++ int gty = gtyValue; ++ ++ // get line of sight Z coordinates ++ double losoz = goz + ((oheight * Config.PART_OF_CHARACTER_HEIGHT) / 100); ++ double lostz = gtz + ((theight * Config.PART_OF_CHARACTER_HEIGHT) / 100); ++ ++ // get X delta and signum ++ final int dx = Math.abs(gtx - gox); ++ final int sx = gox < gtx ? 1 : -1; ++ final byte dirox = sx > 0 ? GeoStructure.CELL_FLAG_E : GeoStructure.CELL_FLAG_W; ++ final byte dirtx = sx > 0 ? GeoStructure.CELL_FLAG_W : GeoStructure.CELL_FLAG_E; ++ ++ // get Y delta and signum ++ final int dy = Math.abs(gty - goy); ++ final int sy = goy < gty ? 1 : -1; ++ final byte diroy = sy > 0 ? GeoStructure.CELL_FLAG_S : GeoStructure.CELL_FLAG_N; ++ final byte dirty = sy > 0 ? GeoStructure.CELL_FLAG_N : GeoStructure.CELL_FLAG_S; ++ ++ // get Z delta ++ final int dm = Math.max(dx, dy); ++ final double dz = (lostz - losoz) / dm; ++ ++ // get direction flag for diagonal movement ++ final byte diroxy = getDirXY(dirox, diroy); ++ final byte dirtxy = getDirXY(dirtx, dirty); ++ ++ // delta, determines axis to move on (+..X axis, -..Y axis) ++ int d = dx - dy; ++ ++ // NSWE direction of movement ++ byte diro; ++ byte dirt; ++ ++ // initialize node values ++ int nox = gox; ++ int noy = goy; ++ int ntx = gtx; ++ int nty = gty; ++ byte nsweo = getNsweNearest(gox, goy, goz); ++ byte nswet = getNsweNearest(gtx, gty, gtz); ++ ++ // loop ++ ABlock block; ++ int index; ++ for (int i = 0; i < ((dm + 1) / 2); i++) ++ { ++ // reset direction flag ++ diro = 0; ++ dirt = 0; + +- if ((curX == prevX) && (curY == prevY)) ++ // calculate next point coordinates ++ final int e2 = 2 * d; ++ if ((e2 > -dy) && (e2 < dx)) + { +- continue; ++ // calculate next point XY coordinates ++ d -= dy; ++ d += dx; ++ nox += sx; ++ ntx -= sx; ++ noy += sy; ++ nty -= sy; ++ diro |= diroxy; ++ dirt |= dirtxy; + } ++ else if (e2 > -dy) ++ { ++ // calculate next point X coordinate ++ d -= dy; ++ nox += sx; ++ ntx -= sx; ++ diro |= dirox; ++ dirt |= dirtx; ++ } ++ else if (e2 < dx) ++ { ++ // calculate next point Y coordinate ++ d += dx; ++ noy += sy; ++ nty -= sy; ++ diro |= diroy; ++ dirt |= dirty; ++ } + +- final int beeCurZ = pointIter.z(); +- int curGeoZ = prevGeoZ; +- +- // Check if the position has geodata. +- if (hasGeoPos(curX, curY)) + { +- final int beeCurGeoZ = getNearestZ(curX, curY, beeCurZ); +- final int nswe = GeoUtils.computeNswe(prevX, prevY, curX, curY); // .computeDirection(prevX, prevY, curX, curY); +- curGeoZ = getLosGeoZ(prevX, prevY, prevGeoZ, curX, curY, nswe); +- int maxHeight; +- if (ptIndex < ELEVATED_SEE_OVER_DISTANCE) ++ // get block of the next cell ++ block = getBlock(nox, noy); ++ ++ // get index of particular layer, based on movement conditions ++ if ((nsweo & diro) == 0) + { +- maxHeight = z + MAX_SEE_OVER_HEIGHT; ++ index = block.getIndexAbove(nox, noy, goz - GeoStructure.CELL_IGNORE_HEIGHT); + } + else + { +- maxHeight = beeCurZ + MAX_SEE_OVER_HEIGHT; ++ index = block.getIndexBelow(nox, noy, goz + GeoStructure.CELL_IGNORE_HEIGHT); + } + +- boolean canSeeThrough = false; +- if ((curGeoZ <= maxHeight) && (curGeoZ <= beeCurGeoZ)) ++ // layer does not exist, return ++ if (index == -1) + { +- if ((nswe & Cell.NSWE_NORTH_EAST) == Cell.NSWE_NORTH_EAST) +- { +- 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 +- { +- canSeeThrough = true; +- } ++ return false; + } + +- if (!canSeeThrough) ++ // get layer and next line of sight Z coordinate ++ goz = block.getHeight(index); ++ losoz += dz; ++ ++ // perform line of sight check, return when fails ++ if ((goz - losoz) > Config.MAX_OBSTACLE_HEIGHT) + { + return false; + } ++ ++ // get layer nswe ++ nsweo = block.getNswe(index); + } ++ { ++ // get block of the next cell ++ block = getBlock(ntx, nty); ++ ++ // get index of particular layer, based on movement conditions ++ if ((nswet & dirt) == 0) ++ { ++ index = block.getIndexAbove(ntx, nty, gtz - GeoStructure.CELL_IGNORE_HEIGHT); ++ } ++ else ++ { ++ index = block.getIndexBelow(ntx, nty, gtz + GeoStructure.CELL_IGNORE_HEIGHT); ++ } ++ ++ // layer does not exist, return ++ if (index == -1) ++ { ++ return false; ++ } ++ ++ // get layer and next line of sight Z coordinate ++ gtz = block.getHeight(index); ++ lostz -= dz; ++ ++ // perform line of sight check, return when fails ++ if ((gtz - lostz) > Config.MAX_OBSTACLE_HEIGHT) ++ { ++ return false; ++ } ++ ++ // get layer nswe ++ nswet = block.getNswe(index); ++ } + +- prevX = curX; +- prevY = curY; +- prevGeoZ = curGeoZ; +- ++ptIndex; ++ // update coords ++ gox = nox; ++ goy = noy; ++ gtx = ntx; ++ gty = nty; + } + +- return true; ++ // when iteration is completed, compare final Z coordinates ++ return Math.abs(goz - gtz) < (GeoStructure.CELL_HEIGHT * 4); + } + + /** +- * Move check. +- * @param x the x coordinate +- * @param y the y coordinate +- * @param zValue the z coordinate +- * @param tx the target's x coordinate +- * @param ty the target's y coordinate +- * @param tzValue the target's z coordinate +- * @param instance the instance +- * @return the last Location (x,y,z) where player can walk - just before wall ++ * Check movement from coordinates to 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 {code boolean} : True if target coordinates are reachable from origin coordinates + */ +- public Location canMoveToTargetLoc(int x, int y, int zValue, int tx, int ty, int tzValue, Instance instance) ++ public boolean canMoveToTarget(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + { +- final int geoX = getGeoX(x); +- final int geoY = getGeoY(y); +- final int z = getNearestZ(geoX, geoY, zValue); +- final int tGeoX = getGeoX(tx); +- final int tGeoY = getGeoY(ty); +- final int tz = getNearestZ(tGeoX, tGeoY, tzValue); ++ // get origin and check existing geo coordinates ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) ++ { ++ return true; ++ } + +- if (DoorData.getInstance().checkIfDoorsBetween(x, y, z, tx, ty, tz, instance, false)) ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coordinates ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) + { +- return new Location(x, y, getHeight(x, y, z)); ++ return true; + } +- if (FenceData.getInstance().checkIfFenceBetween(x, y, z, tx, ty, tz, instance)) ++ ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // target coordinates reached ++ if ((gox == gtx) && (goy == gty) && (goz == gtz)) + { +- return new Location(x, y, getHeight(x, y, z)); ++ return true; + } +- if (WarpedSpaceManager.getInstance().checkForWarpedSpace(x, y, z, tx, ty, tz, instance)) ++ ++ // perform geodata check ++ final GeoLocation loc = checkMove(gox, goy, goz, gtx, gty, gtz, instance); ++ return (loc.getGeoX() == gtx) && (loc.getGeoY() == gty); ++ } ++ ++ /** ++ * Check movement from origin to target. Returns last available point in the checked path. ++ * @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 {@link Location} : Last point where object can walk (just before wall) ++ */ ++ public Location canMoveToTargetLoc(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) ++ { ++ // Mobius: Double check for doors before normal checkMove to avoid exploiting key movement. ++ if (DoorData.getInstance().checkIfDoorsBetween(ox, oy, oz, tx, ty, tz, instance, false)) + { +- return new Location(x, y, getHeight(x, y, z)); ++ return new Location(ox, oy, oz); + } ++ if (FenceData.getInstance().checkIfFenceBetween(ox, oy, oz, tx, ty, tz, instance)) ++ { ++ return new Location(ox, oy, oz); ++ } + +- 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 = z; ++ // get origin and check existing geo coordinates ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) ++ { ++ return new Location(tx, ty, tz); ++ } + +- while (pointIter.next()) ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coordinates ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) + { +- final int curX = pointIter.x(); +- final int curY = pointIter.y(); +- final int curZ = getNearestZ(curX, curY, prevZ); +- +- if (hasGeoPos(prevX, prevY)) +- { +- final int nswe = GeoUtils.computeNswe(prevX, prevY, curX, curY); +- if (!checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, nswe)) +- { +- // Can't move, return previous location. +- return new Location(getWorldX(prevX), getWorldY(prevY), prevZ); +- } +- } +- +- prevX = curX; +- prevY = curY; +- prevZ = curZ; ++ return new Location(tx, ty, tz); + } + +- if (hasGeoPos(prevX, prevY) && (prevZ != tz)) ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // target coordinates reached ++ if ((gox == gtx) && (goy == gty) && (goz == gtz)) + { +- // Different floors, return start location. +- return new Location(x, y, z); ++ return new Location(tx, ty, tz); + } + +- return new Location(tx, ty, tz); ++ // perform geodata check ++ return checkMove(gox, goy, goz, gtx, gty, gtz, instance); + } + + /** +- * 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 fromZvalue 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 toZvalue 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 ++ * With this method you can check if a position is visible or can be reached by beeline movement.
++ * 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 gox : origin X geodata coordinate ++ * @param goy : origin Y geodata coordinate ++ * @param goz : origin Z geodata coordinate ++ * @param gtx : target X geodata coordinate ++ * @param gty : target Y geodata coordinate ++ * @param gtz : target Z geodata coordinate ++ * @param instance ++ * @return {@link GeoLocation} : The last allowed point of movement. + */ +- public boolean canMoveToTarget(int fromX, int fromY, int fromZvalue, int toX, int toY, int toZvalue, Instance instance) ++ protected final GeoLocation checkMove(int gox, int goy, int goz, int gtx, int gty, int gtz, Instance instance) + { +- final int geoX = getGeoX(fromX); +- final int geoY = getGeoY(fromY); +- final int fromZ = getNearestZ(geoX, geoY, fromZvalue); +- final int tGeoX = getGeoX(toX); +- final int tGeoY = getGeoY(toY); +- final int toZ = getNearestZ(tGeoX, tGeoY, toZvalue); +- +- if (DoorData.getInstance().checkIfDoorsBetween(fromX, fromY, fromZ, toX, toY, toZ, instance, false)) ++ if (DoorData.getInstance().checkIfDoorsBetween(gox, goy, goz, gtx, gty, gtz, instance, false)) + { +- return false; ++ return new GeoLocation(gox, goy, goz); + } +- if (FenceData.getInstance().checkIfFenceBetween(fromX, fromY, fromZ, toX, toY, toZ, instance)) ++ if (FenceData.getInstance().checkIfFenceBetween(gox, goy, goz, gtx, gty, gtz, instance)) + { +- return false; ++ return new GeoLocation(gox, goy, goz); + } +- if (WarpedSpaceManager.getInstance().checkForWarpedSpace(fromX, fromY, fromZ, toX, toY, toZ, instance)) ++ if (WarpedSpaceManager.getInstance().checkForWarpedSpace(gox, goy, goz, gtx, gty, gtz, instance)) + { +- return false; ++ return new GeoLocation(gox, goy, goz); + } + +- 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 = fromZ; ++ // get X delta, signum and direction flag ++ final int dx = Math.abs(gtx - gox); ++ final int sx = gox < gtx ? 1 : -1; ++ final byte dirX = sx > 0 ? GeoStructure.CELL_FLAG_E : GeoStructure.CELL_FLAG_W; + +- while (pointIter.next()) ++ // get Y delta, signum and direction flag ++ final int dy = Math.abs(gty - goy); ++ final int sy = goy < gty ? 1 : -1; ++ final byte dirY = sy > 0 ? GeoStructure.CELL_FLAG_S : GeoStructure.CELL_FLAG_N; ++ ++ // get direction flag for diagonal movement ++ final byte dirXY = getDirXY(dirX, dirY); ++ ++ // delta, determines axis to move on (+..X axis, -..Y axis) ++ int d = dx - dy; ++ ++ // NSWE direction of movement ++ byte direction; ++ ++ // load pointer coordinates ++ int gpx = gox; ++ int gpy = goy; ++ int gpz = goz; ++ ++ // load next pointer ++ int nx = gpx; ++ int ny = gpy; ++ ++ // loop ++ int count = 0; ++ while (count++ < Config.MAX_ITERATIONS) + { +- final int curX = pointIter.x(); +- final int curY = pointIter.y(); +- final int curZ = getNearestZ(curX, curY, prevZ); ++ direction = 0; + +- if (hasGeoPos(prevX, prevY)) ++ // calculate next point coordinates ++ final int e2 = 2 * d; ++ if ((e2 > -dy) && (e2 < dx)) + { +- final int nswe = GeoUtils.computeNswe(prevX, prevY, curX, curY); +- if (!checkNearestNsweAntiCornerCut(prevX, prevY, prevZ, nswe)) ++ d -= dy; ++ d += dx; ++ nx += sx; ++ ny += sy; ++ direction |= dirXY; ++ } ++ else if (e2 > -dy) ++ { ++ d -= dy; ++ nx += sx; ++ direction |= dirX; ++ } ++ else if (e2 < dx) ++ { ++ d += dx; ++ ny += sy; ++ direction |= dirY; ++ } ++ ++ // obstacle found, return ++ if ((getNsweNearest(gpx, gpy, gpz) & direction) == 0) ++ { ++ return new GeoLocation(gpx, gpy, gpz); ++ } ++ ++ // update pointer coordinates ++ gpx = nx; ++ gpy = ny; ++ gpz = getHeightNearest(nx, ny, gpz); ++ ++ // target coordinates reached ++ if ((gpx == gtx) && (gpy == gty)) ++ { ++ if (gpz == gtz) + { +- return false; ++ // path found, Z coordinates are okay, return target point ++ return new GeoLocation(gtx, gty, gtz); + } ++ ++ // path found, Z coordinates are not okay, return last good point ++ return new GeoLocation(gpx, gpy, gpz); + } ++ } ++ ++ return new GeoLocation(gox, goy, goz); ++ } ++ ++ /** ++ * Returns diagonal NSWE flag format of combined two NSWE flags. ++ * @param dirX : X direction NSWE flag ++ * @param dirY : Y direction NSWE flag ++ * @return byte : NSWE flag of combined direction ++ */ ++ private static byte getDirXY(byte dirX, byte dirY) ++ { ++ // check axis directions ++ if (dirY == GeoStructure.CELL_FLAG_N) ++ { ++ if (dirX == GeoStructure.CELL_FLAG_W) ++ { ++ return GeoStructure.CELL_FLAG_NW; ++ } + +- prevX = curX; +- prevY = curY; +- prevZ = curZ; ++ return GeoStructure.CELL_FLAG_NE; + } + +- if (hasGeoPos(prevX, prevY) && (prevZ != toZ)) ++ if (dirX == GeoStructure.CELL_FLAG_W) + { +- // Different floors. +- return false; ++ return GeoStructure.CELL_FLAG_SW; + } + +- return true; ++ return GeoStructure.CELL_FLAG_SE; + } + ++ /** ++ * 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 ++ */ ++ public List findPath(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) ++ { ++ return null; ++ } ++ ++ /** ++ * Returns the instance of the {@link GeoEngine}. ++ * @return {@link GeoEngine} : The instance. ++ */ + public static GeoEngine getInstance() + { + return SingletonHolder.INSTANCE; +@@ -752,6 +947,6 @@ + + private static class SingletonHolder + { +- protected static final GeoEngine INSTANCE = new GeoEngine(); ++ protected static final GeoEngine INSTANCE = Config.PATHFINDING ? new GeoEnginePathfinding() : new GeoEngine(); + } +-} ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/GeoEnginePathfinding.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/GeoEnginePathfinding.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/GeoEnginePathfinding.java (working copy) +@@ -20,88 +20,84 @@ + import java.util.LinkedList; + import java.util.List; + import java.util.ListIterator; +-import java.util.logging.Level; +-import java.util.logging.Logger; + + import org.l2jmobius.Config; +-import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNode; +-import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; +-import org.l2jmobius.gameserver.geoengine.pathfinding.CellNode; +-import org.l2jmobius.gameserver.geoengine.pathfinding.CellNodeBuffer; +-import org.l2jmobius.gameserver.geoengine.pathfinding.NodeLoc; +-import org.l2jmobius.gameserver.model.World; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoLocation; ++import org.l2jmobius.gameserver.geoengine.pathfinding.Node; ++import org.l2jmobius.gameserver.geoengine.pathfinding.NodeBuffer; ++import org.l2jmobius.gameserver.model.Location; + import org.l2jmobius.gameserver.model.instancezone.Instance; + + /** +- * @author -Nemesiss- ++ * @author Hasha + */ +-public class GeoEnginePathfinding ++final class GeoEnginePathfinding extends GeoEngine + { +- private static final Logger LOGGER = Logger.getLogger(GeoEnginePathfinding.class.getName()); ++ // pre-allocated buffers ++ private final BufferHolder[] _buffers; + +- private BufferInfo[] _buffers; +- + protected GeoEnginePathfinding() + { +- try ++ super(); ++ ++ final String[] array = Config.PATHFIND_BUFFERS.split(";"); ++ _buffers = new BufferHolder[array.length]; ++ int count = 0; ++ for (int i = 0; i < array.length; i++) + { +- final String[] array = Config.PATHFIND_BUFFERS.split(";"); ++ final String buf = array[i]; ++ final String[] args = buf.split("x"); + +- _buffers = new BufferInfo[array.length]; +- +- String buf; +- String[] args; +- for (int i = 0; i < array.length; i++) ++ try + { +- buf = array[i]; +- args = buf.split("x"); +- if (args.length != 2) +- { +- throw new Exception("Invalid buffer definition: " + buf); +- } +- +- _buffers[i] = new BufferInfo(Integer.parseInt(args[0]), Integer.parseInt(args[1])); ++ final int size = Integer.parseInt(args[1]); ++ count += size; ++ _buffers[i] = new BufferHolder(Integer.parseInt(args[0]), size); + } ++ catch (Exception e) ++ { ++ LOGGER.warning("GeoEnginePathfinding: Can not load buffer setting: " + buf); ++ } + } +- catch (Exception e) +- { +- LOGGER.log(Level.WARNING, "CellPathFinding: Problem during buffer init: " + e.getMessage(), e); +- throw new Error("CellPathFinding: load aborted"); +- } ++ ++ LOGGER.info("GeoEnginePathfinding: Loaded " + count + " node buffers."); + } + +- public boolean pathNodesExist(short regionoffset) ++ @Override ++ public List findPath(int ox, int oy, int oz, int tx, int ty, int tz, Instance instance) + { +- return false; +- } +- +- public List findPath(int x, int y, int z, int tx, int ty, int tz, Instance instance) +- { +- final int gx = GeoEngine.getInstance().getGeoX(x); +- final int gy = GeoEngine.getInstance().getGeoY(y); +- if (!GeoEngine.getInstance().hasGeo(x, y)) ++ // get origin and check existing geo coords ++ final int gox = getGeoX(ox); ++ final int goy = getGeoY(oy); ++ if (!hasGeoPos(gox, goy)) + { + 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)) ++ ++ final short goz = getHeightNearest(gox, goy, oz); ++ ++ // get target and check existing geo coords ++ final int gtx = getGeoX(tx); ++ final int gty = getGeoY(ty); ++ if (!hasGeoPos(gtx, gty)) + { + 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)))); ++ ++ final short gtz = getHeightNearest(gtx, gty, tz); ++ ++ // Prepare buffer for pathfinding calculations ++ final NodeBuffer buffer = getBuffer(64 + (2 * Math.max(Math.abs(gox - gtx), Math.abs(goy - gty)))); + if (buffer == null) + { + return null; + } + +- List path = null; ++ // find path ++ List path = null; + try + { +- final CellNode result = buffer.findPath(gx, gy, gz, gtx, gty, gtz); +- ++ final Node result = buffer.findPath(gox, goy, goz, gtx, gty, gtz); + if (result == null) + { + return null; +@@ -125,33 +121,45 @@ + return path; + } + +- int currentX, currentY, currentZ; +- ListIterator middlePoint; ++ // get path list iterator ++ final ListIterator point = path.listIterator(); + +- middlePoint = path.listIterator(); +- currentX = x; +- currentY = y; +- currentZ = z; ++ // get node A (origin) ++ int nodeAx = gox; ++ int nodeAy = goy; ++ short nodeAz = goz; + +- while (middlePoint.hasNext()) ++ // get node B ++ GeoLocation nodeB = (GeoLocation) point.next(); ++ ++ // iterate thought the path to optimize it ++ int count = 0; ++ while (point.hasNext() && (count++ < Config.MAX_ITERATIONS)) + { +- final AbstractNodeLoc locMiddle = middlePoint.next(); +- if (!middlePoint.hasNext()) +- { +- break; +- } ++ // get node C ++ final GeoLocation nodeC = (GeoLocation) path.get(point.nextIndex()); + +- final AbstractNodeLoc locEnd = path.get(middlePoint.nextIndex()); +- if (GeoEngine.getInstance().canMoveToTarget(currentX, currentY, currentZ, locEnd.getX(), locEnd.getY(), locEnd.getZ(), instance)) ++ // check movement from node A to node C ++ final GeoLocation loc = checkMove(nodeAx, nodeAy, nodeAz, nodeC.getGeoX(), nodeC.getGeoY(), nodeC.getZ(), instance); ++ if ((loc.getGeoX() == nodeC.getGeoX()) && (loc.getGeoY() == nodeC.getGeoY())) + { +- middlePoint.remove(); ++ // can move from node A to node C ++ ++ // remove node B ++ point.remove(); + } + else + { +- currentX = locMiddle.getX(); +- currentY = locMiddle.getY(); +- currentZ = locMiddle.getZ(); ++ // can not move from node A to node C ++ ++ // set node A (node B is part of path, update A coordinates) ++ nodeAx = nodeB.getGeoX(); ++ nodeAy = nodeB.getGeoY(); ++ nodeAz = (short) nodeB.getZ(); + } ++ ++ // set node B ++ nodeB = (GeoLocation) point.next(); + } + + return path; +@@ -158,144 +166,102 @@ + } + + /** +- * Convert geodata position to pathnode position +- * @param geo_pos +- * @return pathnode position ++ * Create list of node locations as result of calculated buffer node tree. ++ * @param node : the entry point ++ * @return List : list of node location + */ +- public short getNodePos(int geo_pos) ++ private static List constructPath(Node node) + { +- return (short) (geo_pos >> 3); // OK? +- } +- +- /** +- * Convert node position to pathnode block position +- * @param node_pos +- * @return pathnode block position (0...255) +- */ +- public short getNodeBlock(int node_pos) +- { +- return (short) (node_pos % 256); +- } +- +- public byte getRegionX(int node_pos) +- { +- return (byte) ((node_pos >> 8) + World.TILE_X_MIN); +- } +- +- public byte getRegionY(int node_pos) +- { +- return (byte) ((node_pos >> 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 node_x rx +- * @return +- */ +- public int calculateWorldX(short node_x) +- { +- return World.MAP_MIN_X + (node_x * 128) + 48; +- } +- +- /** +- * Convert pathnode y to World y position +- * @param node_y +- * @return +- */ +- public int calculateWorldY(short node_y) +- { +- return World.MAP_MIN_Y + (node_y * 128) + 48; +- } +- +- private List constructPath(AbstractNode nodeValue) +- { +- final LinkedList path = new LinkedList<>(); +- int previousDirectionX = Integer.MIN_VALUE; +- int previousDirectionY = Integer.MIN_VALUE; +- int directionX, directionY; ++ // create empty list ++ final LinkedList list = new LinkedList<>(); + +- AbstractNode node = nodeValue; +- while (node.getParent() != null) ++ // set direction X/Y ++ int dx = 0; ++ int dy = 0; ++ ++ // get target parent ++ Node target = node; ++ Node parent = target.getParent(); ++ ++ // while parent exists ++ int count = 0; ++ while ((parent != null) && (count++ < Config.MAX_ITERATIONS)) + { +- directionX = node.getLoc().getNodeX() - node.getParent().getLoc().getNodeX(); +- directionY = node.getLoc().getNodeY() - node.getParent().getLoc().getNodeY(); ++ // get parent <> target direction X/Y ++ final int nx = parent.getLoc().getGeoX() - target.getLoc().getGeoX(); ++ final int ny = parent.getLoc().getGeoY() - target.getLoc().getGeoY(); + +- // only add a new route point if moving direction changes +- if ((directionX != previousDirectionX) || (directionY != previousDirectionY)) ++ // direction has changed? ++ if ((dx != nx) || (dy != ny)) + { +- previousDirectionX = directionX; +- previousDirectionY = directionY; ++ // add node to the beginning of the list ++ list.addFirst(target.getLoc()); + +- path.addFirst(node.getLoc()); +- node.setLoc(null); ++ // update direction X/Y ++ dx = nx; ++ dy = ny; + } + +- node = node.getParent(); ++ // move to next node, set target and get its parent ++ target = parent; ++ parent = target.getParent(); + } + +- return path; ++ // return list ++ return list; + } + +- private CellNodeBuffer alloc(int size) ++ /** ++ * Provides optimize selection of the buffer. When all pre-initialized buffer are locked, creates new buffer. ++ * @param size : pre-calculated minimal required size ++ * @return NodeBuffer : buffer ++ */ ++ private final NodeBuffer getBuffer(int size) + { +- CellNodeBuffer current = null; +- for (BufferInfo i : _buffers) ++ NodeBuffer current = null; ++ for (BufferHolder holder : _buffers) + { +- if (i.mapSize >= size) ++ // Find proper size of buffer ++ if (holder._size < size) + { +- for (CellNodeBuffer buf : i.buffer) ++ continue; ++ } ++ ++ // Find unlocked NodeBuffer ++ for (NodeBuffer buffer : holder._buffer) ++ { ++ if (!buffer.isLocked()) + { +- if (buf.lock()) +- { +- current = buf; +- break; +- } ++ continue; + } +- if (current != null) +- { +- break; +- } + +- // not found, allocate temporary buffer +- current = new CellNodeBuffer(i.mapSize); +- current.lock(); +- if (i.buffer.size() < i.count) +- { +- i.buffer.add(current); +- break; +- } ++ return buffer; + } ++ ++ // NodeBuffer not found, allocate temporary buffer ++ current = new NodeBuffer(holder._size); ++ current.isLocked(); + } + + return current; + } + +- private static final class BufferInfo ++ /** ++ * NodeBuffer container with specified size and count of separate buffers. ++ */ ++ private static final class BufferHolder + { +- final int mapSize; +- final int count; +- ArrayList buffer; ++ final int _size; ++ List _buffer; + +- public BufferInfo(int size, int cnt) ++ public BufferHolder(int size, int count) + { +- mapSize = size; +- count = cnt; +- buffer = new ArrayList<>(count); ++ _size = size; ++ _buffer = new ArrayList<>(count); ++ for (int i = 0; i < count; i++) ++ { ++ _buffer.add(new NodeBuffer(size)); ++ } + } + } +- +- public static GeoEnginePathfinding getInstance() +- { +- return SingletonHolder.INSTANCE; +- } +- +- private static class SingletonHolder +- { +- protected static final GeoEnginePathfinding INSTANCE = new GeoEnginePathfinding(); +- } +-} ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/ABlock.java (working copy) +@@ -0,0 +1,197 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++import java.io.IOException; ++ ++/** ++ * @author Hasha ++ */ ++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 height of cell, which is closest to given coordinates.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @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 getHeightNearestOriginal(int geoX, int geoY, int worldZ); ++ ++ /** ++ * Returns the height of cell, which is first above 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, above given coordinates. ++ */ ++ public abstract short getHeightAbove(int geoX, int geoY, int worldZ); ++ ++ /** ++ * Returns the height of cell, which is first below 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, below given coordinates. ++ */ ++ public abstract short getHeightBelow(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 the NSWE flag byte of cell, which is closest to given coordinates.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @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 getNsweNearestOriginal(int geoX, int geoY, int worldZ); ++ ++ /** ++ * Returns the NSWE flag byte of cell, which is first above 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 getNsweAbove(int geoX, int geoY, int worldZ); ++ ++ /** ++ * Returns the NSWE flag byte of cell, which is first below 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 getNsweBelow(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 above given coordinates.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @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 getIndexAboveOriginal(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 index to data of the cell, which is first layer below given coordinates.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @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 getIndexBelowOriginal(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 height of cell given by cell index.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @param index : Index of the cell. ++ * @return short : Cell geodata Z coordinate, below given coordinates. ++ */ ++ public abstract short getHeightOriginal(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); ++ ++ /** ++ * Returns the NSWE flag byte of cell given by cell index.
++ * Geodata without {@link IGeoObject} are taken in consideration. ++ * @param index : Index of the cell. ++ * @return short : Cell geodata Z coordinate, below given coordinates. ++ */ ++ public abstract byte getNsweOriginal(int index); ++ ++ /** ++ * Sets the NSWE flag byte of cell given by cell index. ++ * @param index : Index of the cell. ++ * @param nswe : New NSWE flag byte. ++ */ ++ public abstract void setNswe(int index, byte nswe); ++ ++ /** ++ * Saves the block in L2D format to {@link BufferedOutputStream}. Used only for L2D geodata conversion. ++ * @param stream : The stream. ++ * @throws IOException : Can't save the block to steam. ++ */ ++ public abstract void saveBlock(BufferedOutputStream stream) throws IOException; ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/BlockComplex.java (working copy) +@@ -0,0 +1,252 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++import java.io.IOException; ++import java.nio.ByteBuffer; ++ ++/** ++ * @author Hasha ++ */ ++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. ++ * @param format : GeoFormat specifying format of loaded data. ++ */ ++ public BlockComplex(ByteBuffer bb, GeoFormat format) ++ { ++ // initialize buffer ++ _buffer = new byte[GeoStructure.BLOCK_CELLS * 3]; ++ ++ // load data ++ for (int i = 0; i < GeoStructure.BLOCK_CELLS; i++) ++ { ++ if (format != GeoFormat.L2D) ++ { ++ // 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); ++ } ++ else ++ { ++ // get nswe ++ final byte nswe = bb.get(); ++ _buffer[i * 3] = nswe; ++ ++ // get height ++ final short height = bb.getShort(); ++ _buffer[(i * 3) + 1] = (byte) (height & 0x00FF); ++ _buffer[(i * 3) + 2] = (byte) (height >> 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 short getHeightNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getHeightNearest(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeightAbove(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 short height = (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ ++ // check and return height ++ return height > worldZ ? height : Short.MIN_VALUE; ++ } ++ ++ @Override ++ public short getHeightBelow(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 short height = (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ ++ // check and return height ++ return height < worldZ ? height : Short.MAX_VALUE; ++ } ++ ++ @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 byte getNsweNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getNsweNearest(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public byte getNsweAbove(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 ? _buffer[index] : 0; ++ } ++ ++ @Override ++ public byte getNsweBelow(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 ? _buffer[index] : 0; ++ } ++ ++ @Override ++ public 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 getIndexAboveOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexAbove(geoX, geoY, worldZ); ++ } ++ ++ @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 int getIndexBelowOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexBelow(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeight(int index) ++ { ++ return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ } ++ ++ @Override ++ public short getHeightOriginal(int index) ++ { ++ return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ } ++ ++ @Override ++ public byte getNswe(int index) ++ { ++ return _buffer[index]; ++ } ++ ++ @Override ++ public byte getNsweOriginal(int index) ++ { ++ return _buffer[index]; ++ } ++ ++ @Override ++ public void setNswe(int index, byte nswe) ++ { ++ _buffer[index] = nswe; ++ } ++ ++ @Override ++ public void saveBlock(BufferedOutputStream stream) throws IOException ++ { ++ // write block type ++ stream.write(GeoStructure.TYPE_COMPLEX_L2D); ++ ++ // write block data ++ stream.write(_buffer, 0, GeoStructure.BLOCK_CELLS * 3); ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/BlockFlat.java (working copy) +@@ -0,0 +1,176 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++import java.io.IOException; ++import java.nio.ByteBuffer; ++ ++/** ++ * @author Hasha ++ */ ++public class BlockFlat extends ABlock ++{ ++ protected final short _height; ++ protected byte _nswe; ++ ++ /** ++ * Creates FlatBlock. ++ * @param bb : Input byte buffer. ++ * @param format : GeoFormat specifying format of loaded data. ++ */ ++ public BlockFlat(ByteBuffer bb, GeoFormat format) ++ { ++ _height = bb.getShort(); ++ _nswe = format != GeoFormat.L2D ? 0x0F : (byte) (0xFF); ++ if (format == GeoFormat.L2OFF) ++ { ++ bb.getShort(); ++ } ++ } ++ ++ @Override ++ public boolean hasGeoPos() ++ { ++ return true; ++ } ++ ++ @Override ++ public short getHeightNearest(int geoX, int geoY, int worldZ) ++ { ++ return _height; ++ } ++ ++ @Override ++ public short getHeightNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return _height; ++ } ++ ++ @Override ++ public short getHeightAbove(int geoX, int geoY, int worldZ) ++ { ++ // check and return height ++ return _height > worldZ ? _height : Short.MIN_VALUE; ++ } ++ ++ @Override ++ public short getHeightBelow(int geoX, int geoY, int worldZ) ++ { ++ // check and return height ++ return _height < worldZ ? _height : Short.MAX_VALUE; ++ } ++ ++ @Override ++ public byte getNsweNearest(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweAbove(int geoX, int geoY, int worldZ) ++ { ++ // check height and return nswe ++ return _height > worldZ ? _nswe : 0; ++ } ++ ++ @Override ++ public byte getNsweBelow(int geoX, int geoY, int worldZ) ++ { ++ // check height and return nswe ++ return _height < worldZ ? _nswe : 0; ++ } ++ ++ @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 getIndexAboveOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexAbove(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public int getIndexBelow(int geoX, int geoY, int worldZ) ++ { ++ // check height and return index ++ return _height < worldZ ? 0 : -1; ++ } ++ ++ @Override ++ public int getIndexBelowOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexBelow(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeight(int index) ++ { ++ return _height; ++ } ++ ++ @Override ++ public short getHeightOriginal(int index) ++ { ++ return _height; ++ } ++ ++ @Override ++ public byte getNswe(int index) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweOriginal(int index) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public void setNswe(int index, byte nswe) ++ { ++ _nswe = nswe; ++ } ++ ++ @Override ++ public void saveBlock(BufferedOutputStream stream) throws IOException ++ { ++ // write block type ++ stream.write(GeoStructure.TYPE_FLAT_L2D); ++ ++ // write height ++ stream.write((byte) (_height & 0x00FF)); ++ stream.write((byte) (_height >> 8)); ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/BlockMultilayer.java (working copy) +@@ -0,0 +1,465 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++import java.io.IOException; ++import java.nio.ByteBuffer; ++import java.nio.ByteOrder; ++import java.util.Arrays; ++ ++/** ++ * @author Hasha ++ */ ++public class BlockMultilayer extends ABlock ++{ ++ private static final int MAX_LAYERS = Byte.MAX_VALUE; ++ ++ private static ByteBuffer _temp; ++ ++ /** ++ * Initializes the temporarily buffer. ++ */ ++ public static void initialize() ++ { ++ // initialize temporarily buffer and sorting mechanism ++ _temp = ByteBuffer.allocate(GeoStructure.BLOCK_CELLS * MAX_LAYERS * 3); ++ _temp.order(ByteOrder.LITTLE_ENDIAN); ++ } ++ ++ /** ++ * Releases temporarily buffer. ++ */ ++ public static void release() ++ { ++ _temp = null; ++ } ++ ++ protected byte[] _buffer; ++ ++ /** ++ * Implicit constructor for children class. ++ */ ++ protected BlockMultilayer() ++ { ++ _buffer = null; ++ } ++ ++ /** ++ * Creates MultilayerBlock. ++ * @param bb : Input byte buffer. ++ * @param format : GeoFormat specifying format of loaded data. ++ */ ++ public BlockMultilayer(ByteBuffer bb, GeoFormat format) ++ { ++ // 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 = format != GeoFormat.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++) ++ { ++ if (format != GeoFormat.L2D) ++ { ++ // get data ++ final short data = bb.getShort(); ++ ++ // add nswe and height ++ _temp.put((byte) (data & 0x000F)); ++ _temp.putShort((short) ((short) (data & 0xFFF0) >> 1)); ++ } ++ else ++ { ++ // add nswe ++ _temp.put(bb.get()); ++ ++ // add height ++ _temp.putShort(bb.getShort()); ++ } ++ } ++ } ++ ++ // 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 short getHeightNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getHeightNearest(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeightAbove(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 height ++ if (height > worldZ) ++ { ++ return (short) height; ++ } ++ ++ // move index to next layer ++ index -= 3; ++ } ++ ++ // none layer found, return minimum value ++ return Short.MIN_VALUE; ++ } ++ ++ @Override ++ public short getHeightBelow(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 height ++ if (height < worldZ) ++ { ++ return (short) height; ++ } ++ ++ // move index to next layer ++ index += 3; ++ } ++ ++ // none layer found, return maximum value ++ return Short.MAX_VALUE; ++ } ++ ++ @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 byte getNsweNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getNsweNearest(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public byte getNsweAbove(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 nswe ++ if (height > worldZ) ++ { ++ return _buffer[index]; ++ } ++ ++ // move index to next layer ++ index -= 3; ++ } ++ ++ // none layer found, block movement ++ return 0; ++ } ++ ++ @Override ++ public byte getNsweBelow(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 nswe ++ if (height < worldZ) ++ { ++ return _buffer[index]; ++ } ++ ++ // move index to next layer ++ index += 3; ++ } ++ ++ // none layer found, block movement ++ return 0; ++ } ++ ++ @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 first layer data (first from bottom) ++ byte layers = _buffer[index++]; ++ ++ // loop though all cell layers, find closest layer ++ 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; ++ } ++ ++ // none layer found ++ return -1; ++ } ++ ++ @Override ++ public int getIndexAboveOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexAbove(geoX, geoY, worldZ); ++ } ++ ++ @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; ++ } ++ ++ // none layer found ++ return -1; ++ } ++ ++ @Override ++ public int getIndexBelowOriginal(int geoX, int geoY, int worldZ) ++ { ++ return getIndexBelow(geoX, geoY, worldZ); ++ } ++ ++ @Override ++ public short getHeight(int index) ++ { ++ // get height ++ return (short) ((_buffer[index + 1] & 0x00FF) | (_buffer[index + 2] << 8)); ++ } ++ ++ @Override ++ public short getHeightOriginal(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]; ++ } ++ ++ @Override ++ public byte getNsweOriginal(int index) ++ { ++ // get nswe ++ return _buffer[index]; ++ } ++ ++ @Override ++ public void setNswe(int index, byte nswe) ++ { ++ // set nswe ++ _buffer[index] = nswe; ++ } ++ ++ @Override ++ public void saveBlock(BufferedOutputStream stream) throws IOException ++ { ++ // write block type ++ stream.write(GeoStructure.TYPE_MULTILAYER_L2D); ++ ++ // for each cell ++ int index = 0; ++ for (int i = 0; i < GeoStructure.BLOCK_CELLS; i++) ++ { ++ // write layers count ++ final byte layers = _buffer[index++]; ++ stream.write(layers); ++ ++ // write cell data ++ stream.write(_buffer, index, layers * 3); ++ ++ // move index to next cell ++ index += layers * 3; ++ } ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/BlockNull.java (working copy) +@@ -0,0 +1,150 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.BufferedOutputStream; ++ ++/** ++ * @author Hasha ++ */ ++public class BlockNull extends ABlock ++{ ++ private final byte _nswe; ++ ++ public BlockNull() ++ { ++ _nswe = (byte) 0xFF; ++ } ++ ++ @Override ++ public boolean hasGeoPos() ++ { ++ return false; ++ } ++ ++ @Override ++ public short getHeightNearest(int geoX, int geoY, int worldZ) ++ { ++ return (short) worldZ; ++ } ++ ++ @Override ++ public short getHeightNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return (short) worldZ; ++ } ++ ++ @Override ++ public short getHeightAbove(int geoX, int geoY, int worldZ) ++ { ++ return (short) worldZ; ++ } ++ ++ @Override ++ public short getHeightBelow(int geoX, int geoY, int worldZ) ++ { ++ return (short) worldZ; ++ } ++ ++ @Override ++ public byte getNsweNearest(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweNearestOriginal(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweAbove(int geoX, int geoY, int worldZ) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweBelow(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) ++ { ++ return 0; ++ } ++ ++ @Override ++ public int getIndexAboveOriginal(int geoX, int geoY, int worldZ) ++ { ++ return 0; ++ } ++ ++ @Override ++ public int getIndexBelow(int geoX, int geoY, int worldZ) ++ { ++ return 0; ++ } ++ ++ @Override ++ public int getIndexBelowOriginal(int geoX, int geoY, int worldZ) ++ { ++ return 0; ++ } ++ ++ @Override ++ public short getHeight(int index) ++ { ++ return 0; ++ } ++ ++ @Override ++ public short getHeightOriginal(int index) ++ { ++ return 0; ++ } ++ ++ @Override ++ public byte getNswe(int index) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public byte getNsweOriginal(int index) ++ { ++ return _nswe; ++ } ++ ++ @Override ++ public void setNswe(int index, byte nswe) ++ { ++ } ++ ++ @Override ++ public void saveBlock(BufferedOutputStream stream) ++ { ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/Cell.java (nonexistent) +@@ -1,48 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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() +- { +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/ComplexBlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/ComplexBlock.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/ComplexBlock.java (nonexistent) +@@ -1,77 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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; +- +-/** +- * @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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/FlatBlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/FlatBlock.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/FlatBlock.java (nonexistent) +@@ -1,56 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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; +- +-/** +- * @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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/GeoFormat.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/GeoFormat.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/GeoFormat.java (working copy) +@@ -0,0 +1,39 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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 Hasha ++ */ ++public enum GeoFormat ++{ ++ L2J("%d_%d.l2j"), ++ L2OFF("%d_%d_conv.dat"), ++ L2D("%d_%d.l2d"); ++ ++ private final String _filename; ++ ++ private GeoFormat(String filename) ++ { ++ _filename = filename; ++ } ++ ++ public String getFilename() ++ { ++ return _filename; ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/GeoLocation.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/GeoLocation.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/GeoLocation.java (working copy) +@@ -0,0 +1,67 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.geoengine.GeoEngine; ++import org.l2jmobius.gameserver.model.Location; ++ ++/** ++ * @author Hasha ++ */ ++public class GeoLocation extends Location ++{ ++ private byte _nswe; ++ ++ public GeoLocation(int x, int y, int z) ++ { ++ super(x, y, GeoEngine.getInstance().getHeightNearest(x, y, z)); ++ _nswe = GeoEngine.getInstance().getNsweNearest(x, y, z); ++ } ++ ++ public void set(int x, int y, short z) ++ { ++ super.setXYZ(x, y, GeoEngine.getInstance().getHeightNearest(x, y, z)); ++ _nswe = GeoEngine.getInstance().getNsweNearest(x, y, z); ++ } ++ ++ public int getGeoX() ++ { ++ return _x; ++ } ++ ++ public int getGeoY() ++ { ++ return _y; ++ } ++ ++ @Override ++ public int getX() ++ { ++ return GeoEngine.getWorldX(_x); ++ } ++ ++ @Override ++ public int getY() ++ { ++ return GeoEngine.getWorldY(_y); ++ } ++ ++ public byte getNSWE() ++ { ++ return _nswe; ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/GeoStructure.java (working copy) +@@ -0,0 +1,70 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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; ++ ++/** ++ * @author Hasha ++ */ ++public class GeoStructure ++{ ++ // cells ++ public static final byte CELL_FLAG_E = 1 << 0; ++ public static final byte CELL_FLAG_W = 1 << 1; ++ public static final byte CELL_FLAG_S = 1 << 2; ++ public static final byte CELL_FLAG_N = 1 << 3; ++ public static final byte CELL_FLAG_SE = 1 << 4; ++ public static final byte CELL_FLAG_SW = 1 << 5; ++ public static final byte CELL_FLAG_NE = 1 << 6; ++ public static final byte CELL_FLAG_NW = (byte) (1 << 7); ++ ++ public static final int CELL_HEIGHT = 8; ++ public static final int CELL_IGNORE_HEIGHT = CELL_HEIGHT * 6; ++ ++ // blocks ++ public static final byte TYPE_FLAT_L2J_L2OFF = 0; ++ public static final byte TYPE_FLAT_L2D = (byte) 0xD0; ++ public static final byte TYPE_COMPLEX_L2J = 1; ++ public static final byte TYPE_COMPLEX_L2OFF = 0x40; ++ public static final byte TYPE_COMPLEX_L2D = (byte) 0xD1; ++ 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) ++ public static final byte TYPE_MULTILAYER_L2D = (byte) 0xD2; ++ ++ 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; ++ ++ // regions ++ 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; ++ ++ // global geodata ++ private static final int GEO_REGIONS_X = ((World.TILE_X_MAX - World.TILE_X_MIN) + 1); ++ private 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 +Index: java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/IBlock.java (nonexistent) +@@ -1,42 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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); +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/IGeoObject.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/IGeoObject.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/IGeoObject.java (working copy) +@@ -0,0 +1,53 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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 Hasha ++ */ ++public interface IGeoObject ++{ ++ /** ++ * Returns geodata X coordinate of the {@link IGeoObject}. ++ * @return int : Geodata X coordinate. ++ */ ++ int getGeoX(); ++ ++ /** ++ * Returns geodata Y coordinate of the {@link IGeoObject}. ++ * @return int : Geodata Y coordinate. ++ */ ++ int getGeoY(); ++ ++ /** ++ * Returns geodata Z coordinate of the {@link IGeoObject}. ++ * @return int : Geodata Z coordinate. ++ */ ++ int getGeoZ(); ++ ++ /** ++ * Returns height of the {@link IGeoObject}. ++ * @return int : Height. ++ */ ++ int getHeight(); ++ ++ /** ++ * Returns {@link IGeoObject} data. ++ * @return byte[][] : {@link IGeoObject} data. ++ */ ++ byte[][] getObjectGeoData(); ++} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/IRegion.java (nonexistent) +@@ -1,47 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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(); +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/MultilayerBlock.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/MultilayerBlock.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/MultilayerBlock.java (nonexistent) +@@ -1,183 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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; +- +-/** +- * @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); +- +- // 1 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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/NullRegion.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/NullRegion.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/NullRegion.java (nonexistent) +@@ -1,55 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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 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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/geodata/Region.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/geodata/Region.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/geodata/Region.java (nonexistent) +@@ -1,92 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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; +- +-/** +- * @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; +- } +-} +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNode.java (nonexistent) +@@ -1,87 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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 T _loc; +- private AbstractNode _parent; +- +- public AbstractNode(T loc) +- { +- _loc = loc; +- } +- +- public void setParent(AbstractNode p) +- { +- _parent = p; +- } +- +- public AbstractNode getParent() +- { +- return _parent; +- } +- +- public T getLoc() +- { +- return _loc; +- } +- +- public void setLoc(T l) +- { +- _loc = l; +- } +- +- @Override +- public int hashCode() +- { +- final int prime = 31; +- int result = 1; +- result = (prime * result) + ((_loc == null) ? 0 : _loc.hashCode()); +- return result; +- } +- +- @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 +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/AbstractNodeLoc.java (nonexistent) +@@ -1,33 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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 int getNodeX(); +- +- public abstract int getNodeY(); +-} +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNode.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNode.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNode.java (nonexistent) +@@ -1,67 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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 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 +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNodeBuffer.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNodeBuffer.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/CellNodeBuffer.java (nonexistent) +@@ -1,348 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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.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 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) +- { +- _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(); +- } +- +- 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 void getNeighbors() +- { +- if (!_current.getLoc().canGoAll()) +- { +- 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); +- } +- +- // SouthEast +- if ((nodeE != null) && (nodeS != null)) +- { +- if (nodeE.getLoc().canGoSouth() && nodeS.getLoc().canGoEast()) +- { +- addNode(x + 1, y + 1, z, true); +- } +- } +- +- // SouthWest +- if ((nodeS != null) && (nodeW != null)) +- { +- if (nodeW.getLoc().canGoSouth() && nodeS.getLoc().canGoWest()) +- { +- addNode(x - 1, y + 1, z, true); +- } +- } +- +- // NorthEast +- if ((nodeN != null) && (nodeE != null)) +- { +- if (nodeE.getLoc().canGoNorth() && nodeN.getLoc().canGoEast()) +- { +- addNode(x + 1, y - 1, z, true); +- } +- } +- +- // NorthWest +- if ((nodeN != null) && (nodeW != null)) +- { +- if (nodeW.getLoc().canGoNorth() && nodeN.getLoc().canGoWest()) +- { +- addNode(x - 1, y - 1, z, true); +- } +- } +- } +- +- private 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(); +- // reinit node if needed +- if (result.getLoc() != null) +- { +- result.getLoc().set(x, y, z); +- } +- else +- { +- result.setLoc(new NodeLoc(x, y, z)); +- } +- } +- +- return result; +- } +- +- private 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 boolean isHighWeight(int x, int y, int z) +- { +- final CellNode result = getNode(x, y, z); +- if (result == null) +- { +- return true; +- } +- +- if (!result.getLoc().canGoAll()) +- { +- return true; +- } +- if (Math.abs(result.getLoc().getZ() - z) > 16) +- { +- return true; +- } +- +- return false; +- } +- +- private 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 +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/Node.java (working copy) +@@ -0,0 +1,87 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.geodata.GeoLocation; ++ ++/** ++ * @author Hasha ++ */ ++public class Node ++{ ++ // node coords and nswe flag ++ private GeoLocation _loc; ++ ++ // node parent (for reverse path construction) ++ private Node _parent; ++ // node child (for moving over nodes during iteration) ++ private Node _child; ++ ++ // node G cost (movement cost = parent movement cost + current movement cost) ++ private double _cost = -1000; ++ ++ public void setLoc(int x, int y, int z) ++ { ++ _loc = new GeoLocation(x, y, z); ++ } ++ ++ public GeoLocation getLoc() ++ { ++ return _loc; ++ } ++ ++ public void setParent(Node parent) ++ { ++ _parent = parent; ++ } ++ ++ public Node getParent() ++ { ++ return _parent; ++ } ++ ++ public void setChild(Node child) ++ { ++ _child = child; ++ } ++ ++ public Node getChild() ++ { ++ return _child; ++ } ++ ++ public void setCost(double cost) ++ { ++ _cost = cost; ++ } ++ ++ public double getCost() ++ { ++ return _cost; ++ } ++ ++ public void free() ++ { ++ // reset node location ++ _loc = null; ++ ++ // reset node parent, child and cost ++ _parent = null; ++ _child = null; ++ _cost = -1000; ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java (nonexistent) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeBuffer.java (working copy) +@@ -0,0 +1,306 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You 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.concurrent.locks.ReentrantLock; ++ ++import org.l2jmobius.Config; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; ++ ++/** ++ * @author DS, Hasha; Credits to Diamond ++ */ ++public class NodeBuffer ++{ ++ private final ReentrantLock _lock = new ReentrantLock(); ++ private final int _size; ++ private final Node[][] _buffer; ++ ++ // center coordinates ++ private int _cx = 0; ++ private int _cy = 0; ++ ++ // target coordinates ++ private int _gtx = 0; ++ private int _gty = 0; ++ private short _gtz = 0; ++ ++ private Node _current = null; ++ ++ /** ++ * Constructor of NodeBuffer. ++ * @param size : one dimension size of buffer ++ */ ++ public NodeBuffer(int size) ++ { ++ // set size ++ _size = size; ++ ++ // initialize buffer ++ _buffer = new Node[size][size]; ++ for (int x = 0; x < size; x++) ++ { ++ for (int y = 0; y < size; y++) ++ { ++ _buffer[x][y] = 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 Node : first node of path ++ */ ++ public Node findPath(int gox, int goy, short goz, int gtx, int gty, short gtz) ++ { ++ // set coordinates (middle of the line (gox,goy) - (gtx,gty), will be in the center of the buffer) ++ _cx = gox + ((gtx - gox - _size) / 2); ++ _cy = goy + ((gty - goy - _size) / 2); ++ ++ _gtx = gtx; ++ _gty = gty; ++ _gtz = gtz; ++ ++ _current = getNode(gox, goy, goz); ++ _current.setCost(getCostH(gox, goy, goz)); ++ ++ int count = 0; ++ do ++ { ++ // reached target? ++ if ((_current.getLoc().getGeoX() == _gtx) && (_current.getLoc().getGeoY() == _gty) && (Math.abs(_current.getLoc().getZ() - _gtz) < 8)) ++ { ++ return _current; ++ } ++ ++ // expand current node ++ expand(); ++ ++ // move pointer ++ _current = _current.getChild(); ++ } ++ while ((_current != null) && (count++ < Config.MAX_ITERATIONS)); ++ ++ return null; ++ } ++ ++ public boolean isLocked() ++ { ++ return _lock.tryLock(); ++ } ++ ++ public void free() ++ { ++ _current = null; ++ ++ for (Node[] nodes : _buffer) ++ { ++ for (Node node : nodes) ++ { ++ if (node.getLoc() != null) ++ { ++ node.free(); ++ } ++ } ++ } ++ ++ _lock.unlock(); ++ } ++ ++ /** ++ * Check _current Node and add its neighbors to the buffer. ++ */ ++ private final void expand() ++ { ++ // can't move anywhere, don't expand ++ final byte nswe = _current.getLoc().getNSWE(); ++ if (nswe == 0) ++ { ++ return; ++ } ++ ++ // get geo coords of the node to be expanded ++ final int x = _current.getLoc().getGeoX(); ++ final int y = _current.getLoc().getGeoY(); ++ final short z = (short) _current.getLoc().getZ(); ++ ++ // can move north, expand ++ if ((nswe & GeoStructure.CELL_FLAG_N) != 0) ++ { ++ addNode(x, y - 1, z, Config.BASE_WEIGHT); ++ } ++ ++ // can move south, expand ++ if ((nswe & GeoStructure.CELL_FLAG_S) != 0) ++ { ++ addNode(x, y + 1, z, Config.BASE_WEIGHT); ++ } ++ ++ // can move west, expand ++ if ((nswe & GeoStructure.CELL_FLAG_W) != 0) ++ { ++ addNode(x - 1, y, z, Config.BASE_WEIGHT); ++ } ++ ++ // can move east, expand ++ if ((nswe & GeoStructure.CELL_FLAG_E) != 0) ++ { ++ addNode(x + 1, y, z, Config.BASE_WEIGHT); ++ } ++ ++ // can move north-west, expand ++ if ((nswe & GeoStructure.CELL_FLAG_NW) != 0) ++ { ++ addNode(x - 1, y - 1, z, Config.DIAGONAL_WEIGHT); ++ } ++ ++ // can move north-east, expand ++ if ((nswe & GeoStructure.CELL_FLAG_NE) != 0) ++ { ++ addNode(x + 1, y - 1, z, Config.DIAGONAL_WEIGHT); ++ } ++ ++ // can move south-west, expand ++ if ((nswe & GeoStructure.CELL_FLAG_SW) != 0) ++ { ++ addNode(x - 1, y + 1, z, Config.DIAGONAL_WEIGHT); ++ } ++ ++ // can move south-east, expand ++ if ((nswe & GeoStructure.CELL_FLAG_SE) != 0) ++ { ++ addNode(x + 1, y + 1, z, Config.DIAGONAL_WEIGHT); ++ } ++ } ++ ++ /** ++ * Returns node, if it exists in buffer. ++ * @param x : node X coord ++ * @param y : node Y coord ++ * @param z : node Z coord ++ * @return Node : node, if exits in buffer ++ */ ++ private final Node getNode(int x, int y, short z) ++ { ++ // check node X out of coordinates ++ final int ix = x - _cx; ++ if ((ix < 0) || (ix >= _size)) ++ { ++ return null; ++ } ++ ++ // check node Y out of coordinates ++ final int iy = y - _cy; ++ if ((iy < 0) || (iy >= _size)) ++ { ++ return null; ++ } ++ ++ // get node ++ final Node result = _buffer[ix][iy]; ++ ++ // check and update ++ if (result.getLoc() == null) ++ { ++ result.setLoc(x, y, z); ++ } ++ ++ // return node ++ return result; ++ } ++ ++ /** ++ * Add node given by coordinates to the buffer. ++ * @param x : geo X coord ++ * @param y : geo Y coord ++ * @param z : geo Z coord ++ * @param weight : weight of movement to new node ++ */ ++ private final void addNode(int x, int y, short z, int weight) ++ { ++ // get node to be expanded ++ final Node node = getNode(x, y, z); ++ if (node == null) ++ { ++ return; ++ } ++ ++ // Z distance between nearby cells is higher than cell size ++ if (node.getLoc().getZ() > (z + (2 * GeoStructure.CELL_HEIGHT))) ++ { ++ return; ++ } ++ ++ // node was already expanded, return ++ if (node.getCost() >= 0) ++ { ++ return; ++ } ++ ++ node.setParent(_current); ++ if (node.getLoc().getNSWE() != (byte) 0xFF) ++ { ++ node.setCost(getCostH(x, y, node.getLoc().getZ()) + (weight * Config.OBSTACLE_MULTIPLIER)); ++ } ++ else ++ { ++ node.setCost(getCostH(x, y, node.getLoc().getZ()) + weight); ++ } ++ ++ Node current = _current; ++ int count = 0; ++ while ((current.getChild() != null) && (count < (Config.MAX_ITERATIONS * 4))) ++ { ++ count++; ++ if (current.getChild().getCost() > node.getCost()) ++ { ++ node.setChild(current.getChild()); ++ break; ++ } ++ current = current.getChild(); ++ } ++ ++ if (count >= (Config.MAX_ITERATIONS * 4)) ++ { ++ System.err.println("Pathfinding: too long loop detected, cost:" + node.getCost()); ++ } ++ ++ current.setChild(node); ++ } ++ ++ /** ++ * @param x : node X coord ++ * @param y : node Y coord ++ * @param i : node Z coord ++ * @return double : node cost ++ */ ++ private final double getCostH(int x, int y, int i) ++ { ++ final int dX = x - _gtx; ++ final int dY = y - _gty; ++ final int dZ = (i - _gtz) / GeoStructure.CELL_HEIGHT; ++ ++ // return (Math.abs(dX) + Math.abs(dY) + Math.abs(dZ)) * Config.HEURISTIC_WEIGHT; // Manhattan distance ++ return Math.sqrt((dX * dX) + (dY * dY) + (dZ * dZ)) * Config.HEURISTIC_WEIGHT; // Direct distance ++ } ++} +\ No newline at end of file +Index: java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeLoc.java +=================================================================== +--- java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeLoc.java (revision 8397) ++++ java/org/l2jmobius/gameserver/geoengine/pathfinding/NodeLoc.java (nonexistent) +@@ -1,183 +0,0 @@ +-/* +- * This file is part of the L2J Mobius project. +- * +- * This program is free software: you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation, either version 3 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +- * General Public License for more details. +- * +- * You 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.Cell; +- +-/** +- * @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 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 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; +- // return super.hashCode(); +- } +- +- @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; +- if (_x != other._x) +- { +- return false; +- } +- if (_y != other._y) +- { +- return false; +- } +- if (_goNorth != other._goNorth) +- { +- return false; +- } +- if (_goEast != other._goEast) +- { +- return false; +- } +- if (_goSouth != other._goSouth) +- { +- return false; +- } +- if (_goWest != other._goWest) +- { +- return false; +- } +- if (_geoHeight != other._geoHeight) +- { +- return false; +- } +- return true; +- } +-} +Index: java/org/l2jmobius/gameserver/model/actor/Creature.java +=================================================================== +--- java/org/l2jmobius/gameserver/model/actor/Creature.java (revision 8399) ++++ java/org/l2jmobius/gameserver/model/actor/Creature.java (working copy) +@@ -66,8 +66,6 @@ + import org.l2jmobius.gameserver.enums.TeleportWhereType; + import org.l2jmobius.gameserver.enums.UserInfoType; + import org.l2jmobius.gameserver.geoengine.GeoEngine; +-import org.l2jmobius.gameserver.geoengine.GeoEnginePathfinding; +-import org.l2jmobius.gameserver.geoengine.pathfinding.AbstractNodeLoc; + import org.l2jmobius.gameserver.instancemanager.IdManager; + import org.l2jmobius.gameserver.instancemanager.MapRegionManager; + import org.l2jmobius.gameserver.instancemanager.QuestManager; +@@ -2577,7 +2575,7 @@ + + public boolean disregardingGeodata; + public int onGeodataPathIndex; +- public List geoPath; ++ public List geoPath; + public int geoPathAccurateTx; + public int geoPathAccurateTy; + public int geoPathGtx; +@@ -3466,7 +3464,7 @@ + if (((originalDistance - distance) > 30) && !isControlBlocked() && !isInVehicle) + { + // Path calculation -- overrides previous movement check +- m.geoPath = GeoEnginePathfinding.getInstance().findPath(curX, curY, curZ, originalX, originalY, originalZ, getInstanceWorld()); ++ m.geoPath = GeoEngine.getInstance().findPath(curX, curY, curZ, originalX, originalY, originalZ, getInstanceWorld()); + if ((m.geoPath == null) || (m.geoPath.size() < 2)) // No path found + { + if (isPlayer() && !_isFlying && !isInWater) +Index: java/org/l2jmobius/gameserver/model/actor/instance/PlayerInstance.java +=================================================================== +--- java/org/l2jmobius/gameserver/model/actor/instance/PlayerInstance.java (revision 8397) ++++ java/org/l2jmobius/gameserver/model/actor/instance/PlayerInstance.java (working copy) +@@ -12704,7 +12704,7 @@ + { + if (Config.CORRECT_PLAYER_Z) + { +- final int nearestZ = GeoEngine.getInstance().getHigherHeight(getX(), getY(), getZ()); ++ final int nearestZ = GeoEngine.getInstance().getHeightNearest(getX(), getY(), getZ()); + if (getZ() < nearestZ) + { + teleToLocation(new Location(getX(), getY(), nearestZ)); +Index: java/org/l2jmobius/gameserver/util/GeoUtils.java +=================================================================== +--- java/org/l2jmobius/gameserver/util/GeoUtils.java (revision 8397) ++++ java/org/l2jmobius/gameserver/util/GeoUtils.java (working copy) +@@ -19,7 +19,7 @@ + import java.awt.Color; + + import org.l2jmobius.gameserver.geoengine.GeoEngine; +-import org.l2jmobius.gameserver.geoengine.geodata.Cell; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; + import org.l2jmobius.gameserver.model.actor.instance.PlayerInstance; + import org.l2jmobius.gameserver.network.serverpackets.ExServerPrimitive; + +@@ -26,25 +26,25 @@ + /** + * @author HorridoJoho + */ +-public final class GeoUtils ++public class GeoUtils + { + public static void debug2DLine(PlayerInstance player, int x, int y, int tx, int ty, int z) + { +- final int gx = GeoEngine.getInstance().getGeoX(x); +- final int gy = GeoEngine.getInstance().getGeoY(y); ++ final int gx = GeoEngine.getGeoX(x); ++ final int gy = GeoEngine.getGeoY(y); + +- final int tgx = GeoEngine.getInstance().getGeoX(tx); +- final int tgy = GeoEngine.getInstance().getGeoY(ty); ++ final int tgx = GeoEngine.getGeoX(tx); ++ final int tgy = GeoEngine.getGeoY(ty); + + final ExServerPrimitive prim = new ExServerPrimitive("Debug2DLine", x, y, z); +- prim.addLine(Color.BLUE, GeoEngine.getInstance().getWorldX(gx), GeoEngine.getInstance().getWorldY(gy), z, GeoEngine.getInstance().getWorldX(tgx), GeoEngine.getInstance().getWorldY(tgy), z); ++ prim.addLine(Color.BLUE, GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), z, GeoEngine.getWorldX(tgx), GeoEngine.getWorldY(tgy), z); + + final LinePointIterator iter = new LinePointIterator(gx, gy, tgx, tgy); + + while (iter.next()) + { +- final int wx = GeoEngine.getInstance().getWorldX(iter.x()); +- final int wy = GeoEngine.getInstance().getWorldY(iter.y()); ++ final int wx = GeoEngine.getWorldX(iter.x()); ++ final int wy = GeoEngine.getWorldY(iter.y()); + + prim.addPoint(Color.RED, wx, wy, z); + } +@@ -53,21 +53,21 @@ + + public static void debug3DLine(PlayerInstance player, int x, int y, int z, int tx, int ty, int tz) + { +- final int gx = GeoEngine.getInstance().getGeoX(x); +- final int gy = GeoEngine.getInstance().getGeoY(y); ++ final int gx = GeoEngine.getGeoX(x); ++ final int gy = GeoEngine.getGeoY(y); + +- final int tgx = GeoEngine.getInstance().getGeoX(tx); +- final int tgy = GeoEngine.getInstance().getGeoY(ty); ++ final int tgx = GeoEngine.getGeoX(tx); ++ final int tgy = GeoEngine.getGeoY(ty); + + final ExServerPrimitive prim = new ExServerPrimitive("Debug3DLine", x, y, z); +- prim.addLine(Color.BLUE, GeoEngine.getInstance().getWorldX(gx), GeoEngine.getInstance().getWorldY(gy), z, GeoEngine.getInstance().getWorldX(tgx), GeoEngine.getInstance().getWorldY(tgy), tz); ++ prim.addLine(Color.BLUE, GeoEngine.getWorldX(gx), GeoEngine.getWorldY(gy), z, GeoEngine.getWorldX(tgx), GeoEngine.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.getInstance().getWorldX(prevX); +- int wy = GeoEngine.getInstance().getWorldY(prevY); ++ int wx = GeoEngine.getWorldX(prevX); ++ int wy = GeoEngine.getWorldY(prevY); + int wz = iter.z(); + prim.addPoint(Color.RED, wx, wy, wz); + +@@ -78,8 +78,8 @@ + + if ((curX != prevX) || (curY != prevY)) + { +- wx = GeoEngine.getInstance().getWorldX(curX); +- wy = GeoEngine.getInstance().getWorldY(curY); ++ wx = GeoEngine.getWorldX(curX); ++ wy = GeoEngine.getWorldY(curY); + wz = iter.z(); + + prim.addPoint(Color.RED, wx, wy, wz); +@@ -93,7 +93,7 @@ + + private static Color getDirectionColor(int x, int y, int z, int nswe) + { +- if (GeoEngine.getInstance().checkNearestNswe(x, y, z, nswe)) ++ if ((GeoEngine.getInstance().getNsweNearest(x, y, z) & nswe) == nswe) + { + return Color.GREEN; + } +@@ -109,9 +109,8 @@ + int iPacket = 0; + + ExServerPrimitive exsp = null; +- final GeoEngine ge = GeoEngine.getInstance(); +- final int playerGx = ge.getGeoX(player.getX()); +- final int playerGy = ge.getGeoY(player.getY()); ++ final int playerGx = GeoEngine.getGeoX(player.getX()); ++ final int playerGy = GeoEngine.getGeoY(player.getY()); + for (int dx = -geoRadius; dx <= geoRadius; ++dx) + { + for (int dy = -geoRadius; dy <= geoRadius; ++dy) +@@ -135,12 +134,12 @@ + final int gx = playerGx + dx; + final int gy = playerGy + dy; + +- final int x = ge.getWorldX(gx); +- final int y = ge.getWorldY(gy); +- final int z = ge.getNearestZ(gx, gy, player.getZ()); ++ final int x = GeoEngine.getWorldX(gx); ++ final int y = GeoEngine.getWorldY(gy); ++ final int z = GeoEngine.getInstance().getHeightNearest(gx, gy, player.getZ()); + + // north arrow +- Color col = getDirectionColor(gx, gy, z, Cell.NSWE_NORTH); ++ Color col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_N); + 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); +@@ -147,7 +146,7 @@ + exsp.addLine(col, x - 4, y - 4, z, x + 4, y - 4, z); + + // east arrow +- col = getDirectionColor(gx, gy, z, Cell.NSWE_EAST); ++ col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_E); + 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); +@@ -154,13 +153,13 @@ + exsp.addLine(col, x + 4, y - 4, z, x + 4, y + 4, z); + + // south arrow +- col = getDirectionColor(gx, gy, z, Cell.NSWE_SOUTH); ++ col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_S); + 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, Cell.NSWE_WEST); ++ col = getDirectionColor(gx, gy, z, GeoStructure.CELL_FLAG_W); + 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); +@@ -188,15 +187,15 @@ + { + if (y > lastY) + { +- return Cell.NSWE_SOUTH_EAST; ++ return GeoStructure.CELL_FLAG_SE; // Direction.SOUTH_EAST; + } + else if (y < lastY) + { +- return Cell.NSWE_NORTH_EAST; ++ return GeoStructure.CELL_FLAG_NE; // Direction.NORTH_EAST; + } + else + { +- return Cell.NSWE_EAST; ++ return GeoStructure.CELL_FLAG_E; // Direction.EAST; + } + } + else if (x < lastX) // west +@@ -203,26 +202,27 @@ + { + if (y > lastY) + { +- return Cell.NSWE_SOUTH_WEST; ++ return GeoStructure.CELL_FLAG_SW; // Direction.SOUTH_WEST; + } + else if (y < lastY) + { +- return Cell.NSWE_NORTH_WEST; ++ return GeoStructure.CELL_FLAG_NW; // Direction.NORTH_WEST; + } + else + { +- return Cell.NSWE_WEST; ++ return GeoStructure.CELL_FLAG_W; // Direction.WEST; + } + } +- else // unchanged x ++ else ++ // unchanged x + { + if (y > lastY) + { +- return Cell.NSWE_SOUTH; ++ return GeoStructure.CELL_FLAG_S; // Direction.SOUTH; + } + else if (y < lastY) + { +- return Cell.NSWE_NORTH; ++ return GeoStructure.CELL_FLAG_N; // Direction.NORTH; + } + else + { +Index: java/org/l2jmobius/tools/geodataconverter/GeoDataConverter.java +=================================================================== +--- java/org/l2jmobius/tools/geodataconverter/GeoDataConverter.java (nonexistent) ++++ java/org/l2jmobius/tools/geodataconverter/GeoDataConverter.java (working copy) +@@ -0,0 +1,386 @@ ++/* ++ * This file is part of the L2J Mobius project. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ */ ++package org.l2jmobius.tools.geodataconverter; ++ ++import java.io.BufferedOutputStream; ++import java.io.File; ++import java.io.FileOutputStream; ++import java.io.RandomAccessFile; ++import java.nio.ByteOrder; ++import java.nio.MappedByteBuffer; ++import java.nio.channels.FileChannel; ++import java.util.Scanner; ++ ++import org.l2jmobius.Config; ++import org.l2jmobius.commons.util.PropertiesParser; ++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.GeoFormat; ++import org.l2jmobius.gameserver.geoengine.geodata.GeoStructure; ++import org.l2jmobius.gameserver.model.World; ++ ++/** ++ * @author Hasha ++ */ ++public class GeoDataConverter ++{ ++ private static GeoFormat _format; ++ private static ABlock[][] _blocks; ++ ++ public static void main(String[] args) ++ { ++ // initialize config ++ loadGeoengineConfigs(); ++ ++ // get geodata format ++ String type = ""; ++ try (Scanner scn = new Scanner(System.in)) ++ { ++ while (!(type.equalsIgnoreCase("J") || type.equalsIgnoreCase("O") || type.equalsIgnoreCase("E"))) ++ { ++ System.out.println("GeoDataConverter: Select source geodata type:"); ++ System.out.println(" J: L2J (e.g. 23_22.l2j)"); ++ System.out.println(" O: L2OFF (e.g. 23_22_conv.dat)"); ++ System.out.println(" E: Exit"); ++ System.out.print("Choice: "); ++ type = scn.next(); ++ } ++ } ++ if (type.equalsIgnoreCase("E")) ++ { ++ System.exit(0); ++ } ++ ++ _format = type.equalsIgnoreCase("J") ? GeoFormat.L2J : GeoFormat.L2OFF; ++ ++ // start conversion ++ System.out.println("GeoDataConverter: Converting all " + _format + " files."); ++ ++ // initialize geodata container ++ _blocks = new ABlock[GeoStructure.REGION_BLOCKS_X][GeoStructure.REGION_BLOCKS_Y]; ++ ++ // initialize multilayer temporarily buffer ++ BlockMultilayer.initialize(); ++ ++ // load geo files ++ int converted = 0; ++ for (int rx = World.TILE_X_MIN; rx <= World.TILE_X_MAX; rx++) ++ { ++ for (int ry = World.TILE_Y_MIN; ry <= World.TILE_Y_MAX; ry++) ++ { ++ final String input = String.format(_format.getFilename(), rx, ry); ++ final String filepath = Config.GEODATA_PATH; ++ final File f = new File(filepath + input); ++ if (f.exists() && !f.isDirectory()) ++ { ++ // load geodata ++ if (!loadGeoBlocks(input)) ++ { ++ System.out.println("GeoDataConverter: Unable to load " + input + " region file."); ++ continue; ++ } ++ ++ // recalculate nswe ++ if (!recalculateNswe()) ++ { ++ System.out.println("GeoDataConverter: Unable to convert " + input + " region file."); ++ continue; ++ } ++ ++ // save geodata ++ final String output = String.format(GeoFormat.L2D.getFilename(), rx, ry); ++ if (!saveGeoBlocks(output)) ++ { ++ System.out.println("GeoDataConverter: Unable to save " + output + " region file."); ++ continue; ++ } ++ ++ converted++; ++ System.out.println("GeoDataConverter: Created " + output + " region file."); ++ } ++ } ++ } ++ System.out.println("GeoDataConverter: Converted " + converted + " " + _format + " to L2D region file(s)."); ++ ++ // release multilayer block temporarily buffer ++ BlockMultilayer.release(); ++ } ++ ++ /** ++ * Loads geo blocks from buffer of the region file. ++ * @param filename : The name of the to load. ++ * @return boolean : True when successful. ++ */ ++ private static boolean loadGeoBlocks(String filename) ++ { ++ // region file is load-able, try to load it ++ try (RandomAccessFile raf = new RandomAccessFile(Config.GEODATA_PATH + filename, "r"); ++ FileChannel fc = raf.getChannel()) ++ { ++ final MappedByteBuffer buffer = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()).load(); ++ buffer.order(ByteOrder.LITTLE_ENDIAN); ++ ++ // load 18B header for L2off geodata (1st and 2nd byte...region X and Y) ++ if (_format == GeoFormat.L2OFF) ++ { ++ for (int i = 0; i < 18; i++) ++ { ++ buffer.get(); ++ } ++ } ++ ++ // 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 (_format == GeoFormat.L2J) ++ { ++ // get block type ++ final byte type = buffer.get(); ++ ++ // load block according to block type ++ switch (type) ++ { ++ case GeoStructure.TYPE_FLAT_L2J_L2OFF: ++ { ++ _blocks[ix][iy] = new BlockFlat(buffer, _format); ++ break; ++ } ++ case GeoStructure.TYPE_COMPLEX_L2J: ++ { ++ _blocks[ix][iy] = new BlockComplex(buffer, _format); ++ break; ++ } ++ case GeoStructure.TYPE_MULTILAYER_L2J: ++ { ++ _blocks[ix][iy] = new BlockMultilayer(buffer, _format); ++ break; ++ } ++ default: ++ { ++ throw new IllegalArgumentException("Unknown block type: " + type); ++ } ++ } ++ } ++ else ++ { ++ // get block type ++ final short type = buffer.getShort(); ++ ++ // load block according to block type ++ switch (type) ++ { ++ case GeoStructure.TYPE_FLAT_L2J_L2OFF: ++ { ++ _blocks[ix][iy] = new BlockFlat(buffer, _format); ++ break; ++ } ++ case GeoStructure.TYPE_COMPLEX_L2OFF: ++ { ++ _blocks[ix][iy] = new BlockComplex(buffer, _format); ++ break; ++ } ++ default: ++ { ++ _blocks[ix][iy] = new BlockMultilayer(buffer, _format); ++ break; ++ } ++ } ++ } ++ } ++ } ++ ++ if (buffer.remaining() > 0) ++ { ++ System.out.println("GeoDataConverter: Region file " + filename + " can be corrupted, remaining " + buffer.remaining() + " bytes to read."); ++ return false; ++ } ++ ++ return true; ++ } ++ catch (Exception e) ++ { ++ System.out.println("GeoDataConverter: Error while loading " + filename + " region file."); ++ return false; ++ } ++ } ++ ++ /** ++ * Recalculate diagonal flags for the region file. ++ * @return boolean : True when successful. ++ */ ++ private static boolean recalculateNswe() ++ { ++ try ++ { ++ for (int x = 0; x < GeoStructure.REGION_CELLS_X; x++) ++ { ++ for (int y = 0; y < GeoStructure.REGION_CELLS_Y; y++) ++ { ++ // get block ++ final ABlock block = _blocks[x / GeoStructure.BLOCK_CELLS_X][y / GeoStructure.BLOCK_CELLS_Y]; ++ ++ // skip flat blocks ++ if (block instanceof BlockFlat) ++ { ++ continue; ++ } ++ ++ // for complex and multilayer blocks go though all layers ++ short height = Short.MAX_VALUE; ++ int index; ++ while ((index = block.getIndexBelow(x, y, height)) != -1) ++ { ++ // get height and nswe ++ height = block.getHeight(index); ++ byte nswe = block.getNswe(index); ++ ++ // update nswe with diagonal flags ++ nswe = updateNsweBelow(x, y, height, nswe); ++ ++ // set nswe of the cell ++ block.setNswe(index, nswe); ++ } ++ } ++ } ++ ++ return true; ++ } ++ catch (Exception e) ++ { ++ return false; ++ } ++ } ++ ++ /** ++ * Updates the NSWE flag with diagonal flags. ++ * @param x : Geodata X coordinate. ++ * @param y : Geodata Y coordinate. ++ * @param z : Geodata Z coordinate. ++ * @param nsweValue : NSWE flag to be updated. ++ * @return byte : Updated NSWE flag. ++ */ ++ private static byte updateNsweBelow(int x, int y, short z, byte nsweValue) ++ { ++ byte nswe = nsweValue; ++ ++ // calculate virtual layer height ++ final short height = (short) (z + GeoStructure.CELL_IGNORE_HEIGHT); ++ ++ // get NSWE of neighbor cells below virtual layer (NPC/PC can fall down of clif, but can not climb it -> NSWE of cell below) ++ final byte nsweN = getNsweBelow(x, y - 1, height); ++ final byte nsweS = getNsweBelow(x, y + 1, height); ++ final byte nsweW = getNsweBelow(x - 1, y, height); ++ final byte nsweE = getNsweBelow(x + 1, y, height); ++ ++ // north-west ++ if ((((nswe & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_W) != 0)) || (((nswe & GeoStructure.CELL_FLAG_W) != 0) && ((nsweW & GeoStructure.CELL_FLAG_N) != 0))) ++ { ++ nswe |= GeoStructure.CELL_FLAG_NW; ++ } ++ ++ // north-east ++ if ((((nswe & GeoStructure.CELL_FLAG_N) != 0) && ((nsweN & GeoStructure.CELL_FLAG_E) != 0)) || (((nswe & GeoStructure.CELL_FLAG_E) != 0) && ((nsweE & GeoStructure.CELL_FLAG_N) != 0))) ++ { ++ nswe |= GeoStructure.CELL_FLAG_NE; ++ } ++ ++ // south-west ++ if ((((nswe & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_W) != 0)) || (((nswe & GeoStructure.CELL_FLAG_W) != 0) && ((nsweW & GeoStructure.CELL_FLAG_S) != 0))) ++ { ++ nswe |= GeoStructure.CELL_FLAG_SW; ++ } ++ ++ // south-east ++ if ((((nswe & GeoStructure.CELL_FLAG_S) != 0) && ((nsweS & GeoStructure.CELL_FLAG_E) != 0)) || (((nswe & GeoStructure.CELL_FLAG_E) != 0) && ((nsweE & GeoStructure.CELL_FLAG_S) != 0))) ++ { ++ nswe |= GeoStructure.CELL_FLAG_SE; ++ } ++ ++ return nswe; ++ } ++ ++ private static byte getNsweBelow(int geoX, int geoY, short worldZ) ++ { ++ // out of geo coordinates ++ if ((geoX < 0) || (geoX >= GeoStructure.REGION_CELLS_X)) ++ { ++ return 0; ++ } ++ ++ // out of geo coordinates ++ if ((geoY < 0) || (geoY >= GeoStructure.REGION_CELLS_Y)) ++ { ++ return 0; ++ } ++ ++ // get block ++ final ABlock block = _blocks[geoX / GeoStructure.BLOCK_CELLS_X][geoY / GeoStructure.BLOCK_CELLS_Y]; ++ ++ // get index, when valid, return nswe ++ final int index = block.getIndexBelow(geoX, geoY, worldZ); ++ return index == -1 ? 0 : block.getNswe(index); ++ } ++ ++ /** ++ * Save region file to file. ++ * @param filename : The name of file to save. ++ * @return boolean : True when successful. ++ */ ++ private static boolean saveGeoBlocks(String filename) ++ { ++ try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(Config.GEODATA_PATH + filename), GeoStructure.REGION_BLOCKS * GeoStructure.BLOCK_CELLS * 3)) ++ { ++ // loop over region blocks and save each block ++ for (int ix = 0; ix < GeoStructure.REGION_BLOCKS_X; ix++) ++ { ++ for (int iy = 0; iy < GeoStructure.REGION_BLOCKS_Y; iy++) ++ { ++ _blocks[ix][iy].saveBlock(bos); ++ } ++ } ++ ++ // flush data to file ++ bos.flush(); ++ ++ return true; ++ } ++ catch (Exception e) ++ { ++ return false; ++ } ++ } ++ ++ private static void loadGeoengineConfigs() ++ { ++ final PropertiesParser geoData = new PropertiesParser(Config.GEOENGINE_CONFIG_FILE); ++ Config.GEODATA_PATH = geoData.getString("GeoDataPath", "./data/geodata/"); ++ Config.COORD_SYNCHRONIZE = geoData.getInt("CoordSynchronize", -1); ++ Config.PART_OF_CHARACTER_HEIGHT = geoData.getInt("PartOfCharacterHeight", 75); ++ Config.MAX_OBSTACLE_HEIGHT = geoData.getInt("MaxObstacleHeight", 32); ++ Config.PATHFINDING = geoData.getBoolean("PathFinding", true); ++ Config.PATHFIND_BUFFERS = geoData.getString("PathFindBuffers", "100x6;128x6;192x6;256x4;320x4;384x4;500x2"); ++ Config.BASE_WEIGHT = geoData.getInt("BaseWeight", 10); ++ Config.DIAGONAL_WEIGHT = geoData.getInt("DiagonalWeight", 14); ++ Config.OBSTACLE_MULTIPLIER = geoData.getInt("ObstacleMultiplier", 10); ++ Config.HEURISTIC_WEIGHT = geoData.getInt("HeuristicWeight", 20); ++ Config.MAX_ITERATIONS = geoData.getInt("MaxIterations", 3500); ++ } ++} +\ No newline at end of file