/*
   * Figure out if our position has put us into a new set of blocks and fire the appropriate events.
   */
  private void checkBlockEntry(
      EntityRef entity, Vector3i oldPosition, Vector3i newPosition, float characterHeight) {
    // TODO: This will only work for tall mobs/players and single block mobs
    // is this a different position than previously
    if (!oldPosition.equals(newPosition)) {
      // get the old position's blocks
      Block[] oldBlocks = new Block[(int) Math.ceil(characterHeight)];
      Vector3i currentPosition = new Vector3i(oldPosition);
      for (int currentHeight = 0; currentHeight < oldBlocks.length; currentHeight++) {
        oldBlocks[currentHeight] = worldProvider.getBlock(currentPosition);
        currentPosition.add(0, 1, 0);
      }

      // get the new position's blocks
      Block[] newBlocks = new Block[(int) Math.ceil(characterHeight)];
      currentPosition = new Vector3i(newPosition);
      for (int currentHeight = 0; currentHeight < characterHeight; currentHeight++) {
        newBlocks[currentHeight] = worldProvider.getBlock(currentPosition);
        currentPosition.add(0, 1, 0);
      }

      for (int i = 0; i < characterHeight; i++) {
        // send a block enter/leave event for this character
        entity.send(new OnEnterBlockEvent(oldBlocks[i], newBlocks[i], new Vector3i(0, i, 0)));
      }
    }
  }
  private void climb(
      final CharacterStateEvent state, CharacterMoveInputEvent input, Vector3f desiredVelocity) {
    if (state.getClimbDirection() == null) {
      return;
    }
    Vector3f tmp;

    Vector3i climbDir3i = state.getClimbDirection();
    Vector3f climbDir3f = climbDir3i.toVector3f();

    Quat4f rotation = new Quat4f(TeraMath.DEG_TO_RAD * state.getYaw(), 0, 0);
    tmp = new Vector3f(0.0f, 0.0f, -1.0f);
    QuaternionUtil.quatRotate(rotation, tmp, tmp);
    float angleToClimbDirection = tmp.angle(climbDir3f);

    boolean clearMovementToDirection = !state.isGrounded();

    // facing the ladder or looking down or up
    if (angleToClimbDirection < Math.PI / 4.0 || Math.abs(input.getPitch()) > 60f) {
      float pitchAmount = state.isGrounded() ? 45f : 90f;
      float pitch = input.getPitch() > 30f ? pitchAmount : -pitchAmount;
      rotation = new Quat4f(TeraMath.DEG_TO_RAD * state.getYaw(), TeraMath.DEG_TO_RAD * pitch, 0);
      QuaternionUtil.quatRotate(rotation, desiredVelocity, desiredVelocity);

      // looking sidewards from ladder
    } else if (angleToClimbDirection < Math.PI * 3.0 / 4.0) {
      float rollAmount = state.isGrounded() ? 45f : 90f;
      tmp = new Vector3f();
      QuaternionUtil.quatRotate(rotation, climbDir3f, tmp);
      float leftOrRight = tmp.x;
      float plusOrMinus = (leftOrRight < 0f ? -1.0f : 1.0f) * (climbDir3i.x != 0 ? -1.0f : 1.0f);
      rotation =
          new Quat4f(
              TeraMath.DEG_TO_RAD * input.getYaw(),
              0f,
              TeraMath.DEG_TO_RAD * rollAmount * plusOrMinus);
      QuaternionUtil.quatRotate(rotation, desiredVelocity, desiredVelocity);

      // facing away from ladder
    } else {
      rotation = new Quat4f(TeraMath.DEG_TO_RAD * state.getYaw(), 0, 0);
      QuaternionUtil.quatRotate(rotation, desiredVelocity, desiredVelocity);
      clearMovementToDirection = false;
    }

    // clear out movement towards or away from the ladder
    if (clearMovementToDirection) {
      if (climbDir3i.x != 0) {
        desiredVelocity.x = 0f;
      }
      if (climbDir3i.z != 0) {
        desiredVelocity.z = 0f;
      }
    }
  }
    @Override
    public boolean isConnectingTo(
        Vector3i blockLocation,
        Side connectSide,
        WorldProvider worldProvider,
        BlockEntityRegistry blockEntityRegistry) {
      Vector3i neighborLocation = new Vector3i(blockLocation);
      neighborLocation.add(connectSide.getVector3i());

      EntityRef neighborEntity = blockEntityRegistry.getBlockEntityAt(neighborLocation);
      return neighborEntity != null && connectsToNeighbor(neighborEntity);
    }
 private ChunkViewCore createWorldView(Region3i region, Vector3i offset) {
   Chunk[] chunks = new Chunk[region.sizeX() * region.sizeY() * region.sizeZ()];
   for (Vector3i chunkPos : region) {
     Chunk chunk = nearCache.get(chunkPos);
     if (chunk == null || !chunk.isReady()) {
       return null;
     }
     chunkPos.sub(region.minX(), region.minY(), region.minZ());
     int index = TeraMath.calculate3DArrayIndex(chunkPos, region.size());
     chunks[index] = chunk;
   }
   return new ChunkViewCoreImpl(
       chunks, region, offset, blockManager.getBlock(BlockManager.AIR_ID));
 }
  private EntityRef createBlockEntity(Vector3i blockPosition, Block block) {
    EntityBuilder builder = entityManager.newBuilder(block.getPrefab().orElse(null));
    builder.addComponent(new LocationComponent(blockPosition.toVector3f()));
    builder.addComponent(new BlockComponent(block, blockPosition));
    if (block.isDestructible() && !builder.hasComponent(HealthComponent.class)) {
      // Block regen should always take the same amount of time,  regardless of its hardness
      builder.addComponent(
          new HealthComponent(
              block.getHardness(), block.getHardness() / BLOCK_REGEN_SECONDS, 1.0f));
    }
    boolean isTemporary = isTemporaryBlock(builder, block);
    if (!isTemporary && !builder.hasComponent(NetworkComponent.class)) {
      builder.addComponent(new NetworkComponent());
    }

    EntityRef blockEntity;
    if (isTemporary) {
      blockEntity = builder.buildWithoutLifecycleEvents();
      temporaryBlockEntities.add(blockEntity);
    } else {
      blockEntity = builder.build();
    }

    blockEntityLookup.put(new Vector3i(blockPosition), blockEntity);
    return blockEntity;
  }
