/* * This file is part of Cubic Chunks Mod, licensed under the MIT License (MIT). * * Copyright (c) 2015 contributors * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package cubicchunks.world.cube; import com.google.common.base.Predicate; import net.minecraft.block.Block; import net.minecraft.block.material.Material; import net.minecraft.block.state.IBlockState; import net.minecraft.crash.CrashReport; import net.minecraft.crash.CrashReportCategory; import net.minecraft.entity.Entity; import net.minecraft.init.Blocks; import net.minecraft.tileentity.TileEntity; import net.minecraft.util.ReportedException; import net.minecraft.util.math.AxisAlignedBB; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.MathHelper; import net.minecraft.world.EnumSkyBlock; import net.minecraft.world.World; import net.minecraft.world.chunk.Chunk; import net.minecraft.world.chunk.storage.ExtendedBlockStorage; import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.event.entity.EntityEvent; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentLinkedQueue; import javax.annotation.Nullable; import cubicchunks.util.AddressTools; import cubicchunks.util.Coords; import cubicchunks.util.CubePos; import cubicchunks.util.XYZAddressable; import cubicchunks.util.ticket.TicketList; import cubicchunks.world.EntityContainer; import cubicchunks.world.ICubicWorld; import cubicchunks.world.ICubicWorldServer; import cubicchunks.world.IHeightMap; import cubicchunks.world.column.Column; import cubicchunks.worldgen.generator.ICubePrimer; import static cubicchunks.CubicChunks.LOGGER; /** * A cube is our extension of minecraft's chunk system to three dimensions. Each cube encloses a cubic area in the world * with a side length of {@link Cube#SIZE}, aligned to multiples of that length and stored within columns. */ public class Cube implements XYZAddressable { /** * Side length of a cube */ public static final int SIZE = 16; private final LightUpdateData lightUpdateData = new LightUpdateData(this); /** * Tickets keep this chunk loaded and ticking. See the docs of {@link TicketList} and {@link * cubicchunks.util.ticket.ITicket} for additional information. */ private TicketList tickets; // tickets prevent this Cube from being unloaded /** * Has anything within the cube changed since it was loaded from disk? */ private boolean isModified = false; /** * Has the cube generator's populate() method been called for this cube? */ private boolean isPopulated = false; /** * Has the cube generator's populate() method been called for every cube potentially writing to this cube during * population? */ private boolean isFullyPopulated = false; /** * Has the initial light map been calculated? */ private boolean isInitialLightingDone = false; /** * The world of this cube */ private ICubicWorld world; /** * The column of this cube */ private Column column; /** * The position of this cube, in cube space */ private CubePos coords; /** * Blocks in this cube */ private ExtendedBlockStorage storage; /** * Entities in this cube */ private EntityContainer entities; /** * The position of tile entities in this cube, and their corresponding tile entity */ private Map<BlockPos, TileEntity> tileEntityMap; /** * The positions of tile entities queued for creation */ private ConcurrentLinkedQueue<BlockPos> tileEntityPosQueue; /** * Is this cube loaded and not queued for unload */ private boolean isCubeLoaded; /** * Create a new cube in the specified column at the specified location. The newly created cube will only contain air * blocks. * * @param column column of this cube * @param cubeY cube y position */ public Cube(Column column, int cubeY) { this.world = column.getCubicWorld(); this.column = column; this.coords = new CubePos(column.getX(), cubeY, column.getZ()); this.tickets = new TicketList(); this.entities = new EntityContainer(); this.tileEntityMap = new HashMap<>(); this.tileEntityPosQueue = new ConcurrentLinkedQueue<>(); } /** * Create a new cube at the specified location by copying blocks from a cube primer. * * @param column column of this cube * @param cubeY cube y position * @param primer primer containing the blocks for this cube */ @SuppressWarnings("deprecation") // when a block is generated, does it really have any extra // information it could give us about its opacity by knowing its location? public Cube(Column column, int cubeY, ICubePrimer primer) { this(column, cubeY); int miny = Coords.cubeToMinBlock(cubeY); IHeightMap opindex = column.getOpacityIndex(); for (int x = 0; x < 16; x++) { for (int z = 0; z < 16; z++) { for (int y = 15; y >= 0; y--) { IBlockState newstate = primer.getBlockState(x, y, z); if (newstate.getMaterial() != Material.AIR) { if (storage == null) { newStorage(); } storage.set(x, y, z, newstate); if (newstate.getLightOpacity() != 0) { column.setModified(true); //TODO: this is a bit of am abstraction leak... maybe ServerHeightMap needs its own isModified opindex.onOpacityChange(x, miny + y, z, newstate.getLightOpacity()); } } } } } isModified = true; } //====================================== //========Chunk vanilla methods========= //====================================== /** * Retrieve the block state at the specified location * * @param pos target location * * @return The block state * * @see Cube#getBlockState(int, int, int) */ public IBlockState getBlockState(BlockPos pos) { return this.getBlockState(pos.getX(), pos.getY(), pos.getZ()); } /** * Set the block state at the specified location * * @param pos target location * @param newstate target state of the block at that position * * @return The the old state of the block at the position, or null if there was no change * * @see Column#setBlockState(BlockPos, IBlockState) */ // forward to Column, as we don't know how to do skylight and stuff public IBlockState setBlockState(BlockPos pos, IBlockState newstate) { return column.setBlockState(pos, newstate); } /** * Retrieve the block state at the specified location * * @param blockX block x position * @param blockY block y position * @param blockZ block z position * * @return The block state * * @see Cube#getBlockState(BlockPos) */ public IBlockState getBlockState(int blockX, int blockY, int blockZ) { try { if (storage == null) { return Blocks.AIR.getDefaultState(); } return storage.get(Coords.blockToLocal(blockX), Coords.blockToLocal(blockY), Coords.blockToLocal(blockZ)); } catch (Throwable t) { CrashReport report = CrashReport.makeCrashReport(t, "Getting block state"); CrashReportCategory category = report.makeCategory("Block being got"); category.setDetail("Location", () -> CrashReportCategory.getCoordinateInfo(blockX, blockY, blockZ)); throw new ReportedException(report); } } /** * Sets a block state in this cube, lighting not included * * @param pos the location of the block * @param newstate the new block state * * @return The old block state, or null if there was no change */ @Nullable public IBlockState setBlockStateDirect(BlockPos pos, IBlockState newstate) { // TODO this method could probably be split up int localX = Coords.blockToLocal(pos.getX()); int localY = Coords.blockToLocal(pos.getY()); int localZ = Coords.blockToLocal(pos.getZ()); IBlockState oldstate = getBlockState(pos); if (oldstate == newstate) { return null; // nothing changed } Block oldblock = oldstate.getBlock(); Block newblock = newstate.getBlock(); if (storage == null) { newStorage(); } storage.set(localX, localY, localZ, newstate); // set the block state! // deal with Block.breakBlock() and TileEntity's if (!this.world.isRemote()) { if (newblock != oldblock) { //Only fire block breaks when the block changes. oldblock.breakBlock((World) this.world, pos, oldstate); } TileEntity te = this.getTileEntity(pos, Chunk.EnumCreateEntityType.CHECK); if (te != null && te.shouldRefresh((World) this.world, pos, oldstate, newstate)) { this.world.removeTileEntity(pos); } } else if (oldblock.hasTileEntity(oldstate)) { TileEntity te = this.getTileEntity(pos, Chunk.EnumCreateEntityType.CHECK); if (te != null && te.shouldRefresh((World) this.world, pos, oldstate, newstate)) { this.world.removeTileEntity(pos); } } if (storage.get(localX, localY, localZ).getBlock() != newblock) { // A TileEntity changed the bock on us!!! return null; // something changed... but its out of our control // (aka another Cube.setBlockState() call handled it) // so return as if 'nothing changed' } // If capturing blocks, only run block physics for TE's. Non-TE's are handled in ForgeHooks.onPlaceItemIntoWorld if (!this.world.isRemote() && oldblock != newblock && (!((World) this.world).captureBlockSnapshots || newblock.hasTileEntity(newstate))) { newblock.onBlockAdded((World) this.world, pos, newstate); } if (newblock.hasTileEntity(newstate)) { TileEntity te = this.getTileEntity(pos, Chunk.EnumCreateEntityType.CHECK); if (te == null) { te = newblock.createTileEntity((World) this.world, newstate); this.world.setTileEntity(pos, te); } if (te != null) { te.updateContainingBlockInfo(); } } this.isModified = true; // a block state changes, so we will need saving return oldstate; } /** * Retrieve the raw light level at the specified location * * @param lightType The type of light (sky or block light) * @param pos The position at which light should be checked * * @return the light level */ public int getLightFor(EnumSkyBlock lightType, BlockPos pos) { //it may not look like this but it's actually the same logic as in vanilla if (this.isEmpty()) { if (this.column.canSeeSky(pos)) { return lightType.defaultLightValue; } return 0; } int localX = Coords.blockToLocal(pos.getX()); int localY = Coords.blockToLocal(pos.getY()); int localZ = Coords.blockToLocal(pos.getZ()); switch (lightType) { case SKY: return getSkylight(localX, localY, localZ); case BLOCK: if (storage == null) { return lightType.defaultLightValue; } return this.storage.getExtBlocklightValue(localX, localY, localZ); default: return lightType.defaultLightValue; } } /** * Set the raw light level at the specified location * * @param lightType The type of light (sky or block light) * @param pos The position at which light should be updated * @param light the light level */ public void setLightFor(EnumSkyBlock lightType, BlockPos pos, int light) { this.isModified = true; int x = Coords.blockToLocal(pos.getX()); int y = Coords.blockToLocal(pos.getY()); int z = Coords.blockToLocal(pos.getZ()); switch (lightType) { case SKY: setSkylight(x, y, z, light); break; case BLOCK: if (storage == null) { newStorage(); } this.storage.setExtBlocklightValue(x, y, z, light); break; } } /** * Set sky light level at this location. Has no effect if the world has no sky. * * @param localX block x position in local block coordinates * @param localY block y position in local block coordinates * @param localZ block z position in local block coordinates * @param value the new light level */ public void setSkylight(int localX, int localY, int localZ, int value) { if (!this.world.getProvider().getHasNoSky()) { if (storage == null) { newStorage(); } this.isModified = true; this.storage.setExtSkylightValue(localX, localY, localZ, value); } } /** * Retrieve sky light levels at this location. Always returns 0 for worlds with no sky. * * @param localX block x position in local block coordinates * @param localY block y position in local block coordinates * @param localZ block z position in local block coordinates * * @return sky light levels at this location. */ public int getSkylight(int localX, int localY, int localZ) { if (this.world.getProvider().getHasNoSky()) { return 0; } if (storage == null) { return EnumSkyBlock.SKY.defaultLightValue; } return this.storage.getExtSkylightValue(localX, localY, localZ); } /** * Retrieve actual light level at the specified location. This is the brightest of all types of light affecting this * block * * @param pos the target position * @param skyLightDampeningTerm skylight falloff factor * * @return actual light level at this location */ public int getLightSubtracted(BlockPos pos, int skyLightDampeningTerm) { // get sky light int skyLight = getLightFor(EnumSkyBlock.SKY, pos); skyLight -= skyLightDampeningTerm; // get block light int blockLight = getLightFor(EnumSkyBlock.BLOCK, pos); // FIGHT!!! return Math.max(blockLight, skyLight); } /** * Create a tile entity at the given position if the block is able to hold one * * @param pos position where the tile entity should be placed * * @return the created tile entity, or <code>null</code> if the block at that position does not provide tile * entities */ @Nullable private TileEntity createTileEntity(BlockPos pos) { IBlockState blockState = getBlockState(pos); Block block = blockState.getBlock(); if (block.hasTileEntity(blockState)) { return block.createTileEntity((World) this.world, blockState); } return null; } /** * Add an entity to this cube * * @param entity entity to add */ public void addEntity(Entity entity) { // make sure the entity is in this cube int cubeX = Coords.getCubeXForEntity(entity); int cubeY = Coords.getCubeYForEntity(entity); int cubeZ = Coords.getCubeZForEntity(entity); if (cubeX != this.coords.getX() || cubeY != this.coords.getY() || cubeZ != this.coords.getZ()) { LOGGER.warn(String.format("Wrong entity (%s) location. Entity thinks it's in (%d,%d,%d) but actua location is (%d,%d,%d)!", entity.getClass().getName(), cubeX, cubeY, cubeZ, this.coords.getX(), this.coords.getY(), this.coords.getZ())); entity.setDead(); } //post the event, we can't send cube position here :( MinecraftForge.EVENT_BUS.post(new EntityEvent.EnteringChunk( entity, this.getX(), this.getZ(), entity.chunkCoordX, entity.chunkCoordZ)); // tell the entity it's in this cube entity.addedToChunk = true; entity.chunkCoordX = this.coords.getX(); entity.chunkCoordY = this.coords.getY(); entity.chunkCoordZ = this.coords.getZ(); this.entities.addEntity(entity); this.isModified = true; } /** * Remove an entity from this cube * * @param entity The entity to remove */ public boolean removeEntity(Entity entity) { boolean wasRemoved = this.entities.remove(entity); if (wasRemoved) { this.isModified = true; } return wasRemoved; } /** * Retrieve the tile entity at the specified location * * @param pos target location * @param createType how fast the tile entity is needed * * @return the tile entity at the specified location, or <code>null</code> if there is no entity and * <code>createType</code> was not {@link net.minecraft.world.chunk.Chunk.EnumCreateEntityType#IMMEDIATE} */ public TileEntity getTileEntity(BlockPos pos, Chunk.EnumCreateEntityType createType) { TileEntity blockEntity = this.tileEntityMap.get(pos); if (blockEntity != null && blockEntity.isInvalid()) { this.tileEntityMap.remove(pos); blockEntity = null; } if (blockEntity == null) { if (createType == Chunk.EnumCreateEntityType.IMMEDIATE) { blockEntity = createTileEntity(pos); this.world.setTileEntity(pos, blockEntity); } else if (createType == Chunk.EnumCreateEntityType.QUEUED) { this.tileEntityPosQueue.add(pos); } } return blockEntity; } /** * Add a tile entity to this cube * * @param tileEntity The tile entity to add */ public void addTileEntity(TileEntity tileEntity) { this.addTileEntity(tileEntity.getPos(), tileEntity); if (this.isCubeLoaded) { //TODO: test to see if this is needed this.getCubicWorld().addTileEntity(tileEntity); } } /** * Add a tile entity to this cube at the specified location * * @param pos The target location * @param tileEntity The tile entity to add */ public void addTileEntity(BlockPos pos, TileEntity tileEntity) { // update the tile entity tileEntity.setWorldObj((World) this.world); tileEntity.setPos(pos); IBlockState blockState = this.getBlockState(pos); // is this block supposed to have a tile entity? if (blockState.getBlock().hasTileEntity(blockState)) { // cleanup the old tile entity TileEntity oldBlockEntity = this.tileEntityMap.get(pos); if (oldBlockEntity != null) { oldBlockEntity.invalidate(); } // install the new tile entity tileEntity.validate(); this.tileEntityMap.put(pos, tileEntity); this.isModified = true; tileEntity.onLoad(); } } /** * Remove the tile entity at the specified location * * @param pos target location */ public void removeTileEntity(BlockPos pos) { //it doesn't make sense to me to check if cube is loaded, but vanilla does it if (this.isCubeLoaded) { //TODO: test and see if this is needed TileEntity tileEntity = this.tileEntityMap.remove(pos); if (tileEntity != null) { tileEntity.invalidate(); this.isModified = true; } } } /** * Retrieve all matching entities within a specific area of the world that are also in this cube * * @param excluded don't include this entity in the results * @param queryBox section of the world being checked * @param out list to which found entities should be added * @param predicate filter to match entities against */ public void getEntitiesWithinAABBForEntity(Entity excluded, AxisAlignedBB queryBox, List<Entity> out, Predicate<? super Entity> predicate) { this.entities.getEntitiesWithinAABBForEntity(excluded, queryBox, out, predicate); } /** * Retrieve all matching entities of the specified type within a specific area of the world that are also in this * world * * @param entityType the type of entity to retrieve * @param queryBox section of the world being checked * @param out list to which found entities should be added * @param predicate filter to match entities against * @param <T> type parameter for the class of entities being searched for */ public <T extends Entity> void getEntitiesOfTypeWithinAAAB(Class<? extends T> entityType, AxisAlignedBB queryBox, List<T> out, Predicate<? super T> predicate) { this.entities.getEntitiesOfTypeWithinAAAB(entityType, queryBox, out, predicate); } /** * Tick this cube * * @param tryToTickFaster Whether costly calculations should be skipped in order to catch up with ticks */ public void tickCube(boolean tryToTickFaster) { if (!this.isInitialLightingDone && this.isPopulated) { this.tryDoFirstLight(); //TODO: Very icky light population code! REMOVE IT! } while (!this.tileEntityPosQueue.isEmpty()) { BlockPos blockpos = this.tileEntityPosQueue.poll(); IBlockState state = this.getBlockState(blockpos); Block block = state.getBlock(); if (this.getTileEntity(blockpos, Chunk.EnumCreateEntityType.CHECK) == null && block.hasTileEntity(state)) { TileEntity tileentity = this.createTileEntity(blockpos); this.world.setTileEntity(blockpos, tileentity); this.world.markBlockRangeForRenderUpdate(blockpos, blockpos); } } } /** * Calculate diffuse skylight in this cube if surrounding cubes are loaded */ //TODO: Redo light population code private void tryDoFirstLight() { BlockPos pos = this.getCoords().getMinBlockPos(); final int radius = 17; // TODO replace hardcoded constant with reference to Cube#SIZE if (!world.isAreaLoaded(pos.add(-radius, -radius, -radius), pos.add(15 + radius, 15 + radius, 15 + radius))) { return; } //client cubes (setClientCube) are always fully generated, isInitialLightingDone is never false ((ICubicWorldServer) this.world).getFirstLightProcessor().diffuseSkylight(this); this.isInitialLightingDone = true; } //================================= //=========Other methods=========== //================================= /** * Check if there are any non-air blocks in this cube * * @return <code>true</code> if this cube contains only air blocks, <code>false</code> otherwise */ public boolean isEmpty() { return storage == null || this.storage.isEmpty(); } /** * Return the long-encoded address of this cube's coordinates * * @return the cube's address * * @see AddressTools#getAddress(int, int, int) */ public long getAddress() { return AddressTools.getAddress(this.coords.getX(), this.coords.getY(), this.coords.getZ()); } /** * Convert an integer-encoded address to a local block to a global block position * * @param localAddress the address of the block * * @return the block position */ public BlockPos localAddressToBlockPos(int localAddress) { int x = Coords.localToBlock(this.coords.getX(), AddressTools.getLocalX(localAddress)); int y = Coords.localToBlock(this.coords.getY(), AddressTools.getLocalY(localAddress)); int z = Coords.localToBlock(this.coords.getZ(), AddressTools.getLocalZ(localAddress)); return new BlockPos(x, y, z); } /** * @return this cube's world */ public ICubicWorld getCubicWorld() { return this.world; } /** * @return this cube's column */ public Column getColumn() { return this.column; } /** * Retrieve this cube's x coordinate in cube space * * @return cube x position */ public int getX() { return this.coords.getX(); } /** * Retrieve this cube's y coordinate in cube space * * @return cube y position */ public int getY() { return this.coords.getY(); } /** * Retrieve this cube's z coordinate in cube space * * @return cube z position */ public int getZ() { return this.coords.getZ(); } /** * @return this cube's position */ public CubePos getCoords() { return this.coords; } /** * Check whether a given global block position is contained in this cube * * @param blockPos the position of the block * * @return <code>true</code> if the position is within this cube, <code>false</code> otherwise */ public boolean containsBlockPos(BlockPos blockPos) { return this.coords.getX() == Coords.blockToCube(blockPos.getX()) && this.coords.getY() == Coords.blockToCube(blockPos.getY()) && this.coords.getZ() == Coords.blockToCube(blockPos.getZ()); } public ExtendedBlockStorage getStorage() { return this.storage; } public ExtendedBlockStorage setStorage(ExtendedBlockStorage ebs) { return this.storage = ebs; } private void newStorage() { storage = new ExtendedBlockStorage(Coords.cubeToMinBlock(getY()), !world.getProvider().getHasNoSky()); } /** * Retrieve a map of positions to their respective tile entities * * @return a map containing all tile entities in this cube */ public Map<BlockPos, TileEntity> getTileEntityMap() { return this.tileEntityMap; } /** * Retrieve a list of entities in this cube * * @return the entities' container */ public EntityContainer getEntityContainer() { return this.entities; } /** * Finish the cube loading process */ public void onLoad() { // tell the world about tile entities this.world.addTileEntities(this.tileEntityMap.values()); this.world.loadEntities(this.entities.getEntities()); this.isCubeLoaded = true; } /** * Mark this cube as no longer part of this world */ public void onUnload() { //first mark as unloaded so that entity list and tile entity map isn't modified while iterating //and it also preserves all entities/time entities so they can be saved this.isCubeLoaded = false; // tell the world to forget about entities this.world.unloadEntities(this.entities.getEntities()); for (Entity entity : this.entities.getEntities()) { //CHECKED: 1.10.2-12.18.1.2092 entity.addedToChunk = false; // World tries to remove entities from Cubes // if (addedToCube || Column is loaded) // so we need to set addedToChunk to false as a hack! // else World would reload this Cube! } // tell the world to forget about tile entities for (TileEntity blockEntity : this.tileEntityMap.values()) { this.world.removeTileEntity(blockEntity.getPos()); } } /** * Check if any modifications happened to this cube since it was loaded from disk * * @return <code>true</code> if this cube should be written back to disk */ public boolean needsSaving() { return this.entities.needsSaving(true, this.world.getTotalWorldTime(), this.isModified); } /** * Mark this cube as saved to disk */ public void markSaved() { this.entities.markSaved(this.world.getTotalWorldTime()); this.isModified = false; } /** * Retrieve a list of tickets currently holding this cube loaded * * @return the list of tickets */ public TicketList getTickets() { return tickets; } public void markForRenderUpdate() { this.world.markBlockRangeForRenderUpdate( Coords.cubeToMinBlock(this.coords.getX()), Coords.cubeToMinBlock(this.coords.getY()), Coords.cubeToMinBlock(this.coords.getZ()), Coords.cubeToMaxBlock(this.coords.getX()), Coords.cubeToMaxBlock(this.coords.getY()), Coords.cubeToMaxBlock(this.coords.getZ()) ); } /** * Return a seed for random number generation. This seed is persistent across server restarts and returns the same * value on every call for any given cube in any given world. * * @return this cube's random seed */ public long cubeRandomSeed() { long hash = 3; hash = 41*hash + this.world.getSeed(); hash = 41*hash + getX(); hash = 41*hash + getY(); return 41*hash + getZ(); } public LightUpdateData getLightUpdateData() { return this.lightUpdateData; } /** * Mark this cube as a client side cube. Less work is done in this case, as we expect to receive updates from the * server */ public void setClientCube() { this.isPopulated = true; this.isFullyPopulated = true; this.isInitialLightingDone = true; } /** * Check whether this cube was populated, i.e. if this cube was passed as argument to {@link * cubicchunks.worldgen.generator.ICubeGenerator#populate(Cube)}. Check there for more information regarding * population. * * @return <code>true</code> if this cube has been populated, <code>false</code> otherwise */ public boolean isPopulated() { return isPopulated; } /** * Mark this cube as populated. This means that this cube was passed as argument to {@link * cubicchunks.worldgen.generator.ICubeGenerator#populate(Cube)}. Check there for more information regarding * population. * * @param populated whether this cube was populated */ public void setPopulated(boolean populated) { this.isPopulated = populated; this.isModified = true; } /** * Check whether this cube was fully populated, i.e. if any cube potentially writing to this cube was passed as an * argument to {@link cubicchunks.worldgen.generator.ICubeGenerator#populate(Cube)}. Check there for more * information regarding population * * @return <code>true</code> if this cube has been populated, <code>false</code> otherwise */ public boolean isFullyPopulated() { return this.isFullyPopulated; } /** * Mark this cube as fully populated. This means that any cube potentially writing to this cube was passed as an * argument to {@link cubicchunks.worldgen.generator.ICubeGenerator#populate(Cube)}. Check there for more * information regarding population * * @param populated whether this cube was fully populated */ public void setFullyPopulated(boolean populated) { this.isFullyPopulated = populated; this.isModified = true; } /** * Check whether this cube's initial diffuse skylight has been calculated * * @return <code>true</code> if it has been calculated, <code>false</code> otherwise */ public boolean isInitialLightingDone() { return isInitialLightingDone; } /** * Notify this cube that it's initial diffuse skylight has been calculated */ public void setInitialLightingDone(boolean initialLightingDone) { this.isInitialLightingDone = initialLightingDone; this.isModified = true; } public static class LightUpdateData { private final Cube cube; private final short[] minMaxHeights = new short[256]; //TODO: nullify minMaxHeights if toUpdateCounter is 0 private int toUpdateCounter = 0; public LightUpdateData(Cube cube) { this.cube = cube; Arrays.fill(minMaxHeights, (short) 0xFFFF); } public void queueLightUpdate(int localX, int localZ, int minY, int maxY) { if (localX < 0 || localX > 15) { throw new IndexOutOfBoundsException("LocalX must be between 0 and 15, but was " + localX); } if (localZ < 0 || localZ > 15) { throw new IndexOutOfBoundsException("LocalZ must be between 0 and 15, but was " + localZ); } if (minY > maxY) { throw new IllegalArgumentException("minY > maxY (" + minY + " > " + maxY + ")"); } minY -= Coords.cubeToMinBlock(cube.getY()); maxY -= Coords.cubeToMinBlock(cube.getY()); minY = MathHelper.clamp(minY, 0, 15); maxY = MathHelper.clamp(maxY, 0, 15); int index = index(localX, localZ); short v = minMaxHeights[localX << 4 | localZ]; if (v == -1) { toUpdateCounter++; assert toUpdateCounter >= 0 && toUpdateCounter <= 256; } int min = unpackMin(v); int max = unpackMax(v); if (minY < min) { min = minY; } if (maxY > max) { max = maxY; } v = pack(min, max); assert v >= 0 && v < 256; this.minMaxHeights[index] = v; } public int getMin(int localX, int localZ) { return unpackMin(minMaxHeights[index(localX, localZ)]); } public int getMax(int localX, int localZ) { return unpackMax(minMaxHeights[index(localX, localZ)]); } public void remove(int localX, int localZ) { int index = index(localX, localZ); if (minMaxHeights[index] != -1) { toUpdateCounter--; } minMaxHeights[index] = -1; } private short pack(int min, int max) { return (short) (min << 4 | max); } private int unpackMin(short val) { if (val == -1) { return 16; } return val >> 4; } private int unpackMax(short val) { if (val == -1) { return -1; } return val & 0xf; } private int index(int x, int z) { return x << 4 | z; } } }