/* * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software * Foundation, either version 3 of the License, or (at your option) any later * version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License along with * this program. If not, see . */ package com.l2jserver.gameserver; import gnu.trove.TShortObjectHashMap; import java.io.BufferedOutputStream; import java.io.BufferedReader; import java.io.File; import java.io.FileOutputStream; import java.io.FileReader; import java.io.LineNumberReader; import java.io.RandomAccessFile; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.IntBuffer; import java.nio.MappedByteBuffer; import java.nio.channels.FileChannel; import java.util.StringTokenizer; import java.util.logging.Level; import java.util.logging.Logger; import com.l2jserver.Config; import com.l2jserver.gameserver.datatables.DoorTable; import com.l2jserver.gameserver.model.L2Object; import com.l2jserver.gameserver.model.L2Spawn; import com.l2jserver.gameserver.model.L2World; import com.l2jserver.gameserver.model.Location; import com.l2jserver.gameserver.model.actor.instance.L2DefenderInstance; import com.l2jserver.gameserver.model.actor.instance.L2DoorInstance; import com.l2jserver.gameserver.model.actor.instance.L2PcInstance; import com.l2jserver.gameserver.util.Point3D; /** * * @author -Nemesiss- */ public class GeoEngine extends GeoData { private static Logger _log = Logger.getLogger(GeoData.class.getName()); private static final byte EAST = 1; private static final byte WEST = 2; private static final byte SOUTH = 4; private static final byte NORTH = 8; private static final byte NSWE_ALL = 15; private static TShortObjectHashMap _geodata = new TShortObjectHashMap(); private static TShortObjectHashMap _geodataIndex = new TShortObjectHashMap(); private static BufferedOutputStream _geoBugsOut; public static GeoEngine getInstance() { return SingletonHolder._instance; } private GeoEngine() { nInitGeodata(); } //Public Methods /** * @see com.l2jserver.gameserver.GeoData#getType(int, int) */ @Override public short getType(int x, int y) { return nGetType((x - L2World.MAP_MIN_X) >> 4, (y - L2World.MAP_MIN_Y) >> 4); } /** * @see com.l2jserver.gameserver.GeoData#getHeight(int, int, int) */ @Override public short getHeight(int x, int y, int z) { return nGetHeight((x - L2World.MAP_MIN_X) >> 4, (y - L2World.MAP_MIN_Y) >> 4, z); } /** * @see com.l2jserver.gameserver.GeoData#getSpawnHeight(int, int, int, int, int) */ @Override public short getSpawnHeight(int x, int y, int zmin, int zmax, L2Spawn spawn) { return nGetSpawnHeight((x - L2World.MAP_MIN_X) >> 4, (y - L2World.MAP_MIN_Y) >> 4, zmin, zmax, spawn); } /** * @see com.l2jserver.gameserver.GeoData#geoPosition(int, int) */ @Override public String geoPosition(int x, int y) { int gx = (x - L2World.MAP_MIN_X) >> 4; int gy = (y - L2World.MAP_MIN_Y) >> 4; return "bx: " + getBlock(gx) + " by: " + getBlock(gy) + " cx: " + getCell(gx) + " cy: " + getCell(gy) + " region offset: " + getRegionOffset(gx, gy); } /** * @see com.l2jserver.gameserver.GeoData#canSeeTarget(L2Object, Point3D) */ @Override public boolean canSeeTarget(L2Object cha, Point3D target) { if (DoorTable.getInstance().checkIfDoorsBetween(cha.getX(), cha.getY(), cha.getZ(), target.getX(), target.getY(), target.getZ(), cha.getInstanceId())) return false; if (cha.getZ() >= target.getZ()) return canSeeTarget(cha.getX(), cha.getY(), cha.getZ(), target.getX(), target.getY(), target.getZ()); else return canSeeTarget(target.getX(), target.getY(), target.getZ(), cha.getX(), cha.getY(), cha.getZ()); } /** * @see com.l2jserver.gameserver.GeoData#canSeeTarget(com.l2jserver.gameserver.model.L2Object, com.l2jserver.gameserver.model.L2Object) */ @Override public boolean canSeeTarget(L2Object cha, L2Object target) { if (cha == null || target == null) return false; // To be able to see over fences and give the player the viewpoint // game client has, all coordinates are lifted 45 from ground. // Because of layer selection in LOS algorithm (it selects -45 there // and some layers can be very close...) do not change this without // changing the LOS code. // Basically the +45 is character height. Raid bosses are naturally higher, // dwarves shorter, but this should work relatively well. // If this is going to be improved, use e.g. // ((L2Character)cha).getTemplate().collisionHeight int z = cha.getZ() + 45; if (cha instanceof L2DefenderInstance) z += 30; // well they don't move closer to balcony fence at the moment :( int z2 = target.getZ() + 45; if (!(target instanceof L2DoorInstance) && DoorTable.getInstance().checkIfDoorsBetween(cha.getX(), cha.getY(), z, target.getX(), target.getY(), z2, cha.getInstanceId())) return false; if (target instanceof L2DoorInstance) return true; // door coordinates are hinge coords.. if (target instanceof L2DefenderInstance) z2 += 30; // well they don't move closer to balcony fence at the moment :( if (cha.getZ() >= target.getZ()) return canSeeTarget(cha.getX(), cha.getY(), z, target.getX(), target.getY(), z2); else return canSeeTarget(target.getX(), target.getY(), z2, cha.getX(), cha.getY(), z); } /** * @see com.l2jserver.gameserver.GeoData#canSeeTargetDebug(com.l2jserver.gameserver.model.actor.instance.L2PcInstance, com.l2jserver.gameserver.model.L2Object) */ @Override public boolean canSeeTargetDebug(L2PcInstance gm, L2Object target) { // comments: see above int z = gm.getZ() + 45; int z2 = target.getZ() + 45; if (target instanceof L2DoorInstance) { gm.sendMessage("door always true"); return true; // door coordinates are hinge coords.. } if (gm.getZ() >= target.getZ()) return canSeeDebug(gm, (gm.getX() - L2World.MAP_MIN_X) >> 4, (gm.getY() - L2World.MAP_MIN_Y) >> 4, z, (target.getX() - L2World.MAP_MIN_X) >> 4, (target.getY() - L2World.MAP_MIN_Y) >> 4, z2); else return canSeeDebug(gm, (target.getX() - L2World.MAP_MIN_X) >> 4, (target.getY() - L2World.MAP_MIN_Y) >> 4, z2, (gm.getX() - L2World.MAP_MIN_X) >> 4, (gm.getY() - L2World.MAP_MIN_Y) >> 4, z); } /** * @see com.l2jserver.gameserver.GeoData#getNSWE(int, int, int) */ @Override public short getNSWE(int x, int y, int z) { return nGetNSWE((x - L2World.MAP_MIN_X) >> 4, (y - L2World.MAP_MIN_Y) >> 4, z); } @Override public boolean canMoveFromToTarget(int x, int y, int z, int tx, int ty, int tz, int instanceId) { Location destiny = moveCheck(x, y, z, tx, ty, tz, instanceId); return (destiny.getX() == tx && destiny.getY() == ty && destiny.getZ() == tz); } /** * @see com.l2jserver.gameserver.GeoData#moveCheck(int, int, int, int, int, int, int) */ @Override public Location moveCheck(int x, int y, int z, int tx, int ty, int tz, int instanceId) { Location startpoint = new Location(x, y, z); if (DoorTable.getInstance().checkIfDoorsBetween(x, y, z, tx, ty, tz, instanceId)) return startpoint; Location destiny = new Location(tx, ty, tz); return moveCheck(startpoint, destiny, (x - L2World.MAP_MIN_X) >> 4, (y - L2World.MAP_MIN_Y) >> 4, z, (tx - L2World.MAP_MIN_X) >> 4, (ty - L2World.MAP_MIN_Y) >> 4, tz); } /** * @see com.l2jserver.gameserver.GeoData#addGeoDataBug(com.l2jserver.gameserver.model.actor.instance.L2PcInstance, java.lang.String) */ @Override public void addGeoDataBug(L2PcInstance gm, String comment) { int gx = (gm.getX() - L2World.MAP_MIN_X) >> 4; int gy = (gm.getY() - L2World.MAP_MIN_Y) >> 4; int bx = getBlock(gx); int by = getBlock(gy); int cx = getCell(gx); int cy = getCell(gy); int rx = (gx >> 11) + Config.WORLD_X_MIN; int ry = (gy >> 11) + Config.WORLD_X_MAX; String out = rx + ";" + ry + ";" + bx + ";" + by + ";" + cx + ";" + cy + ";" + gm.getZ() + ";" + comment + "\n"; try { _geoBugsOut.write(out.getBytes()); _geoBugsOut.flush(); gm.sendMessage("GeoData bug saved!"); } catch (Exception e) { _log.log(Level.WARNING, "", e); gm.sendMessage("GeoData bug save Failed!"); } } @Override public boolean canSeeTarget(int x, int y, int z, int tx, int ty, int tz) { return canSee((x - L2World.MAP_MIN_X) >> 4, (y - L2World.MAP_MIN_Y) >> 4, z, (tx - L2World.MAP_MIN_X) >> 4, (ty - L2World.MAP_MIN_Y) >> 4, tz); } @Override public boolean hasGeo(int x, int y) { int gx = (x - L2World.MAP_MIN_X) >> 4; int gy = (y - L2World.MAP_MIN_Y) >> 4; short region = getRegionOffset(gx, gy); if (_geodata.contains(region)) return true; return false; } private static boolean canSee(int x, int y, double z, int tx, int ty, int tz) { int dx = (tx - x); int dy = (ty - y); final double dz = (tz - z); final int distance2 = dx * dx + dy * dy; if (distance2 > 90000) // (300*300) 300*16 = 4800 in world coord { //Avoid too long check return false; } // very short checks: 9 => 144 world distance // this ensures NLOS function has enough points to calculate, // it might not work when distance is small and path vertical else if (distance2 < 82) { // 150 should be too deep/high. if (dz * dz > 22500) { short region = getRegionOffset(x, y); // geodata is loaded for region and mobs should have correct Z coordinate... // so there would likely be a floor in between the two if (_geodata.contains(region)) return false; } return true; } // Increment in Z coordinate when moving along X or Y axis // and not straight to the target. This is done because // calculation moves either in X or Y direction. final int inc_x = sign(dx); final int inc_y = sign(dy); dx = Math.abs(dx); dy = Math.abs(dy); final double inc_z_directionx = dz * dx / (distance2); final double inc_z_directiony = dz * dy / (distance2); // next_* are used in NLOS check from x,y int next_x = x; int next_y = y; // creates path to the target // calculation stops when next_* == target if (dx >= dy)// dy/dx <= 1 { int delta_A = 2 * dy; int d = delta_A - dx; int delta_B = delta_A - 2 * dx; for (int i = 0; i < dx; i++) { x = next_x; y = next_y; if (d > 0) { d += delta_B; next_x += inc_x; z += inc_z_directionx; if (!nLOS(x, y, (int) z, inc_x, 0, inc_z_directionx, tz, false)) return false; next_y += inc_y; z += inc_z_directiony; //_log.warning("1: next_x:"+next_x+" next_y"+next_y); if (!nLOS(next_x, y, (int) z, 0, inc_y, inc_z_directiony, tz, false)) return false; } else { d += delta_A; next_x += inc_x; //_log.warning("2: next_x:"+next_x+" next_y"+next_y); z += inc_z_directionx; if (!nLOS(x, y, (int) z, inc_x, 0, inc_z_directionx, tz, false)) return false; } } } else { int delta_A = 2 * dx; int d = delta_A - dy; int delta_B = delta_A - 2 * dy; for (int i = 0; i < dy; i++) { x = next_x; y = next_y; if (d > 0) { d += delta_B; next_y += inc_y; z += inc_z_directiony; if (!nLOS(x, y, (int) z, 0, inc_y, inc_z_directiony, tz, false)) return false; next_x += inc_x; z += inc_z_directionx; //_log.warning("3: next_x:"+next_x+" next_y"+next_y); if (!nLOS(x, next_y, (int) z, inc_x, 0, inc_z_directionx, tz, false)) return false; } else { d += delta_A; next_y += inc_y; //_log.warning("4: next_x:"+next_x+" next_y"+next_y); z += inc_z_directiony; if (!nLOS(x, y, (int) z, 0, inc_y, inc_z_directiony, tz, false)) return false; } } } return true; } /* * Debug function for checking if there's a line of sight between * two coordinates. * * Creates points for line of sight check (x,y,z towards target) and * in each point, layer and movement checks are made with NLOS function. * * Coordinates here are geodata x,y but z coordinate is world coordinate */ private static boolean canSeeDebug(L2PcInstance gm, int x, int y, double z, int tx, int ty, int tz) { int dx = (tx - x); int dy = (ty - y); final double dz = (tz - z); final int distance2 = dx * dx + dy * dy; if (distance2 > 90000) // (300*300) 300*16 = 4800 in world coord { //Avoid too long check gm.sendMessage("dist > 300"); return false; } // very short checks: 9 => 144 world distance // this ensures NLOS function has enough points to calculate, // it might not work when distance is small and path vertical else if (distance2 < 82) { // 150 should be too deep/high. if (dz * dz > 22500) { short region = getRegionOffset(x, y); // geodata is loaded for region and mobs should have correct Z coordinate... // so there would likely be a floor in between the two if (_geodata.get(region) != null) return false; } return true; } // Increment in Z coordinate when moving along X or Y axis // and not straight to the target. This is done because // calculation moves either in X or Y direction. final int inc_x = sign(dx); final int inc_y = sign(dy); dx = Math.abs(dx); dy = Math.abs(dy); final double inc_z_directionx = dz * dx / (distance2); final double inc_z_directiony = dz * dy / (distance2); gm.sendMessage("Los: from X: " + x + "Y: " + y + "--->> X: " + tx + " Y: " + ty); // next_* are used in NLOS check from x,y int next_x = x; int next_y = y; // creates path to the target // calculation stops when next_* == target if (dx >= dy)// dy/dx <= 1 { int delta_A = 2 * dy; int d = delta_A - dx; int delta_B = delta_A - 2 * dx; for (int i = 0; i < dx; i++) { x = next_x; y = next_y; if (d > 0) { d += delta_B; next_x += inc_x; z += inc_z_directionx; if (!nLOS(x, y, (int) z, inc_x, 0, inc_z_directionx, tz, true)) return false; next_y += inc_y; z += inc_z_directiony; //_log.warning("1: next_x:"+next_x+" next_y"+next_y); if (!nLOS(next_x, y, (int) z, 0, inc_y, inc_z_directiony, tz, true)) return false; } else { d += delta_A; next_x += inc_x; //_log.warning("2: next_x:"+next_x+" next_y"+next_y); z += inc_z_directionx; if (!nLOS(x, y, (int) z, inc_x, 0, inc_z_directionx, tz, true)) return false; } } } else { int delta_A = 2 * dx; int d = delta_A - dy; int delta_B = delta_A - 2 * dy; for (int i = 0; i < dy; i++) { x = next_x; y = next_y; if (d > 0) { d += delta_B; next_y += inc_y; z += inc_z_directiony; if (!nLOS(x, y, (int) z, 0, inc_y, inc_z_directiony, tz, true)) return false; next_x += inc_x; z += inc_z_directionx; //_log.warning("3: next_x:"+next_x+" next_y"+next_y); if (!nLOS(x, next_y, (int) z, inc_x, 0, inc_z_directionx, tz, true)) return false; } else { d += delta_A; next_y += inc_y; //_log.warning("4: next_x:"+next_x+" next_y"+next_y); z += inc_z_directiony; if (!nLOS(x, y, (int) z, 0, inc_y, inc_z_directiony, tz, true)) return false; } } } return true; } /* * MoveCheck */ private static Location moveCheck(Location startpoint, Location destiny, int x, int y, double z, int tx, int ty, int tz) { int dx = (tx - x); int dy = (ty - y); final int distance2 = dx * dx + dy * dy; if (distance2 == 0) return destiny; if (distance2 > 36100) // 190*190*16 = 3040 world coord { // Avoid too long check // Currently we calculate a middle point // for wyvern users and otherwise for comfort double divider = Math.sqrt((double) 30000 / distance2); tx = x + (int) (divider * dx); ty = y + (int) (divider * dy); int dz = (tz - startpoint.getZ()); tz = startpoint.getZ() + (int) (divider * dz); dx = (tx - x); dy = (ty - y); //return startpoint; } // Increment in Z coordinate when moving along X or Y axis // and not straight to the target. This is done because // calculation moves either in X or Y direction. final int inc_x = sign(dx); final int inc_y = sign(dy); dx = Math.abs(dx); dy = Math.abs(dy); //gm.sendMessage("MoveCheck: from X: "+x+ "Y: "+y+ "--->> X: "+tx+" Y: "+ty); // next_* are used in NcanMoveNext check from x,y int next_x = x; int next_y = y; double tempz = z; // creates path to the target, using only x or y direction // calculation stops when next_* == target if (dx >= dy)// dy/dx <= 1 { int delta_A = 2 * dy; int d = delta_A - dx; int delta_B = delta_A - 2 * dx; for (int i = 0; i < dx; i++) { x = next_x; y = next_y; if (d > 0) { d += delta_B; next_x += inc_x; tempz = nCanMoveNext(x, y, (int) z, next_x, next_y, tz); if (tempz == Double.MIN_VALUE) return new Location((x << 4) + L2World.MAP_MIN_X, (y << 4) + L2World.MAP_MIN_Y, (int) z); else z = tempz; next_y += inc_y; //_log.warning("2: next_x:"+next_x+" next_y"+next_y); tempz = nCanMoveNext(next_x, y, (int) z, next_x, next_y, tz); if (tempz == Double.MIN_VALUE) return new Location((x << 4) + L2World.MAP_MIN_X, (y << 4) + L2World.MAP_MIN_Y, (int) z); else z = tempz; } else { d += delta_A; next_x += inc_x; //_log.warning("3: next_x:"+next_x+" next_y"+next_y); tempz = nCanMoveNext(x, y, (int) z, next_x, next_y, tz); if (tempz == Double.MIN_VALUE) return new Location((x << 4) + L2World.MAP_MIN_X, (y << 4) + L2World.MAP_MIN_Y, (int) z); else z = tempz; } } } else { int delta_A = 2 * dx; int d = delta_A - dy; int delta_B = delta_A - 2 * dy; for (int i = 0; i < dy; i++) { x = next_x; y = next_y; if (d > 0) { d += delta_B; next_y += inc_y; tempz = nCanMoveNext(x, y, (int) z, next_x, next_y, tz); if (tempz == Double.MIN_VALUE) return new Location((x << 4) + L2World.MAP_MIN_X, (y << 4) + L2World.MAP_MIN_Y, (int) z); else z = tempz; next_x += inc_x; //_log.warning("5: next_x:"+next_x+" next_y"+next_y); tempz = nCanMoveNext(x, next_y, (int) z, next_x, next_y, tz); if (tempz == Double.MIN_VALUE) return new Location((x << 4) + L2World.MAP_MIN_X, (y << 4) + L2World.MAP_MIN_Y, (int) z); else z = tempz; } else { d += delta_A; next_y += inc_y; //_log.warning("6: next_x:"+next_x+" next_y"+next_y); tempz = nCanMoveNext(x, y, (int) z, next_x, next_y, tz); if (tempz == Double.MIN_VALUE) return new Location((x << 4) + L2World.MAP_MIN_X, (y << 4) + L2World.MAP_MIN_Y, (int) z); else z = tempz; } } } if (z == startpoint.getZ()) // geodata hasn't modified Z in any coordinate, i.e. doesn't exist return destiny; else return new Location(destiny.getX(), destiny.getY(), (int) z); } private static byte sign(int x) { if (x >= 0) return +1; else return -1; } //GeoEngine private static void nInitGeodata() { LineNumberReader lnr = null; try { _log.info("Geo Engine: - Loading Geodata..."); File Data = new File("./data/geodata/geo_index.txt"); if (!Data.exists()) return; lnr = new LineNumberReader(new BufferedReader(new FileReader(Data))); } catch (Exception e) { _log.log(Level.WARNING, "", e); throw new Error("Failed to Load geo_index File."); } String line; try { while ((line = lnr.readLine()) != null) { if (line.trim().length() == 0) continue; StringTokenizer st = new StringTokenizer(line, "_"); byte rx = Byte.parseByte(st.nextToken()); byte ry = Byte.parseByte(st.nextToken()); loadGeodataFile(rx, ry); } } catch (Exception e) { _log.log(Level.WARNING, "", e); throw new Error("Failed to Read geo_index File."); } finally { try { lnr.close(); } catch (Exception e) { } } try { File geo_bugs = new File("./data/geodata/geo_bugs.txt"); _geoBugsOut = new BufferedOutputStream(new FileOutputStream(geo_bugs, true)); } catch (Exception e) { _log.log(Level.WARNING, "", e); throw new Error("Failed to Load geo_bugs.txt File."); } } public static void unloadGeodata(byte rx, byte ry) { short regionoffset = (short) ((rx << 5) + ry); _geodataIndex.remove(regionoffset); _geodata.remove(regionoffset); } public static boolean loadGeodataFile(byte rx, byte ry) { if (rx < Config.WORLD_X_MIN || rx > Config.WORLD_X_MAX || ry < Config.WORLD_Y_MIN || ry > Config.WORLD_Y_MAX) { _log.warning("Failed to Load GeoFile: invalid region " + rx +","+ ry + "\n"); return false; } String fname = "./data/geodata/" + rx + "_" + ry + ".l2j"; short regionoffset = (short) ((rx << 5) + ry); _log.info("Geo Engine: - Loading: " + fname + " -> region offset: " + regionoffset + "X: " + rx + " Y: " + ry); File Geo = new File(fname); int size, index = 0, block = 0, flor = 0; FileChannel roChannel = null; try { // Create a read-only memory-mapped file roChannel = new RandomAccessFile(Geo, "r").getChannel(); size = (int) roChannel.size(); MappedByteBuffer geo; if (Config.FORCE_GEODATA) //Force O/S to Loads this buffer's content into physical memory. //it is not guarantee, because the underlying operating system may have paged out some of the buffer's data geo = roChannel.map(FileChannel.MapMode.READ_ONLY, 0, size).load(); else geo = roChannel.map(FileChannel.MapMode.READ_ONLY, 0, size); geo.order(ByteOrder.LITTLE_ENDIAN); if (size > 196608) { // Indexing geo files, so we will know where each block starts IntBuffer indexs = IntBuffer.allocate(65536); while (block < 65536) { byte type = geo.get(index); indexs.put(block, index); block++; index++; if (type == 0) index += 2; // 1x short else if (type == 1) index += 128; // 64 x short else { int b; for (b = 0; b < 64; b++) { byte layers = geo.get(index); index += (layers << 1) + 1; if (layers > flor) flor = layers; } } } _geodataIndex.put(regionoffset, indexs); } _geodata.put(regionoffset, geo); _log.info("Geo Engine: - Max Layers: " + flor + " Size: " + size + " Loaded: " + index); } catch (Exception e) { _log.log(Level.WARNING, "Failed to Load GeoFile at block: " + block, e); return false; } finally { try { roChannel.close(); } catch (Exception e) { } } return true; } //Geodata Methods /** * @param x * @param y * @return Region Offset */ private static short getRegionOffset(int x, int y) { int rx = x >> 11; // =/(256 * 8) int ry = y >> 11; return (short) (((rx + Config.WORLD_X_MIN) << 5) + (ry + Config.WORLD_Y_MIN)); } /** * @param pos * @return Block Index: 0-255 */ private static int getBlock(int geo_pos) { return (geo_pos >> 3) % 256; } /** * @param pos * @return Cell Index: 0-7 */ private static int getCell(int geo_pos) { return geo_pos % 8; } //Geodata Functions /** * @param x * @param y * @return Type of geo_block: 0-2 */ private static short nGetType(int x, int y) { short region = getRegionOffset(x, y); int blockX = getBlock(x); int blockY = getBlock(y); int index = 0; final IntBuffer idx = _geodataIndex.get(region); //Geodata without index - it is just empty so index can be calculated on the fly if (idx == null) index = ((blockX << 8) + blockY) * 3; //Get Index for current block of current geodata region else index = idx.get((blockX << 8) + blockY); //Buffer that Contains current Region GeoData ByteBuffer geo = _geodata.get(region); if (geo == null) { if (Config.DEBUG) _log.warning("Geo Region - Region Offset: " + region + " dosnt exist!!"); return 0; } return geo.get(index); } /** * @param x * @param y * @param z * @return Nearest Z */ private static short nGetHeight(int geox, int geoy, int z) { short region = getRegionOffset(geox, geoy); int blockX = getBlock(geox); int blockY = getBlock(geoy); int cellX, cellY, index; final IntBuffer idx = _geodataIndex.get(region); //Geodata without index - it is just empty so index can be calculated on the fly if (idx == null) index = ((blockX << 8) + blockY) * 3; //Get Index for current block of current region geodata else index = idx.get(((blockX << 8)) + (blockY)); //Buffer that Contains current Region GeoData ByteBuffer geo = _geodata.get(region); if (geo == null) { if (Config.DEBUG) _log.warning("Geo Region - Region Offset: " + region + " dosnt exist!!"); return (short) z; } //Read current block type: 0-flat,1-complex,2-multilevel byte type = geo.get(index); index++; if (type == 0)//flat return geo.getShort(index); else if (type == 1)//complex { cellX = getCell(geox); cellY = getCell(geoy); index += ((cellX << 3) + cellY) << 1; short height = geo.getShort(index); height = (short) (height & 0x0fff0); height = (short) (height >> 1); //height / 2 return height; } else //multilevel { cellX = getCell(geox); cellY = getCell(geoy); int offset = (cellX << 3) + cellY; while (offset > 0) { byte lc = geo.get(index); index += (lc << 1) + 1; offset--; } byte layers = geo.get(index); index++; short height = -1; if (layers <= 0 || layers > 125) { _log.warning("Broken geofile (case1), region: " + region + " - invalid layer count: " + layers + " at: " + geox + " " + geoy); return (short) z; } short temph = Short.MIN_VALUE; while (layers > 0) { height = geo.getShort(index); height = (short) (height & 0x0fff0); height = (short) (height >> 1); //height / 2 if ((z - temph) * (z - temph) > (z - height) * (z - height)) temph = height; layers--; index += 2; } return temph; } } /** * @param x * @param y * @param z * @return One layer higher Z than parameter Z */ private static short nGetUpperHeight(int geox, int geoy, int z) { short region = getRegionOffset(geox, geoy); int blockX = getBlock(geox); int blockY = getBlock(geoy); int cellX, cellY, index; //Geodata without index - it is just empty so index can be calculated on the fly final IntBuffer idx = _geodataIndex.get(region); if (idx == null) index = ((blockX << 8) + blockY) * 3; //Get Index for current block of current region geodata else index = idx.get(((blockX << 8)) + (blockY)); //Buffer that Contains current Region GeoData ByteBuffer geo = _geodata.get(region); if (geo == null) { if (Config.DEBUG) _log.warning("Geo Region - Region Offset: " + region + " dosnt exist!!"); return (short) z; } //Read current block type: 0-flat,1-complex,2-multilevel byte type = geo.get(index); index++; if (type == 0)//flat return geo.getShort(index); else if (type == 1)//complex { cellX = getCell(geox); cellY = getCell(geoy); index += ((cellX << 3) + cellY) << 1; short height = geo.getShort(index); height = (short) (height & 0x0fff0); height = (short) (height >> 1); //height / 2 return height; } else //multilevel { cellX = getCell(geox); cellY = getCell(geoy); int offset = (cellX << 3) + cellY; while (offset > 0) { byte lc = geo.get(index); index += (lc << 1) + 1; offset--; } byte layers = geo.get(index); index++; short height = -1; if (layers <= 0 || layers > 125) { _log.warning("Broken geofile (case1), region: " + region + " - invalid layer count: " + layers + " at: " + geox + " " + geoy); return (short) z; } short temph = Short.MAX_VALUE; while (layers > 0) // from higher to lower { height = geo.getShort(index); height = (short) (height & 0x0fff0); height = (short) (height >> 1); //height / 2 if (height < z) return temph; temph = height; layers--; index += 2; } return temph; } } /** * @param x * @param y * @param zmin * @param zmax * @return Z betwen zmin and zmax */ private static short nGetSpawnHeight(int geox, int geoy, int zmin, int zmax, L2Spawn spawn) { short region = getRegionOffset(geox, geoy); int blockX = getBlock(geox); int blockY = getBlock(geoy); int cellX, cellY, index; short temph = Short.MIN_VALUE; final IntBuffer idx = _geodataIndex.get(region); //Geodata without index - it is just empty so index can be calculated on the fly if (idx == null) index = ((blockX << 8) + blockY) * 3; //Get Index for current block of current region geodata else index = idx.get(((blockX << 8)) + (blockY)); //Buffer that Contains current Region GeoData ByteBuffer geo = _geodata.get(region); if (geo == null) { if (Config.DEBUG) _log.warning("Geo Region - Region Offset: " + region + " dosnt exist!!"); return (short) zmin; } //Read current block type: 0-flat,1-complex,2-multilevel byte type = geo.get(index); index++; if (type == 0)//flat temph = geo.getShort(index); else if (type == 1)//complex { cellX = getCell(geox); cellY = getCell(geoy); index += ((cellX << 3) + cellY) << 1; short height = geo.getShort(index); height = (short) (height & 0x0fff0); height = (short) (height >> 1); //height / 2 temph = height; } else //multilevel { cellX = getCell(geox); cellY = getCell(geoy); short height; int offset = (cellX << 3) + cellY; while (offset > 0) { byte lc = geo.get(index); index += (lc << 1) + 1; offset--; } //Read current block type: 0-flat,1-complex,2-multilevel byte layers = geo.get(index); index++; if (layers <= 0 || layers > 125) { _log.warning("Broken geofile (case2), region: " + region + " - invalid layer count: " + layers + " at: " + geox + " " + geoy); return (short) zmin; } while (layers > 0) { height = geo.getShort(index); height = (short) (height & 0x0fff0); height = (short) (height >> 1); //height / 2 if ((zmin - temph) * (zmin - temph) > (zmin - height) * (zmin - height)) temph = height; layers--; index += 2; } if (temph > zmax + 200 || temph < zmin - 200) { if (Config.DEBUG) _log.warning("SpawnHeight Error - Couldnt find correct layer to spawn NPC - GeoData or Spawnlist Bug!: zmin: " + zmin + " zmax: " + zmax + " value: " + temph + " Spawn: " + spawn + " at: " + geox + " : " + geoy); return (short) zmin; } } if (temph > zmax + 1000 || temph < zmin - 1000) { if (Config.DEBUG) _log.warning("SpawnHeight Error - Spawnlist z value is wrong or GeoData error: zmin: " + zmin + " zmax: " + zmax + " value: " + temph + " Spawn: " + spawn + " at: " + geox + " : " + geoy); return (short) zmin; } return temph; } /** * @param x * @param y * @param z * @param tx * @param ty * @param tz * @return True if char can move to (tx,ty,tz) */ private static double nCanMoveNext(int x, int y, int z, int tx, int ty, int tz) { short region = getRegionOffset(x, y); int blockX = getBlock(x); int blockY = getBlock(y); int cellX, cellY; short NSWE = 0; int index = 0; final IntBuffer idx = _geodataIndex.get(region); //Geodata without index - it is just empty so index can be calculated on the fly if (idx == null) index = ((blockX << 8) + blockY) * 3; //Get Index for current block of current region geodata else index = idx.get(((blockX << 8)) + (blockY)); //Buffer that Contains current Region GeoData ByteBuffer geo = _geodata.get(region); if (geo == null) { if (Config.DEBUG) _log.warning("Geo Region - Region Offset: " + region + " dosnt exist!!"); return z; } //Read current block type: 0-flat,1-complex,2-multilevel byte type = geo.get(index); index++; if (type == 0) //flat return geo.getShort(index); else if (type == 1) //complex { cellX = getCell(x); cellY = getCell(y); index += ((cellX << 3) + cellY) << 1; short height = geo.getShort(index); NSWE = (short) (height & 0x0F); height = (short) (height & 0x0fff0); height = (short) (height >> 1); //height / 2 if (checkNSWE(NSWE, x, y, tx, ty)) return height; else return Double.MIN_VALUE; } else //multilevel, type == 2 { cellX = getCell(x); cellY = getCell(y); int offset = (cellX << 3) + cellY; while (offset > 0) // iterates (too many times?) to get to layer count { byte lc = geo.get(index); index += (lc << 1) + 1; offset--; } byte layers = geo.get(index); //_log.warning("layers"+layers); index++; short height = -1; if (layers <= 0 || layers > 125) { _log.warning("Broken geofile (case3), region: " + region + " - invalid layer count: " + layers + " at: " + x + " " + y); return z; } short tempz = Short.MIN_VALUE; while (layers > 0) { height = geo.getShort(index); height = (short) (height & 0x0fff0); height = (short) (height >> 1); //height / 2 // searches the closest layer to current z coordinate if ((z - tempz) * (z - tempz) > (z - height) * (z - height)) { //layercurr = layers; tempz = height; NSWE = geo.getShort(index); NSWE = (short) (NSWE & 0x0F); } layers--; index += 2; } if (checkNSWE(NSWE, x, y, tx, ty)) return tempz; else return Double.MIN_VALUE; } } /** * @param x * @param y * @param z * @param inc_x * @param inc_y * @param tz * @return True if Char can see target */ private static boolean nLOS(int x, int y, int z, int inc_x, int inc_y, double inc_z, int tz, boolean debug) { short region = getRegionOffset(x, y); int blockX = getBlock(x); int blockY = getBlock(y); int cellX, cellY; short NSWE = 0; int index; final IntBuffer idx = _geodataIndex.get(region); //Geodata without index - it is just empty so index can be calculated on the fly if (idx == null) index = ((blockX << 8) + blockY) * 3; //Get Index for current block of current region geodata else index = idx.get(((blockX << 8)) + (blockY)); //Buffer that Contains current Region GeoData ByteBuffer geo = _geodata.get(region); if (geo == null) { if (Config.DEBUG) _log.warning("Geo Region - Region Offset: " + region + " dosnt exist!!"); return true; } //Read current block type: 0-flat,1-complex,2-multilevel byte type = geo.get(index); index++; if (type == 0) //flat, movement and sight always possible { short height = geo.getShort(index); if (debug) _log.warning("flatheight:" + height); if (z > height) return z+inc_z > height; else return z+inc_z < height; } else if (type == 1) //complex { cellX = getCell(x); cellY = getCell(y); index += ((cellX << 3) + cellY) << 1; short height = geo.getShort(index); NSWE = (short) (height & 0x0F); height = (short) (height & 0x0fff0); height = (short) (height >> 1); //height / 2 if (!checkNSWE(NSWE, x, y, x + inc_x, y + inc_y)) { if (debug) _log.warning("height:" + height + " z" + z); if (z < nGetUpperHeight(x + inc_x, y + inc_y, height)) return false; // an obstacle high enough return true; } else return true; } else //multilevel, type == 2 { cellX = getCell(x); cellY = getCell(y); int offset = (cellX << 3) + cellY; while (offset > 0) // iterates (too many times?) to get to layer count { byte lc = geo.get(index); index += (lc << 1) + 1; offset--; } byte layers = geo.get(index); index++; short tempZ = -1; if (layers <= 0 || layers > 125) { _log.warning("Broken geofile (case4), region: " + region + " - invalid layer count: " + layers + " at: " + x + " " + y); return false; } short upperHeight = Short.MAX_VALUE; // big positive value short lowerHeight = Short.MIN_VALUE; // big negative value byte temp_layers = layers; boolean highestlayer = true; while (temp_layers > 0) // from higher to lower { // reads tempZ for current layer, result in world z coordinate tempZ = geo.getShort(index); tempZ = (short) (tempZ & 0x0fff0); tempZ = (short) (tempZ >> 1); //tempZ / 2 if (z > tempZ) { lowerHeight = tempZ; NSWE = geo.getShort(index); NSWE = (short) (NSWE & 0x0F); break; } else { highestlayer = false; upperHeight = tempZ; } temp_layers--; index += 2; } if (debug) _log.warning("z:" + z + " x: " + cellX + " y:" + cellY + " la " + layers + " lo:" + lowerHeight + " up:" + upperHeight); // Check if LOS goes under a layer/floor // clearly under layer but not too much under // lowerheight here only for geodata bug checking, layers very close? maybe could be removed if ((z - upperHeight) < -10 && (z - upperHeight) > inc_z - 20 && (z - lowerHeight) > 40) { if (debug) _log.warning("false, incz" + inc_z); return false; } // or there's a fence/wall ahead when we're not on highest layer if (!highestlayer) { //a probable wall, there's movement block and layers above you if (!checkNSWE(NSWE, x, y, x + inc_x, y + inc_y)) // cannot move { if (debug) _log.warning("block and next in x" + inc_x + " y" + inc_y + " is:" + nGetUpperHeight(x + inc_x, y + inc_y, lowerHeight)); // check one inc_x inc_y further, for the height there if (z < nGetUpperHeight(x + inc_x, y + inc_y, lowerHeight)) return false; // a wall return true; // we see over it, e.g. a fence } else return true; } if (!checkNSWE(NSWE, x, y, x + inc_x, y + inc_y)) { // check one inc_x inc_y further, for the height there if (z < nGetUpperHeight(x + inc_x, y + inc_y, lowerHeight)) return false; // we hit an obstacle high enough return true; } else return true; } } /** * @param x * @param y * @param z * @return NSWE: 0-15 */ private static short nGetNSWE(int x, int y, int z) { short region = getRegionOffset(x, y); int blockX = getBlock(x); int blockY = getBlock(y); int cellX, cellY; short NSWE = 0; int index = 0; final IntBuffer idx = _geodataIndex.get(region); //Geodata without index - it is just empty so index can be calculated on the fly if (idx == null) index = ((blockX << 8) + blockY) * 3; //Get Index for current block of current region geodata else index = idx.get(((blockX << 8)) + (blockY)); //Buffer that Contains current Region GeoData ByteBuffer geo = _geodata.get(region); if (geo == null) { if (Config.DEBUG) _log.warning("Geo Region - Region Offset: " + region + " dosnt exist!!"); return 15; } //Read current block type: 0-flat,1-complex,2-multilevel byte type = geo.get(index); index++; if (type == 0)//flat return 15; else if (type == 1)//complex { cellX = getCell(x); cellY = getCell(y); index += ((cellX << 3) + cellY) << 1; short height = geo.getShort(index); NSWE = (short) (height & 0x0F); } else //multilevel { cellX = getCell(x); cellY = getCell(y); int offset = (cellX << 3) + cellY; while (offset > 0) { byte lc = geo.get(index); index += (lc << 1) + 1; offset--; } byte layers = geo.get(index); index++; short height = -1; if (layers <= 0 || layers > 125) { _log.warning("Broken geofile (case5), region: " + region + " - invalid layer count: " + layers + " at: " + x + " " + y); return 15; } short tempz = Short.MIN_VALUE; while (layers > 0) { height = geo.getShort(index); height = (short) (height & 0x0fff0); height = (short) (height >> 1); //height / 2 if ((z - tempz) * (z - tempz) > (z - height) * (z - height)) { tempz = height; NSWE = geo.get(index); NSWE = (short) (NSWE & 0x0F); } layers--; index += 2; } } return NSWE; } /** * @param x * @param y * @param z * @return array [0] - height, [1] - NSWE */ @Override public short getHeightAndNSWE(int x, int y, int z) { short region = getRegionOffset(x, y); int blockX = getBlock(x); int blockY = getBlock(y); int cellX, cellY; int index = 0; final IntBuffer idx = _geodataIndex.get(region); //Geodata without index - it is just empty so index can be calculated on the fly if (idx == null) index = ((blockX << 8) + blockY) * 3; //Get Index for current block of current region geodata else index = idx.get(((blockX << 8)) + (blockY)); //Buffer that Contains current Region GeoData ByteBuffer geo = _geodata.get(region); if (geo == null) { if (Config.DEBUG) _log.warning("Geo Region - Region Offset: " + region + " dosnt exist!!"); return (short)((z << 1) | NSWE_ALL); } //Read current block type: 0-flat,1-complex,2-multilevel byte type = geo.get(index); index++; if (type == 0)//flat return (short)((geo.getShort(index) << 1) | NSWE_ALL); else if (type == 1)//complex { cellX = getCell(x); cellY = getCell(y); index += ((cellX << 3) + cellY) << 1; return geo.getShort(index); } else //multilevel { cellX = getCell(x); cellY = getCell(y); int offset = (cellX << 3) + cellY; while (offset > 0) { byte lc = geo.get(index); index += (lc << 1) + 1; offset--; } byte layers = geo.get(index); index++; short height = -1; if (layers <= 0 || layers > 125) { _log.warning("Broken geofile (case1), region: " + region + " - invalid layer count: " + layers + " at: " + x + " " + y); return (short)((z << 1) | NSWE_ALL); } short temph = Short.MIN_VALUE; short result = 0; while (layers > 0) { short block = geo.getShort(index); height = (short) (block & 0x0fff0); height = (short) (height >> 1); //height / 2 if ((z - temph) * (z - temph) > (z - height) * (z - height)) { temph = height; result = block; } layers--; index += 2; } return result; } } /** * @param NSWE * @param x * @param y * @param tx * @param ty * @return True if NSWE dont block given direction */ private static boolean checkNSWE(short NSWE, int x, int y, int tx, int ty) { //Check NSWE if (NSWE == 15) return true; if (tx > x)//E { if ((NSWE & EAST) == 0) return false; } else if (tx < x)//W { if ((NSWE & WEST) == 0) return false; } if (ty > y)//S { if ((NSWE & SOUTH) == 0) return false; } else if (ty < y)//N { if ((NSWE & NORTH) == 0) return false; } return true; } @SuppressWarnings("synthetic-access") private static class SingletonHolder { protected static final GeoEngine _instance = new GeoEngine(); } }