Beispiel #6
0
/** @author Immortius */
@API
public final class ChunkConstants {
  public static final int SIZE_X = 32;
  public static final int SIZE_Y = 64;
  public static final int SIZE_Z = 32;

  public static final int INNER_CHUNK_POS_FILTER_X = TeraMath.ceilPowerOfTwo(SIZE_X) - 1;
  public static final int INNER_CHUNK_POS_FILTER_Y = TeraMath.ceilPowerOfTwo(SIZE_Y) - 1;
  public static final int INNER_CHUNK_POS_FILTER_Z = TeraMath.ceilPowerOfTwo(SIZE_Z) - 1;

  public static final int POWER_X = TeraMath.sizeOfPower(SIZE_X);
  public static final int POWER_Y = TeraMath.sizeOfPower(SIZE_Y);
  public static final int POWER_Z = TeraMath.sizeOfPower(SIZE_Z);

  public static final byte MAX_LIGHT = 0x0f;
  public static final byte MAX_SUNLIGHT = 0x0f;
  public static final byte MAX_SUNLIGHT_REGEN = 63;
  public static final byte SUNLIGHT_REGEN_THRESHOLD = 48;

  public static final Vector3i CHUNK_POWER = new Vector3i(POWER_X, POWER_Y, POWER_Z);
  public static final Vector3i CHUNK_SIZE = new Vector3i(SIZE_X, SIZE_Y, SIZE_Z);
  public static final Vector3i INNER_CHUNK_POS_FILTER =
      new Vector3i(INNER_CHUNK_POS_FILTER_X, INNER_CHUNK_POS_FILTER_Y, INNER_CHUNK_POS_FILTER_Z);
  public static final Region3i CHUNK_REGION =
      Region3i.createFromMinAndSize(Vector3i.zero(), CHUNK_SIZE);

  public static final Vector3i LOCAL_REGION_EXTENTS = new Vector3i(1, 1, 1);

