Package l2p.gameserver.geodata

Source Code of l2p.gameserver.geodata.GeoEngine

package l2p.gameserver.geodata;

import java.io.File;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import l2p.Config;
import l2p.common.ParallelExecutor;
import l2p.gameserver.geodata.GeoOptimizer.BlockLink;
import l2p.gameserver.model.L2Object;
import l2p.gameserver.model.L2Territory;
import l2p.gameserver.model.L2World;
import l2p.gameserver.model.instances.L2DoorInstance;
import l2p.gameserver.modules.data.DoorTable;
import l2p.gameserver.taskmanager.MemoryWatchDog;
import l2p.util.GArray;
import l2p.util.Location;
import l2p.util.Log;
import l2p.util.Rnd;
import l2p.util.Util;

/**
* @Author: Diamond
* @CoAuthor: DRiN
* @Date: 01/03/2009
*/
public class GeoEngine
{
  private static final Logger log = Logger.getLogger(GeoEngine.class.getName());
  public static final byte EAST = 1, WEST = 2, SOUTH = 4, NORTH = 8, NSWE_ALL = 15, NSWE_NONE = 0;
  public static final byte BLOCKTYPE_FLAT = 0;
  public static final byte BLOCKTYPE_COMPLEX = 1;
  public static final byte BLOCKTYPE_MULTILEVEL = 2;
  public static final int BlocksInMap = 256 * 256;
  public static int MAX_LAYERS = 1; // меньше 1 быть не должно, что бы создавались временные массивы как минимум short[2]
  private static final int Door_MaxZDiff = 256;
  /**
   * Даный массив содержит всю геодату на сервере. <BR>
   * Первые 2 [][] (byte[*][*][][]) являются x и y региона.<BR>
   * Третий [] (byte[][][*][]) является контейнером для всех блоков в регионе.<BR>
   * Четвертый [] (byte[][][][*]) является блоком геодаты.<BR>
   */
  private static final byte[][][][][] geodata = new byte[L2World.WORLD_SIZE_X][L2World.WORLD_SIZE_Y][][][];

  public static short getType(int x, int y, int refIndex)
  {
    return NgetType(x - L2World.MAP_MIN_X >> 4, y - L2World.MAP_MIN_Y >> 4, refIndex);
  }

  public static int getHeight(Location loc, int refIndex)
  {
    return getHeight(loc.x, loc.y, loc.z, refIndex);
  }

  public static int getHeight(int x, int y, int z, int refIndex)
  {
    if(!Config.GEODATA_ENABLED)
    {
      return z;
    }
    return NgetHeight(x - L2World.MAP_MIN_X >> 4, y - L2World.MAP_MIN_Y >> 4, z, refIndex);
  }

  public static boolean canMoveToCoord(int x, int y, int z, int tx, int ty, int tz, int refIndex)
  {
    return canMove(x, y, z, tx, ty, tz, false, refIndex) == 0;
  }

  public static byte getNSWE(int x, int y, int z, int refIndex)
  {
    return NgetNSWE(x - L2World.MAP_MIN_X >> 4, y - L2World.MAP_MIN_Y >> 4, z, refIndex);
  }

  public static Location moveCheck(int x, int y, int z, int tx, int ty, int refIndex)
  {
    return MoveCheck(x, y, z, tx, ty, false, false, false, refIndex);
  }

  public static Location moveCheck(int x, int y, int z, int tx, int ty, boolean returnPrev, int refIndex)
  {
    return MoveCheck(x, y, z, tx, ty, false, false, returnPrev, refIndex);
  }

  public static Location moveCheckWithCollision(int x, int y, int z, int tx, int ty, int refIndex)
  {
    return MoveCheck(x, y, z, tx, ty, true, false, false, refIndex);
  }

  public static Location moveCheckWithCollision(int x, int y, int z, int tx, int ty, boolean returnPrev, int refIndex)
  {
    return MoveCheck(x, y, z, tx, ty, true, false, returnPrev, refIndex);
  }

  public static Location moveCheckBackward(int x, int y, int z, int tx, int ty, int refIndex)
  {
    return MoveCheck(x, y, z, tx, ty, false, true, false, refIndex);
  }

  public static Location moveCheckBackward(int x, int y, int z, int tx, int ty, boolean returnPrev, int refIndex)
  {
    return MoveCheck(x, y, z, tx, ty, false, true, returnPrev, refIndex);
  }

  public static Location moveCheckBackwardWithCollision(int x, int y, int z, int tx, int ty, int refIndex)
  {
    return MoveCheck(x, y, z, tx, ty, true, true, false, refIndex);
  }

  public static Location moveCheckBackwardWithCollision(int x, int y, int z, int tx, int ty, boolean returnPrev, int refIndex)
  {
    return MoveCheck(x, y, z, tx, ty, true, true, returnPrev, refIndex);
  }

  public static Location moveInWaterCheck(int x, int y, int z, int tx, int ty, int tz, int refIndex)
  {
    return MoveInWaterCheck(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, refIndex);
  }

  public static Location moveCheckForAI(Location loc1, Location loc2, int refIndex)
  {
    return MoveCheckForAI(loc1.x - L2World.MAP_MIN_X >> 4, loc1.y - L2World.MAP_MIN_Y >> 4, loc1.z, loc2.x - L2World.MAP_MIN_X >> 4, loc2.y - L2World.MAP_MIN_Y >> 4, refIndex);
  }

  public static Location moveCheckInAir(int x, int y, int z, int tx, int ty, int tz, float ColRadius, int refIndex)
  {
    int gx = x - L2World.MAP_MIN_X >> 4;
    int gy = y - L2World.MAP_MIN_Y >> 4;
    int tgx = tx - L2World.MAP_MIN_X >> 4;
    int tgy = ty - L2World.MAP_MIN_Y >> 4;
    int nz = NgetHeight(tgx, tgy, tz, refIndex);
    // Не даем опуститься ниже, чем пол + 32
    if(tz <= nz + 32)
    {
      tz = nz + 32;
    }
    Location result = canSee(gx, gy, z, tgx, tgy, tz, true, refIndex);
    if(result.equals(gx, gy, z))
    {
      return null;
    }
    return result.geo2world();
  }

  public static boolean canSeeTarget(L2Object actor, L2Object target, boolean air)
  {
    if(target == null)
    {
      return false;
    }
    // Костыль конечно, но решает кучу проблем с дверьми
    if(target instanceof GeoControl || actor.equals(target))
    {
      return true;
    }
    return canSeeCoord(actor, target.getX(), target.getY(), target.getZ() + (int) target.getColHeight() + 64/*, actor.isPlayer*/, air);
  }

  public static boolean canSeeCoord(L2Object actor, int tx, int ty, int tz, boolean air)
  {
    return actor != null && canSeeCoord(actor.getX(), actor.getY(), actor.getZ() + (int) actor.getColHeight() + 64, tx, ty, tz, air, actor.getReflection().getGeoIndex());
  }

  public static boolean canSeeCoord(int x, int y, int z, int tx, int ty, int tz, boolean air, int refIndex)
  {
    int mx = x - L2World.MAP_MIN_X >> 4;
    int my = y - L2World.MAP_MIN_Y >> 4;
    int tmx = tx - L2World.MAP_MIN_X >> 4;
    int tmy = ty - L2World.MAP_MIN_Y >> 4;
    return canSee(mx, my, z, tmx, tmy, tz, air, refIndex).equals(tmx, tmy, tz) && canSee(tmx, tmy, tz, mx, my, z, air, refIndex).equals(mx, my, z);
  }

  public static boolean canMoveWithCollision(int x, int y, int z, int tx, int ty, int tz, int refIndex)
  {
    return canMove(x, y, z, tx, ty, tz, true, refIndex) == 0;
  }

  /**
   * @param NSWE
   * @param x
   * @param y
   * @param tx
   * @param ty
   * @return True if NSWE dont block given direction
   */
  public static boolean checkNSWE(byte NSWE, int x, int y, int tx, int ty)
  {
    if(NSWE == NSWE_ALL)
    {
      return true;
    }
    if(NSWE == NSWE_NONE)
    {
      return false;
    }
    if(tx > x)
    {
      if((NSWE & EAST) == 0)
      {
        return false;
      }
    }
    else if(tx < x)
    {
      if((NSWE & WEST) == 0)
      {
        return false;
      }
    }
    if(ty > y)
    {
      if((NSWE & SOUTH) == 0)
      {
        return false;
      }
    }
    else if(ty < y)
    {
      if((NSWE & NORTH) == 0)
      {
        return false;
      }
    }
    return true;
  }

  public static String geoXYZ2Str(int _x, int _y, int _z)
  {
    return "(" + String.valueOf((_x << 4) + L2World.MAP_MIN_X + 8) + " " + String.valueOf((_y << 4) + L2World.MAP_MIN_Y + 8) + " " + _z + ")";
  }

  public static String NSWE2Str(byte nswe)
  {
    String result = "";
    if((nswe & NORTH) == NORTH)
    {
      result += "N";
    }
    if((nswe & SOUTH) == SOUTH)
    {
      result += "S";
    }
    if((nswe & WEST) == WEST)
    {
      result += "W";
    }
    if((nswe & EAST) == EAST)
    {
      result += "E";
    }
    return result.isEmpty() ? "X" : result;
  }

