package net.minecraft.server.level; import com.mojang.logging.LogUtils; import java.util.Collections; import java.util.List; import java.util.Locale; import java.util.concurrent.atomic.AtomicLong; import java.util.function.Predicate; import java.util.function.Supplier; import net.minecraft.CrashReport; import net.minecraft.CrashReportCategory; import net.minecraft.CrashReportDetail; import net.minecraft.ReportedException; import net.minecraft.Util; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.core.Holder; import net.minecraft.core.RegistryAccess; import net.minecraft.core.SectionPos; import net.minecraft.core.particles.ParticleOptions; import net.minecraft.nbt.CompoundTag; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.MinecraftServer; import net.minecraft.sounds.SoundEvent; import net.minecraft.sounds.SoundSource; import net.minecraft.util.RandomSource; import net.minecraft.util.StaticCache2D; import net.minecraft.world.DifficultyInstance; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.player.Player; import net.minecraft.world.flag.FeatureFlagSet; import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.LevelHeightAccessor; import net.minecraft.world.level.WorldGenLevel; import net.minecraft.world.level.biome.Biome; import net.minecraft.world.level.biome.BiomeManager; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.EntityBlock; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.border.WorldBorder; import net.minecraft.world.level.chunk.ChunkAccess; import net.minecraft.world.level.chunk.ChunkSource; import net.minecraft.world.level.chunk.status.ChunkStatus; import net.minecraft.world.level.chunk.status.ChunkStep; import net.minecraft.world.level.chunk.status.ChunkType; import net.minecraft.world.level.dimension.DimensionType; import net.minecraft.world.level.entity.EntityTypeTest; import net.minecraft.world.level.gameevent.GameEvent; import net.minecraft.world.level.gameevent.GameEvent.Context; import net.minecraft.world.level.levelgen.Heightmap; import net.minecraft.world.level.lighting.LevelLightEngine; import net.minecraft.world.level.material.Fluid; import net.minecraft.world.level.material.FluidState; import net.minecraft.world.level.storage.LevelData; import net.minecraft.world.phys.AABB; import net.minecraft.world.phys.Vec3; import net.minecraft.world.ticks.LevelTickAccess; import net.minecraft.world.ticks.WorldGenTickAccess; import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; public class WorldGenRegion implements WorldGenLevel { private static final Logger LOGGER = LogUtils.getLogger(); private final StaticCache2D cache; private final ChunkAccess center; private final ServerLevel level; private final long seed; private final LevelData levelData; private final RandomSource random; private final DimensionType dimensionType; private final WorldGenTickAccess blockTicks = new WorldGenTickAccess<>(blockPos -> this.getChunk(blockPos).getBlockTicks()); private final WorldGenTickAccess fluidTicks = new WorldGenTickAccess<>(blockPos -> this.getChunk(blockPos).getFluidTicks()); private final BiomeManager biomeManager; private final ChunkStep generatingStep; @Nullable private Supplier currentlyGenerating; private final AtomicLong subTickCount = new AtomicLong(); private static final ResourceLocation WORLDGEN_REGION_RANDOM = ResourceLocation.withDefaultNamespace("worldgen_region_random"); public WorldGenRegion(ServerLevel level, StaticCache2D cache, ChunkStep generatingStep, ChunkAccess center) { this.generatingStep = generatingStep; this.cache = cache; this.center = center; this.level = level; this.seed = level.getSeed(); this.levelData = level.getLevelData(); this.random = level.getChunkSource().randomState().getOrCreateRandomFactory(WORLDGEN_REGION_RANDOM).at(this.center.getPos().getWorldPosition()); this.dimensionType = level.dimensionType(); this.biomeManager = new BiomeManager(this, BiomeManager.obfuscateSeed(this.seed)); } public boolean isOldChunkAround(ChunkPos pos, int radius) { return this.level.getChunkSource().chunkMap.isOldChunkAround(pos, radius); } public ChunkPos getCenter() { return this.center.getPos(); } @Override public void setCurrentlyGenerating(@Nullable Supplier currentlyGenerating) { this.currentlyGenerating = currentlyGenerating; } @Override public ChunkAccess getChunk(int chunkX, int chunkZ) { return this.getChunk(chunkX, chunkZ, ChunkStatus.EMPTY); } @Nullable @Override public ChunkAccess getChunk(int x, int z, ChunkStatus chunkStatus, boolean requireChunk) { int i = this.center.getPos().getChessboardDistance(x, z); ChunkStatus chunkStatus2 = i >= this.generatingStep.directDependencies().size() ? null : this.generatingStep.directDependencies().get(i); GenerationChunkHolder generationChunkHolder; if (chunkStatus2 != null) { generationChunkHolder = this.cache.get(x, z); if (chunkStatus.isOrBefore(chunkStatus2)) { ChunkAccess chunkAccess = generationChunkHolder.getChunkIfPresentUnchecked(chunkStatus2); if (chunkAccess != null) { return chunkAccess; } } } else { generationChunkHolder = null; } CrashReport crashReport = CrashReport.forThrowable( new IllegalStateException("Requested chunk unavailable during world generation"), "Exception generating new chunk" ); CrashReportCategory crashReportCategory = crashReport.addCategory("Chunk request details"); crashReportCategory.setDetail("Requested chunk", String.format(Locale.ROOT, "%d, %d", x, z)); crashReportCategory.setDetail("Generating status", (CrashReportDetail)(() -> this.generatingStep.targetStatus().getName())); crashReportCategory.setDetail("Requested status", chunkStatus::getName); crashReportCategory.setDetail( "Actual status", (CrashReportDetail)(() -> generationChunkHolder == null ? "[out of cache bounds]" : generationChunkHolder.getPersistedStatus().getName()) ); crashReportCategory.setDetail("Maximum allowed status", (CrashReportDetail)(() -> chunkStatus2 == null ? "null" : chunkStatus2.getName())); crashReportCategory.setDetail("Dependencies", this.generatingStep.directDependencies()::toString); crashReportCategory.setDetail("Requested distance", i); crashReportCategory.setDetail("Generating chunk", this.center.getPos()::toString); throw new ReportedException(crashReport); } @Override public boolean hasChunk(int chunkX, int chunkZ) { int i = this.center.getPos().getChessboardDistance(chunkX, chunkZ); return i < this.generatingStep.directDependencies().size(); } @Override public BlockState getBlockState(BlockPos pos) { return this.getChunk(SectionPos.blockToSectionCoord(pos.getX()), SectionPos.blockToSectionCoord(pos.getZ())).getBlockState(pos); } @Override public FluidState getFluidState(BlockPos pos) { return this.getChunk(pos).getFluidState(pos); } @Nullable @Override public Player getNearestPlayer(double x, double y, double z, double distance, Predicate predicate) { return null; } @Override public int getSkyDarken() { return 0; } @Override public BiomeManager getBiomeManager() { return this.biomeManager; } @Override public Holder getUncachedNoiseBiome(int x, int y, int z) { return this.level.getUncachedNoiseBiome(x, y, z); } @Override public float getShade(Direction direction, boolean shade) { return 1.0F; } @Override public LevelLightEngine getLightEngine() { return this.level.getLightEngine(); } @Override public boolean destroyBlock(BlockPos pos, boolean dropBlock, @Nullable Entity entity, int recursionLeft) { BlockState blockState = this.getBlockState(pos); if (blockState.isAir()) { return false; } else { if (dropBlock) { BlockEntity blockEntity = blockState.hasBlockEntity() ? this.getBlockEntity(pos) : null; Block.dropResources(blockState, this.level, pos, blockEntity, entity, ItemStack.EMPTY); } return this.setBlock(pos, Blocks.AIR.defaultBlockState(), 3, recursionLeft); } } @Nullable @Override public BlockEntity getBlockEntity(BlockPos pos) { ChunkAccess chunkAccess = this.getChunk(pos); BlockEntity blockEntity = chunkAccess.getBlockEntity(pos); if (blockEntity != null) { return blockEntity; } else { CompoundTag compoundTag = chunkAccess.getBlockEntityNbt(pos); BlockState blockState = chunkAccess.getBlockState(pos); if (compoundTag != null) { if ("DUMMY".equals(compoundTag.getStringOr("id", ""))) { if (!blockState.hasBlockEntity()) { return null; } blockEntity = ((EntityBlock)blockState.getBlock()).newBlockEntity(pos, blockState); } else { blockEntity = BlockEntity.loadStatic(pos, blockState, compoundTag, this.level.registryAccess()); } if (blockEntity != null) { chunkAccess.setBlockEntity(blockEntity); return blockEntity; } } if (blockState.hasBlockEntity()) { LOGGER.warn("Tried to access a block entity before it was created. {}", pos); } return null; } } @Override public boolean ensureCanWrite(BlockPos pos) { int i = SectionPos.blockToSectionCoord(pos.getX()); int j = SectionPos.blockToSectionCoord(pos.getZ()); ChunkPos chunkPos = this.getCenter(); int k = Math.abs(chunkPos.x - i); int l = Math.abs(chunkPos.z - j); if (k <= this.generatingStep.blockStateWriteRadius() && l <= this.generatingStep.blockStateWriteRadius()) { if (this.center.isUpgrading()) { LevelHeightAccessor levelHeightAccessor = this.center.getHeightAccessorForGeneration(); if (levelHeightAccessor.isOutsideBuildHeight(pos.getY())) { return false; } } return true; } else { Util.logAndPauseIfInIde( "Detected setBlock in a far chunk [" + i + ", " + j + "], pos: " + pos + ", status: " + this.generatingStep.targetStatus() + (this.currentlyGenerating == null ? "" : ", currently generating: " + (String)this.currentlyGenerating.get()) ); return false; } } @Override public boolean setBlock(BlockPos pos, BlockState state, int flags, int recursionLeft) { if (!this.ensureCanWrite(pos)) { return false; } else { ChunkAccess chunkAccess = this.getChunk(pos); BlockState blockState = chunkAccess.setBlockState(pos, state, flags); if (blockState != null) { this.level.updatePOIOnBlockStateChange(pos, blockState, state); } if (state.hasBlockEntity()) { if (chunkAccess.getPersistedStatus().getChunkType() == ChunkType.LEVELCHUNK) { BlockEntity blockEntity = ((EntityBlock)state.getBlock()).newBlockEntity(pos, state); if (blockEntity != null) { chunkAccess.setBlockEntity(blockEntity); } else { chunkAccess.removeBlockEntity(pos); } } else { CompoundTag compoundTag = new CompoundTag(); compoundTag.putInt("x", pos.getX()); compoundTag.putInt("y", pos.getY()); compoundTag.putInt("z", pos.getZ()); compoundTag.putString("id", "DUMMY"); chunkAccess.setBlockEntityNbt(compoundTag); } } else if (blockState != null && blockState.hasBlockEntity()) { chunkAccess.removeBlockEntity(pos); } if (state.hasPostProcess(this, pos) && (flags & 16) == 0) { this.markPosForPostprocessing(pos); } return true; } } private void markPosForPostprocessing(BlockPos pos) { this.getChunk(pos).markPosForPostprocessing(pos); } @Override public boolean addFreshEntity(Entity entity) { int i = SectionPos.blockToSectionCoord(entity.getBlockX()); int j = SectionPos.blockToSectionCoord(entity.getBlockZ()); this.getChunk(i, j).addEntity(entity); return true; } @Override public boolean removeBlock(BlockPos pos, boolean isMoving) { return this.setBlock(pos, Blocks.AIR.defaultBlockState(), 3); } @Override public WorldBorder getWorldBorder() { return this.level.getWorldBorder(); } @Override public boolean isClientSide() { return false; } @Deprecated @Override public ServerLevel getLevel() { return this.level; } @Override public RegistryAccess registryAccess() { return this.level.registryAccess(); } @Override public FeatureFlagSet enabledFeatures() { return this.level.enabledFeatures(); } @Override public LevelData getLevelData() { return this.levelData; } @Override public DifficultyInstance getCurrentDifficultyAt(BlockPos pos) { if (!this.hasChunk(SectionPos.blockToSectionCoord(pos.getX()), SectionPos.blockToSectionCoord(pos.getZ()))) { throw new RuntimeException("We are asking a region for a chunk out of bound"); } else { return new DifficultyInstance(this.level.getDifficulty(), this.level.getDayTime(), 0L, this.level.getMoonBrightness()); } } @Nullable @Override public MinecraftServer getServer() { return this.level.getServer(); } @Override public ChunkSource getChunkSource() { return this.level.getChunkSource(); } @Override public long getSeed() { return this.seed; } @Override public LevelTickAccess getBlockTicks() { return this.blockTicks; } @Override public LevelTickAccess getFluidTicks() { return this.fluidTicks; } @Override public int getSeaLevel() { return this.level.getSeaLevel(); } @Override public RandomSource getRandom() { return this.random; } @Override public int getHeight(Heightmap.Types heightmapType, int x, int z) { return this.getChunk(SectionPos.blockToSectionCoord(x), SectionPos.blockToSectionCoord(z)).getHeight(heightmapType, x & 15, z & 15) + 1; } @Override public void playSound(@Nullable Entity entity, BlockPos pos, SoundEvent sound, SoundSource source, float volume, float pitch) { } @Override public void addParticle(ParticleOptions particle, double x, double y, double z, double xSpeed, double ySpeed, double zSpeed) { } @Override public void levelEvent(@Nullable Entity entity, int type, BlockPos pos, int data) { } @Override public void gameEvent(Holder gameEvent, Vec3 pos, Context context) { } @Override public DimensionType dimensionType() { return this.dimensionType; } @Override public boolean isStateAtPosition(BlockPos pos, Predicate state) { return state.test(this.getBlockState(pos)); } @Override public boolean isFluidAtPosition(BlockPos pos, Predicate predicate) { return predicate.test(this.getFluidState(pos)); } @Override public List getEntities(EntityTypeTest entityTypeTest, AABB bounds, Predicate predicate) { return Collections.emptyList(); } @Override public List getEntities(@Nullable Entity entity, AABB area, @Nullable Predicate predicate) { return Collections.emptyList(); } @Override public List players() { return Collections.emptyList(); } @Override public int getMinY() { return this.level.getMinY(); } @Override public int getHeight() { return this.level.getHeight(); } @Override public long nextSubTickCount() { return this.subTickCount.getAndIncrement(); } }