  private ChunkConstants() {}
}
Beispiel #7
0
 public LocalChunkView(Chunk[] chunks, PropagationRules rules) {
   this.chunks = chunks;
   this.rules = rules;
   topLeft.set(
       chunks[13].getPosition().x - 1,
       chunks[13].getPosition().y - 1,
       chunks[13].getPosition().z - 1);
 }
 @Override
 public ChunkViewCore getLocalView(Vector3i centerChunkPos) {
   Region3i region =
       Region3i.createFromCenterExtents(centerChunkPos, ChunkConstants.LOCAL_REGION_EXTENTS);
   if (getChunk(centerChunkPos) != null) {
     return createWorldView(region, Vector3i.one());
   }
   return null;
 }
  @ReceiveEvent
  public void onActivate(
      ActivateEvent event, EntityRef entity, TunnelActionComponent tunnelActionComponent) {

    Vector3f dir = new Vector3f(event.getDirection());
    dir.scale(4.0f);
    Vector3f origin = new Vector3f(event.getOrigin());
    origin.add(dir);
    Vector3i blockPos = new Vector3i();

    int particleEffects = 0;
    int blockCounter = tunnelActionComponent.maxDestroyedBlocks;
    for (int s = 0; s <= tunnelActionComponent.maxTunnelDepth; s++) {
      origin.add(dir);
      if (!worldProvider.isBlockRelevant(origin)) {
        break;
      }

      for (int i = 0; i < tunnelActionComponent.maxRaysCast; i++) {
        Vector3f direction = random.nextVector3f();
        Vector3f impulse = new Vector3f(direction);
        impulse.scale(tunnelActionComponent.explosiveForce);

        for (int j = 0; j < 3; j++) {
          Vector3f target = new Vector3f(origin);

          target.x += direction.x * j;
          target.y += direction.y * j;
          target.z += direction.z * j;

          blockPos.set((int) target.x, (int) target.y, (int) target.z);

          Block currentBlock = worldProvider.getBlock(blockPos);

          if (currentBlock.isDestructible()) {
            if (particleEffects < tunnelActionComponent.maxParticalEffects) {
              EntityBuilder builder = entityManager.newBuilder("engine:smokeExplosion");
              builder.getComponent(LocationComponent.class).setWorldPosition(target);
              builder.build();
              particleEffects++;
            }
            if (random.nextFloat() < tunnelActionComponent.thoroughness) {
              EntityRef blockEntity = blockEntityRegistry.getEntityAt(blockPos);
              blockEntity.send(
                  new DoDamageEvent(
                      tunnelActionComponent.damageAmount, tunnelActionComponent.damageType));
            }

            blockCounter--;
          }

          if (blockCounter <= 0) {
            return;
          }
        }
      }
    }
    // No blocks were destroyed, so cancel the event
    if (blockCounter == tunnelActionComponent.maxDestroyedBlocks) {
      event.consume();
    }
  }
/**
 * @author Immortius
 * @author Florian
 */
public class LocalChunkProvider implements ChunkProvider, GeneratingChunkProvider {

  private static final Logger logger = LoggerFactory.getLogger(LocalChunkProvider.class);
  private static final int UNLOAD_PER_FRAME = 64;
  private static final Vector3i UNLOAD_LEEWAY = Vector3i.one();

  private StorageManager storageManager;
  private final EntityManager entityManager;

  private ChunkGenerationPipeline pipeline;
  private TaskMaster<ChunkUnloadRequest> unloadRequestTaskMaster;
  private WorldGenerator generator;

  private Map<EntityRef, ChunkRelevanceRegion> regions = Maps.newHashMap();

  private Map<Vector3i, Chunk> nearCache = Maps.newConcurrentMap();

  private final Set<Vector3i> preparingChunks = Sets.newHashSet();
  private final BlockingQueue<ReadyChunkInfo> readyChunks = Queues.newLinkedBlockingQueue();
  private List<ReadyChunkInfo> sortedReadyChunks = Lists.newArrayList();
  private final BlockingQueue<TShortObjectMap<TIntList>> deactivateBlocksQueue =
      Queues.newLinkedBlockingQueue();

  private EntityRef worldEntity = EntityRef.NULL;

  private ReadWriteLock regionLock = new ReentrantReadWriteLock();

  private BlockManager blockManager;
  private BlockEntityRegistry registry;

  private LightMerger<ReadyChunkInfo> lightMerger = new LightMerger<>(this);

  public LocalChunkProvider(
      StorageManager storageManager, EntityManager entityManager, WorldGenerator generator) {
    this.blockManager = CoreRegistry.get(BlockManager.class);
    this.storageManager = storageManager;
    this.entityManager = entityManager;
    this.generator = generator;
    this.pipeline = new ChunkGenerationPipeline(new ChunkTaskRelevanceComparator());
    this.unloadRequestTaskMaster = TaskMaster.createFIFOTaskMaster("Chunk-Unloader", 4);
    ChunkMonitor.fireChunkProviderInitialized(this);
  }

  public void setBlockEntityRegistry(BlockEntityRegistry value) {
    this.registry = value;
  }

  @Override
  public ChunkViewCore getLocalView(Vector3i centerChunkPos) {
    Region3i region =
        Region3i.createFromCenterExtents(centerChunkPos, ChunkConstants.LOCAL_REGION_EXTENTS);
    if (getChunk(centerChunkPos) != null) {
      return createWorldView(region, Vector3i.one());
    }
    return null;
  }

  @Override
  public ChunkViewCore getSubviewAroundBlock(Vector3i blockPos, int extent) {
    Region3i region = ChunkMath.getChunkRegionAroundWorldPos(blockPos, extent);
    return createWorldView(region, new Vector3i(-region.min().x, -region.min().y, -region.min().z));
  }

