package net.minecraft.world.level.levelgen; import com.mojang.logging.LogUtils; import com.mojang.serialization.Codec; import io.netty.buffer.ByteBuf; import it.unimi.dsi.fastutil.objects.ObjectArrayList; import it.unimi.dsi.fastutil.objects.ObjectList; import it.unimi.dsi.fastutil.objects.ObjectListIterator; import java.util.EnumSet; import java.util.Set; import java.util.function.IntFunction; import java.util.function.Predicate; import net.minecraft.core.BlockPos; import net.minecraft.network.codec.ByteBufCodecs; import net.minecraft.network.codec.StreamCodec; import net.minecraft.util.BitStorage; import net.minecraft.util.ByIdMap; import net.minecraft.util.Mth; import net.minecraft.util.SimpleBitStorage; import net.minecraft.util.StringRepresentable; import net.minecraft.util.ByIdMap.OutOfBoundsStrategy; import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.LeavesBlock; import net.minecraft.world.level.block.state.BlockBehaviour; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.chunk.ChunkAccess; import org.slf4j.Logger; public class Heightmap { private static final Logger LOGGER = LogUtils.getLogger(); static final Predicate NOT_AIR = blockState -> !blockState.isAir(); static final Predicate MATERIAL_MOTION_BLOCKING = BlockBehaviour.BlockStateBase::blocksMotion; private final BitStorage data; private final Predicate isOpaque; private final ChunkAccess chunk; public Heightmap(ChunkAccess chunk, Heightmap.Types type) { this.isOpaque = type.isOpaque(); this.chunk = chunk; int i = Mth.ceillog2(chunk.getHeight() + 1); this.data = new SimpleBitStorage(i, 256); } public static void primeHeightmaps(ChunkAccess chunk, Set types) { if (!types.isEmpty()) { int i = types.size(); ObjectList objectList = new ObjectArrayList<>(i); ObjectListIterator objectListIterator = objectList.iterator(); int j = chunk.getHighestSectionPosition() + 16; BlockPos.MutableBlockPos mutableBlockPos = new BlockPos.MutableBlockPos(); for (int k = 0; k < 16; k++) { for (int l = 0; l < 16; l++) { for (Heightmap.Types types2 : types) { objectList.add(chunk.getOrCreateHeightmapUnprimed(types2)); } for (int m = j - 1; m >= chunk.getMinY(); m--) { mutableBlockPos.set(k, m, l); BlockState blockState = chunk.getBlockState(mutableBlockPos); if (!blockState.is(Blocks.AIR)) { while (objectListIterator.hasNext()) { Heightmap heightmap = (Heightmap)objectListIterator.next(); if (heightmap.isOpaque.test(blockState)) { heightmap.setHeight(k, l, m + 1); objectListIterator.remove(); } } if (objectList.isEmpty()) { break; } objectListIterator.back(i); } } } } } } public boolean update(int x, int y, int z, BlockState state) { int i = this.getFirstAvailable(x, z); if (y <= i - 2) { return false; } else { if (this.isOpaque.test(state)) { if (y >= i) { this.setHeight(x, z, y + 1); return true; } } else if (i - 1 == y) { BlockPos.MutableBlockPos mutableBlockPos = new BlockPos.MutableBlockPos(); for (int j = y - 1; j >= this.chunk.getMinY(); j--) { mutableBlockPos.set(x, j, z); if (this.isOpaque.test(this.chunk.getBlockState(mutableBlockPos))) { this.setHeight(x, z, j + 1); return true; } } this.setHeight(x, z, this.chunk.getMinY()); return true; } return false; } } public int getFirstAvailable(int x, int z) { return this.getFirstAvailable(getIndex(x, z)); } public int getHighestTaken(int x, int z) { return this.getFirstAvailable(getIndex(x, z)) - 1; } private int getFirstAvailable(int index) { return this.data.get(index) + this.chunk.getMinY(); } private void setHeight(int x, int z, int value) { this.data.set(getIndex(x, z), value - this.chunk.getMinY()); } public void setRawData(ChunkAccess chunk, Heightmap.Types type, long[] data) { long[] ls = this.data.getRaw(); if (ls.length == data.length) { System.arraycopy(data, 0, ls, 0, data.length); } else { LOGGER.warn("Ignoring heightmap data for chunk " + chunk.getPos() + ", size does not match; expected: " + ls.length + ", got: " + data.length); primeHeightmaps(chunk, EnumSet.of(type)); } } public long[] getRawData() { return this.data.getRaw(); } private static int getIndex(int x, int z) { return x + z * 16; } public static enum Types implements StringRepresentable { WORLD_SURFACE_WG(0, "WORLD_SURFACE_WG", Heightmap.Usage.WORLDGEN, Heightmap.NOT_AIR), WORLD_SURFACE(1, "WORLD_SURFACE", Heightmap.Usage.CLIENT, Heightmap.NOT_AIR), OCEAN_FLOOR_WG(2, "OCEAN_FLOOR_WG", Heightmap.Usage.WORLDGEN, Heightmap.MATERIAL_MOTION_BLOCKING), OCEAN_FLOOR(3, "OCEAN_FLOOR", Heightmap.Usage.LIVE_WORLD, Heightmap.MATERIAL_MOTION_BLOCKING), MOTION_BLOCKING(4, "MOTION_BLOCKING", Heightmap.Usage.CLIENT, blockState -> blockState.blocksMotion() || !blockState.getFluidState().isEmpty()), MOTION_BLOCKING_NO_LEAVES( 5, "MOTION_BLOCKING_NO_LEAVES", Heightmap.Usage.CLIENT, blockState -> (blockState.blocksMotion() || !blockState.getFluidState().isEmpty()) && !(blockState.getBlock() instanceof LeavesBlock) ); public static final Codec CODEC = StringRepresentable.fromEnum(Heightmap.Types::values); private static final IntFunction BY_ID = ByIdMap.continuous(types -> types.id, values(), OutOfBoundsStrategy.ZERO); public static final StreamCodec STREAM_CODEC = ByteBufCodecs.idMapper(BY_ID, types -> types.id); private final int id; private final String serializationKey; private final Heightmap.Usage usage; private final Predicate isOpaque; private Types(final int id, final String serializationKey, final Heightmap.Usage usage, final Predicate isOpaque) { this.id = id; this.serializationKey = serializationKey; this.usage = usage; this.isOpaque = isOpaque; } public String getSerializationKey() { return this.serializationKey; } public boolean sendToClient() { return this.usage == Heightmap.Usage.CLIENT; } public boolean keepAfterWorldgen() { return this.usage != Heightmap.Usage.WORLDGEN; } public Predicate isOpaque() { return this.isOpaque; } @Override public String getSerializedName() { return this.serializationKey; } } public static enum Usage { WORLDGEN, LIVE_WORLD, CLIENT; } }