/* * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software * Foundation, either version 3 of the License, or (at your option) any later * version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License along with * this program. If not, see . */ package net.sf.l2j.gameserver; 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.Map; import java.util.StringTokenizer; import java.util.logging.Logger; import javolution.util.FastMap; import net.sf.l2j.Config; import net.sf.l2j.gameserver.datatables.DoorTable; import net.sf.l2j.gameserver.model.L2Object; import net.sf.l2j.gameserver.model.L2World; import net.sf.l2j.gameserver.model.Location; import net.sf.l2j.gameserver.model.actor.instance.L2DoorInstance; import net.sf.l2j.gameserver.model.actor.instance.L2PcInstance; import net.sf.l2j.gameserver.model.actor.instance.L2SiegeGuardInstance; import net.sf.l2j.util.Point3D; /** * * @author -Nemesiss- */ public class GeoEngine extends GeoData { private static Logger _log = Logger.getLogger(GeoData.class.getName()); private static GeoEngine _instance; private final static byte _e = 1; private final static byte _w = 2; private final static byte _s = 4; private final static byte _n = 8; private static Map _geodata = new FastMap(); private static Map _geodataIndex = new FastMap(); private static BufferedOutputStream _geoBugsOut; public static GeoEngine getInstance() { if(_instance == null) _instance = new GeoEngine(); return _instance; } public GeoEngine() { nInitGeodata(); } //Public Methods /** * @see net.sf.l2j.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 net.sf.l2j.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 net.sf.l2j.gameserver.GeoData#getSpawnHeight(int, int, int, int, int) */ @Override public short getSpawnHeight(int x, int y, int zmin, int zmax, int spawnid) { return nGetSpawnHeight((x - L2World.MAP_MIN_X) >> 4,(y - L2World.MAP_MIN_Y) >> 4,zmin,zmax,spawnid); } /** * @see net.sf.l2j.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 net.sf.l2j.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())) 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 net.sf.l2j.gameserver.GeoData#canSeeTarget(net.sf.l2j.gameserver.model.L2Object, net.sf.l2j.gameserver.model.L2Object) */ @Override public boolean canSeeTarget(L2Object cha, L2Object target) { // 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 L2SiegeGuardInstance) 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)) return false; if(target instanceof L2DoorInstance) return true; // door coordinates are hinge coords.. if(target instanceof L2SiegeGuardInstance) 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 net.sf.l2j.gameserver.GeoData#canSeeTargetDebug(net.sf.l2j.gameserver.model.actor.instance.L2PcInstance, net.sf.l2j.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 net.sf.l2j.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); } /** * @see net.sf.l2j.gameserver.GeoData#moveCheck(int, int, int, int, int, int) */ @Override public Location moveCheck(int x, int y, int z, int tx, int ty, int tz) { Location startpoint = new Location(x,y,z); if (DoorTable.getInstance().checkIfDoorsBetween(x,y,z,tx,ty,tz)) 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 net.sf.l2j.gameserver.GeoData#addGeoDataBug(net.sf.l2j.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) + 16; int ry = (gy >> 11) + 10; 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) { e.printStackTrace(); gm.sendMessage("GeoData bug save Failed!"); } } // Private Methods private 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); } 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.get(region) != null) return false; return true; } 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.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); // 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; next_y += inc_y; z += inc_z_directiony; //_log.warning("1: next_x:"+next_x+" next_y"+next_y); if (!nLOS(x,y,(int)z,inc_x,inc_y,inc_z_directionx+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; next_x += inc_x; z += inc_z_directionx; //_log.warning("3: next_x:"+next_x+" next_y"+next_y); if (!nLOS(x,y,(int)z,inc_x,inc_y,inc_z_directionx+inc_z_directiony,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; next_y += inc_y; z += inc_z_directiony; //_log.warning("1: next_x:"+next_x+" next_y"+next_y); if (!nLOS(x,y,(int)z,inc_x,inc_y,inc_z_directionx+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; next_x += inc_x; z += inc_z_directionx; //_log.warning("3: next_x:"+next_x+" next_y"+next_y); if (!nLOS(x,y,(int)z,inc_x,inc_y,inc_z_directionx+inc_z_directiony,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; next_y += inc_y; //_log.warning("2: 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 { 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; next_x += inc_x; //_log.warning("5: 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 { 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) { e.printStackTrace(); 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) { e.printStackTrace(); throw new Error("Failed to Read geo_index File."); } try { File geo_bugs = new File("./data/geodata/geo_bugs.txt"); _geoBugsOut = new BufferedOutputStream(new FileOutputStream(geo_bugs,true)); } catch (Exception e) { e.printStackTrace(); 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) { 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; try { // Create a read-only memory-mapped file FileChannel 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) { e.printStackTrace(); _log.warning("Failed to Load GeoFile at block: "+block+"\n"); return false; } 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+16) << 5) + (ry+10)); } /** * @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; //Geodata without index - it is just empty so index can be calculated on the fly if(_geodataIndex.get(region) == null) index = ((blockX << 8) + blockY)*3; //Get Index for current block of current geodata region else index = _geodataIndex.get(region).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; //Geodata without index - it is just empty so index can be calculated on the fly if(_geodataIndex.get(region) == null) index = ((blockX << 8) + blockY)*3; //Get Index for current block of current region geodata else index = _geodataIndex.get(region).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 if(_geodataIndex.get(region) == null) index = ((blockX << 8) + blockY)*3; //Get Index for current block of current region geodata else index = _geodataIndex.get(region).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, int spawnid) { short region = getRegionOffset(geox,geoy); int blockX = getBlock(geox); int blockY = getBlock(geoy); int cellX, cellY, index; short temph = Short.MIN_VALUE; //Geodata without index - it is just empty so index can be calculated on the fly if(_geodataIndex.get(region) == null) index = ((blockX << 8) + blockY)*3; //Get Index for current block of current region geodata else index = _geodataIndex.get(region).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+" SpawnId: "+spawnid+" 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+" SpawnId: "+spawnid+" 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; //Geodata without index - it is just empty so index can be calculated on the fly if(_geodataIndex.get(region) == null) index = ((blockX << 8) + blockY)*3; //Get Index for current block of current region geodata else index = _geodataIndex.get(region).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; //Geodata without index - it is just empty so index can be calculated on the fly if(_geodataIndex.get(region) == null) index = ((blockX << 8) + blockY)*3; //Get Index for current block of current region geodata else index = _geodataIndex.get(region).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 { if(debug) _log.warning("flatheight:"+geo.getShort(index)); return true; } 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 = false; 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-10 && (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 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; //Geodata without index - it is just empty so index can be calculated on the fly if(_geodataIndex.get(region) == null) index = ((blockX << 8) + blockY)*3; //Get Index for current block of current region geodata else index = _geodataIndex.get(region).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 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 & _e) == 0) return false; } else if (tx < x)//W { if ((NSWE & _w) == 0) return false; } if (ty > y)//S { if ((NSWE & _s) == 0) return false; } else if (ty < y)//N { if ((NSWE & _n) == 0) return false; } return true; } }