  @Override
  public ChunkViewCore getSubviewAroundChunk(Vector3i chunkPos) {
    Region3i region =
        Region3i.createFromCenterExtents(chunkPos, ChunkConstants.LOCAL_REGION_EXTENTS);
    if (getChunk(chunkPos) != null) {
      return createWorldView(
          region, new Vector3i(-region.min().x, -region.min().y, -region.min().z));
    }
    return null;
  }

  private ChunkViewCore createWorldView(Region3i region, Vector3i offset) {
    Chunk[] chunks = new Chunk[region.sizeX() * region.sizeY() * region.sizeZ()];
    for (Vector3i chunkPos : region) {
      Chunk chunk = nearCache.get(chunkPos);
      if (chunk == null || !chunk.isReady()) {
        return null;
      }
      chunkPos.sub(region.minX(), region.minY(), region.minZ());
      int index = TeraMath.calculate3DArrayIndex(chunkPos, region.size());
      chunks[index] = chunk;
    }
    return new ChunkViewCoreImpl(
        chunks, region, offset, blockManager.getBlock(BlockManager.AIR_ID));
  }

  public void setWorldEntity(EntityRef worldEntity) {
    this.worldEntity = worldEntity;
  }

  @Override
  public void addRelevanceEntity(EntityRef entity, Vector3i distance) {
    addRelevanceEntity(entity, distance, null);
  }

  @Override
  public void addRelevanceEntity(
      EntityRef entity, Vector3i distance, ChunkRegionListener listener) {
    if (!entity.exists()) {
      return;
    }
    regionLock.readLock().lock();
    try {
      ChunkRelevanceRegion region = regions.get(entity);
      if (region != null) {
        region.setRelevanceDistance(distance);
        return;
      }
    } finally {
      regionLock.readLock().unlock();
    }

    ChunkRelevanceRegion region = new ChunkRelevanceRegion(entity, distance);
    if (listener != null) {
      region.setListener(listener);
    }
    regionLock.writeLock().lock();
    try {
      regions.put(entity, region);
    } finally {
      regionLock.writeLock().unlock();
    }
    for (Vector3i pos : region.getCurrentRegion()) {
      Chunk chunk = getChunk(pos);
      if (chunk != null) {
        region.chunkReady(chunk);
      } else {
        createOrLoadChunk(pos);
      }
    }
  }

  @Override
  public void updateRelevanceEntity(EntityRef entity, Vector3i distance) {
    regionLock.readLock().lock();
    try {
      ChunkRelevanceRegion region = regions.get(entity);
      if (region != null) {
        region.setRelevanceDistance(distance);
      }
    } finally {
      regionLock.readLock().unlock();
    }
  }

  @Override
  public void removeRelevanceEntity(EntityRef entity) {
    regionLock.writeLock().lock();
    try {
      regions.remove(entity);
    } finally {
      regionLock.writeLock().unlock();
    }
  }

  @Override
  public void completeUpdate() {
    ReadyChunkInfo readyChunkInfo = lightMerger.completeMerge();
    if (readyChunkInfo != null) {
      Chunk chunk = readyChunkInfo.getChunk();
      chunk.lock();
      try {
        chunk.markReady();
        updateAdjacentChunksReadyFieldOf(chunk);
        updateAdjacentChunksReadyFieldOfAdjChunks(chunk);

        if (!readyChunkInfo.isNewChunk()) {
          PerformanceMonitor.startActivity("Generating Block Entities");
          generateBlockEntities(chunk);
          PerformanceMonitor.endActivity();
        }
        if (readyChunkInfo.getChunkStore() != null) {
          readyChunkInfo.getChunkStore().restoreEntities();
        }

        if (!readyChunkInfo.isNewChunk()) {
          PerformanceMonitor.startActivity("Sending OnAddedBlocks");
          readyChunkInfo
              .getBlockPositionMapppings()
              .forEachEntry(
                  new TShortObjectProcedure<TIntList>() {
                    @Override
                    public boolean execute(short id, TIntList positions) {
                      if (positions.size() > 0) {
                        blockManager
                            .getBlock(id)
                            .getEntity()
                            .send(new OnAddedBlocks(positions, registry));
                      }
                      return true;
                    }
                  });
          PerformanceMonitor.endActivity();
        }

        PerformanceMonitor.startActivity("Sending OnActivateBlocks");
        readyChunkInfo
            .getBlockPositionMapppings()
            .forEachEntry(
                new TShortObjectProcedure<TIntList>() {
                  @Override
                  public boolean execute(short id, TIntList positions) {
                    if (positions.size() > 0) {
                      blockManager
                          .getBlock(id)
                          .getEntity()
                          .send(new OnActivatedBlocks(positions, registry));
                    }
                    return true;
                  }
                });
        PerformanceMonitor.endActivity();

        if (!readyChunkInfo.isNewChunk()) {
          worldEntity.send(new OnChunkGenerated(readyChunkInfo.getPos()));
        }
        worldEntity.send(new OnChunkLoaded(readyChunkInfo.getPos()));
        for (ChunkRelevanceRegion region : regions.values()) {
          region.chunkReady(chunk);
        }
      } finally {
        chunk.unlock();
      }
    }
  }

