/* * 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; }
/** @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() {} }
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); } }