package net.minecraft.world.level.chunk; import com.google.common.collect.Lists; import com.mojang.logging.LogUtils; import it.unimi.dsi.fastutil.shorts.ShortList; import java.util.Collections; import java.util.EnumSet; import java.util.List; import java.util.Map; import net.minecraft.core.BlockPos; import net.minecraft.core.Holder; import net.minecraft.core.HolderLookup; import net.minecraft.core.SectionPos; import net.minecraft.nbt.CompoundTag; import net.minecraft.util.ProblemReporter; import net.minecraft.world.entity.Entity; import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.LevelHeightAccessor; import net.minecraft.world.level.biome.Biome; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.chunk.status.ChunkStatus; import net.minecraft.world.level.levelgen.BelowZeroRetrogen; import net.minecraft.world.level.levelgen.Heightmap; import net.minecraft.world.level.levelgen.blending.BlendingData; import net.minecraft.world.level.levelgen.structure.BoundingBox; import net.minecraft.world.level.levelgen.structure.Structure; import net.minecraft.world.level.levelgen.structure.StructureStart; import net.minecraft.world.level.lighting.LevelLightEngine; import net.minecraft.world.level.lighting.LightEngine; import net.minecraft.world.level.material.Fluid; import net.minecraft.world.level.material.FluidState; import net.minecraft.world.level.material.Fluids; import net.minecraft.world.level.storage.TagValueOutput; import net.minecraft.world.ticks.LevelChunkTicks; import net.minecraft.world.ticks.ProtoChunkTicks; import net.minecraft.world.ticks.TickContainerAccess; import org.jspecify.annotations.Nullable; import org.slf4j.Logger; public class ProtoChunk extends ChunkAccess { private static final Logger LOGGER = LogUtils.getLogger(); @Nullable private volatile LevelLightEngine lightEngine; private volatile ChunkStatus status = ChunkStatus.EMPTY; private final List entities = Lists.newArrayList(); @Nullable private CarvingMask carvingMask; @Nullable private BelowZeroRetrogen belowZeroRetrogen; private final ProtoChunkTicks blockTicks; private final ProtoChunkTicks fluidTicks; public ProtoChunk( final ChunkPos chunkPos, final UpgradeData upgradeData, final LevelHeightAccessor levelHeightAccessor, final PalettedContainerFactory containerFactory, @Nullable final BlendingData blendingData ) { this(chunkPos, upgradeData, null, new ProtoChunkTicks<>(), new ProtoChunkTicks<>(), levelHeightAccessor, containerFactory, blendingData); } public ProtoChunk( final ChunkPos chunkPos, final UpgradeData upgradeData, @Nullable final LevelChunkSection[] sections, final ProtoChunkTicks blockTicks, final ProtoChunkTicks fluidTicks, final LevelHeightAccessor levelHeightAccessor, final PalettedContainerFactory containerFactory, @Nullable final BlendingData blendingData ) { super(chunkPos, upgradeData, levelHeightAccessor, containerFactory, 0L, sections, blendingData); this.blockTicks = blockTicks; this.fluidTicks = fluidTicks; } @Override public TickContainerAccess getBlockTicks() { return this.blockTicks; } @Override public TickContainerAccess getFluidTicks() { return this.fluidTicks; } @Override public ChunkAccess.PackedTicks getTicksForSerialization(final long currentTick) { return new ChunkAccess.PackedTicks(this.blockTicks.pack(currentTick), this.fluidTicks.pack(currentTick)); } @Override public BlockState getBlockState(final BlockPos pos) { int y = pos.getY(); if (this.isOutsideBuildHeight(y)) { return Blocks.VOID_AIR.defaultBlockState(); } else { LevelChunkSection section = this.getSection(this.getSectionIndex(y)); return section.hasOnlyAir() ? Blocks.AIR.defaultBlockState() : section.getBlockState(pos.getX() & 15, y & 15, pos.getZ() & 15); } } @Override public FluidState getFluidState(final BlockPos pos) { int y = pos.getY(); if (this.isOutsideBuildHeight(y)) { return Fluids.EMPTY.defaultFluidState(); } else { LevelChunkSection section = this.getSection(this.getSectionIndex(y)); return section.hasOnlyAir() ? Fluids.EMPTY.defaultFluidState() : section.getFluidState(pos.getX() & 15, y & 15, pos.getZ() & 15); } } @Nullable @Override public BlockState setBlockState(final BlockPos pos, final BlockState state, @Block.UpdateFlags final int flags) { int x = pos.getX(); int y = pos.getY(); int z = pos.getZ(); if (this.isOutsideBuildHeight(y)) { return Blocks.VOID_AIR.defaultBlockState(); } else { int sectionIndex = this.getSectionIndex(y); LevelChunkSection section = this.getSection(sectionIndex); boolean wasEmpty = section.hasOnlyAir(); if (wasEmpty && state.is(Blocks.AIR)) { return state; } else { int localX = SectionPos.sectionRelative(x); int localY = SectionPos.sectionRelative(y); int localZ = SectionPos.sectionRelative(z); BlockState oldState = section.setBlockState(localX, localY, localZ, state); if (this.status.isOrAfter(ChunkStatus.INITIALIZE_LIGHT)) { boolean isEmpty = section.hasOnlyAir(); if (isEmpty != wasEmpty) { this.lightEngine.updateSectionStatus(pos, isEmpty); } if (LightEngine.hasDifferentLightProperties(oldState, state)) { this.skyLightSources.update(this, localX, y, localZ); this.lightEngine.checkBlock(pos); } } EnumSet heightmapsAfter = this.getPersistedStatus().heightmapsAfter(); EnumSet toPrime = null; for (Heightmap.Types type : heightmapsAfter) { Heightmap heightmap = (Heightmap)this.heightmaps.get(type); if (heightmap == null) { if (toPrime == null) { toPrime = EnumSet.noneOf(Heightmap.Types.class); } toPrime.add(type); } } if (toPrime != null) { Heightmap.primeHeightmaps(this, toPrime); } for (Heightmap.Types typex : heightmapsAfter) { ((Heightmap)this.heightmaps.get(typex)).update(localX, y, localZ, state); } return oldState; } } } @Override public void setBlockEntity(final BlockEntity blockEntity) { this.pendingBlockEntities.remove(blockEntity.getBlockPos()); this.blockEntities.put(blockEntity.getBlockPos(), blockEntity); } @Nullable @Override public BlockEntity getBlockEntity(final BlockPos pos) { return (BlockEntity)this.blockEntities.get(pos); } public Map getBlockEntities() { return this.blockEntities; } public void addEntity(final CompoundTag tag) { this.entities.add(tag); } @Override public void addEntity(final Entity entity) { if (!entity.isPassenger()) { try (ProblemReporter.ScopedCollector reporter = new ProblemReporter.ScopedCollector(entity.problemPath(), LOGGER)) { TagValueOutput output = TagValueOutput.createWithContext(reporter, entity.registryAccess()); entity.save(output); this.addEntity(output.buildResult()); } } } @Override public void setStartForStructure(final Structure structure, final StructureStart structureStart) { BelowZeroRetrogen belowZeroRetrogen = this.getBelowZeroRetrogen(); if (belowZeroRetrogen != null && structureStart.isValid()) { BoundingBox boundingBox = structureStart.getBoundingBox(); LevelHeightAccessor heightAccessor = this.getHeightAccessorForGeneration(); if (boundingBox.minY() < heightAccessor.getMinY() || boundingBox.maxY() > heightAccessor.getMaxY()) { return; } } super.setStartForStructure(structure, structureStart); } public List getEntities() { return this.entities; } @Override public ChunkStatus getPersistedStatus() { return this.status; } public void setPersistedStatus(final ChunkStatus status) { this.status = status; if (this.belowZeroRetrogen != null && status.isOrAfter(this.belowZeroRetrogen.targetStatus())) { this.setBelowZeroRetrogen(null); } this.markUnsaved(); } @Override public Holder getNoiseBiome(final int quartX, final int quartY, final int quartZ) { if (this.getHighestGeneratedStatus().isOrAfter(ChunkStatus.BIOMES)) { return super.getNoiseBiome(quartX, quartY, quartZ); } else { throw new IllegalStateException("Asking for biomes before we have biomes"); } } public static short packOffsetCoordinates(final BlockPos pos) { int x = pos.getX(); int y = pos.getY(); int z = pos.getZ(); int dx = x & 15; int dy = y & 15; int dz = z & 15; return (short)(dx | dy << 4 | dz << 8); } public static BlockPos unpackOffsetCoordinates(final short packedData, final int sectionY, final ChunkPos chunkPos) { int posX = SectionPos.sectionToBlockCoord(chunkPos.x(), packedData & 15); int posY = SectionPos.sectionToBlockCoord(sectionY, packedData >>> 4 & 15); int posZ = SectionPos.sectionToBlockCoord(chunkPos.z(), packedData >>> 8 & 15); return new BlockPos(posX, posY, posZ); } @Override public void markPosForPostProcessing(final BlockPos blockPos) { if (this.isInsideBuildHeight(blockPos)) { ChunkAccess.getOrCreateOffsetList(this.postProcessing, this.getSectionIndex(blockPos.getY())).add(packOffsetCoordinates(blockPos)); } } @Override public void addPackedPostProcess(final ShortList packedOffsets, final int sectionIndex) { ChunkAccess.getOrCreateOffsetList(this.postProcessing, sectionIndex).addAll(packedOffsets); } public Map getBlockEntityNbts() { return Collections.unmodifiableMap(this.pendingBlockEntities); } @Nullable @Override public CompoundTag getBlockEntityNbtForSaving(final BlockPos blockPos, final HolderLookup.Provider registryAccess) { BlockEntity blockEntity = this.getBlockEntity(blockPos); return blockEntity != null ? blockEntity.saveWithFullMetadata(registryAccess) : (CompoundTag)this.pendingBlockEntities.get(blockPos); } @Override public void removeBlockEntity(final BlockPos pos) { this.blockEntities.remove(pos); this.pendingBlockEntities.remove(pos); } @Nullable public CarvingMask getCarvingMask() { return this.carvingMask; } public CarvingMask getOrCreateCarvingMask() { if (this.carvingMask == null) { this.carvingMask = new CarvingMask(this.getHeight(), this.getMinY()); } return this.carvingMask; } public void setCarvingMask(final CarvingMask data) { this.carvingMask = data; } public void setLightEngine(final LevelLightEngine lightEngine) { this.lightEngine = lightEngine; } public void setBelowZeroRetrogen(@Nullable final BelowZeroRetrogen belowZeroRetrogen) { this.belowZeroRetrogen = belowZeroRetrogen; } @Nullable @Override public BelowZeroRetrogen getBelowZeroRetrogen() { return this.belowZeroRetrogen; } private static LevelChunkTicks unpackTicks(final ProtoChunkTicks ticks) { return new LevelChunkTicks<>(ticks.scheduledTicks()); } public LevelChunkTicks unpackBlockTicks() { return unpackTicks(this.blockTicks); } public LevelChunkTicks unpackFluidTicks() { return unpackTicks(this.fluidTicks); } @Override public LevelHeightAccessor getHeightAccessorForGeneration() { return (LevelHeightAccessor)(this.isUpgrading() ? BelowZeroRetrogen.UPGRADE_HEIGHT_ACCESSOR : this); } }