  @Override
  public void beginUpdate() {
    regionLock.readLock().lock();
    try {
      updateRelevance();
      deactivateBlocks();
      checkForUnload();
      makeChunksAvailable();
    } finally {
      regionLock.readLock().unlock();
    }
  }

  private void makeChunksAvailable() {
    List<ReadyChunkInfo> newReadyChunks = Lists.newArrayListWithExpectedSize(readyChunks.size());
    readyChunks.drainTo(newReadyChunks);
    for (ReadyChunkInfo readyChunkInfo : newReadyChunks) {
      nearCache.put(readyChunkInfo.getPos(), readyChunkInfo.getChunk());
      preparingChunks.remove(readyChunkInfo.getPos());
    }
    if (!newReadyChunks.isEmpty()) {
      sortedReadyChunks.addAll(newReadyChunks);
      Collections.sort(sortedReadyChunks, new ReadyChunkRelevanceComparator());
    }
    if (!sortedReadyChunks.isEmpty()) {
      boolean loaded = false;
      for (int i = sortedReadyChunks.size() - 1; i >= 0 && !loaded; i--) {
        ReadyChunkInfo chunkInfo = sortedReadyChunks.get(i);
        PerformanceMonitor.startActivity("Make Chunk Available");
        if (makeChunkAvailable(chunkInfo)) {
          sortedReadyChunks.remove(i);
          loaded = true;
        }
        PerformanceMonitor.endActivity();
      }
    }
  }

  private void deactivateBlocks() {
    List<TShortObjectMap<TIntList>> deactivatedBlockSets =
        Lists.newArrayListWithExpectedSize(deactivateBlocksQueue.size());
    deactivateBlocksQueue.drainTo(deactivatedBlockSets);
    for (TShortObjectMap<TIntList> deactivatedBlockSet : deactivatedBlockSets) {
      deactivatedBlockSet.forEachEntry(
          new TShortObjectProcedure<TIntList>() {
            @Override
            public boolean execute(short id, TIntList positions) {
              if (positions.size() > 0) {
                blockManager
                    .getBlock(id)
                    .getEntity()
                    .send(new BeforeDeactivateBlocks(positions, registry));
              }
              return true;
            }
          });
    }
  }

  private void checkForUnload() {
    PerformanceMonitor.startActivity("Unloading irrelevant chunks");
    int unloaded = 0;
    logger.debug("Compacting cache");
    Iterator<Vector3i> iterator = nearCache.keySet().iterator();
    while (iterator.hasNext()) {
      Vector3i pos = iterator.next();
      boolean keep = false;
      for (ChunkRelevanceRegion region : regions.values()) {
        if (region.getCurrentRegion().expand(UNLOAD_LEEWAY).encompasses(pos)) {
          keep = true;
          break;
        }
      }
      if (!keep) {
        // TODO: need some way to not dispose chunks being edited or processed (or do so safely)
        // Note: Above won't matter if all changes are on the main thread
        if (unloadChunkInternal(pos)) {
          iterator.remove();
          if (++unloaded >= UNLOAD_PER_FRAME) {
            break;
          }
        }
      }
    }
    PerformanceMonitor.endActivity();
  }

  private boolean unloadChunkInternal(Vector3i pos) {
    Chunk chunk = nearCache.get(pos);
    if (chunk.isLocked()) {
      return false;
    }

    chunk.lock();
    try {
      if (!chunk.isReady()) {
        // Chunk hasn't been finished or changed, so just drop it.
        Iterator<ReadyChunkInfo> infoIterator = sortedReadyChunks.iterator();
        while (infoIterator.hasNext()) {
          ReadyChunkInfo next = infoIterator.next();
          if (next.getPos().equals(chunk.getPosition())) {
            infoIterator.remove();
            break;
          }
        }
        return true;
      }
      worldEntity.send(new BeforeChunkUnload(pos));
      for (ChunkRelevanceRegion region : regions.values()) {
        region.chunkUnloaded(pos);
      }
      storageManager.deactivateChunk(chunk);
      chunk.dispose();
      updateAdjacentChunksReadyFieldOfAdjChunks(chunk);

      try {
        unloadRequestTaskMaster.put(new ChunkUnloadRequest(chunk, this));
      } catch (InterruptedException e) {
        logger.error("Failed to enqueue unload request for {}", chunk.getPosition(), e);
      }

      return true;
    } finally {
      chunk.unlock();
    }
  }