  private static boolean NLOS_WATER(int x, int y, int z, int next_x, int next_y, int next_z, int refIndex)
  {
    Layer[] layers1 = NGetLayers(x, y, refIndex);
    Layer[] layers2 = NGetLayers(next_x, next_y, refIndex);
    if(layers1.length == 0 || layers2.length == 0)
    {
      return true;
    }
    // Находим ближайший к целевой клетке слой
    short z2 = Short.MIN_VALUE;
    for(Layer layer : layers2)
    {
      if(Math.abs(next_z - z2) > Math.abs(next_z - layer.height))
      {
        z2 = layer.height;
      }
    }
    // Луч проходит над преградой
    if(next_z + 32 >= z2)
    {
      return true;
    }
    // Либо перед нами стена, либо над нами потолок. Ищем слой пониже, для уточнения
    short z3 = Short.MIN_VALUE;
    for(Layer layer : layers2)
    {
      if(layer.height < z2 + Config.MIN_LAYER_HEIGHT && Math.abs(next_z - z3) > Math.abs(next_z - layer.height))
      {
        z3 = layer.height;
      }
    }
    // Ниже нет слоев, значит это стена
    if(z3 == Short.MIN_VALUE)
    {
      return false;
    }
    // Собираем данные о предыдущей клетке, игнорируя верхние слои
    short z1 = Short.MIN_VALUE;
    byte NSWE1 = NSWE_ALL;
    for(Layer layer : layers1)
    {
      if(layer.height < z + Config.MIN_LAYER_HEIGHT && Math.abs(z - z1) > Math.abs(z - layer.height))
      {
        z1 = layer.height;
        NSWE1 = layer.nswe;
      }
    }
    // Невозможная ситуация, но все же...
    if(z1 < -30000)
    {
      return true;
    }
    // Если есть NSWE, то считаем за стену
    return checkNSWE(NSWE1, x, y, next_x, next_y);
  }

  private static int FindNearestLowerLayer(short[] layers, int z)
  {
    short h, nearest_layer_h = Short.MIN_VALUE;
    int nearest_layer = Integer.MIN_VALUE;
    for(int i = 1; i <= layers[0]; i++)
    {
      h = (short) ((short) (layers[i] & 0x0fff0) >> 1);
      if(h < z && nearest_layer_h < h)
      {
        nearest_layer_h = h;
        nearest_layer = layers[i];
      }
    }
    return nearest_layer;
  }

  private static short CheckNoOneLayerInRangeAndFindNearestLowerLayer(short[] layers, int z0, int z1)
  {
    int z_min, z_max;
    if(z0 > z1)
    {
      z_min = z1;
      z_max = z0;
    }
    else
    {
      z_min = z0;
      z_max = z1;
    }
    short h, nearest_layer = Short.MIN_VALUE, nearest_layer_h = Short.MIN_VALUE;
    for(int i = 1; i <= layers[0]; i++)
    {
      h = (short) ((short) (layers[i] & 0x0fff0) >> 1);
      if(z_min <= h && h <= z_max)
      {
        return Short.MIN_VALUE;
      }
      if(h < z0 && nearest_layer_h < h)
      {
        nearest_layer_h = h;
        nearest_layer = layers[i];
      }
    }
    return nearest_layer;
  }

  public static boolean canSeeWallCheck(Layer layer, Layer nearest_lower_neighbor, byte directionNSWE)
  {
    return (layer.nswe & directionNSWE) != 0 || layer.height <= nearest_lower_neighbor.height || Math.abs(layer.height - nearest_lower_neighbor.height) < Config.MAX_Z_DIFF;
  }

  public static boolean canSeeWallCheck(short layer, short nearest_lower_neighbor, byte directionNSWE, int curr_z, boolean air)
  {
    short nearest_lower_neighborh = (short) ((short) (nearest_lower_neighbor & 0x0fff0) >> 1);
    if(air)
    {
      return nearest_lower_neighborh < curr_z;
    }
    short layerh = (short) ((short) (layer & 0x0fff0) >> 1);
    int zdiff = nearest_lower_neighborh - layerh;
    return (layer & 0x0F & directionNSWE) != 0 || zdiff > -Config.MAX_Z_DIFF && zdiff != 0;
  }

  /**
   * проверка видимости
   *
   * @return возвращает последнюю точку которую видно (в формате геокоординат)
   *         в результате (Location) h является кодом, если >= 0 то успешно достигли последней точки, если меньше то не последней
   */
  public static Location canSee(int _x, int _y, int _z, int _tx, int _ty, int _tz, boolean air, int refIndex)
  {
    int diff_x = _tx - _x, diff_y = _ty - _y, diff_z = _tz - _z;
    int dx = Math.abs(diff_x), dy = Math.abs(diff_y);
    float steps = Math.max(dx, dy);
    int curr_x = _x, curr_y = _y, curr_z = _z;
    short[] curr_layers = new short[MAX_LAYERS + 1];
    NGetLayers(curr_x, curr_y, curr_layers, refIndex);
    Location result = new Location(_x, _y, _z, -1);
    if(steps == 0)
    {
      if(CheckNoOneLayerInRangeAndFindNearestLowerLayer(curr_layers, curr_z, curr_z + diff_z) != Short.MIN_VALUE)
      {
        result.set(_tx, _ty, _tz, 1);
      }
      return result;
    }
    float step_x = diff_x / steps, step_y = diff_y / steps, step_z = diff_z / steps;
    int half_step_z = (int) (step_z / 2);
    float next_x = curr_x, next_y = curr_y, next_z = curr_z;
    int i_next_x, i_next_y, i_next_z, middle_z;
    short[] tmp_layers = new short[MAX_LAYERS + 1];
    short src_nearest_lower_layer, dst_nearest_lower_layer, tmp_nearest_lower_layer;
    for(int i = 0; i < steps; i++)
    {
      if(curr_layers[0] == 0)
      {
        result.set(_tx, _ty, _tz, 0);
        return result; // Здесь нет геодаты, разрешаем
      }
      next_x += step_x;
      next_y += step_y;
      next_z += step_z;
      i_next_x = (int) (next_x + 0.5f);
      i_next_y = (int) (next_y + 0.5f);
      i_next_z = (int) (next_z + 0.5f);
      middle_z = curr_z + half_step_z;
      if((src_nearest_lower_layer = CheckNoOneLayerInRangeAndFindNearestLowerLayer(curr_layers, curr_z, middle_z)) == Short.MIN_VALUE)
      {
        return result.setH(-10);
      } // либо есть преграждающая поверхность, либо нет снизу слоя и значит это "пустота", то что за стеной или за колоной
      NGetLayers(curr_x, curr_y, curr_layers, refIndex);
      if(curr_layers[0] == 0)
      {
        result.set(_tx, _ty, _tz, 0);
        return result; // Здесь нет геодаты, разрешаем
      }
      if((dst_nearest_lower_layer = CheckNoOneLayerInRangeAndFindNearestLowerLayer(curr_layers, i_next_z, middle_z)) == Short.MIN_VALUE)
      {
        return result.setH(-11);
      } // либо есть преграда, либо нет снизу слоя и значит это "пустота", то что за стеной или за колоной
      if(curr_x == i_next_x)
      {
        //движемся по вертикали
        if(!canSeeWallCheck(src_nearest_lower_layer, dst_nearest_lower_layer, i_next_y > curr_y ? SOUTH : NORTH, curr_z, air))
        {
          return result.setH(-20);
        }
      }
      else if(curr_y == i_next_y)
      {
        //движемся по горизонтали
        if(!canSeeWallCheck(src_nearest_lower_layer, dst_nearest_lower_layer, i_next_x > curr_x ? EAST : WEST, curr_z, air))
        {
          return result.setH(-21);
        }
      }
      else
      {
        //движемся по диагонали
        NGetLayers(curr_x, i_next_y, tmp_layers, refIndex);
        if(tmp_layers[0] == 0)
        {
          result.set(_tx, _ty, _tz, 0);
          return result; // Здесь нет геодаты, разрешаем
        }
        if((tmp_nearest_lower_layer = CheckNoOneLayerInRangeAndFindNearestLowerLayer(tmp_layers, i_next_z, middle_z)) == Short.MIN_VALUE)
        {
          return result.setH(-30);
        } // либо есть преграда, либо нет снизу слоя и значит это "пустота", то что за стеной или за колоной
        if(!(canSeeWallCheck(src_nearest_lower_layer, tmp_nearest_lower_layer, i_next_y > curr_y ? SOUTH : NORTH, curr_z, air) && canSeeWallCheck(tmp_nearest_lower_layer, dst_nearest_lower_layer, i_next_x > curr_x ? EAST : WEST, curr_z, air)))
        {
          NGetLayers(i_next_x, curr_y, tmp_layers, refIndex);
          if(tmp_layers[0] == 0)
          {
            result.set(_tx, _ty, _tz, 0);
            return result; // Здесь нет геодаты, разрешаем
          }
          if((tmp_nearest_lower_layer = CheckNoOneLayerInRangeAndFindNearestLowerLayer(tmp_layers, i_next_z, middle_z)) == Short.MIN_VALUE)
          {
            return result.setH(-31);
          } // либо есть преграда, либо нет снизу слоя и значит это "пустота", то что за стеной или за колоной
          if(!canSeeWallCheck(src_nearest_lower_layer, tmp_nearest_lower_layer, i_next_x > curr_x ? EAST : WEST, curr_z, air))
          {
            return result.setH(-32);
          }
          if(!canSeeWallCheck(tmp_nearest_lower_layer, dst_nearest_lower_layer, i_next_x > curr_x ? EAST : WEST, curr_z, air))
          {
            return result.setH(-33);
          }
        }
      }
      result.set(curr_x, curr_y, curr_z);
      curr_x = i_next_x;
      curr_y = i_next_y;
      curr_z = i_next_z;
    }
    result.set(_tx, _ty, _tz, 0xFF);
    return result;
  }

