/*
* This file is part of LanternServer, licensed under the MIT License (MIT).
*
* Copyright (c) LanternPowered <https://www.lanternpowered.org>
* Copyright (c) SpongePowered <https://www.spongepowered.org>
* Copyright (c) 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 org.lanternpowered.server.world.chunk;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.lanternpowered.server.world.chunk.LanternChunkLayout.CHUNK_BIOME_VOLUME;
import static org.lanternpowered.server.world.chunk.LanternChunkLayout.CHUNK_MASK;
import static org.lanternpowered.server.world.chunk.LanternChunkLayout.CHUNK_SIZE;
import com.flowpowered.math.vector.Vector2i;
import com.flowpowered.math.vector.Vector3d;
import com.flowpowered.math.vector.Vector3i;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import it.unimi.dsi.fastutil.objects.ObjectIterator;
import it.unimi.dsi.fastutil.shorts.Short2ObjectMap;
import it.unimi.dsi.fastutil.shorts.Short2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.shorts.Short2ShortMap;
import it.unimi.dsi.fastutil.shorts.Short2ShortOpenHashMap;
import org.lanternpowered.server.block.CachedSimpleObjectProvider;
import org.lanternpowered.server.block.ConstantObjectProvider;
import org.lanternpowered.server.block.LanternBlockSnapshot;
import org.lanternpowered.server.block.LanternBlockType;
import org.lanternpowered.server.block.LanternScheduledBlockUpdate;
import org.lanternpowered.server.block.ObjectProvider;
import org.lanternpowered.server.block.SimpleObjectProvider;
import org.lanternpowered.server.block.TileEntityProvider;
import org.lanternpowered.server.block.action.BlockAction;
import org.lanternpowered.server.block.tile.ITileEntityRefreshBehavior;
import org.lanternpowered.server.block.tile.LanternTileEntity;
import org.lanternpowered.server.data.property.AbstractDirectionRelativePropertyHolder;
import org.lanternpowered.server.data.property.AbstractPropertyHolder;
import org.lanternpowered.server.data.property.LanternPropertyRegistry;
import org.lanternpowered.server.entity.LanternEntity;
import org.lanternpowered.server.entity.LanternEntityType;
import org.lanternpowered.server.game.registry.type.block.BlockRegistryModule;
import org.lanternpowered.server.game.registry.type.world.biome.BiomeRegistryModule;
import org.lanternpowered.server.util.NibbleArray;
import org.lanternpowered.server.util.VecHelper;
import org.lanternpowered.server.world.LanternWorld;
import org.lanternpowered.server.world.TrackerIdAllocator;
import org.lanternpowered.server.world.extent.AbstractExtent;
import org.lanternpowered.server.world.extent.ExtentViewDownsize;
import org.lanternpowered.server.world.extent.worker.LanternMutableBiomeVolumeWorker;
import org.lanternpowered.server.world.extent.worker.LanternMutableBlockVolumeWorker;
import org.spongepowered.api.block.BlockSnapshot;
import org.spongepowered.api.block.BlockState;
import org.spongepowered.api.block.BlockType;
import org.spongepowered.api.block.BlockTypes;
import org.spongepowered.api.block.ScheduledBlockUpdate;
import org.spongepowered.api.block.tileentity.TileEntity;
import org.spongepowered.api.data.DataContainer;
import org.spongepowered.api.data.DataHolder;
import org.spongepowered.api.data.DataTransactionResult;
import org.spongepowered.api.data.DataView;
import org.spongepowered.api.data.Property;
import org.spongepowered.api.data.key.Key;
import org.spongepowered.api.data.manipulator.DataManipulator;
import org.spongepowered.api.data.merge.MergeFunction;
import org.spongepowered.api.data.persistence.InvalidDataException;
import org.spongepowered.api.data.property.PropertyStore;
import org.spongepowered.api.data.value.BaseValue;
import org.spongepowered.api.data.value.immutable.ImmutableValue;
import org.spongepowered.api.entity.Entity;
import org.spongepowered.api.entity.EntitySnapshot;
import org.spongepowered.api.entity.EntityType;
import org.spongepowered.api.event.cause.Cause;
import org.spongepowered.api.item.inventory.ItemStack;
import org.spongepowered.api.util.AABB;
import org.spongepowered.api.util.Direction;
import org.spongepowered.api.util.GuavaCollectors;
import org.spongepowered.api.util.PositionOutOfBoundsException;
import org.spongepowered.api.world.BlockChangeFlag;
import org.spongepowered.api.world.Chunk;
import org.spongepowered.api.world.Location;
import org.spongepowered.api.world.World;
import org.spongepowered.api.world.biome.BiomeType;
import org.spongepowered.api.world.biome.BiomeTypes;
import org.spongepowered.api.world.extent.ArchetypeVolume;
import org.spongepowered.api.world.extent.Extent;
import org.spongepowered.api.world.extent.worker.MutableBiomeVolumeWorker;
import org.spongepowered.api.world.extent.worker.MutableBlockVolumeWorker;
import java.util.Arrays;
import java.util.BitSet;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.PriorityBlockingQueue;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.StampedLock;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import javax.annotation.Nullable;
public class LanternChunk implements AbstractExtent, Chunk {
public static long key(int cx, int cz) {
return ((long) cx & 0x3ffffff) << 38 | ((long) cz & 0x3ffffff);
}
// The size of a chunk section in the x, y and z directions
public static final int CHUNK_SECTION_SIZE = 16;
// The volume of a chunk and a chunk section (xz plane)
public static final int CHUNK_AREA = CHUNK_SECTION_SIZE * CHUNK_SECTION_SIZE;
// The volume of a chunk section
public static final int CHUNK_SECTION_VOLUME = CHUNK_AREA * CHUNK_SECTION_SIZE;
// The amount of sections inside one chunk
public static final int CHUNK_SECTIONS = 16;
// The volume of a chunk
public static final int CHUNK_VOLUME = CHUNK_SECTION_VOLUME * CHUNK_SECTIONS;
// The height of a chunk
public static final int CHUNK_HEIGHT = CHUNK_SECTION_SIZE * CHUNK_SECTIONS;
// A bit mask that can be used to get ALL the chunk sections
public static final int ALL_SECTIONS_BIT_MASK = (1 << CHUNK_SECTIONS) - 1;
public static final class TrackerData {
private int notifierId;
private int creatorId;
TrackerData() {
this(-1, -1);
}
public TrackerData(int notifierId, int creatorId) {
this.notifierId = notifierId;
this.creatorId = creatorId;
}
public int getNotifierId() {
return this.notifierId;
}
public int getCreatorId() {
return this.creatorId;
}
}
public static class ChunkSection {
/**
* The block types array.
*/
final short[] types;
/**
* The amount of blocks per block type/state in
* this chunk section.
*/
final Short2ShortMap typesCountMap = new Short2ShortOpenHashMap();
/**
* The light level arrays.
*/
final NibbleArray lightFromSky;
final NibbleArray lightFromBlock;
final Short2ObjectMap<LanternTileEntity> tileEntities;
/**
* The amount of non air blocks in this chunk section.
*/
int nonAirCount;
ChunkSection() {
this(null, null);
}
ChunkSection(short[] types) {
this(checkNotNull(types, "types"), null);
}
ChunkSection(short[] types, int nonAirCount) {
this(checkNotNull(types, "types"), (Integer) nonAirCount);
}
private ChunkSection(@Nullable short[] types, @Nullable Integer nonAirCount) {
if (types != null) {
checkArgument(types.length == CHUNK_SECTION_VOLUME, "Type array length mismatch: Got "
+ types.length + ", but expected " + CHUNK_SECTION_VOLUME);
this.types = new short[CHUNK_SECTION_VOLUME];
System.arraycopy(types, 0, this.types, 0, CHUNK_SECTION_VOLUME);
if (nonAirCount == null) {
this.recountTypes();
} else {
this.nonAirCount = nonAirCount;
}
} else {
this.types = new short[CHUNK_SECTION_VOLUME];
}
this.tileEntities = new Short2ObjectOpenHashMap<>();
this.lightFromBlock = new NibbleArray(CHUNK_SECTION_VOLUME);
this.lightFromSky = new NibbleArray(CHUNK_SECTION_VOLUME);
}
public ChunkSection(short[] types, NibbleArray lightFromSky, NibbleArray lightFromBlock,
Short2ObjectMap<LanternTileEntity> tileEntities) {
checkArgument(types.length == CHUNK_SECTION_VOLUME, "Type array length mismatch: Got "
+ types.length + ", but expected " + CHUNK_SECTION_VOLUME);
checkArgument(lightFromSky.length() == CHUNK_SECTION_VOLUME, "Sky light nibble array length mismatch: Got "
+ lightFromSky.length() + ", but expected " + CHUNK_SECTION_VOLUME);
checkArgument(lightFromSky.length() == CHUNK_SECTION_VOLUME, "Block light nibble array length mismatch: Got "
+ lightFromBlock.length() + ", but expected " + CHUNK_SECTION_VOLUME);
this.lightFromBlock = lightFromBlock;
this.lightFromSky = lightFromSky;
this.tileEntities = tileEntities;
this.types = types;
// Count the non air blocks.
this.recountTypes();
}
/**
* Calculate the index into internal arrays for the given coordinates.
*
* @param x The x coordinate
* @param y The y coordinate
* @param z The z coordinate
* @return The index in the array
*/
public static int index(int x, int y, int z) {
return (y << 8) | (z << 4) | x;
}
/**
* Recounts the amount of non air blocks.
*/
private void recountTypes() {
this.nonAirCount = 0;
this.typesCountMap.clear();
for (short type : this.types) {
if (type != 0) {
this.nonAirCount++;
this.typesCountMap.put(type, (short) (this.typesCountMap.get(type) + 1));
}
}
}
private ChunkSectionSnapshot asSnapshot(boolean skylight) {
final Short2ShortMap typeCounts = new Short2ShortOpenHashMap(this.typesCountMap);
final int count = this.types.length - this.nonAirCount;
if (count > 0) {
typeCounts.put((short) 0, (short) count);
}
return new ChunkSectionSnapshot(this.types.clone(), typeCounts, new Short2ObjectOpenHashMap<>(this.tileEntities),
this.lightFromBlock.getPackedArray(), skylight ? this.lightFromSky.getPackedArray() : null);
}
}
public static class ChunkSectionSnapshot {
// The block types array.
public final short[] types;
// The types count map.
public final Short2ShortMap typesCountMap;
// The tile entities
public final Short2ObjectMap<LanternTileEntity> tileEntities;
// The light level arrays.
@Nullable public final byte[] lightFromSky;
public final byte[] lightFromBlock;
private ChunkSectionSnapshot(short[] types, Short2ShortMap typesCountMap, Short2ObjectMap<LanternTileEntity> tileEntities,
byte[] lightFromBlock, @Nullable byte[] lightFromSky) {
this.tileEntities = tileEntities;
this.lightFromBlock = lightFromBlock;
this.typesCountMap = typesCountMap;
this.lightFromSky = lightFromSky;
this.types = types;
}
}
private final PriorityBlockingQueue<LanternScheduledBlockUpdate> scheduledBlockUpdateQueue =
new PriorityBlockingQueue<>();
private final AtomicInteger scheduledBlockUpdateCounter = new AtomicInteger();
private final ConcurrentObjectArray<Short2ObjectMap<TrackerData>> trackerData;
// The chunk sections column
private ConcurrentObjectArray<ChunkSection> chunkSections;
private volatile long inhabitedTime;
// The height map of the chunk
// This is lazily updated, meaning that it won't
// updated every time a block changes (to avoid
// looping through all the chunks for the lowest block)
private byte[] heightMap;
// The height map update flags
// Where 0 means no change, and 1 means loop down
private final BitSet heightMapUpdateFlags = new BitSet(CHUNK_AREA);
// The lock for the height map and height map update flags
private final StampedLock heightMapLock = new StampedLock();
// The biomes array
private short[] biomes;
// The lock for the biomes array
private final StampedLock biomesLock = new StampedLock();
private final Vector3i min;
private final Vector3i max;
private final Vector3i biomeMin;
private final Vector3i biomeMax;
private final Vector2i areaMin;
private final Vector2i areaMax;
private final Vector3i pos;
final Vector2i chunkPos;
private final LanternWorld world;
// Not sure why this is needed
private final UUID uniqueId;
private final int x;
private final int z;
private final long key;
// The lock that will be locking the chunk while it's getting loaded/saved
final ReentrantLock lock = new ReentrantLock();
// This condition is present while loading the chunk, a thread attempting to
// load this chunk while it's loading will have to wait for the loading condition to complete.
final Condition lockCondition = this.lock.newCondition();
// Whether the chunk loading successful was
boolean loadingSuccess;
// Whether the chunk unloading successful was
boolean unloadingSuccess;
// Whether this chunk is finished loading
volatile boolean loaded;
// Whether this is populated by the world generator
volatile boolean populated;
// Whether this chunk is currently being populated
volatile boolean populating;
// The state of the lock
volatile LockState lockState = LockState.NONE;
private boolean dirtyBlockActions;
// Whether the light in this chunk is populated
private boolean lightPopulated;
// The set which contains all the entities in this chunk
@SuppressWarnings("unchecked")
private final Set<LanternEntity>[] entities = new Set[CHUNK_SECTIONS];
{
for (int i = 0; i < this.entities.length; i++) {
this.entities[i] = Sets.newConcurrentHashSet();
}
}
/**
* The states that the chunk lock can have.
*/
public enum LockState {
/**
* When the chunk is locked by loading.
*/
LOADING,
/**
* When the chunk is locked by unloading.
*/
UNLOADING,
/**
* When the chunk is locked by saving.
*/
SAVING,
/**
* The chunk isn't locked.
*/
NONE,
}
public LanternChunk(LanternWorld world, int x, int z) {
this.world = world;
this.key = key(x, z);
this.x = x;
this.z = z;
final UUID worldUUID = this.world.getUniqueId();
this.uniqueId = new UUID(worldUUID.getMostSignificantBits() ^ (x * 2 + 1),
worldUUID.getLeastSignificantBits() ^ (z * 2 + 1));
this.pos = new Vector3i(x, 0, z);
this.chunkPos = this.pos.toVector2(true);
this.min = LanternChunkLayout.INSTANCE.toWorld(this.pos).get();
this.max = this.min.add(CHUNK_MASK);
this.areaMin = this.min.toVector2(true);
this.areaMax = this.max.toVector2(true);
this.biomeMin = new Vector3i(this.min.getX(), 1, this.min.getZ());
this.biomeMax = new Vector3i(this.max.getX(), 1, this.max.getZ());
//noinspection unchecked
final Short2ObjectMap<TrackerData>[] trackerDataSections = new Short2ObjectMap[CHUNK_SECTIONS];
for (int i = 0; i < trackerDataSections.length; i++) {
trackerDataSections[i] = new Short2ObjectOpenHashMap<>();
}
this.trackerData = new ConcurrentObjectArray<>(trackerDataSections);
}
public long getKey() {
return this.key;
}
public ConcurrentObjectArray<Short2ObjectMap<TrackerData>> getTrackerData() {
return this.trackerData;
}
/**
* Initializes a empty chunk.
* (Only used for initializing the chunk.)
*/
void initializeEmpty() {
//noinspection ConstantConditions
if (this.chunkSections != null || this.biomes != null) {
throw new IllegalStateException("Chunk is already initialized!");
}
this.heightMap = new byte[CHUNK_AREA];
this.chunkSections = new ConcurrentObjectArray<>(new ChunkSection[CHUNK_SECTIONS]);
this.biomes = new short[CHUNK_AREA];
this.loaded = true;
}
/**
* Initializes the sections of the chunk.
* (Only used for initializing the chunk.)
*
* @param sections the sections
*/
public void initializeSections(ChunkSection[] sections) {
checkArgument(sections.length == CHUNK_SECTIONS, "Sections array length mismatch: Got "
+ sections.length + ", but expected " + CHUNK_SECTIONS);
this.chunkSections = new ConcurrentObjectArray<>(sections);
this.loaded = true;
}
/**
* Initializes the biomes array of the chunk.
* (Only used for initializing the chunk.)
*
* @param biomes the biomes
*/
public void initializeBiomes(short[] biomes) {
checkArgument(biomes.length == CHUNK_AREA, "Biomes array length mismatch: Got "
+ biomes.length + ", but expected " + CHUNK_AREA);
this.biomes = biomes;
}
public void initializeHeightMap(@Nullable int[] heightMap) {
if (heightMap != null) {
checkArgument(heightMap.length == CHUNK_AREA, "Height map array length mismatch: Got "
+ heightMap.length + ", but expected " + CHUNK_AREA);
final byte[] heightMap0 = new byte[CHUNK_AREA];
for (int i = 0; i < CHUNK_AREA; i++) {
final int height = heightMap[i];
heightMap0[i] = (byte) (height < 0 ? 0 : height > 255 ? 255 : height);
}
this.heightMap = heightMap0;
} else {
this.heightMap = new byte[CHUNK_AREA];
this.heightMapUpdateFlags.set(0, this.heightMapUpdateFlags.size(), true);
this.automaticHeightMap();
}
}
public void initializeLight() {
if (this.lightPopulated) { // Fast fail
return;
}
for (int y = 0; y < CHUNK_SECTIONS; y++) {
ChunkSection section = this.chunkSections.getRawObjects()[y];
if (section != null) {
// Just fill the light array for now
this.chunkSections.getRawObjects()[y].lightFromSky.fill((byte) 15);
}
}
this.lightPopulated = true;
}
public void setLightPopulated(boolean lightPopulated) {
this.lightPopulated = lightPopulated;
}
public boolean isLightPopulated() {
return this.lightPopulated;
}
public ChunkSectionSnapshot[] getSectionSnapshots(boolean skylight) {
return this.getSectionSnapshots(skylight, ALL_SECTIONS_BIT_MASK);
}
public ChunkSectionSnapshot[] getSectionSnapshots(boolean skylight, int sectionBitMask) {
final ChunkSectionSnapshot[] array = new ChunkSectionSnapshot[CHUNK_SECTIONS];
for (int i = 0; i < array.length; i++) {
if ((sectionBitMask & (1 << i)) == 0) {
continue;
}
final int index = i;
this.chunkSections.work(index, section -> {
if (section != null) {
array[index] = section.asSnapshot(skylight);
}
}, false, true);
}
return array;
}
public int[] getHeightMap() {
final int[] heightMap0 = new int[this.heightMap.length];
for (int i = 0; i < heightMap0.length; i++) {
heightMap0[i] = this.heightMap[i];
}
return heightMap0;
}
/**
* Gets the highest non air block (y coordinate) at the
* x and z coordinates.
*
* @param x The x coordinate
* @param z The z coordinate
* @return The y coordinate
*/
public int getHighestBlockAt(int x, int z) {
this.checkAreaBounds(x, z);
if (!this.loaded) {
return 0;
}
final int index = (z & 0xf) << 4 | x & 0xf;
long stamp = this.heightMapLock.tryOptimisticRead();
boolean lower = stamp != 0L && this.heightMapUpdateFlags.get(index);
int height = stamp == 0L ? 0 : this.heightMap[index] & 0xff;
if (stamp == 0L || !this.heightMapLock.validate(stamp)) {
stamp = this.heightMapLock.readLock();
lower = this.heightMapUpdateFlags.get(index);
height = this.heightMap[index] & 0xff;
} else {
stamp = 0L;
}
// We have to update the height map for the coordinates
if (lower) {
long stamp1 = this.heightMapLock.tryConvertToWriteLock(stamp);
if (stamp1 == 0L) {
// We couldn't convert the lock, so create one anyway
this.heightMapLock.unlockRead(stamp);
stamp1 = this.heightMapLock.writeLock();
// We were to late to acquire the lock, something else modified the index first
if (!this.heightMapUpdateFlags.get(index)) {
return height;
}
}
final int sections = height >> 4;
// 0: The height we are looping through
final int[] values0 = { 0 };
// 0: Finished
final boolean[] values1 = { false };
// Loop trough all the chunk sections
for (int i = sections; i >= 0; i--) {
final int j = i;
// We do this section by section to avoid
// having to lock the section too many times
this.chunkSections.work(i, section -> {
if (section == null) {
values0[0] -= CHUNK_SECTION_SIZE;
} else {
int y = CHUNK_SECTION_SIZE;
// Loop down in the section until we may find a
// non empty block
while (--y >= 0) {
if (section.types[(y << 8) | index] != 0) {
values0[0] = j << 4 | y;
values1[0] = true;
break;
}
}
}
}, false);
if (values1[0]) {
break;
}
}
this.heightMap[index] = (byte) height;
this.heightMapUpdateFlags.clear(index);
height = values0[0];
this.heightMapLock.unlockWrite(stamp1);
} else if (stamp != 0L) {
this.heightMapLock.unlockRead(stamp);
}
return height;
}
/**
* Automatically fill the height map after chunks have been initialized.
*/
private void automaticHeightMap() {
// The height map flags before we updated it through this method
final BitSet heightMapFlags;
// The height map before we updated it through this method
final byte[] oldHeightMap;
long stamp = this.heightMapLock.readLock();
try {
heightMapFlags = (BitSet) this.heightMapUpdateFlags.clone();
oldHeightMap = this.heightMap.clone();
} finally {
this.heightMapLock.unlockRead(stamp);
}
// The height map before we updated it through this method
final byte[] heightMap = oldHeightMap.clone();
// Which coordinates are finished
final boolean[] finished = new boolean[CHUNK_AREA];
// 0: The amount of finished searches
final int[] values0 = { 0 };
// 0: Whether we found the first non-null chunk
// 1: Finished
final boolean[] values1 = { false, false };
// Loop trough all the chunk sections
for (int i = CHUNK_SECTIONS - 1; i >= 0; i--) {
// We do this section by section to avoid
// having to lock the section too many times
this.chunkSections.work(i, section -> {
if (section == null) {
return;
}
finish:
for (int index = 0; index < CHUNK_AREA; index++) {
if (!finished[index] && heightMapFlags.get(index)) {
int y = CHUNK_SECTION_SIZE;
// Loop down in the section until we may find a
// non empty block
while (--y >= 0) {
if (section.types[(y << 8) | index] != 0) {
finished[index] = true;
heightMap[index] = (byte) y;
if (++values0[0] >= CHUNK_AREA) {
values1[0] = true;
break finish;
}
break;
}
}
}
}
}, false, true);
if (values1[1]) {
break;
}
}
stamp = this.heightMapLock.writeLock();
try {
for (int index = 0; index < CHUNK_AREA; index++) {
if (heightMapFlags.get(index) && this.heightMap[index] == oldHeightMap[index] &&
this.heightMapUpdateFlags.get(index)) {
this.heightMap[index] = heightMap[index];
this.heightMapUpdateFlags.clear(index);
}
}
} finally {
this.heightMapLock.unlockWrite(stamp);
}
}
public void setPopulated(boolean populated) {
this.populated = populated;
}
/**
* Gets the x coordinate of the chunk.
*
* @return The x coordinate
*/
public int getX() {
return this.x;
}
/**
* Gets the z coordinate of the chunk.
*
* @return The z coordinate
*/
public int getZ() {
return this.z;
}
/**
* Gets the coordinates of the chunk.
*
* @return The coordinates
*/
public Vector2i getCoords() {
return this.chunkPos;
}
/**
* Gets a copy of the biomes array.
*
* @return The biomes
*/
public short[] getBiomes() {
long stamp = this.biomesLock.tryOptimisticRead();
short[] biomes = this.biomes.clone();
if (!this.biomesLock.validate(stamp)) {
stamp = this.biomesLock.readLock();
try {
biomes = this.biomes.clone();
} finally {
this.biomesLock.unlockRead(stamp);
}
}
return biomes;
}
/**
* Sets the biomes array.
*
* @param biomes the biomes
*/
public void setBiomes(short[] biomes) {
checkArgument(biomes.length == CHUNK_AREA, "Biomes array length mismatch: Got "
+ biomes.length + ", but expected " + CHUNK_AREA);
biomes = biomes.clone();
final long stamp = this.biomesLock.writeLock();
try {
this.biomes = biomes;
} finally {
this.biomesLock.unlockWrite(stamp);
}
}
/**
* Gets the biome at the coordinates.
*
* @param x the x coordinate
* @param z the z coordinate
* @return the biome value
*/
public short getBiomeId(int x, int y, int z) {
checkBiomeBounds(x, y, z);
if (!this.loaded) {
return 0;
}
final int index = (z & 0xf) << 4 | x & 0xf;
long stamp = this.biomesLock.tryOptimisticRead();
short biome = this.biomes[index];
if (!this.biomesLock.validate(stamp)) {
stamp = this.biomesLock.readLock();
try {
biome = this.biomes[index];
} finally {
this.biomesLock.unlockRead(stamp);
}
}
return biome;
}
/**
* Sets the biome at the coordinates.
*
* @param x the x coordinate
* @param z the z coordinate
* @param biome the biome value
*/
public void setBiomeId(int x, int y, int z, short biome) {
checkBiomeBounds(x, y, z);
if (!this.loaded) {
return;
}
int index = (z & 0xf) << 4 | x & 0xf;
long stamp = this.biomesLock.writeLock();
try {
this.biomes[index] = biome;
} finally {
this.biomesLock.unlockWrite(stamp);
}
}
public short getType(Vector3i coordinates) {
return getType(coordinates.getX(), coordinates.getY(), coordinates.getZ());
}
/**
* Gets the type of the block at the coordinates.
*
* @param x the x coordinate
* @param y the y coordinate
* @param z the z coordinate
* @return the block type
*/
public short getType(int x, int y, int z) {
checkVolumeBounds(x, y, z);
if (!this.loaded) {
return 0;
}
return this.chunkSections.work(y >> 4, section -> {
if (section != null) {
return section.types[ChunkSection.index(x & 0xf, y & 0xf, z & 0xf)];
}
return (short) 0;
}, false);
}
@Override
public boolean setBlock(int x, int y, int z, BlockState block, Cause cause) {
return setBlock(x, y, z, block, BlockChangeFlag.ALL, cause);
}
@Override
public boolean setBlock(int x, int y, int z, BlockState block, BlockChangeFlag flag, Cause cause) {
checkNotNull(block, "block");
checkNotNull(flag, "flag");
checkNotNull(cause, "cause");
checkVolumeBounds(x, y, z);
if (!this.loaded) {
return false;
}
final short type = BlockRegistryModule.get().getStateInternalIdAndData(block);
final short type1;
// Air doesn't have metadata values
if (type >> 4 == 0 && type != 0) {
type1 = 0;
} else {
type1 = type;
}
final BlockState[] changeData = new BlockState[1];
final int rx = x & 0xf;
final int rz = z & 0xf;
this.chunkSections.work(y >> 4, section -> {
if (section == null) {
// The section is already filled with air,
// so we can fail fast
if (type1 == 0) {
return section;
}
// Create a new section
section = new ChunkSection();
}
final int index = ChunkSection.index(rx, y & 0xf, rz);
final short oldType = section.types[index];
if (oldType == type1) {
return section;
}
if (oldType != 0) {
short count = section.typesCountMap.get(oldType);
if (count > 0) {
if (--count <= 0) {
section.typesCountMap.remove(oldType);
} else {
section.typesCountMap.put(oldType, count);
}
}
}
if (type1 != 0) {
section.typesCountMap.put(type1, (short) (section.typesCountMap.get(type1) + 1));
if (oldType == 0) {
section.nonAirCount++;
}
} else {
section.nonAirCount--;
}
final BlockState oldState = BlockRegistryModule.get().getStateByInternalIdAndData(oldType).get();
changeData[0] = oldState;
// The section is empty, destroy it
if (section.nonAirCount <= 0) {
return null;
}
final LanternTileEntity tileEntity = section.tileEntities.get((short) index);
boolean remove = false;
boolean refresh = false;
final Optional<TileEntityProvider> tileEntityProvider = ((LanternBlockType) block.getType()).getTileEntityProvider();
if (tileEntity != null) {
if (oldType == 0 || type1 == 0) {
remove = true;
} else if ((tileEntity instanceof ITileEntityRefreshBehavior &&
((ITileEntityRefreshBehavior) tileEntity).shouldRefresh(oldState, block)) || oldType >> 4 != type1 >> 4) {
// The default behavior will only refresh if the
// block type is changed and not the block state
remove = true;
refresh = true;
}
if (refresh && !tileEntityProvider.isPresent()) {
refresh = false;
}
} else if (tileEntityProvider.isPresent()) {
refresh = true;
}
if (remove) {
tileEntity.setValid(false);
}
if (refresh) {
final Location<World> location = tileEntity != null ? tileEntity.getLocation() : new Location<>(this.world, x, y, z);
final LanternTileEntity newTileEntity = (LanternTileEntity) tileEntityProvider.get().get(block, location, null);
section.tileEntities.put((short) index, newTileEntity);
newTileEntity.setLocation(location);
newTileEntity.setValid(true);
} else if (remove) {
section.tileEntities.remove((short) index);
}
section.types[index] = type1;
return section;
});
final int index = rz << 4 | rx;
long stamp = this.heightMapLock.writeLock();
try {
// TODO: Check first and then use the write lock?
if (type != 0 && (this.heightMap[index] & 0xff) < y) {
this.heightMap[index] = (byte) y;
this.heightMapUpdateFlags.clear(index);
} else if (type == 0 && (this.heightMap[index] & 0xff) == y) {
this.heightMapUpdateFlags.set(index);
}
} finally {
this.heightMapLock.unlock(stamp);
}
if (changeData[0] != null) {
this.world.getEventListener().onBlockChange(x, y, z, changeData[0], block);
}
return true;
}
public void addBlockAction(int x, int y, int z, BlockType blockType, BlockAction blockAction) {
checkVolumeBounds(x, y, z);
if (!this.loaded) {
return;
}
if (this.getBlock(x, y, z).getType() == blockType) {
this.world.getEventListener().onBlockAction(x, y, z, blockType, blockAction);
}
}
/**
* Gets the block light at the coordinates.
*
* @param x the x coordinate
* @param y the y coordinate
* @param z the z coordinate
* @return the block light value
*/
public byte getBlockLight(int x, int y, int z) {
checkVolumeBounds(x, y, z);
if (!this.loaded) {
return 0;
}
return this.chunkSections.work(y >> 4, section -> section == null ? 0 :
section.lightFromBlock.get(ChunkSection.index(x & 0xf, y & 0xf, z & 0xf)), false);
}
/**
* Gets the block light at the coordinates.
*
* @param x the x coordinate
* @param y the y coordinate
* @param z the z coordinate
* @return the sky light value
*/
public byte getSkyLight(int x, int y, int z) {
checkVolumeBounds(x, y, z);
if (!this.loaded) {
return 15;
}
return this.chunkSections.work(y >> 4, section -> section == null ? 15 :
section.lightFromSky.get(ChunkSection.index(x & 0xf, y & 0xf, z & 0xf)), false);
}
@Override
public Location<Chunk> getLocation(Vector3i position) {
return getLocation(position.getX(), position.getY(), position.getZ());
}
@Override
public Location<Chunk> getLocation(Vector3d position) {
return getLocation(position.getX(), position.getY(), position.getZ());
}
@Override
public BlockSnapshot createSnapshot(int x, int y, int z) {
final BlockState state = getBlock(x, y, z);
final Location<World> loc = new Location<>(this.world, x, y, z);
// TODO: Tile entity data
return new LanternBlockSnapshot(loc, state, ((LanternBlockType) state.getType()).getExtendedBlockStateProvider().get(state, loc, null),
getCreator(x, y, z), getNotifier(x, y, z), ImmutableMap.of());
}
@Override
public boolean restoreSnapshot(int x, int y, int z, BlockSnapshot snapshot, boolean force, BlockChangeFlag flag, Cause cause) {
return false;
}
@Override
public Optional<Entity> restoreSnapshot(EntitySnapshot snapshot, Vector3d position) {
// TODO Auto-generated method stub
return null;
}
@Override
public boolean spawnEntity(Entity entity, Cause cause) {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean spawnEntities(Iterable<? extends Entity> entities, Cause cause) {
return false;
}
@Override
public Set<EntityHit> getIntersectingEntities(Vector3d start, Vector3d end, Predicate<EntityHit> filter) {
return null;
}
@Override
public Set<EntityHit> getIntersectingEntities(Vector3d start, Vector3d direction, double distance, Predicate<EntityHit> filter) {
return null;
}
public PriorityBlockingQueue<LanternScheduledBlockUpdate> getScheduledBlockUpdateQueue() {
return this.scheduledBlockUpdateQueue;
}
@Override
public Collection<ScheduledBlockUpdate> getScheduledUpdates(int x, int y, int z) {
checkVolumeBounds(x, y, z);
if (!this.loaded) {
return Collections.emptyList();
}
final Vector3i position = new Vector3i(x, y, z);
return this.scheduledBlockUpdateQueue.stream()
.filter(update -> update.getLocation().getBlockPosition().equals(position))
.collect(GuavaCollectors.toImmutableSet());
}
@Override
public ScheduledBlockUpdate addScheduledUpdate(int x, int y, int z, int priority, int ticks) {
checkVolumeBounds(x, y, z);
final int entryId = this.scheduledBlockUpdateCounter.getAndIncrement();
final Location<World> location = new Location<>(this.world, new Vector3i(x, y, z));
final LanternScheduledBlockUpdate update = new LanternScheduledBlockUpdate(entryId, location, ticks, priority);
this.scheduledBlockUpdateQueue.add(update);
return update;
}
@Override
public void removeScheduledUpdate(int x, int y, int z, ScheduledBlockUpdate update) {
checkVolumeBounds(x, y, z);
this.scheduledBlockUpdateQueue.remove(update);
}
public void pulse() {
// The update entry
LanternScheduledBlockUpdate update;
while ((update = this.scheduledBlockUpdateQueue.peek()) != null && update.getTicks() <= 0) {
// Remove the entry from the queue
this.scheduledBlockUpdateQueue.poll();
// TODO: Update
}
getTileEntities().forEach(tileEntity -> ((LanternTileEntity) tileEntity).pulse());
}
@Override
public boolean isLoaded() {
return this.loaded;
}
@Override
public Extent getExtentView(Vector3i newMin, Vector3i newMax) {
checkVolumeBounds(newMin);
checkVolumeBounds(newMax);
return new ExtentViewDownsize(this, newMin, newMax);
}
@Override
public MutableBiomeVolumeWorker<Chunk> getBiomeWorker() {
return new LanternMutableBiomeVolumeWorker<>(this);
}
@Override
public MutableBlockVolumeWorker<Chunk> getBlockWorker(Cause cause) {
return new LanternMutableBlockVolumeWorker<>(this, cause);
}
@Override
public Optional<UUID> getCreator(int x, int y, int z) {
checkVolumeBounds(x, y, z);
final int creatorId = this.trackerData.work(y >> 4, trackerDataMap -> {
final TrackerData trackerData = trackerDataMap.get((short) ChunkSection.index(x & 0xf, y & 0xf, z & 0xf));
return trackerData == null ? TrackerIdAllocator.INVALID_ID : trackerData.creatorId;
}, false);
return creatorId == TrackerIdAllocator.INVALID_ID ? Optional.empty() : this.world.getProperties().getTrackerIdAllocator().get(creatorId);
}
@Override
public Optional<UUID> getNotifier(int x, int y, int z) {
checkVolumeBounds(x, y, z);
final int notifierId = this.trackerData.work(y >> 4, trackerDataMap -> {
final TrackerData trackerData = trackerDataMap.get((short) ChunkSection.index(x & 0xf, y & 0xf, z & 0xf));
return trackerData == null ? TrackerIdAllocator.INVALID_ID : trackerData.notifierId;
}, false);
return notifierId == TrackerIdAllocator.INVALID_ID ? Optional.empty() : this.world.getProperties().getTrackerIdAllocator().get(notifierId);
}
@Override
public void setCreator(int x, int y, int z, @Nullable UUID uuid) {
checkVolumeBounds(x, y, z);
final int creatorId = uuid == null ? -1 : this.world.getProperties().getTrackerIdAllocator().get(uuid);
this.trackerData.work(y >> 4, trackerDataMap -> {
final short index = (short) ChunkSection.index(x & 0xf, y & 0xf, z & 0xf);
TrackerData trackerData = trackerDataMap.get(index);
if (creatorId == -1) {
if (trackerData != null && trackerData.notifierId == -1) {
trackerDataMap.remove(index);
return;
} else if (trackerData == null) {
return;
}
}
if (trackerData == null) {
trackerData = new TrackerData();
trackerDataMap.put(index, trackerData);
}
trackerData.creatorId = index;
}, true);
}
@Override
public void setNotifier(int x, int y, int z, @Nullable UUID uuid) {
checkVolumeBounds(x, y, z);
final int notifierId = uuid == null ? -1 : this.world.getProperties().getTrackerIdAllocator().get(uuid);
this.trackerData.work(y >> 4, trackerDataMap -> {
final short index = (short) ChunkSection.index(x & 0xf, y & 0xf, z & 0xf);
TrackerData trackerData = trackerDataMap.get(index);
if (notifierId == -1) {
if (trackerData != null && trackerData.creatorId == -1) {
trackerDataMap.remove(index);
return;
} else if (trackerData == null) {
return;
}
}
if (trackerData == null) {
trackerData = new TrackerData();
trackerDataMap.put(index, trackerData);
}
trackerData.notifierId = index;
}, true);
}
@Override
public Optional<AABB> getBlockSelectionBox(int x, int y, int z) {
final BlockState block = getBlock(x, y, z);
if (block.getType() == BlockTypes.AIR) {
return Optional.empty();
}
final ObjectProvider<AABB> aabbObjectProvider = ((LanternBlockType) block.getType()).getBoundingBoxProvider();
if (aabbObjectProvider instanceof ConstantObjectProvider || aabbObjectProvider instanceof CachedSimpleObjectProvider
|| aabbObjectProvider instanceof SimpleObjectProvider) {
return Optional.of(aabbObjectProvider.get(block, null, null).offset(x, y, z));
}
return Optional.of(aabbObjectProvider.get(block, new Location<>(this.world, x, y, z), null).offset(x, y, z));
}
@Override
public Set<AABB> getIntersectingBlockCollisionBoxes(AABB box) {
checkNotNull(box, "box");
final Vector3i min = box.getMin().toInt();
final Vector3i max = box.getMax().toInt();
checkVolumeBounds(min);
checkVolumeBounds(max);
final ImmutableSet.Builder<AABB> builder = ImmutableSet.builder();
final int minX = min.getX();
final int minY = min.getY();
final int minZ = min.getZ();
final int maxX = max.getX();
final int maxY = max.getY();
final int maxZ = max.getZ();
for (int x = minX; x <= maxX; x++) {
for (int z = minZ; z <= maxZ; z++) {
for (int y = minY; y <= maxY; y++) {
final Optional<AABB> optAABB = getBlockSelectionBox(x, y, z);
if (optAABB.isPresent()) {
final AABB aabb = optAABB.get();
if (aabb.intersects(box)) {
builder.add(box);
}
}
}
}
}
return builder.build();
}
@Override
public Set<AABB> getIntersectingCollisionBoxes(Entity owner, AABB box) {
checkNotNull(owner, "owner");
checkNotNull(box, "box");
final ImmutableSet.Builder<AABB> collisionBoxes = ImmutableSet.builder();
final int maxYSection = fixEntityYSection(((int) Math.ceil(box.getMax().getY() + 2.0)) >> 4);
final int minYSection = fixEntityYSection(((int) Math.floor(box.getMin().getY() - 2.0)) >> 4);
for (int i = minYSection; i <= maxYSection; i++) {
for (LanternEntity entity : this.entities[i]) {
final Optional<AABB> aabb = entity.getBoundingBox();
if (aabb.isPresent() && aabb.get().intersects(box)) {
collisionBoxes.add(aabb.get());
}
}
}
final Vector3i min = box.getMin().toInt();
final Vector3i max = box.getMax().toInt();
for (int x = min.getX(); x <= max.getX(); x++) {
for (int y = min.getY(); y <= max.getY(); y++) {
for (int z = min.getZ(); z <= max.getZ(); z++) {
final Optional<AABB> aabb = getBlockSelectionBox(x, y, z);
if (aabb.isPresent() && aabb.get().intersects(box)) {
collisionBoxes.add(aabb.get());
}
}
}
}
return collisionBoxes.build();
}
@Override
public Set<Entity> getIntersectingEntities(AABB box, Predicate<Entity> filter) {
checkNotNull(box, "box");
checkNotNull(filter, "filter");
final ImmutableSet.Builder<Entity> entities = ImmutableSet.builder();
final int maxYSection = fixEntityYSection(((int) Math.ceil(box.getMax().getY() + 2.0)) >> 4);
final int minYSection = fixEntityYSection(((int) Math.floor(box.getMin().getY() - 2.0)) >> 4);
addIntersectingEntities(entities, maxYSection, minYSection, box, filter);
return entities.build();
}
public static int fixEntityYSection(int section) {
return section < 0 ? 0 : section >= CHUNK_SECTIONS ? CHUNK_SECTIONS - 1 : section;
}
public void addIntersectingEntities(ImmutableSet.Builder<Entity> builder, int maxYSection, int minYSection, AABB box, Predicate<Entity> filter) {
for (int i = minYSection; i <= maxYSection; i++) {
for (LanternEntity entity : this.entities[i]) {
final Optional<AABB> aabb = entity.getBoundingBox();
if (aabb.isPresent()) {
if (aabb.get().intersects(box) && filter.test(entity)) {
builder.add(entity);
}
} else if (box.contains(entity.getPosition()) && filter.test(entity)) {
builder.add(entity);
}
}
}
}
public void addIntersectingEntitiesBoxes(ImmutableSet.Builder<AABB> builder, int maxYSection, int minYSection,
AABB box, Predicate<Entity> filter) {
for (int i = minYSection; i <= maxYSection; i++) {
for (LanternEntity entity : this.entities[i]) {
final Optional<AABB> aabb = entity.getBoundingBox();
if (aabb.isPresent()) {
if (aabb.get().intersects(box) && filter.test(entity)) {
builder.add(aabb.get());
}
} else if (box.contains(entity.getPosition()) && filter.test(entity)) {
builder.add(aabb.get());
}
}
}
}
@Override
public ArchetypeVolume createArchetypeVolume(Vector3i min, Vector3i max, Vector3i origin) {
return null;
}
private void forEachEntity(Consumer<LanternEntity> consumer) {
for (Set<LanternEntity> entities : this.entities) {
final Iterator<LanternEntity> iterator = entities.iterator();
while (iterator.hasNext()) {
final LanternEntity entity = iterator.next();
// Only remove the entities that are "destroyed",
// the other ones can be resurrected after chunk loading
if (entity.getRemoveState() == LanternEntity.RemoveState.DESTROYED) {
iterator.remove();
} else {
consumer.accept(entity);
}
}
}
}
/**
* Resurrects all the {@link Entity}s that
* were temporarily "disabled" (chunk being unloaded).
*/
void resurrectEntities() {
forEachEntity(LanternEntity::resurrect);
}
/**
* Bury all the {@link Entity}s that will be
* temporarily "disabled" (chunk being unloaded).
*/
void buryEntities() {
forEachEntity(entity -> entity.remove(LanternEntity.RemoveState.CHUNK_UNLOAD));
}
public void addEntity(LanternEntity entity, int section) {
this.entities[section].add(entity);
}
public void removeEntity(LanternEntity entity, int section) {
this.entities[section].remove(entity);
}
@Override
public Optional<Entity> getEntity(UUID uniqueId) {
final Optional<Entity> optEntity = this.world.getEntity(uniqueId);
if (optEntity.isPresent()) {
final Vector3d pos = ((LanternEntity) optEntity.get()).getPosition();
if (VecHelper.inBounds(pos.getFloorX(), pos.getFloorZ(), this.areaMin, this.areaMax)) {
return optEntity;
}
}
return Optional.empty();
}
@Override
public Collection<Entity> getEntities() {
final ImmutableList.Builder<Entity> entities = ImmutableList.builder();
forEachEntity(entities::add);
return entities.build();
}
@Override
public Collection<Entity> getEntities(Predicate<Entity> filter) {
return getEntities().stream().filter(filter).collect(GuavaCollectors.toImmutableList());
}
@SuppressWarnings("unchecked")
@Override
public Entity createEntity(EntityType type, Vector3d position) {
checkNotNull(position, "position");
final LanternEntityType entityType = (LanternEntityType) checkNotNull(type, "type");
checkVolumeBounds(position.getFloorX(), position.getFloorY(), position.getFloorZ());
//noinspection unchecked
final LanternEntity entity = (LanternEntity) entityType.getEntityConstructor().apply(UUID.randomUUID());
entity.setPositionAndWorld(this.world, position);
return entity;
}
@Override
public Optional<Entity> createEntity(DataContainer entityContainer) {
// TODO Auto-generated method stub
return Optional.empty();
}
@Override
public Optional<Entity> createEntity(DataContainer entityContainer, Vector3d position) {
checkNotNull(position, "position");
checkVolumeBounds(position.getFloorX(), position.getFloorY(), position.getFloorZ());
final Optional<Entity> optEntity = createEntity(entityContainer);
if (optEntity.isPresent()) {
((LanternEntity) optEntity.get()).setPosition(position);
}
return optEntity;
}
@Override
public Collection<TileEntity> getTileEntities() {
final ImmutableSet.Builder<TileEntity> tileEntities = ImmutableSet.builder();
for (int i = 0; i < CHUNK_SECTIONS; i++) {
this.chunkSections.work(i, chunkSection -> {
if (chunkSection == null) {
return;
}
final ObjectIterator<LanternTileEntity> it = chunkSection.tileEntities.values().iterator();
while (it.hasNext()) {
final LanternTileEntity tileEntity = it.next();
if (tileEntity.isValid()) {
tileEntities.add(tileEntity);
} else {
it.remove();
}
}
}, true);
}
return tileEntities.build();
}
@Override
public Collection<TileEntity> getTileEntities(Predicate<TileEntity> filter) {
return getTileEntities().stream().filter(filter).collect(GuavaCollectors.toImmutableSet());
}
@Override
public Optional<TileEntity> getTileEntity(int x, int y, int z) {
checkVolumeBounds(x, y, z);
final short index = (short) ChunkSection.index(x & 0xf, y & 0xf, z & 0xf);
return this.chunkSections.work(y >> 4, chunkSection -> {
if (chunkSection == null) {
return Optional.empty();
}
final LanternTileEntity tileEntity = chunkSection.tileEntities.get(index);
// Remove invalid tile entities
if (tileEntity != null && !tileEntity.isValid()) {
chunkSection.tileEntities.remove(index);
return Optional.empty();
}
return Optional.ofNullable(tileEntity);
}, true);
}
private void checkAreaBounds(int x, int z) {
if (!VecHelper.inBounds(x, z, this.areaMin, this.areaMax)) {
throw new PositionOutOfBoundsException(new Vector2i(x, z), this.areaMin, this.areaMax);
}
}
@Override
public Vector3i getBlockMin() {
return this.min;
}
@Override
public Vector3i getBlockMax() {
return this.max;
}
@Override
public Vector3i getBlockSize() {
return CHUNK_SIZE;
}
@Override
public boolean containsBlock(int x, int y, int z) {
return VecHelper.inBounds(x, y, z, this.min, this.max);
}
@Override
public BlockState getBlock(int x, int y, int z) {
return BlockRegistryModule.get().getStateByInternalIdAndData(getType(x, y, z)).orElse(BlockTypes.AIR.getDefaultState());
}
@Override
public BlockType getBlockType(int x, int y, int z) {
return getBlock(x, y, z).getType();
}
@Override
public void setBiome(int x, int y, int z, BiomeType biome) {
setBiomeId(x, y, z, BiomeRegistryModule.get().getInternalId(biome));
}
@Override
public Vector3i getBiomeMin() {
return this.biomeMax;
}
@Override
public Vector3i getBiomeMax() {
return this.biomeMin;
}
@Override
public Vector3i getBiomeSize() {
return CHUNK_BIOME_VOLUME;
}
@Override
public boolean containsBiome(int x, int y, int z) {
return VecHelper.inBounds(x, z, this.areaMin, this.areaMax);
}
@Override
public BiomeType getBiome(int x, int y, int z) {
return BiomeRegistryModule.get().getByInternalId(getBiomeId(x, y, z)).orElse(BiomeTypes.OCEAN);
}
@Override
public <T extends Property<?, ?>> Optional<T> getProperty(int x, int y, int z, Direction direction, Class<T> propertyClass) {
return getProperty0(x, y, z, direction, propertyClass);
}
@SuppressWarnings("unchecked")
@Override
public <T extends Property<?, ?>> Optional<T> getProperty(int x, int y, int z, Class<T> propertyClass) {
return getProperty0(x, y, z, null, propertyClass);
}
private <T extends Property<?, ?>> Optional<T> getProperty0(int x, int y, int z, @Nullable Direction direction, Class<T> propertyClass) {
checkVolumeBounds(x, y, z);
if (!this.loaded) {
return Optional.empty();
}
final Location<World> location = new Location<>(this.world, x, y, z);
Optional<T> property;
if (direction != null) {
property = AbstractDirectionRelativePropertyHolder.getPropertyFor(location, direction, propertyClass);
} else {
property = AbstractPropertyHolder.getPropertyFor(location, propertyClass);
}
if (direction == null && !property.isPresent()) {
final Optional<TileEntity> tileEntity = getTileEntity(x, y, z);
if (tileEntity.isPresent()) {
property = tileEntity.get().getProperty(propertyClass);
}
}
return property;
}
@Override
public Collection<Property<?, ?>> getProperties(int x, int y, int z) {
if (!this.loaded) {
return Collections.emptyList();
}
final Location<World> location = new Location<>(this.world, x, y, z);
final ImmutableList.Builder<Property<?, ?>> builder = ImmutableList.builder();
builder.addAll(LanternPropertyRegistry.getInstance().getPropertiesFor(location));
getTileEntity(x, y, z).ifPresent(tile -> builder.addAll(tile.getApplicableProperties()));
return builder.build();
}
private static final Direction[] CARDINAL_FACES = Arrays.stream(Direction.values()).filter(Direction::isCardinal).toArray(Direction[]::new);
@Override
public Collection<Direction> getFacesWithProperty(int x, int y, int z, Class<? extends Property<?, ?>> propertyClass) {
if (!this.loaded) {
return Collections.emptyList();
}
final Location<World> location = new Location<>(this.world, x, y, z);
//noinspection unchecked
final Optional<PropertyStore<?>> store = (Optional) LanternPropertyRegistry.getInstance().getStore(propertyClass);
if (!store.isPresent()) {
return Collections.emptyList();
}
return Arrays.stream(CARDINAL_FACES)
.filter(direction -> AbstractDirectionRelativePropertyHolder.getPropertyFor(location, direction, store.get()).isPresent())
.collect(GuavaCollectors.toImmutableList());
}
@Override
public <E> Optional<E> get(int x, int y, int z, Key<? extends BaseValue<E>> key) {
if (!this.loaded) {
return Optional.empty();
}
final BlockState blockState = getBlock(x, y, z);
Optional<E> value = blockState.get(key);
if (!value.isPresent()) {
final Optional<TileEntity> tileEntity = getTileEntity(x, y, z);
if (tileEntity.isPresent()) {
value = tileEntity.get().get(key);
}
}
return value;
}
@Override
public <E, V extends BaseValue<E>> Optional<V> getValue(int x, int y, int z, Key<V> key) {
if (!this.loaded) {
return Optional.empty();
}
final BlockState blockState = getBlock(x, y, z);
Optional<V> value = blockState.getValue(key);
if (!value.isPresent()) {
final Optional<TileEntity> tileEntity = getTileEntity(x, y, z);
if (tileEntity.isPresent()) {
value = tileEntity.get().getValue(key);
}
}
return value;
}
@Override
public <T extends DataManipulator<?, ?>> Optional<T> get(int x, int y, int z, Class<T> manipulatorClass) {
// TODO Auto-generated method stub
return null;
}
@Override
public <T extends DataManipulator<?, ?>> Optional<T> getOrCreate(int x, int y, int z, Class<T> manipulatorClass) {
// TODO Auto-generated method stub
return null;
}
@Override
public boolean supports(int x, int y, int z, Key<?> key) {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean supports(int x, int y, int z, Class<? extends DataManipulator<?, ?>> manipulatorClass) {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean supports(int x, int y, int z, DataManipulator<?, ?> manipulator) {
// TODO Auto-generated method stub
return false;
}
@Override
public ImmutableSet<Key<?>> getKeys(int x, int y, int z) {
// TODO Auto-generated method stub
return null;
}
@Override
public ImmutableSet<ImmutableValue<?>> getValues(int x, int y, int z) {
// TODO Auto-generated method stub
return null;
}
@Override
public <E> DataTransactionResult transform(int x, int y, int z, Key<? extends BaseValue<E>> key, Function<E, E> function) {
// TODO Auto-generated method stub
return null;
}
@Override
public <E> DataTransactionResult offer(int x, int y, int z, Key<? extends BaseValue<E>> key, E value) {
// TODO Auto-generated method stub
return null;
}
@Override
public <E> DataTransactionResult offer(int x, int y, int z, Key<? extends BaseValue<E>> key, E value, Cause cause) {
return null;
}
@Override
public <E> DataTransactionResult offer(int x, int y, int z, BaseValue<E> value) {
// TODO Auto-generated method stub
return null;
}
@Override
public DataTransactionResult offer(int x, int y, int z, DataManipulator<?, ?> manipulator) {
// TODO Auto-generated method stub
return null;
}
@Override
public DataTransactionResult offer(int x, int y, int z, DataManipulator<?, ?> manipulator, MergeFunction function) {
// TODO Auto-generated method stub
return null;
}
@Override
public DataTransactionResult offer(int x, int y, int z, DataManipulator<?, ?> manipulator, MergeFunction function, Cause cause) {
return null;
}
@Override
public DataTransactionResult offer(int x, int y, int z, Iterable<DataManipulator<?, ?>> manipulators) {
// TODO Auto-generated method stub
return null;
}
@Override
public DataTransactionResult offer(Vector3i blockPosition, Iterable<DataManipulator<?, ?>> values, MergeFunction function) {
// TODO Auto-generated method stub
return null;
}
@Override
public DataTransactionResult remove(int x, int y, int z, Class<? extends DataManipulator<?, ?>> manipulatorClass) {
// TODO Auto-generated method stub
return null;
}
@Override
public DataTransactionResult remove(int x, int y, int z, Key<?> key) {
// TODO Auto-generated method stub
return null;
}
@Override
public DataTransactionResult undo(int x, int y, int z, DataTransactionResult result) {
// TODO Auto-generated method stub
return null;
}
@Override
public DataTransactionResult copyFrom(int xTo, int yTo, int zTo, DataHolder from) {
// TODO Auto-generated method stub
return null;
}
@Override
public DataTransactionResult copyFrom(int xTo, int yTo, int zTo, int xFrom, int yFrom, int zFrom) {
// TODO Auto-generated method stub
return null;
}
@Override
public DataTransactionResult copyFrom(int xTo, int yTo, int zTo, DataHolder from, MergeFunction function) {
// TODO Auto-generated method stub
return null;
}
@Override
public DataTransactionResult copyFrom(int xTo, int yTo, int zTo, int xFrom, int yFrom, int zFrom, MergeFunction function) {
// TODO Auto-generated method stub
return null;
}
@Override
public Collection<DataManipulator<?, ?>> getManipulators(int x, int y, int z) {
// TODO Auto-generated method stub
return null;
}
@Override
public boolean validateRawData(int x, int y, int z, DataView container) {
// TODO Auto-generated method stub
return false;
}
@Override
public void setRawData(int x, int y, int z, DataView container) throws InvalidDataException {
// TODO Auto-generated method stub
}
@Override
public UUID getUniqueId() {
return this.uniqueId;
}
@Override
public Location<Chunk> getLocation(int x, int y, int z) {
return new Location<>(this, x, y, z);
}
@Override
public Location<Chunk> getLocation(double x, double y, double z) {
return new Location<>(this, x, y, z);
}
@Override
public Vector3i getPosition() {
return this.pos;
}
@Override
public World getWorld() {
return this.world;
}
@Override
public boolean isPopulated() {
return this.populated;
}
@Override
public boolean loadChunk(boolean generate) {
if (this.world.getChunkManager().load(this, () -> Cause.source(this.world).named("chunk", this).build(), generate)) {
this.loaded = true;
return true;
}
return false;
}
@Override
public boolean unloadChunk() {
if (this.world.getChunkManager().unload(this, () -> Cause.source(this.world).named("chunk", this).build())) {
this.loaded = false;
return true;
}
return false;
}
// Typo
@Override
public int getInhabittedTime() {
return (int) Math.min(Integer.MAX_VALUE, this.inhabitedTime);
}
public long getInhabitedTime() {
return this.inhabitedTime;
}
public void setInhabitedTime(long inhabitedTime) {
this.inhabitedTime = inhabitedTime;
}
@Override
public double getRegionalDifficultyFactor() {
// TODO Auto-generated method stub
return 0;
}
@Override
public double getRegionalDifficultyPercentage() {
// TODO Auto-generated method stub
return 0;
}
@Override
public boolean hitBlock(int x, int y, int z, Direction side, Cause cause) {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean interactBlock(int x, int y, int z, Direction side, Cause cause) {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean interactBlockWith(int x, int y, int z, ItemStack itemStack, Direction side, Cause cause) {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean digBlock(int x, int y, int z, Cause cause) {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean digBlockWith(int x, int y, int z, ItemStack itemStack, Cause cause) {
// TODO Auto-generated method stub
return false;
}
@Override
public int getBlockDigTimeWith(int x, int y, int z, ItemStack itemStack, Cause cause) {
// TODO Auto-generated method stub
return 0;
}
@Override
public boolean placeBlock(int x, int y, int z, BlockState block, Direction direction, Cause cause) {
return false;
}
}