  private boolean areAdjacentChunksReady(Chunk chunk) {
    Vector3i centerChunkPos = chunk.getPosition();
    for (Side side : Side.values()) {
      Vector3i adjChunkPos = side.getAdjacentPos(centerChunkPos);
      Chunk adjChunk = nearCache.get(adjChunkPos);
      boolean adjChunkReady = (adjChunk != null && adjChunk.isReady());
      if (!adjChunkReady) {
        return false;
      }
    }
    return true;
  }

  private void updateAdjacentChunksReadyFieldOf(Chunk chunk) {
    chunk.setAdjacentChunksReady(areAdjacentChunksReady(chunk));
  }

  private void updateAdjacentChunksReadyFieldOfAdjChunks(Chunk chunkInCenter) {
    Vector3i centerChunkPos = chunkInCenter.getPosition();
    for (Side side : Side.values()) {
      Vector3i adjChunkPos = side.getAdjacentPos(centerChunkPos);
      Chunk adjChunk = nearCache.get(adjChunkPos);
      if (adjChunk != null) {
        updateAdjacentChunksReadyFieldOf(adjChunk);
      }
    }
  }

  private void updateRelevance() {
    for (ChunkRelevanceRegion chunkRelevanceRegion : regions.values()) {
      chunkRelevanceRegion.update();
      if (chunkRelevanceRegion.isDirty()) {
        for (Vector3i pos : chunkRelevanceRegion.getNeededChunks()) {
          Chunk chunk = nearCache.get(pos);
          if (chunk != null && chunk.isReady()) {
            chunkRelevanceRegion.chunkReady(chunk);
          } else if (chunk == null) {
            createOrLoadChunk(pos);
          }
        }
        chunkRelevanceRegion.setUpToDate();
      }
    }
  }

  private boolean makeChunkAvailable(final ReadyChunkInfo readyChunkInfo) {
    final Chunk chunk = nearCache.get(readyChunkInfo.getPos());
    if (chunk == null) {
      return false;
    }
    for (Vector3i pos : Region3i.createFromCenterExtents(readyChunkInfo.getPos(), 1)) {
      if (nearCache.get(pos) == null) {
        return false;
      }
    }
    lightMerger.beginMerge(chunk, readyChunkInfo);
    return true;
  }

  // Generates all non-temporary block entities
  private void generateBlockEntities(Chunk chunk) {
    ChunkBlockIterator i = chunk.getBlockIterator();
    while (i.next()) {
      if (i.getBlock().isKeepActive()) {
        registry.getBlockEntityAt(i.getBlockPos());
      }
    }
  }

  void gatherBlockPositionsForDeactivate(Chunk chunk) {
    try {
      deactivateBlocksQueue.put(createBatchBlockEventMappings(chunk));
    } catch (InterruptedException e) {
      logger.error("Failed to queue deactivation of blocks for {}", chunk.getPosition());
    }
  }

  private TShortObjectMap<TIntList> createBatchBlockEventMappings(Chunk chunk) {
    TShortObjectMap<TIntList> batchBlockMap = new TShortObjectHashMap<>();
    for (Block block : blockManager.listRegisteredBlocks()) {
      if (block.isLifecycleEventsRequired()) {
        batchBlockMap.put(block.getId(), new TIntArrayList());
      }
    }

    ChunkBlockIterator i = chunk.getBlockIterator();
    while (i.next()) {
      if (i.getBlock().isLifecycleEventsRequired()) {
        TIntList positionList = batchBlockMap.get(i.getBlock().getId());
        positionList.add(i.getBlockPos().x);
        positionList.add(i.getBlockPos().y);
        positionList.add(i.getBlockPos().z);
      }
    }
    return batchBlockMap;
  }

  @Override
  public Chunk getChunk(int x, int y, int z) {
    return getChunk(new Vector3i(x, y, z));
  }

  @Override
  public Chunk getChunk(Vector3i pos) {
    Chunk chunk = nearCache.get(pos);
    if (isChunkReady(chunk)) {
      return chunk;
    }
    return null;
  }

  @Override
  public Collection<Chunk> getAllChunks() {
    return nearCache.values();
  }

  public void restart() {
    pipeline.restart();
    unloadRequestTaskMaster.restart();
    lightMerger.restart();
  }

  @Override
  public void shutdown() {
    pipeline.shutdown();
    unloadRequestTaskMaster.shutdown(new ChunkUnloadRequest(), true);
    lightMerger.shutdown();
  }

  @Override
  public void dispose() {
    shutdown();

    for (Chunk chunk : nearCache.values()) {
      unloadChunkInternal(chunk.getPosition());
      chunk.dispose();
    }
    nearCache.clear();
    /*
     * The chunk monitor needs to clear chunk references, so it's important
     * that no new chunk get created
     */
    ChunkMonitor.fireChunkProviderDisposed(this);
  }