  private static Location MoveInWaterCheck(int x, int y, int z, int tx, int ty, int tz, int refIndex)
  {
    int dx = tx - x;
    int dy = ty - y;
    int dz = tz - z;
    int inc_x = sign(dx);
    int inc_y = sign(dy);
    dx = Math.abs(dx);
    dy = Math.abs(dy);
    if(dx + dy == 0)
    {
      return new Location(x, y, z).geo2world();
    }
    float inc_z_for_x = dx == 0 ? 0 : dz / dx;
    float inc_z_for_y = dy == 0 ? 0 : dz / dy;
    int prev_x;
    int prev_y;
    int prev_z;
    int next_x = x;
    int next_y = y;
    int next_z = z;
    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++)
      {
        prev_x = x;
        prev_y = y;
        prev_z = z;
        x = next_x;
        y = next_y;
        z = next_z;
        if(d > 0)
        {
          d += delta_B;
          next_x += inc_x;
          next_z += inc_z_for_x;
          next_y += inc_y;
          next_z += inc_z_for_y;
        }
        else
        {
          d += delta_A;
          next_x += inc_x;
          next_z += inc_z_for_x;
        }
        if(!NLOS_WATER(x, y, z, next_x, next_y, next_z, refIndex))
        {
          return new Location(prev_x, prev_y, prev_z).geo2world();
        }
      }
    }
    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++)
      {
        prev_x = x;
        prev_y = y;
        prev_z = z;
        x = next_x;
        y = next_y;
        z = next_z;
        if(d > 0)
        {
          d += delta_B;
          next_x += inc_x;
          next_z += inc_z_for_x;
          next_y += inc_y;
          next_z += inc_z_for_y;
        }
        else
        {
          d += delta_A;
          next_y += inc_y;
          next_z += inc_z_for_y;
        }
        if(!NLOS_WATER(x, y, z, next_x, next_y, next_z, refIndex))
        {
          return new Location(prev_x, prev_y, prev_z).geo2world();
        }
      }
    }
    return new Location(next_x, next_y, next_z).geo2world();
  }

  /**
   * проверка проходимости по прямой
   *
   * @return 0 - проходимо, в ином случае код причины непроходимости (используется при отладке)
   */
  public static int canMove(int __x, int __y, int _z, int __tx, int __ty, int _tz, boolean withCollision, int refIndex)
  {
    int _x = __x - L2World.MAP_MIN_X >> 4;
    int _y = __y - L2World.MAP_MIN_Y >> 4;
    int _tx = __tx - L2World.MAP_MIN_X >> 4;
    int _ty = __ty - L2World.MAP_MIN_Y >> 4;
    int diff_x = _tx - _x, diff_y = _ty - _y, diff_z;
    int dx = Math.abs(diff_x), dy = Math.abs(diff_y), dz;
    float steps = Math.max(dx, dy);
    if(steps == 0)
    {
      return -5;
    }
    int curr_x = _x, curr_y = _y, curr_z = _z;
    short[] curr_layers = new short[MAX_LAYERS + 1];
    NGetLayers(curr_x, curr_y, curr_layers, refIndex);
    if(curr_layers[0] == 0)
    {
      return 0;
    }
    float step_x = diff_x / steps, step_y = diff_y / steps;
    float next_x = curr_x, next_y = curr_y;
    int i_next_x, i_next_y;
    short[] next_layers = new short[MAX_LAYERS + 1];
    short[] temp_layers = new short[MAX_LAYERS + 1];
    short[] curr_next_switcher;
    for(int i = 0; i < steps; i++)
    {
      next_x += step_x;
      next_y += step_y;
      i_next_x = (int) (next_x + 0.5f);
      i_next_y = (int) (next_y + 0.5f);
      NGetLayers(i_next_x, i_next_y, next_layers, refIndex);
      if((curr_z = NcanMoveNext(curr_x, curr_y, curr_z, curr_layers, i_next_x, i_next_y, next_layers, temp_layers, withCollision, refIndex)) == Integer.MIN_VALUE)
      {
        return 1;
      }
      curr_next_switcher = curr_layers;
      curr_layers = next_layers;
      next_layers = curr_next_switcher;
      curr_x = i_next_x;
      curr_y = i_next_y;
    }
    diff_z = curr_z - _tz;
    dz = Math.abs(diff_z);
    if(Config.ALLOW_FALL_FROM_WALLS)
    {
      return diff_z < Config.MAX_Z_DIFF ? 0 : diff_z * 10000;
    }
    return dz > Config.MAX_Z_DIFF ? dz * 1000 : 0;
  }

  public static Location MoveCheck(int __x, int __y, int _z, int __tx, int __ty, boolean withCollision, boolean backwardMove, boolean returnPrev, int refIndex)
  {
    int _x = __x - L2World.MAP_MIN_X >> 4;
    int _y = __y - L2World.MAP_MIN_Y >> 4;
    int _tx = __tx - L2World.MAP_MIN_X >> 4;
    int _ty = __ty - L2World.MAP_MIN_Y >> 4;
    int diff_x = _tx - _x, diff_y = _ty - _y;
    int dx = Math.abs(diff_x), dy = Math.abs(diff_y);
    float steps = Math.max(dx, dy);
    if(steps == 0)
    {
      return new Location(__x, __y, _z);
    }
    float step_x = diff_x / steps, step_y = diff_y / steps;
    int curr_x = _x, curr_y = _y, curr_z = _z;
    float next_x = curr_x, next_y = curr_y;
    int i_next_x, i_next_y, i_next_z;
    short[] next_layers = new short[MAX_LAYERS + 1];
    short[] temp_layers = new short[MAX_LAYERS + 1];
    short[] curr_layers = new short[MAX_LAYERS + 1];
    short[] curr_next_switcher;
    NGetLayers(curr_x, curr_y, curr_layers, refIndex);
    int prev_x = curr_x, prev_y = curr_y, prev_z = curr_z;
    for(int i = 0; i < steps; i++)
    {
      next_x += step_x;
      next_y += step_y;
      i_next_x = (int) (next_x + 0.5f);
      i_next_y = (int) (next_y + 0.5f);
      NGetLayers(i_next_x, i_next_y, next_layers, refIndex);
      if((i_next_z = NcanMoveNext(curr_x, curr_y, curr_z, curr_layers, i_next_x, i_next_y, next_layers, temp_layers, withCollision, refIndex)) == Integer.MIN_VALUE)
      {
        break;
      }
      if(backwardMove && NcanMoveNext(i_next_x, i_next_y, i_next_z, next_layers, curr_x, curr_y, curr_layers, temp_layers, withCollision, refIndex) == Integer.MIN_VALUE)
      {
        break;
      }
      curr_next_switcher = curr_layers;
      curr_layers = next_layers;
      next_layers = curr_next_switcher;
      if(returnPrev)
      {
        prev_x = curr_x;
        prev_y = curr_y;
        prev_z = curr_z;
      }
      curr_x = i_next_x;
      curr_y = i_next_y;
      curr_z = i_next_z;
    }
    if(returnPrev)
    {
      curr_x = prev_x;
      curr_y = prev_y;
      curr_z = prev_z;
    }
    //if(curr_x == _x && curr_y == _y)
    //  return new Location(__x, __y, _z);
    //log.info("move" + (backwardMove ? " back" : "") + (withCollision ? " +collision" : "") + ": " + curr_x + " " + curr_y + " " + curr_z + " / xyz: " + __x + " " + __y + " " + _z + " / to xy: " + __tx + " " + __ty + " / geo xy: " + _x + " " + _y + " / geo to xy: " + _tx + " " + _ty);
    return new Location(curr_x, curr_y, curr_z).geo2world();
  }

  /**
   * Аналогичен CanMove, но возвращает весь пройденный путь. В гео координатах.
   */
  public static ArrayList<Location> MoveList(int __x, int __y, int _z, int __tx, int __ty, int refIndex, boolean onlyFullPath)
  {
    int _x = __x - L2World.MAP_MIN_X >> 4;
    int _y = __y - L2World.MAP_MIN_Y >> 4;
    int _tx = __tx - L2World.MAP_MIN_X >> 4;
    int _ty = __ty - L2World.MAP_MIN_Y >> 4;
    int diff_x = _tx - _x, diff_y = _ty - _y;
    int dx = Math.abs(diff_x), dy = Math.abs(diff_y);
    float steps = Math.max(dx, dy);
    if(steps == 0) // Никуда не идем
    {
      return new ArrayList<Location>(0);
    }
    float step_x = diff_x / steps, step_y = diff_y / steps;
    int curr_x = _x, curr_y = _y, curr_z = _z;
    float next_x = curr_x, next_y = curr_y;
    int i_next_x, i_next_y, i_next_z;
    short[] next_layers = new short[MAX_LAYERS + 1];
    short[] temp_layers = new short[MAX_LAYERS + 1];
    short[] curr_layers = new short[MAX_LAYERS + 1];
    short[] curr_next_switcher;
    NGetLayers(curr_x, curr_y, curr_layers, refIndex);
    if(curr_layers[0] == 0)
    {
      return null;
    }
    ArrayList<Location> result = new ArrayList<Location>();
    result.add(new Location(curr_x, curr_y, curr_z)); // Первая точка
    for(int i = 0; i < steps; i++)
    {
      next_x += step_x;
      next_y += step_y;
      i_next_x = (int) (next_x + 0.5f);
      i_next_y = (int) (next_y + 0.5f);
      NGetLayers(i_next_x, i_next_y, next_layers, refIndex);
      if((i_next_z = NcanMoveNext(curr_x, curr_y, curr_z, curr_layers, i_next_x, i_next_y, next_layers, temp_layers, false, refIndex)) == Integer.MIN_VALUE)
      {
        if(onlyFullPath)
        {
          return null;
        }
        else
        {
          break;
        }
      }
      curr_next_switcher = curr_layers;
      curr_layers = next_layers;
      next_layers = curr_next_switcher;
      curr_x = i_next_x;
      curr_y = i_next_y;
      curr_z = i_next_z;
      result.add(new Location(curr_x, curr_y, curr_z));
    }
    return result;
  }

  /**
   * Используется только для антипаровоза в AI
   */
  private static Location MoveCheckForAI(int x, int y, int z, int tx, int ty, int refIndex)
  {
    int dx = tx - x;
    int dy = ty - y;
    int inc_x = sign(dx);
    int inc_y = sign(dy);
    dx = Math.abs(dx);
    dy = Math.abs(dy);
    if(dx + dy < 2 || dx == 2 && dy == 0 || dx == 0 && dy == 2)
    {
      return new Location(x, y, z).geo2world();
    }
    int prev_x;
    int prev_y;
    int prev_z;
    int next_x = x;
    int next_y = y;
    int next_z = z;
    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++)
      {
        prev_x = x;
        prev_y = y;
        prev_z = z;
        x = next_x;
        y = next_y;
        z = next_z;
        if(d > 0)
        {
          d += delta_B;
          next_x += inc_x;
          next_y += inc_y;
        }
        else
        {
          d += delta_A;
          next_x += inc_x;
        }
        next_z = NcanMoveNextForAI(x, y, z, next_x, next_y, refIndex);
        if(next_z == 0)
        {
          return new Location(prev_x, prev_y, prev_z).geo2world();
        }
      }
    }
    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++)
      {
        prev_x = x;
        prev_y = y;
        prev_z = z;
        x = next_x;
        y = next_y;
        z = next_z;
        if(d > 0)
        {
          d += delta_B;
          next_x += inc_x;
          next_y += inc_y;
        }
        else
        {
          d += delta_A;
          next_y += inc_y;
        }
        next_z = NcanMoveNextForAI(x, y, z, next_x, next_y, refIndex);
        if(next_z == 0)
        {
          return new Location(prev_x, prev_y, prev_z).geo2world();
        }
      }
    }
    return new Location(next_x, next_y, next_z).geo2world();
  }

  private static boolean NcanMoveNextExCheck(int x, int y, int h, int nextx, int nexty, int hexth, short[] temp_layers, int refIndex)
  {
    NGetLayers(x, y, temp_layers, refIndex);
    if(temp_layers[0] == 0)
    {
      return true;
    }
    int temp_layer;
    if((temp_layer = FindNearestLowerLayer(temp_layers, h + Config.MIN_LAYER_HEIGHT)) == Integer.MIN_VALUE)
    {
      return false;
    }
    short temp_layer_h = (short) ((short) (temp_layer & 0x0fff0) >> 1);
    if(Math.abs(temp_layer_h - hexth) >= Config.MAX_Z_DIFF || Math.abs(temp_layer_h - h) >= Config.MAX_Z_DIFF)
    {
      return false;
    }
    return checkNSWE((byte) (temp_layer & 0x0F), x, y, nextx, nexty);
  }

  /**
   * @return возвращает высоту следующего блока, либо Integer.MIN_VALUE если двигатся нельзя
   */
  public static int NcanMoveNext(int x, int y, int z, short[] layers, int next_x, int next_y, short[] next_layers, short[] temp_layers, boolean withCollision, int refIndex)
  {
    if(layers[0] == 0 || next_layers[0] == 0)
    {
      return z;
    }
    int layer, next_layer;
    if((layer = FindNearestLowerLayer(layers, z + Config.MIN_LAYER_HEIGHT)) == Integer.MIN_VALUE)
    {
      return Integer.MIN_VALUE;
    }
    byte layer_nswe = (byte) (layer & 0x0F);
    if(!checkNSWE(layer_nswe, x, y, next_x, next_y))
    {
      return Integer.MIN_VALUE;
    }
    short layer_h = (short) ((short) (layer & 0x0fff0) >> 1);
    if((next_layer = FindNearestLowerLayer(next_layers, layer_h + Config.MIN_LAYER_HEIGHT)) == Integer.MIN_VALUE)
    {
      return Integer.MIN_VALUE;
    }
    short next_layer_h = (short) ((short) (next_layer & 0x0fff0) >> 1);
    /*if(withCollision && next_layer_h + Config.MAX_Z_DIFF < layer_h)
      return Integer.MIN_VALUE;*/
    // если движение не по диагонали
    if(x == next_x || y == next_y)
    {
      if(withCollision)
      {
        //short[] heightNSWE = temp_layers;
        if(x == next_x)
        {
          NgetHeightAndNSWE(x - 1, y, layer_h, temp_layers, refIndex);
          if(Math.abs(temp_layers[0] - layer_h) > 15 || !checkNSWE(layer_nswe, x - 1, y, x, y) || !checkNSWE((byte) temp_layers[1], x - 1, y, x - 1, next_y))
          {
            return Integer.MIN_VALUE;
          }
          NgetHeightAndNSWE(x + 1, y, layer_h, temp_layers, refIndex);
          if(Math.abs(temp_layers[0] - layer_h) > 15 || !checkNSWE(layer_nswe, x + 1, y, x, y) || !checkNSWE((byte) temp_layers[1], x + 1, y, x + 1, next_y))
          {
            return Integer.MIN_VALUE;
          }
          return next_layer_h;
        }
        NgetHeightAndNSWE(x, y - 1, layer_h, temp_layers, refIndex);
        if(Math.abs(temp_layers[0] - layer_h) >= Config.MAX_Z_DIFF || !checkNSWE(layer_nswe, x, y - 1, x, y) || !checkNSWE((byte) temp_layers[1], x, y - 1, next_x, y - 1))
        {
          return Integer.MIN_VALUE;
        }
        NgetHeightAndNSWE(x, y + 1, layer_h, temp_layers, refIndex);
        if(Math.abs(temp_layers[0] - layer_h) >= Config.MAX_Z_DIFF || !checkNSWE(layer_nswe, x, y + 1, x, y) || !checkNSWE((byte) temp_layers[1], x, y + 1, next_x, y + 1))
        {
          return Integer.MIN_VALUE;
        }
      }
      return next_layer_h;
    }
    if(!NcanMoveNextExCheck(x, next_y, layer_h, next_x, next_y, next_layer_h, temp_layers, refIndex))
    {
      return Integer.MIN_VALUE;
    }
    if(!NcanMoveNextExCheck(next_x, y, layer_h, next_x, next_y, next_layer_h, temp_layers, refIndex))
    {
      return Integer.MIN_VALUE;
    }
    //FIXME if(withCollision)
    return next_layer_h;
  }

  /**
   * Используется только для антипаровоза в AI
   */
  public static int NcanMoveNextForAI(int x, int y, int z, int next_x, int next_y, int refIndex)
  {
    Layer[] layers1 = NGetLayers(x, y, refIndex);
    Layer[] layers2 = NGetLayers(next_x, next_y, refIndex);
    if(layers1.length == 0 || layers2.length == 0)
    {
      return z == 0 ? 1 : z;
    }
    short z1 = Short.MIN_VALUE;
    short z2 = Short.MIN_VALUE;
    byte NSWE1 = NSWE_ALL;
    byte NSWE2 = NSWE_ALL;
    for(Layer layer : layers1)
    {
      if(layer.height < z + Config.MIN_LAYER_HEIGHT && Math.abs(z - z1) > Math.abs(z - layer.height))
      {
        z1 = layer.height;
        NSWE1 = layer.nswe;
      }
    }
    // Вторая попытка с более мягкими условиями
    if(z1 < -30000)
    {
      for(Layer layer : layers1)
      {
        if(Math.abs(z - z1) > Math.abs(z - layer.height))
        {
          z1 = layer.height;
          NSWE1 = layer.nswe;
        }
      }
    }
    if(z1 < -30000)
    {
      return 0;
    }
    for(Layer layer : layers2)
    {
      if(layer.height < z1 + Config.MIN_LAYER_HEIGHT && Math.abs(z1 - z2) > Math.abs(z1 - layer.height))
      {
        z2 = layer.height;
        NSWE2 = layer.nswe;
      }
    }
    // Вторая попытка с более мягкими условиями
    if(z2 < -30000)
    {
      for(Layer layer : layers2)
      {
        if(Math.abs(z1 - z2) > Math.abs(z1 - layer.height))
        {
          z2 = layer.height;
          NSWE2 = layer.nswe;
        }
      }
    }
    if(z2 < -30000)
    {
      return 0;
    }
    if(z1 > z2 && z1 - z2 > Config.MAX_Z_DIFF)
    {
      return 0;
    }
    if(!checkNSWE(NSWE1, x, y, next_x, next_y) || !checkNSWE(NSWE2, next_x, next_y, x, y))
    {
      return 0;
    }
    return z2 == 0 ? 1 : z2;
  }

  /**
   * в нулевую ячейку кладется длина
   *
   * @param geoX
   * @param geoY
   * @param result
   */
  public static void NGetLayers(int geoX, int geoY, short[] result, int refIndex)
  {
    result[0] = 0;
    byte[] block = getGeoBlockFromGeoCoords(geoX, geoY, refIndex);
    if(block == null)
    {
      return;
    }
    int cellX, cellY;
    int index = 0;
    // Read current block type: 0 - flat, 1 - complex, 2 - multilevel
    byte type = block[index];
    index++;
    switch(type)
    {
      case BLOCKTYPE_FLAT:
        short height = makeShort(block[index + 1], block[index]);
        height = (short) (height & 0x0fff0);
        result[0]++;
        result[1] = (short) ((short) (height << 1) | NSWE_ALL);
        return;
      case BLOCKTYPE_COMPLEX:
        cellX = getCell(geoX);
        cellY = getCell(geoY);
        index += (cellX << 3) + cellY << 1;
        height = makeShort(block[index + 1], block[index]);
        result[0]++;
        result[1] = height;
        return;
      case BLOCKTYPE_MULTILEVEL:
        cellX = getCell(geoX);
        cellY = getCell(geoY);
        int offset = (cellX << 3) + cellY;
        while(offset > 0)
        {
          byte lc = block[index];
          index += (lc << 1) + 1;
          offset--;
        }
        byte layer_count = block[index];
        index++;
        if(layer_count <= 0 || layer_count > MAX_LAYERS)
        {
          return;
        }
        result[0] = layer_count;
        while(layer_count > 0)
        {
          result[layer_count] = makeShort(block[index + 1], block[index]);
          layer_count--;
          index += 2;
        }
        return;
      default:
        log.severe("GeoEngine: Unknown block type");
        return;
    }
  }

  public static Layer[] NGetLayers(int geoX, int geoY, int refIndex)
  {
    byte[] block = getGeoBlockFromGeoCoords(geoX, geoY, refIndex);
    if(block == null)
    {
      return new Layer[0];
    }
    int cellX, cellY;
    int index = 0;
    // Read current block type: 0 - flat, 1 - complex, 2 - multilevel
    byte type = block[index];
    index++;
    switch(type)
    {
      case BLOCKTYPE_FLAT:
        short height = makeShort(block[index + 1], block[index]);
        height = (short) (height & 0x0fff0);
        return new Layer[] {new Layer(height, NSWE_ALL)};
      case BLOCKTYPE_COMPLEX:
        cellX = getCell(geoX);
        cellY = getCell(geoY);
        index += (cellX << 3) + cellY << 1;
        height = makeShort(block[index + 1], block[index]);
        return new Layer[] {new Layer((short) ((short) (height & 0x0fff0) >> 1), (byte) (height & 0x0F))};
      case BLOCKTYPE_MULTILEVEL:
        cellX = getCell(geoX);
        cellY = getCell(geoY);
        int offset = (cellX << 3) + cellY;
        while(offset > 0)
        {
          byte lc = block[index];
          index += (lc << 1) + 1;
          offset--;
        }
        byte layer_count = block[index];
        index++;
        if(layer_count <= 0 || layer_count > MAX_LAYERS)
        {
          return new Layer[0];
        }
        Layer[] layers = new Layer[layer_count];
        while(layer_count > 0)
        {
          height = makeShort(block[index + 1], block[index]);
          layer_count--;
          layers[layer_count] = new Layer((short) ((short) (height & 0x0fff0) >> 1), (byte) (height & 0x0F));
          index += 2;
        }
        return layers;
      default:
        log.severe("GeoEngine: Unknown block type");
        return new Layer[0];
    }
  }

  private static short NgetType(int geoX, int geoY, int refIndex)
  {
    byte[] block = getGeoBlockFromGeoCoords(geoX, geoY, refIndex);
    if(block == null)
    {
      return 0;
    }
    return block[0];
  }

  public static int NgetHeight(int geoX, int geoY, int z, int refIndex)
  {
    byte[] block = getGeoBlockFromGeoCoords(geoX, geoY, refIndex);
    if(block == null)
    {
      return z;
    }
    int cellX, cellY, index = 0;
    // Read current block type: 0 - flat, 1 - complex, 2 - multilevel
    byte type = block[index];
    index++;
    short height;
    switch(type)
    {
      case BLOCKTYPE_FLAT:
        height = makeShort(block[index + 1], block[index]);
        return (short) (height & 0x0fff0);
      case BLOCKTYPE_COMPLEX:
        cellX = getCell(geoX);
        cellY = getCell(geoY);
        index += (cellX << 3) + cellY << 1;
        height = makeShort(block[index + 1], block[index]);
        return (short) ((short) (height & 0x0fff0) >> 1); // height / 2
      case BLOCKTYPE_MULTILEVEL:
        cellX = getCell(geoX);
        cellY = getCell(geoY);
        int offset = (cellX << 3) + cellY;
        while(offset > 0)
        {
          byte lc = block[index];
          index += (lc << 1) + 1;
          offset--;
        }
        byte layers = block[index];
        index++;
        if(layers <= 0 || layers > MAX_LAYERS)
        {
          return (short) z;
        }
        int z_nearest_lower_limit = z + Config.MIN_LAYER_HEIGHT;
        int z_nearest_lower = Integer.MIN_VALUE;
        int z_nearest = Integer.MIN_VALUE;
        while(layers > 0)
        {
          height = (short) ((short) (makeShort(block[index + 1], block[index]) & 0x0fff0) >> 1);
          if(height < z_nearest_lower_limit)
          {
            z_nearest_lower = Math.max(z_nearest_lower, height);
          }
          else if(Math.abs(z - height) < Math.abs(z - z_nearest))
          {
            z_nearest = height;
          }
          layers--;
          index += 2;
        }
        return z_nearest_lower != Integer.MIN_VALUE ? z_nearest_lower : z_nearest;
      default:
        log.severe("GeoEngine: Unknown blockType");
        return z;
    }
  }

  /**
   * @param geoX позиция геодаты
   * @param geoY позиция геодаты
   * @param z    координата без изменений
   * @return NSWE: 0-15
   */
  public static byte NgetNSWE(int geoX, int geoY, int z, int refIndex)
  {
    byte[] block = getGeoBlockFromGeoCoords(geoX, geoY, refIndex);
    if(block == null)
    {
      return NSWE_ALL;
    }
    int cellX, cellY;
    int index = 0;
    // Read current block type: 0 - flat, 1 - complex, 2 - multilevel
    byte type = block[index];
    index++;
    switch(type)
    {
      case BLOCKTYPE_FLAT:
        return NSWE_ALL;
      case BLOCKTYPE_COMPLEX:
        cellX = getCell(geoX);
        cellY = getCell(geoY);
        index += (cellX << 3) + cellY << 1;
        short height = makeShort(block[index + 1], block[index]);
        return (byte) (height & 0x0F);
      case BLOCKTYPE_MULTILEVEL:
        cellX = getCell(geoX);
        cellY = getCell(geoY);
        int offset = (cellX << 3) + cellY;
        while(offset > 0)
        {
          byte lc = block[index];
          index += (lc << 1) + 1;
          offset--;
        }
        byte layers = block[index];
        index++;
        if(layers <= 0 || layers > MAX_LAYERS)
        {
          return NSWE_ALL;
        }
        short tempz1 = Short.MIN_VALUE;
        short tempz2 = Short.MIN_VALUE;
        int index_nswe1 = NSWE_NONE;
        int index_nswe2 = NSWE_NONE;
        int z_nearest_lower_limit = z + Config.MIN_LAYER_HEIGHT;
        while(layers > 0)
        {
          height = (short) ((short) (makeShort(block[index + 1], block[index]) & 0x0fff0) >> 1); // height / 2
          if(height < z_nearest_lower_limit)
          {
            if(height > tempz1)
            {
              tempz1 = height;
              index_nswe1 = index;
            }
          }
          else if(Math.abs(z - height) < Math.abs(z - tempz2))
          {
            tempz2 = height;
            index_nswe2 = index;
          }
          layers--;
          index += 2;
        }
        if(index_nswe1 > 0)
        {
          return (byte) (makeShort(block[index_nswe1 + 1], block[index_nswe1]) & 0x0F);
        }
        if(index_nswe2 > 0)
        {
          return (byte) (makeShort(block[index_nswe2 + 1], block[index_nswe2]) & 0x0F);
        }
        return NSWE_ALL;
      default:
        log.severe("GeoEngine: Unknown block type.");
        return NSWE_ALL;
    }
  }

  public static void NgetHeightAndNSWE(int geoX, int geoY, short z, short[] result, int refIndex)
  {
    byte[] block = getGeoBlockFromGeoCoords(geoX, geoY, refIndex);
    if(block == null)
    {
      result[0] = z;
      result[1] = NSWE_ALL;
      return;
    }
    int cellX, cellY, index = 0;
    short height, NSWE = NSWE_ALL;
    // Read current block type: 0 - flat, 1 - complex, 2 - multilevel
    byte type = block[index];
    index++;
    switch(type)
    {
      case BLOCKTYPE_FLAT:
        height = makeShort(block[index + 1], block[index]);
        result[0] = (short) (height & 0x0fff0);
        result[1] = NSWE_ALL;
        return;
      case BLOCKTYPE_COMPLEX:
        cellX = getCell(geoX);
        cellY = getCell(geoY);
        index += (cellX << 3) + cellY << 1;
        height = makeShort(block[index + 1], block[index]);
        result[0] = (short) ((short) (height & 0x0fff0) >> 1); // height / 2
        result[1] = (short) (height & 0x0F);
        return;
      case BLOCKTYPE_MULTILEVEL:
        cellX = getCell(geoX);
        cellY = getCell(geoY);
        int offset = (cellX << 3) + cellY;
        while(offset > 0)
        {
          byte lc = block[index];
          index += (lc << 1) + 1;
          offset--;
        }
        byte layers = block[index];
        index++;
        if(layers <= 0 || layers > MAX_LAYERS)
        {
          result[0] = z;
          result[1] = NSWE_ALL;
          return;
        }
        short tempz1 = Short.MIN_VALUE;
        short tempz2 = Short.MIN_VALUE;
        int index_nswe1 = 0;
        int index_nswe2 = 0;
        int z_nearest_lower_limit = z + Config.MIN_LAYER_HEIGHT;
        while(layers > 0)
        {
          height = (short) ((short) (makeShort(block[index + 1], block[index]) & 0x0fff0) >> 1); // height / 2
          if(height < z_nearest_lower_limit)
          {
            if(height > tempz1)
            {
              tempz1 = height;
              index_nswe1 = index;
            }
          }
          else if(Math.abs(z - height) < Math.abs(z - tempz2))
          {
            tempz2 = height;
            index_nswe2 = index;
          }
          layers--;
          index += 2;
        }
        if(index_nswe1 > 0)
        {
          NSWE = makeShort(block[index_nswe1 + 1], block[index_nswe1]);
          NSWE = (short) (NSWE & 0x0F);
        }
        else if(index_nswe2 > 0)
        {
          NSWE = makeShort(block[index_nswe2 + 1], block[index_nswe2]);
          NSWE = (short) (NSWE & 0x0F);
        }
        result[0] = tempz1 > Short.MIN_VALUE ? tempz1 : tempz2;
        result[1] = NSWE;
        return;
      default:
        log.severe("GeoEngine: Unknown block type.");
        result[0] = z;
        result[1] = NSWE_ALL;
        return;
    }
  }

  protected static short makeShort(byte b1, byte b0)
  {
    return (short) (b1 << 8 | b0 & 0xff);
  }

  /**
   * @param geoPos позиция геодаты
   * @return Block Index: 0-255
   */
  protected static int getBlock(int geoPos)
  {
    return (geoPos >> 3) % 256;
  }

  /**
   * @param geoPos позиция геодаты
   * @return Cell Index: 0-7
   */
  protected static int getCell(int geoPos)
  {
    return geoPos % 8;
  }

  /**
   * Создает индекс блока геодаты по заданым координатам блока.
   *
   * @param blockX блок по geoX
   * @param blockY блок по geoY
   * @return индекс блока
   */
  protected static int getBlockIndex(int blockX, int blockY)
  {
    return (blockX << 8) + blockY;
  }

  private static byte sign(int x)
  {
    if(x >= 0)
    {
      return +1;
    }
    return -1;
  }

  /**
   * Возвращает актуальный блок для текущих геокоординат.<BR>
   * Является заготовкой для возвращения отдельніх блоков с дверьми
   *
   * @param geoX геокоордината
   * @param geoY геокоордината
   * @return текущий блок геодаты, или null если нет геодаты.
   */
  private static byte[] getGeoBlockFromGeoCoords(int geoX, int geoY, int refIndex)
  {
    int ix = geoX >> 11;
    int iy = geoY >> 11;
    if(ix < 0 || ix >= L2World.WORLD_SIZE_X || iy < 0 || iy >= L2World.WORLD_SIZE_Y)
    {
      return null;
    }
    byte[][][] region = geodata[ix][iy];
    if(region == null)
    {
      return null;
    }
    int blockX = getBlock(geoX);
    int blockY = getBlock(geoY);
    if(region.length <= refIndex)
    {
      refIndex = 0;
    }
    return region[getBlockIndex(blockX, blockY)][refIndex];
  }

  /**
   * Загрузка геодаты в память
   */
  public static void loadGeo()
  {
    if(Config.GEODATA_ENABLED)
    {
      log.info("Geo Engine: - Loading Geodata...");
      File f = new File("./geodata");
      if(!f.exists() || !f.isDirectory())
      {
        log.info("Geo Engine: Files missing, loading aborted.");
        Config.GEODATA_ENABLED = false;
        return;
      }
      int counter = 0;
      Pattern p = Pattern.compile(Config.GEOFILES_PATTERN);
      ParallelExecutor multiloader = Config.LOAD_MULTITHREADED ? new ParallelExecutor("Geodata Loader") : null;
      for(File q : f.listFiles())
      {
        if(q.isDirectory())
        {
          continue;
        }
        String fn = q.getName();
        Matcher m = p.matcher(fn);
        if(m.matches())
        {
          fn = fn.substring(0, 5); // обрезаем .l2j
          String[] xy = fn.split("_");
          if(multiloader != null)
          {
            try
            {
              multiloader.execute(GeoEngine.class, "LoadGeodataFile", Byte.parseByte(xy[0]), Byte.parseByte(xy[1]));
            }
            catch(Exception e)
            {
              e.printStackTrace();
            }
          }
          else
          {
            LoadGeodataFile(Byte.parseByte(xy[0]), Byte.parseByte(xy[1]));
          }
          counter++;
        }
      }
      if(multiloader != null)
      {
        try
        {
          multiloader.waitForFinishAndDestroy();
        }
        catch(InterruptedException e)
        {
          e.printStackTrace();
        }
      }
      log.info("Geo Engine: - Loaded " + counter + " map(s), max layers: " + MAX_LAYERS);
      if(counter == 0)
      {
        Config.GEODATA_ENABLED = false;
      }
      if(Config.COMPACT_GEO)
      {
        compact(true);
      }
      if(Config.ALLOW_DOORS)
      {
        for(L2DoorInstance door : DoorTable.getInstance().getDoors())
        {
          if(!door.isOpen() && door.getGeodata())
          {
            applyControl(door);
            door.geoOpen = false;
          }
        }
      }
    }
    else
    {
      log.info("Geo Engine: - Skipping geodata loading...");
    }
  }

  public static void DumpGeodata(String dir)
  {
    new File(dir).mkdirs();
    for(int mapX = 0; mapX < L2World.WORLD_SIZE_X; mapX++)
    {
      for(int mapY = 0; mapY < L2World.WORLD_SIZE_Y; mapY++)
      {
        if(geodata[mapX][mapY] == null)
        {
          continue;
        }
        int rx = mapX + Config.GEO_X_FIRST;
        int ry = mapY + Config.GEO_Y_FIRST;
        String fName = dir + "/" + rx + "_" + ry + ".l2j";
        log.info("Dumping geo: " + fName);
        DumpGeodataFile(fName, (byte) rx, (byte) ry);
      }
    }
  }

  public static boolean DumpGeodataFile(int cx, int cy)
  {
    return DumpGeodataFileMap((byte) (Math.floor((float) cx / (float) 32768) + 20), (byte) (Math.floor((float) cy / (float) 32768) + 18));
  }

  public static boolean DumpGeodataFileMap(byte rx, byte ry)
  {
    String name = "./log/" + rx + "_" + ry + ".l2j";
    return DumpGeodataFile(name, rx, ry);
  }

  public static boolean DumpGeodataFile(String _name, byte rx, byte ry)
  {
    int ix = rx - Config.GEO_X_FIRST;
    int iy = ry - Config.GEO_Y_FIRST;
    byte[][][] geoblocks = geodata[ix][iy];
    if(geoblocks == null)
    {
      return false;
    }
    try
    {
      File f = new File(_name);
      if(f.exists())
      {
        f.delete();
      }
      FileChannel wChannel = new RandomAccessFile(f, "rw").getChannel();
      for(byte[][] geoblock : geoblocks)
      {
        ByteBuffer buffer = ByteBuffer.wrap(geoblock[0]);
        wChannel.write(buffer);
      }
      wChannel.close();
    }
    catch(Exception e)
    {
      e.printStackTrace();
      return false;
    }
    return true;
  }

  /**
   * Загрузка региона геодаты.
   *
   * @param rx регион x
   * @param ry регион y
   */
  public static boolean LoadGeodataFile(byte rx, byte ry)
  {
    String fname = "./geodata/" + rx + "_" + ry + ".l2j";
    int ix = rx - Config.GEO_X_FIRST;
    int iy = ry - Config.GEO_Y_FIRST;
    if(ix < 0 || iy < 0 || ix > (L2World.MAP_MAX_X >> 15) + Math.abs(L2World.MAP_MIN_X >> 15) || iy > (L2World.MAP_MAX_Y >> 15) + Math.abs(L2World.MAP_MIN_Y >> 15))
    {
      log.info("Geo Engine: File " + fname + " was not loaded!!! ");
      return false;
    }
    //log.info("Geo Engine: - Loading: " + fname);
    File Geo = new File(fname);
    int size, index = 0, block = 0, flor = 0;
    try
    {
      byte[] geo;
      synchronized(geodata)
      {
        // Create a read-only memory-mapped file
        FileChannel roChannel = new RandomAccessFile(Geo, "r").getChannel();
        size = (int) roChannel.size();
        ByteBuffer buffer = roChannel.map(FileChannel.MapMode.READ_ONLY, 0, size);
        roChannel.close();
        buffer.order(ByteOrder.LITTLE_ENDIAN);
        geo = new byte[buffer.remaining()];
        buffer.get(geo, 0, geo.length);
      }
      if(size >= BlocksInMap * 3)
      {
        byte[][][] blocks = new byte[BlocksInMap][1][]; // 256 * 256 блоков в регионе геодаты
        // Indexing geo files, so we will know where each block starts
        for(block = 0; block < blocks.length; block++)
        {
          byte type = geo[index];
          index++;
          byte[] geoBlock;
          switch(type)
          {
            case BLOCKTYPE_FLAT:
              // Создаем блок геодаты
              geoBlock = new byte[2 + 1];
              // Читаем нужные даные с геодаты
              geoBlock[0] = type;
              geoBlock[1] = geo[index];
              geoBlock[2] = geo[index + 1];
              // Увеличиваем индекс
              index += 2;
              // Добавляем блок геодаты
              blocks[block][0] = geoBlock;
              break;
            case BLOCKTYPE_COMPLEX:
              // Создаем блок геодаты
              geoBlock = new byte[128 + 1];
              // Читаем даные с геодаты
              geoBlock[0] = type;
              System.arraycopy(geo, index, geoBlock, 1, 128);
              // Увеличиваем индекс
              index += 128;
              // Добавляем блок геодаты
              blocks[block][0] = geoBlock;
              break;
            case BLOCKTYPE_MULTILEVEL:
              // Оригинальный индекс
              int orgIndex = index;
              // Считаем длину блока геодаты
              for(int b = 0; b < 64; b++)
              {
                byte layers = geo[index];
                MAX_LAYERS = Math.max(MAX_LAYERS, layers);
                index += (layers << 1) + 1;
                if(layers > flor)
                {
                  flor = layers;
                }
              }
              // Получаем длину
              int diff = index - orgIndex;
              // Создаем массив геодаты
              geoBlock = new byte[diff + 1];
              // Читаем даные с геодаты
              geoBlock[0] = type;
              System.arraycopy(geo, orgIndex, geoBlock, 1, diff);
              // Добавляем блок геодаты
              blocks[block][0] = geoBlock;
              break;
            default:
              log.severe("GeoEngine: invalid block type: " + type);
          }
        }
        synchronized(geodata)
        {
          geodata[ix][iy] = blocks;
        }
        return true;
      }
      return false;
    }
    catch(Exception e)
    {
      e.printStackTrace();
      log.warning("Failed to Load GeoFile at block: " + block + "\n");
      return false;
    }
  }

  /**
   * Преобразовывает FLAT блоки в COMPLEX<br>
   *
   * @param ix         регион x
   * @param iy         регион y
   * @param blockIndex индекс блока в регионе
   */
  private static void copyBlock(int ix, int iy, int blockIndex)
  {
    byte[][][] region = geodata[ix][iy];
    if(region == null)
    {
      Log.add("door at null region? [" + ix + "][" + iy + "]", "doors");
      return;
    }
    byte[] block = region[blockIndex][0];
    byte blockType = block[0];
    switch(blockType)
    {
      case BLOCKTYPE_FLAT:
        short height = makeShort(block[2], block[1]);
        height &= 0x0fff0;
        height <<= 1;
        height |= NORTH;
        height |= SOUTH;
        height |= WEST;
        height |= EAST;
        byte[] newblock = new byte[129];
        newblock[0] = BLOCKTYPE_COMPLEX;
        for(int i = 1; i < 129; i += 2)
        {
          newblock[i + 1] = (byte) (height >> 8);
          newblock[i] = (byte) (height & 0x00ff);
        }
        region[blockIndex][0] = newblock;
        break;
      default:
        if(Config.COMPACT_GEO)
        {
          region[blockIndex][0] = region[blockIndex][0].clone();
        }
        break;
    }
  }

  private static boolean check_door_z(int minZ, int maxZ, int geoZ)
  {
    if(minZ <= geoZ && geoZ <= maxZ)
    {
      return true;
    }
    return Math.abs((minZ + maxZ) / 2 - geoZ) <= Door_MaxZDiff;
  }

  private static boolean check_cell_in_door(int geoX, int geoY, L2Territory pos)
  {
    geoX = (geoX << 4) + L2World.MAP_MIN_X + 8;
    geoY = (geoY << 4) + L2World.MAP_MIN_Y + 8;
    for(int ax = geoX; ax < geoX + 16; ax++)
    {
      for(int ay = geoY; ay < geoY + 16; ay++)
      {
        if(pos.isInside(ax, ay))
        {
          return true;
        }
      }
    }
    return false;
  }

  public static void returnGeoAtControl(GeoControl control)
  {
    L2Territory pos = control.getGeoPos();
    HashMap<Long, Byte> around = control.getGeoAround();
    int refIndex = control.getReflection().getGeoIndex();
    if(around == null)
    {
      Log.add("GeoEngine: Attempt to open 'not closed' door", "doors");
      Thread.dumpStack();
      return;
    }
    short height;
    byte old_nswe;
    synchronized(around)
    {
      for(long geoXY : around.keySet())
      {
        int geoX = (int) geoXY;
        int geoY = (int) (geoXY >> 32);
        // Получение мировых координат
        int ix = geoX >> 11;
        int iy = geoY >> 11;
        // Получение индекса блока
        int blockX = getBlock(geoX);
        int blockY = getBlock(geoY);
        int blockIndex = getBlockIndex(blockX, blockY);
        byte[][][] region = geodata[ix][iy];
        if(region == null)
        {
          Log.add("GeoEngine: Attempt to open door at block with no geodata", "doors");
          return;
        }
        if(region.length <= refIndex)
        {
          refIndex = 0;
        }
        byte[] block = region[blockIndex][refIndex];
        int cellX = getCell(geoX);
        int cellY = getCell(geoY);
        int index = 0;
        byte blockType = block[index];
        index++;
        switch(blockType)
        {
          case BLOCKTYPE_COMPLEX:
            index += (cellX << 3) + cellY << 1;
            // Получаем высоту клетки
            height = makeShort(block[index + 1], block[index]);
            old_nswe = (byte) (height & 0x0F);
            height &= 0xfff0;
            height >>= 1;
            // around
            height <<= 1;
            height &= 0xfff0;
            height |= old_nswe;
            if(control.isGeoCloser())
            {
              height |= around.get(geoXY);
            }
            else
            {
              height &= ~around.get(geoXY);
            }
            // Записываем высоту в массив
            block[index + 1] = (byte) (height >> 8);
            block[index] = (byte) (height & 0x00ff);
            break;
          case BLOCKTYPE_MULTILEVEL:
            // Последний валидный индекс для двери
            int neededIndex = -1;
            // Далее следует стандартный механизм получения высоты
            int offset = (cellX << 3) + cellY;
            while(offset > 0)
            {
              byte lc = block[index];
              index += (lc << 1) + 1;
              offset--;
            }
            byte layers = block[index];
            index++;
            if(layers <= 0 || layers > MAX_LAYERS)
            {
              break;
            }
            short temph = Short.MIN_VALUE;
            old_nswe = NSWE_ALL;
            while(layers > 0)
            {
              height = makeShort(block[index + 1], block[index]);
              byte tmp_nswe = (byte) (height & 0x0F);
              height &= 0xfff0;
              height >>= 1;
              int z_diff_last = Math.abs(pos.getZmin() - temph);
              int z_diff_curr = Math.abs(pos.getZmin() - height);
              if(z_diff_last > z_diff_curr)
              {
                old_nswe = tmp_nswe;
                temph = height;
                neededIndex = index;
              }
              layers--;
              index += 2;
            }
            // around
            temph <<= 1;
            temph &= 0xfff0;
            temph |= old_nswe;
            if(control.isGeoCloser())
            {
              temph |= around.get(geoXY);
            }
            else
            {
              temph &= ~around.get(geoXY);
            }
            // записываем высоту
            block[neededIndex + 1] = (byte) (temph >> 8);
            block[neededIndex] = (byte) (temph & 0x00ff);
            break;
        }
      }
    }
  }

  public static void applyControl(GeoControl control)
  {
    L2Territory pos = control.getGeoPos();
    HashMap<Long, Byte> around = control.getGeoAround();
    int refIndex = control.getReflection().getGeoIndex();
    boolean first_time = around == null;
    // TODO сделать этот самый Init :)
    // т.е. скопировать кусок геодаты из реального мира в инстанс, еще до закрытия двери.
    if(around == null)
    {
      around = new HashMap<Long, Byte>();
      GArray<Long> around_blocks = new GArray<Long>();
      int minX = pos.getXmin() - L2World.MAP_MIN_X >> 4;
      int maxX = pos.getXmax() - L2World.MAP_MIN_X >> 4;
      int minY = pos.getYmin() - L2World.MAP_MIN_Y >> 4;
      int maxY = pos.getYmax() - L2World.MAP_MIN_Y >> 4;
      for(int geoX = minX; geoX <= maxX; geoX++)
      {
        for(int geoY = minY; geoY <= maxY; geoY++)
        {
          if(check_cell_in_door(geoX, geoY, pos))
          {
            around_blocks.add(makeLong(geoX, geoY));
          }
        }
      }
      for(long geoXY : around_blocks)
      {
        int geoX = (int) geoXY;
        int geoY = (int) (geoXY >> 32);
        long aroundN_geoXY = makeLong(geoX, geoY - 1); // close S
        long aroundS_geoXY = makeLong(geoX, geoY + 1); // close N
        long aroundW_geoXY = makeLong(geoX - 1, geoY); // close E
        long aroundE_geoXY = makeLong(geoX + 1, geoY); // close W
        around.put(geoXY, NSWE_ALL);
        byte _nswe;
        if(!around_blocks.contains(aroundN_geoXY))
        {
          _nswe = around.containsKey(aroundN_geoXY) ? around.remove(aroundN_geoXY) : 0;
          _nswe |= SOUTH;
          around.put(aroundN_geoXY, _nswe);
        }
        if(!around_blocks.contains(aroundS_geoXY))
        {
          _nswe = around.containsKey(aroundS_geoXY) ? around.remove(aroundS_geoXY) : 0;
          _nswe |= NORTH;
          around.put(aroundS_geoXY, _nswe);
        }
        if(!around_blocks.contains(aroundW_geoXY))
        {
          _nswe = around.containsKey(aroundW_geoXY) ? around.remove(aroundW_geoXY) : 0;
          _nswe |= EAST;
          around.put(aroundW_geoXY, _nswe);
        }
        if(!around_blocks.contains(aroundE_geoXY))
        {
          _nswe = around.containsKey(aroundE_geoXY) ? around.remove(aroundE_geoXY) : 0;
          _nswe |= WEST;
          around.put(aroundE_geoXY, _nswe);
        }
      }
      around_blocks.clear();
      control.setGeoAround(around);
    }
    short height;
    byte old_nswe, close_nswe;
    synchronized(around)
    {
      Long[] around_keys = around.keySet().toArray(new Long[around.size()]);
      for(long geoXY : around_keys)
      {
        int geoX = (int) geoXY;
        int geoY = (int) (geoXY >> 32);
        // Получение мировых координат
        int ix = geoX >> 11;
        int iy = geoY >> 11;
        // Получение индекса блока
        int blockX = getBlock(geoX);
        int blockY = getBlock(geoY);
        int blockIndex = getBlockIndex(blockX, blockY);
        // Попытка скопировать блок геодаты, если уже существует, то не скопируется
        if(first_time)
        {
          copyBlock(ix, iy, blockIndex);
        }
        byte[][][] region = geodata[ix][iy];
        if(region == null)
        {
          Log.add("GeoEngine: Attempt to close door at block with no geodata", "doors");
          return;
        }
        if(region.length <= refIndex)
        {
          refIndex = 0;
        }
        byte[] block = region[blockIndex][refIndex];
        int cellX = getCell(geoX);
        int cellY = getCell(geoY);
        int index = 0;
        byte blockType = block[index];
        index++;
        switch(blockType)
        {
          case BLOCKTYPE_COMPLEX:
            index += (cellX << 3) + cellY << 1;
            // Получаем высоту клетки
            height = makeShort(block[index + 1], block[index]);
            old_nswe = (byte) (height & 0x0F);
            height &= 0xfff0;
            height >>= 1;
            if(first_time)
            {
              close_nswe = around.remove(geoXY);
              // подходящий слой не найден
              if(!check_door_z(pos.getZmin(), pos.getZmax(), height))
              {
                break;
              }
              if(control.isGeoCloser())
              {
                close_nswe &= old_nswe;
              }
              else
              {
                close_nswe &= ~old_nswe;
              }
              around.put(geoXY, close_nswe);
            }
            else
            {
              close_nswe = around.get(geoXY);
            }
            // around
            height <<= 1;
            height &= 0xfff0;
            height |= old_nswe;
            if(control.isGeoCloser())
            {
              height &= ~close_nswe;
            }
            else
            {
              height |= close_nswe;
            }
            // Записываем высоту в массив
            block[index + 1] = (byte) (height >> 8);
            block[index] = (byte) (height & 0x00ff);
            break;
          case BLOCKTYPE_MULTILEVEL:
            // Последний валидный индекс для двери
            int neededIndex = -1;
            // Далее следует стандартный механизм получения высоты
            int offset = (cellX << 3) + cellY;
            while(offset > 0)
            {
              byte lc = block[index];
              index += (lc << 1) + 1;
              offset--;
            }
            byte layers = block[index];
            index++;
            if(layers <= 0 || layers > MAX_LAYERS)
            {
              break;
            }
            short temph = Short.MIN_VALUE;
            old_nswe = NSWE_ALL;
            while(layers > 0)
            {
              height = makeShort(block[index + 1], block[index]);
              byte tmp_nswe = (byte) (height & 0x0F);
              height &= 0xfff0;
              height >>= 1;
              int z_diff_last = Math.abs(pos.getZmin() - temph);
              int z_diff_curr = Math.abs(pos.getZmin() - height);
              if(z_diff_last > z_diff_curr)
              {
                old_nswe = tmp_nswe;
                temph = height;
                neededIndex = index;
              }
              layers--;
              index += 2;
            }
            if(first_time)
            {
              close_nswe = around.remove(geoXY);
              // подходящий слой не найден
              if(temph == Short.MIN_VALUE || !check_door_z(pos.getZmin(), pos.getZmax(), temph))
              {
                break;
              }
              if(control.isGeoCloser())
              {
                close_nswe &= old_nswe;
              }
              else
              {
                close_nswe &= ~old_nswe;
              }
              around.put(geoXY, close_nswe);
            }
            else
            {
              close_nswe = around.get(geoXY);
            }
            // around
            temph <<= 1;
            temph &= 0xfff0;
            temph |= old_nswe;
            if(control.isGeoCloser())
            {
              temph &= ~close_nswe;
            }
            else
            {
              temph |= close_nswe;
            }
            // записываем высоту
            block[neededIndex + 1] = (byte) (temph >> 8);
            block[neededIndex] = (byte) (temph & 0x00ff);
            break;
        }
      }
    }
  }

  public static long makeLong(int nLo, int nHi)
  {
    return (long) nHi << 32 | nLo & 0x00000000ffffffffL;
  }

  public static Location findPointToStay(int x, int y, int z, int j, int k, int refIndex)
  {
    Location pos;
    for(int i = 0; i < 100; i++)
    {
      pos = Rnd.coordsRandomize(x, y, z, 0, j, k);
      if(canMoveToCoord(x, y, z, pos.x, pos.y, pos.z, refIndex) && canMoveToCoord(pos.x, pos.y, pos.z, x, y, z, refIndex))
      {
        return pos;
      }
    }
    return new Location(x, y, z);
  }

  /**
   * загружает заранее сгенерированые карты соовпадений в блоках и благодаря им оптимизирует размещение геодаты в памяти
   *
   * @return количество оптимизированых блоков
   */
  public static void compact(boolean andClean)
  {
    long freeMemBefore = 0;
    long total = 0, optimized = 0;
    BlockLink[] links;
    byte[][][] link_region;
    if(andClean)
    {
      Util.gc(2, 100);
      freeMemBefore = MemoryWatchDog.getMemFree();
    }
    for(int mapX = 0; mapX < L2World.WORLD_SIZE_X; mapX++)
    {
      for(int mapY = 0; mapY < L2World.WORLD_SIZE_Y; mapY++)
      {
        if(geodata[mapX][mapY] == null)
        {
          continue;
        }
        total += BlocksInMap;
        links = GeoOptimizer.loadBlockMatches("./geodata/matches/" + (mapX + Config.GEO_X_FIRST) + "_" + (mapY + Config.GEO_Y_FIRST) + ".matches");
        if(links == null)
        {
          continue;
        }
        for(int i = 0; i < links.length; i++)
        {
          link_region = geodata[links[i].linkMapX][links[i].linkMapY];
          if(link_region == null)
          {
            continue;
          }
          link_region[links[i].linkBlockIndex][0] = geodata[mapX][mapY][links[i].blockIndex][0];
          optimized++;
        }
      }
    }
    String logStr = String.format("Geo Engine: - Compacted %d of %d blocks...", optimized, total);
    if(andClean)
    {
      Util.gc(2, 100);
      logStr = String.format("%s Optimized ~%d Mb of memory", logStr, (MemoryWatchDog.getMemFree() - freeMemBefore) / 0x100000);
    }
    log.info(logStr);
  }

  /**
   * сравнение двух байт-массивов
   *
   * @param a1
   * @param a2
   * @return
   */
  public static boolean equalsData(byte[] a1, byte[] a2)
  {
    if(a1.length != a2.length)
    {
      return false;
    }
    for(int i = 0; i < a1.length; i++)
    {
      if(a1[i] != a2[i])
      {
        return false;
      }
    }
    return true;
  }

  /**
   * сравнение двух блоков геодаты
   *
   * @param mapX1
   * @param mapY1
   * @param blockIndex1
   * @param mapX2
   * @param mapY2
   * @param blockIndex2
   * @return
   */
  public static boolean compareGeoBlocks(int mapX1, int mapY1, int blockIndex1, int mapX2, int mapY2, int blockIndex2)
  {
    return equalsData(geodata[mapX1][mapY1][blockIndex1][0], geodata[mapX2][mapY2][blockIndex2][0]);
  }

  private static void initChecksums()
  {
    log.info("Geo Engine: - Generating Checksums...");
    new File("./geodata/checksum").mkdirs();
    ParallelExecutor executor = new ParallelExecutor("initChecksums", Thread.MIN_PRIORITY);
    GeoOptimizer.checkSums = new int[L2World.WORLD_SIZE_X][L2World.WORLD_SIZE_Y][];
    for(int mapX = 0; mapX < L2World.WORLD_SIZE_X; mapX++)
    {
      for(int mapY = 0; mapY < L2World.WORLD_SIZE_Y; mapY++)
      {
        if(geodata[mapX][mapY] != null)
        {
          executor.execute(new GeoOptimizer.CheckSumLoader(mapX, mapY, geodata[mapX][mapY]));
        }
      }
    }
    try
    {
      executor.waitForFinishAndDestroy();
    }
    catch(InterruptedException e)
    {
      e.printStackTrace();
    }
  }

  private static void initBlockMatches(int maxScanRegions)
  {
    log.info("Geo Engine: - Generating Block Matches...");
    new File("./geodata/matches").mkdirs();
    ParallelExecutor executor = new ParallelExecutor("initBlockMatches", Thread.NORM_PRIORITY - 1);
    for(int mapX = 0; mapX < L2World.WORLD_SIZE_X; mapX++)
    {
      for(int mapY = 0; mapY < L2World.WORLD_SIZE_Y; mapY++)
      {
        if(geodata[mapX][mapY] != null && GeoOptimizer.checkSums != null && GeoOptimizer.checkSums[mapX][mapY] != null)
        {
          executor.execute(new GeoOptimizer.GeoBlocksMatchFinder(mapX, mapY, maxScanRegions));
        }
      }
    }
    try
    {
      executor.waitForFinishAndDestroy();
    }
    catch(InterruptedException e)
    {
      e.printStackTrace();
    }
  }

  public static void deleteChecksumFiles()
  {
    for(int mapX = 0; mapX < L2World.WORLD_SIZE_X; mapX++)
    {
      for(int mapY = 0; mapY < L2World.WORLD_SIZE_Y; mapY++)
      {
        if(geodata[mapX][mapY] == null)
        {
          continue;
        }
        new File("./geodata/checksum/" + (mapX + Config.GEO_X_FIRST) + "_" + (mapY + Config.GEO_Y_FIRST) + ".crc").delete();
      }
    }
  }

  public static void genBlockMatches(int maxScanRegions)
  {
    initChecksums();
    initBlockMatches(maxScanRegions);
  }

  public static void unload()
  {
    for(int mapX = 0; mapX < L2World.WORLD_SIZE_X; mapX++)
    {
      for(int mapY = 0; mapY < L2World.WORLD_SIZE_Y; mapY++)
      {
        geodata[mapX][mapY] = null;
      }
    }
  }
}
TOP

Related Classes of l2p.gameserver.geodata.GeoEngine

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.