  @Override
  public boolean reloadChunk(Vector3i coords) {
    if (!nearCache.containsKey(coords)) {
      return false;
    }

    if (unloadChunkInternal(coords)) {
      nearCache.remove(coords);
      createOrLoadChunk(coords);
      return true;
    }

    return false;
  }

  @Override
  public void purgeWorld() {
    ChunkMonitor.fireChunkProviderDisposed(this);
    pipeline.shutdown();
    unloadRequestTaskMaster.shutdown(new ChunkUnloadRequest(), true);
    lightMerger.shutdown();

    for (Chunk chunk : nearCache.values()) {
      if (chunk.isReady()) {
        worldEntity.send(new BeforeChunkUnload(chunk.getPosition()));
        storageManager.deactivateChunk(chunk);
        chunk.dispose();
      }
    }
    nearCache.clear();
    readyChunks.clear();
    sortedReadyChunks.clear();
    storageManager.deleteWorld();
    preparingChunks.clear();
    worldEntity.send(new PurgeWorldEvent());

    pipeline = new ChunkGenerationPipeline(new ChunkTaskRelevanceComparator());
    unloadRequestTaskMaster = TaskMaster.createFIFOTaskMaster("Chunk-Unloader", 8);
    lightMerger = new LightMerger<>(this);
    lightMerger.restart();
    ChunkMonitor.fireChunkProviderInitialized(this);

    for (ChunkRelevanceRegion chunkRelevanceRegion : regions.values()) {
      for (Vector3i pos : chunkRelevanceRegion.getCurrentRegion()) {
        createOrLoadChunk(pos);
      }
      chunkRelevanceRegion.setUpToDate();
    }
  }

  private void createOrLoadChunk(Vector3i chunkPos) {
    Chunk chunk = nearCache.get(chunkPos);
    if (chunk == null && !preparingChunks.contains(chunkPos)) {
      preparingChunks.add(chunkPos);
      pipeline.doTask(
          new AbstractChunkTask(chunkPos) {
            @Override
            public String getName() {
              return "Create or Load Chunk";
            }

            @Override
            public void run() {
              ChunkStore chunkStore = storageManager.loadChunkStore(getPosition());
              Chunk chunk;
              if (chunkStore == null) {
                chunk = new ChunkImpl(getPosition());
                generator.createChunk(chunk);
              } else {
                chunk = chunkStore.getChunk();
              }

              InternalLightProcessor.generateInternalLighting(chunk);
              chunk.deflate();
              readyChunks.offer(
                  new ReadyChunkInfo(chunk, createBatchBlockEventMappings(chunk), chunkStore));
            }
          });
    }
  }

  @Override
  public void onChunkIsReady(Chunk chunk) {
    readyChunks.offer(new ReadyChunkInfo(chunk, createBatchBlockEventMappings(chunk)));
  }

  @Override
  public Chunk getChunkUnready(Vector3i pos) {
    return nearCache.get(pos);
  }

  @Override
  public boolean isChunkReady(Vector3i pos) {
    return isChunkReady(nearCache.get(pos));
  }

  private boolean isChunkReady(Chunk chunk) {
    return chunk != null && chunk.isReady();
  }

  private class ChunkTaskRelevanceComparator implements Comparator<ChunkTask> {

    @Override
    public int compare(ChunkTask o1, ChunkTask o2) {
      return score(o1) - score(o2);
    }

    private int score(ChunkTask task) {
      if (task.isTerminateSignal()) {
        return -1;
      }
      return score(task.getPosition());
    }

    private int score(Vector3i chunk) {
      int score = Integer.MAX_VALUE;

      regionLock.readLock().lock();
      try {
        for (ChunkRelevanceRegion region : regions.values()) {
          int dist = distFromRegion(chunk, region.getCenter());
          if (dist < score) {
            score = dist;
          }
        }
        return score;
      } finally {
        regionLock.readLock().unlock();
      }
    }

    private int distFromRegion(Vector3i pos, Vector3i regionCenter) {
      return pos.gridDistance(regionCenter);
    }
  }

  private class ReadyChunkRelevanceComparator implements Comparator<ReadyChunkInfo> {

    @Override
    public int compare(ReadyChunkInfo o1, ReadyChunkInfo o2) {
      return score(o2.getPos()) - score(o1.getPos());
    }

    private int score(Vector3i chunk) {
      int score = Integer.MAX_VALUE;

      regionLock.readLock().lock();
      try {
        for (ChunkRelevanceRegion region : regions.values()) {
          int dist = distFromRegion(chunk, region.getCenter());
          if (dist < score) {
            score = dist;
          }
        }
        return score;
      } finally {
        regionLock.readLock().unlock();
      }
    }

    private int distFromRegion(Vector3i pos, Vector3i regionCenter) {
      return pos.gridDistance(regionCenter);
    }
  }
}
 private int distFromRegion(Vector3i pos, Vector3i regionCenter) {
   return pos.gridDistance(regionCenter);
 }
  /**
   * Checks whether a character should change movement mode (from being underwater or in a ladder).
   * A higher and lower point of the character is tested for being in water, only if both points are
   * in water does the character count as swimming. <br>
   * <br>
   * Sends the OnEnterLiquidEvent and OnLeaveLiquidEvent events.
   *
   * @param movementComp The movement component of the character.
   * @param state The current state of the character.
   */
  private void checkMode(
      final CharacterMovementComponent movementComp,
      final CharacterStateEvent state,
      final CharacterStateEvent oldState,
      EntityRef entity,
      boolean firstRun) {
    // If we are ghosting or we can't move, the mode cannot be changed.
    if (!state.getMode().respondToEnvironment) {
      return;
    }

    Vector3f worldPos = state.getPosition();
    Vector3f top = new Vector3f(worldPos);
    Vector3f bottom = new Vector3f(worldPos);
    top.y += 0.5f * movementComp.height;
    bottom.y -= 0.5f * movementComp.height;

    final boolean topUnderwater = worldProvider.getBlock(top).isLiquid();
    final boolean bottomUnderwater = worldProvider.getBlock(bottom).isLiquid();

    final boolean newSwimming = !topUnderwater && bottomUnderwater;
    final boolean newDiving = topUnderwater && bottomUnderwater;
    boolean newClimbing = false;

    // TODO: refactor this knot of if-else statements into something easy to read. Some sub-methods
    // and switch statements would be nice.
    if (!newSwimming && !newDiving) { // TODO: generalize to isClimbingAllowed() or similar
      Vector3f[] sides = {
        new Vector3f(worldPos),
        new Vector3f(worldPos),
        new Vector3f(worldPos),
        new Vector3f(worldPos),
        new Vector3f(worldPos)
      };
      float factor = 1.0f;
      sides[0].x += factor * movementComp.radius;
      sides[1].x -= factor * movementComp.radius;
      sides[2].z += factor * movementComp.radius;
      sides[3].z -= factor * movementComp.radius;
      sides[4].y -= movementComp.height;

      float distance = 100f;

      for (Vector3f side : sides) {
        Block block = worldProvider.getBlock(side);
        if (block.isClimbable()) {
          // If any of our sides are near a climbable block, check if we are near to the side
          Vector3i myPos = new Vector3i(worldPos, 0.5f);
          Vector3i climbBlockPos = new Vector3i(side, 0.5f);
          Vector3i dir = new Vector3i(block.getDirection().getVector3i());
          float currentDistance = 10f;

          if (dir.x != 0
              && Math.abs(worldPos.x - (float) climbBlockPos.x + (float) dir.x * .5f)
                  < movementComp.radius + 0.1f) {
            newClimbing = true;
            if (myPos.x < climbBlockPos.x) {
              dir.x = -dir.x;
            }
            currentDistance = Math.abs(climbBlockPos.z - worldPos.z);

          } else if (dir.z != 0
              && Math.abs(worldPos.z - (float) climbBlockPos.z + (float) dir.z * .5f)
                  < movementComp.radius + 0.1f) {
            newClimbing = true;
            if (myPos.z < climbBlockPos.z) {
              dir.z = -dir.z;
            }
            currentDistance = Math.abs(climbBlockPos.z - worldPos.z);
          }

          // if there are multiple climb blocks, choose the nearest one. This can happen when there
          // are two
          // adjacent ledges around a corner.
          if (currentDistance < distance) {
            distance = currentDistance;
            state.setClimbDirection(dir);
          }
        }
      }
    }

    if (newDiving) {
      if (state.getMode() != MovementMode.DIVING) {
        state.setMode(MovementMode.DIVING);
      }
    } else if (newSwimming) {
      if (state.getMode() != MovementMode.SWIMMING) {
        state.setMode(MovementMode.SWIMMING);
      }
      state.getVelocity().y += 0.02;
    } else if (state.getMode() == MovementMode.SWIMMING) {
      if (newClimbing) {
        state.setMode(MovementMode.CLIMBING);
        state.getVelocity().y = 0;
      } else {
        if (state.getVelocity().y > 0) {
          state.getVelocity().y += 4;
        }
        state.setMode(MovementMode.WALKING);
      }
    } else if (newClimbing != (state.getMode() == MovementMode.CLIMBING)) {
      // We need to toggle the climbing mode
      state.getVelocity().y = 0;
      state.setMode((newClimbing) ? MovementMode.CLIMBING : MovementMode.WALKING);
    }
  }