/* * Decompiled with CFR 0.152. * * Could not load the following classes: * com.google.common.annotations.VisibleForTesting * com.google.common.collect.Lists * com.mojang.datafixers.DataFixer * com.mojang.datafixers.util.Pair * com.mojang.logging.LogUtils * it.unimi.dsi.fastutil.ints.Int2ObjectMap * it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap * it.unimi.dsi.fastutil.longs.LongSet * it.unimi.dsi.fastutil.objects.Object2IntMap$Entry * it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap * it.unimi.dsi.fastutil.objects.ObjectArrayList * it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet * it.unimi.dsi.fastutil.objects.ObjectOpenHashSet * org.jspecify.annotations.Nullable * org.slf4j.Logger */ package net.minecraft.server.level; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.Lists; import com.mojang.datafixers.DataFixer; import com.mojang.datafixers.util.Pair; import com.mojang.logging.LogUtils; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.longs.LongSet; import it.unimi.dsi.fastutil.objects.Object2IntMap; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; import it.unimi.dsi.fastutil.objects.ObjectArrayList; import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import java.io.BufferedWriter; import java.io.IOException; import java.io.Writer; import java.nio.file.Files; import java.nio.file.OpenOption; import java.nio.file.Path; import java.util.ArrayList; import java.util.Collection; import java.util.Comparator; import java.util.List; import java.util.Locale; import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.UUID; import java.util.concurrent.Executor; import java.util.function.BooleanSupplier; import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.stream.Stream; import net.minecraft.CrashReport; import net.minecraft.CrashReportCategory; import net.minecraft.ReportType; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.core.Holder; import net.minecraft.core.HolderSet; import net.minecraft.core.SectionPos; import net.minecraft.core.particles.ExplosionParticleInfo; import net.minecraft.core.particles.ParticleOptions; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.core.registries.Registries; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.MutableComponent; import net.minecraft.network.protocol.Packet; import net.minecraft.network.protocol.game.ClientboundBlockDestructionPacket; import net.minecraft.network.protocol.game.ClientboundBlockEventPacket; import net.minecraft.network.protocol.game.ClientboundDamageEventPacket; import net.minecraft.network.protocol.game.ClientboundEntityEventPacket; import net.minecraft.network.protocol.game.ClientboundExplodePacket; import net.minecraft.network.protocol.game.ClientboundGameEventPacket; import net.minecraft.network.protocol.game.ClientboundLevelEventPacket; import net.minecraft.network.protocol.game.ClientboundLevelParticlesPacket; import net.minecraft.network.protocol.game.ClientboundSoundEntityPacket; import net.minecraft.network.protocol.game.ClientboundSoundPacket; import net.minecraft.resources.Identifier; import net.minecraft.resources.ResourceKey; import net.minecraft.server.MinecraftServer; import net.minecraft.server.ServerScoreboard; import net.minecraft.server.level.ChunkMap; import net.minecraft.server.level.ServerChunkCache; import net.minecraft.server.level.ServerEntityGetter; import net.minecraft.server.level.ServerPlayer; import net.minecraft.server.players.SleepStatus; import net.minecraft.server.waypoints.ServerWaypointManager; import net.minecraft.sounds.SoundEvent; import net.minecraft.sounds.SoundSource; import net.minecraft.tags.BlockTags; import net.minecraft.tags.TagKey; import net.minecraft.util.AbortableIterationConsumer; import net.minecraft.util.CsvOutput; import net.minecraft.util.Mth; import net.minecraft.util.ProgressListener; import net.minecraft.util.RandomSource; import net.minecraft.util.Util; import net.minecraft.util.datafix.DataFixTypes; import net.minecraft.util.debug.DebugSubscriptions; import net.minecraft.util.debug.LevelDebugSynchronizers; import net.minecraft.util.profiling.Profiler; import net.minecraft.util.profiling.ProfilerFiller; import net.minecraft.util.random.WeightedList; import net.minecraft.util.valueproviders.IntProvider; import net.minecraft.util.valueproviders.UniformInt; import net.minecraft.world.Difficulty; import net.minecraft.world.DifficultyInstance; import net.minecraft.world.RandomSequences; import net.minecraft.world.TickRateManager; import net.minecraft.world.attribute.EnvironmentAttributeSystem; import net.minecraft.world.attribute.EnvironmentAttributes; import net.minecraft.world.damagesource.DamageSource; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.EntitySpawnReason; import net.minecraft.world.entity.EntityType; import net.minecraft.world.entity.LightningBolt; import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.Mob; import net.minecraft.world.entity.MobCategory; import net.minecraft.world.entity.ReputationEventHandler; import net.minecraft.world.entity.ai.navigation.PathNavigation; import net.minecraft.world.entity.ai.village.ReputationEventType; import net.minecraft.world.entity.ai.village.poi.PoiManager; import net.minecraft.world.entity.ai.village.poi.PoiRecord; import net.minecraft.world.entity.ai.village.poi.PoiType; import net.minecraft.world.entity.ai.village.poi.PoiTypes; import net.minecraft.world.entity.animal.horse.SkeletonHorse; import net.minecraft.world.entity.boss.EnderDragonPart; import net.minecraft.world.entity.boss.enderdragon.EnderDragon; import net.minecraft.world.entity.player.Player; import net.minecraft.world.entity.raid.Raid; import net.minecraft.world.entity.raid.Raids; import net.minecraft.world.flag.FeatureFlagSet; import net.minecraft.world.item.alchemy.PotionBrewing; import net.minecraft.world.item.crafting.RecipeManager; import net.minecraft.world.level.BlockEventData; import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.CustomSpawner; import net.minecraft.world.level.Explosion; import net.minecraft.world.level.ExplosionDamageCalculator; import net.minecraft.world.level.Level; import net.minecraft.world.level.MoonPhase; import net.minecraft.world.level.NaturalSpawner; import net.minecraft.world.level.ServerExplosion; import net.minecraft.world.level.StructureManager; import net.minecraft.world.level.WorldGenLevel; 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.SnowLayerBlock; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.entity.FuelValues; import net.minecraft.world.level.block.entity.TickingBlockEntity; 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.ChunkGenerator; import net.minecraft.world.level.chunk.LevelChunk; import net.minecraft.world.level.chunk.LevelChunkSection; import net.minecraft.world.level.chunk.status.ChunkStatus; import net.minecraft.world.level.chunk.storage.EntityStorage; import net.minecraft.world.level.chunk.storage.RegionStorageInfo; import net.minecraft.world.level.chunk.storage.SimpleRegionStorage; import net.minecraft.world.level.dimension.BuiltinDimensionTypes; import net.minecraft.world.level.dimension.DimensionType; import net.minecraft.world.level.dimension.LevelStem; import net.minecraft.world.level.dimension.end.EndDragonFight; import net.minecraft.world.level.entity.EntityTickList; import net.minecraft.world.level.entity.EntityTypeTest; import net.minecraft.world.level.entity.LevelCallback; import net.minecraft.world.level.entity.LevelEntityGetter; import net.minecraft.world.level.entity.PersistentEntitySectionManager; import net.minecraft.world.level.gameevent.DynamicGameEventListener; import net.minecraft.world.level.gameevent.GameEvent; import net.minecraft.world.level.gameevent.GameEventDispatcher; import net.minecraft.world.level.gamerules.GameRule; import net.minecraft.world.level.gamerules.GameRules; import net.minecraft.world.level.levelgen.Heightmap; import net.minecraft.world.level.levelgen.structure.BoundingBox; import net.minecraft.world.level.levelgen.structure.Structure; import net.minecraft.world.level.levelgen.structure.StructureCheck; import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager; import net.minecraft.world.level.material.Fluid; import net.minecraft.world.level.material.FluidState; import net.minecraft.world.level.pathfinder.PathTypeCache; import net.minecraft.world.level.portal.PortalForcer; import net.minecraft.world.level.redstone.ExperimentalRedstoneUtils; import net.minecraft.world.level.redstone.Orientation; import net.minecraft.world.level.saveddata.maps.MapId; import net.minecraft.world.level.saveddata.maps.MapIndex; import net.minecraft.world.level.saveddata.maps.MapItemSavedData; import net.minecraft.world.level.storage.DimensionDataStorage; import net.minecraft.world.level.storage.LevelData; import net.minecraft.world.level.storage.LevelStorageSource; import net.minecraft.world.level.storage.ServerLevelData; import net.minecraft.world.phys.AABB; import net.minecraft.world.phys.Vec3; import net.minecraft.world.phys.shapes.BooleanOp; import net.minecraft.world.phys.shapes.Shapes; import net.minecraft.world.phys.shapes.VoxelShape; import net.minecraft.world.ticks.LevelTicks; import net.minecraft.world.waypoints.WaypointTransmitter; import org.jspecify.annotations.Nullable; import org.slf4j.Logger; public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGetter { public static final BlockPos END_SPAWN_POINT = new BlockPos(100, 50, 0); public static final IntProvider RAIN_DELAY = UniformInt.of(12000, 180000); public static final IntProvider RAIN_DURATION = UniformInt.of(12000, 24000); private static final IntProvider THUNDER_DELAY = UniformInt.of(12000, 180000); public static final IntProvider THUNDER_DURATION = UniformInt.of(3600, 15600); private static final Logger LOGGER = LogUtils.getLogger(); private static final int EMPTY_TIME_NO_TICK = 300; private static final int MAX_SCHEDULED_TICKS_PER_TICK = 65536; private final List players = Lists.newArrayList(); private final ServerChunkCache chunkSource; private final MinecraftServer server; private final ServerLevelData serverLevelData; private final EntityTickList entityTickList = new EntityTickList(); private final ServerWaypointManager waypointManager; private final EnvironmentAttributeSystem environmentAttributes; private final PersistentEntitySectionManager entityManager; private final GameEventDispatcher gameEventDispatcher; public boolean noSave; private final SleepStatus sleepStatus; private int emptyTime; private final PortalForcer portalForcer; private final LevelTicks blockTicks = new LevelTicks(this::isPositionTickingWithEntitiesLoaded); private final LevelTicks fluidTicks = new LevelTicks(this::isPositionTickingWithEntitiesLoaded); private final PathTypeCache pathTypesByPosCache = new PathTypeCache(); private final Set navigatingMobs = new ObjectOpenHashSet(); private volatile boolean isUpdatingNavigations; protected final Raids raids; private final ObjectLinkedOpenHashSet blockEvents = new ObjectLinkedOpenHashSet(); private final List blockEventsToReschedule = new ArrayList(64); private boolean handlingTick; private final List customSpawners; private @Nullable EndDragonFight dragonFight; private final Int2ObjectMap dragonParts = new Int2ObjectOpenHashMap(); private final StructureManager structureManager; private final StructureCheck structureCheck; private final boolean tickTime; private final RandomSequences randomSequences; private final LevelDebugSynchronizers debugSynchronizers = new LevelDebugSynchronizers(this); public ServerLevel(MinecraftServer server, Executor executor, LevelStorageSource.LevelStorageAccess levelStorage, ServerLevelData levelData, ResourceKey dimension, LevelStem levelStem, boolean isDebug, long biomeZoomSeed, List customSpawners, boolean tickTime, @Nullable RandomSequences randomSequences) { super(levelData, dimension, server.registryAccess(), levelStem.type(), false, isDebug, biomeZoomSeed, server.getMaxChainedNeighborUpdates()); this.tickTime = tickTime; this.server = server; this.customSpawners = customSpawners; this.serverLevelData = levelData; ChunkGenerator generator = levelStem.generator(); boolean syncWrites = server.forceSynchronousWrites(); DataFixer fixerUpper = server.getFixerUpper(); EntityStorage entityStorage = new EntityStorage(new SimpleRegionStorage(new RegionStorageInfo(levelStorage.getLevelId(), dimension, "entities"), levelStorage.getDimensionPath(dimension).resolve("entities"), fixerUpper, syncWrites, DataFixTypes.ENTITY_CHUNK), this, server); this.entityManager = new PersistentEntitySectionManager(Entity.class, new EntityCallbacks(), entityStorage); this.chunkSource = new ServerChunkCache(this, levelStorage, fixerUpper, server.getStructureManager(), executor, generator, server.getPlayerList().getViewDistance(), server.getPlayerList().getSimulationDistance(), syncWrites, this.entityManager::updateChunkStatus, () -> server.overworld().getDataStorage()); this.chunkSource.getGeneratorState().ensureStructuresGenerated(); this.portalForcer = new PortalForcer(this); this.prepareWeather(); this.raids = this.getDataStorage().computeIfAbsent(Raids.getType(this.dimensionTypeRegistration())); if (!server.isSingleplayer()) { levelData.setGameType(server.getDefaultGameType()); } long seed = server.getWorldData().worldGenOptions().seed(); this.structureCheck = new StructureCheck(this.chunkSource.chunkScanner(), this.registryAccess(), server.getStructureManager(), dimension, generator, this.chunkSource.randomState(), this, generator.getBiomeSource(), seed, fixerUpper); this.structureManager = new StructureManager(this, server.getWorldData().worldGenOptions(), this.structureCheck); this.dragonFight = this.dimension() == Level.END && this.dimensionTypeRegistration().is(BuiltinDimensionTypes.END) ? new EndDragonFight(this, seed, server.getWorldData().endDragonFightData()) : null; this.sleepStatus = new SleepStatus(); this.gameEventDispatcher = new GameEventDispatcher(this); this.randomSequences = Objects.requireNonNullElseGet(randomSequences, () -> this.getDataStorage().computeIfAbsent(RandomSequences.TYPE)); this.waypointManager = new ServerWaypointManager(); this.environmentAttributes = EnvironmentAttributeSystem.builder().addDefaultLayers(this).build(); this.updateSkyBrightness(); } @Deprecated @VisibleForTesting public void setDragonFight(@Nullable EndDragonFight fight) { this.dragonFight = fight; } public void setWeatherParameters(int clearTime, int rainTime, boolean raining, boolean thundering) { this.serverLevelData.setClearWeatherTime(clearTime); this.serverLevelData.setRainTime(rainTime); this.serverLevelData.setThunderTime(rainTime); this.serverLevelData.setRaining(raining); this.serverLevelData.setThundering(thundering); } @Override public Holder getUncachedNoiseBiome(int quartX, int quartY, int quartZ) { return this.getChunkSource().getGenerator().getBiomeSource().getNoiseBiome(quartX, quartY, quartZ, this.getChunkSource().randomState().sampler()); } public StructureManager structureManager() { return this.structureManager; } @Override public EnvironmentAttributeSystem environmentAttributes() { return this.environmentAttributes; } public void tick(BooleanSupplier haveTime) { int percentage; ProfilerFiller profiler = Profiler.get(); this.handlingTick = true; TickRateManager tickRateManager = this.tickRateManager(); boolean runs = tickRateManager.runsNormally(); if (runs) { profiler.push("world border"); this.getWorldBorder().tick(); profiler.popPush("weather"); this.advanceWeatherCycle(); profiler.pop(); } if (this.sleepStatus.areEnoughSleeping(percentage = this.getGameRules().get(GameRules.PLAYERS_SLEEPING_PERCENTAGE).intValue()) && this.sleepStatus.areEnoughDeepSleeping(percentage, this.players)) { if (this.getGameRules().get(GameRules.ADVANCE_TIME).booleanValue()) { long newTime = this.levelData.getDayTime() + 24000L; this.setDayTime(newTime - newTime % 24000L); } this.wakeUpAllPlayers(); if (this.getGameRules().get(GameRules.ADVANCE_WEATHER).booleanValue() && this.isRaining()) { this.resetWeatherCycle(); } } this.updateSkyBrightness(); if (runs) { this.tickTime(); } profiler.push("tickPending"); if (!this.isDebug() && runs) { long tick = this.getGameTime(); profiler.push("blockTicks"); this.blockTicks.tick(tick, 65536, this::tickBlock); profiler.popPush("fluidTicks"); this.fluidTicks.tick(tick, 65536, this::tickFluid); profiler.pop(); } profiler.popPush("raid"); if (runs) { this.raids.tick(this); } profiler.popPush("chunkSource"); this.getChunkSource().tick(haveTime, true); profiler.popPush("blockEvents"); if (runs) { this.runBlockEvents(); } this.handlingTick = false; profiler.pop(); boolean isActive = this.chunkSource.hasActiveTickets(); if (isActive) { this.resetEmptyTime(); } if (runs) { ++this.emptyTime; } if (this.emptyTime < 300) { profiler.push("entities"); if (this.dragonFight != null && runs) { profiler.push("dragonFight"); this.dragonFight.tick(); profiler.pop(); } this.entityTickList.forEach(entity -> { if (entity.isRemoved()) { return; } if (tickRateManager.isEntityFrozen((Entity)entity)) { return; } profiler.push("checkDespawn"); entity.checkDespawn(); profiler.pop(); if (!(entity instanceof ServerPlayer) && !this.chunkSource.chunkMap.getDistanceManager().inEntityTickingRange(entity.chunkPosition().toLong())) { return; } Entity vehicle = entity.getVehicle(); if (vehicle != null) { if (vehicle.isRemoved() || !vehicle.hasPassenger((Entity)entity)) { entity.stopRiding(); } else { return; } } profiler.push("tick"); this.guardEntityTick(this::tickNonPassenger, entity); profiler.pop(); }); profiler.popPush("blockEntities"); this.tickBlockEntities(); profiler.pop(); } profiler.push("entityManagement"); this.entityManager.tick(); profiler.pop(); profiler.push("debugSynchronizers"); if (this.debugSynchronizers.hasAnySubscriberFor(DebugSubscriptions.NEIGHBOR_UPDATES)) { this.neighborUpdater.setDebugListener(blockPos -> this.debugSynchronizers.broadcastEventToTracking((BlockPos)blockPos, DebugSubscriptions.NEIGHBOR_UPDATES, blockPos)); } else { this.neighborUpdater.setDebugListener(null); } this.debugSynchronizers.tick(this.server.debugSubscribers()); profiler.pop(); this.environmentAttributes().invalidateTickCache(); } @Override public boolean shouldTickBlocksAt(long chunkPos) { return this.chunkSource.chunkMap.getDistanceManager().inBlockTickingRange(chunkPos); } protected void tickTime() { if (!this.tickTime) { return; } long time = this.levelData.getGameTime() + 1L; this.serverLevelData.setGameTime(time); Profiler.get().push("scheduledFunctions"); this.serverLevelData.getScheduledEvents().tick(this.server, time); Profiler.get().pop(); if (this.getGameRules().get(GameRules.ADVANCE_TIME).booleanValue()) { this.setDayTime(this.levelData.getDayTime() + 1L); } } public void setDayTime(long newTime) { this.serverLevelData.setDayTime(newTime); } public long getDayCount() { return this.getDayTime() / 24000L; } public void tickCustomSpawners(boolean spawnEnemies) { for (CustomSpawner spawner : this.customSpawners) { spawner.tick(this, spawnEnemies); } } private void wakeUpAllPlayers() { this.sleepStatus.removeAllSleepers(); this.players.stream().filter(LivingEntity::isSleeping).collect(Collectors.toList()).forEach(player -> player.stopSleepInBed(false, false)); } public void tickChunk(LevelChunk chunk, int tickSpeed) { ChunkPos chunkPos = chunk.getPos(); int minX = chunkPos.getMinBlockX(); int minZ = chunkPos.getMinBlockZ(); ProfilerFiller profiler = Profiler.get(); profiler.push("iceandsnow"); for (int i = 0; i < tickSpeed; ++i) { if (this.random.nextInt(48) != 0) continue; this.tickPrecipitation(this.getBlockRandomPos(minX, 0, minZ, 15)); } profiler.popPush("tickBlocks"); if (tickSpeed > 0) { LevelChunkSection[] sections = chunk.getSections(); for (int sectionIndex = 0; sectionIndex < sections.length; ++sectionIndex) { LevelChunkSection section = sections[sectionIndex]; if (!section.isRandomlyTicking()) continue; int sectionY = chunk.getSectionYFromSectionIndex(sectionIndex); int minYInSection = SectionPos.sectionToBlockCoord(sectionY); for (int i = 0; i < tickSpeed; ++i) { FluidState fluidState; BlockPos pos = this.getBlockRandomPos(minX, minYInSection, minZ, 15); profiler.push("randomTick"); BlockState blockState = section.getBlockState(pos.getX() - minX, pos.getY() - minYInSection, pos.getZ() - minZ); if (blockState.isRandomlyTicking()) { blockState.randomTick(this, pos, this.random); } if ((fluidState = blockState.getFluidState()).isRandomlyTicking()) { fluidState.randomTick(this, pos, this.random); } profiler.pop(); } } } profiler.pop(); } public void tickThunder(LevelChunk chunk) { BlockPos pos; ChunkPos chunkPos = chunk.getPos(); boolean raining = this.isRaining(); int minX = chunkPos.getMinBlockX(); int minZ = chunkPos.getMinBlockZ(); ProfilerFiller profiler = Profiler.get(); profiler.push("thunder"); if (raining && this.isThundering() && this.random.nextInt(100000) == 0 && this.isRainingAt(pos = this.findLightningTargetAround(this.getBlockRandomPos(minX, 0, minZ, 15)))) { LightningBolt bolt; SkeletonHorse horse; boolean isTrap; DifficultyInstance difficulty = this.getCurrentDifficultyAt(pos); boolean bl = isTrap = this.getGameRules().get(GameRules.SPAWN_MOBS) != false && this.random.nextDouble() < (double)difficulty.getEffectiveDifficulty() * 0.01 && !this.getBlockState(pos.below()).is(BlockTags.LIGHTNING_RODS); if (isTrap && (horse = EntityType.SKELETON_HORSE.create(this, EntitySpawnReason.EVENT)) != null) { horse.setTrap(true); horse.setAge(0); horse.setPos(pos.getX(), pos.getY(), pos.getZ()); this.addFreshEntity(horse); } if ((bolt = EntityType.LIGHTNING_BOLT.create(this, EntitySpawnReason.EVENT)) != null) { bolt.snapTo(Vec3.atBottomCenterOf(pos)); bolt.setVisualOnly(isTrap); this.addFreshEntity(bolt); } } profiler.pop(); } @VisibleForTesting public void tickPrecipitation(BlockPos pos) { BlockPos topPos = this.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, pos); BlockPos belowPos = topPos.below(); Biome biome = this.getBiome(topPos).value(); if (biome.shouldFreeze(this, belowPos)) { this.setBlockAndUpdate(belowPos, Blocks.ICE.defaultBlockState()); } if (this.isRaining()) { Biome.Precipitation precipitation; int maxHeight = this.getGameRules().get(GameRules.MAX_SNOW_ACCUMULATION_HEIGHT); if (maxHeight > 0 && biome.shouldSnow(this, topPos)) { BlockState state = this.getBlockState(topPos); if (state.is(Blocks.SNOW)) { int currentLayers = state.getValue(SnowLayerBlock.LAYERS); if (currentLayers < Math.min(maxHeight, 8)) { BlockState newState = (BlockState)state.setValue(SnowLayerBlock.LAYERS, currentLayers + 1); Block.pushEntitiesUp(state, newState, this, topPos); this.setBlockAndUpdate(topPos, newState); } } else { this.setBlockAndUpdate(topPos, Blocks.SNOW.defaultBlockState()); } } if ((precipitation = biome.getPrecipitationAt(belowPos, this.getSeaLevel())) != Biome.Precipitation.NONE) { BlockState belowState = this.getBlockState(belowPos); belowState.getBlock().handlePrecipitation(belowState, this, belowPos, precipitation); } } } private Optional findLightningRod(BlockPos center) { Optional nearbyLightningRod = this.getPoiManager().findClosest(p -> p.is(PoiTypes.LIGHTNING_ROD), lightningRodPos -> lightningRodPos.getY() == this.getHeight(Heightmap.Types.WORLD_SURFACE, lightningRodPos.getX(), lightningRodPos.getZ()) - 1, center, 128, PoiManager.Occupancy.ANY); return nearbyLightningRod.map(blockPos -> blockPos.above(1)); } protected BlockPos findLightningTargetAround(BlockPos pos) { BlockPos center = this.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, pos); Optional lightningRodTarget = this.findLightningRod(center); if (lightningRodTarget.isPresent()) { return lightningRodTarget.get(); } AABB search = AABB.encapsulatingFullBlocks(center, center.atY(this.getMaxY() + 1)).inflate(3.0); List entities = this.getEntitiesOfClass(LivingEntity.class, search, input -> input.isAlive() && this.canSeeSky(input.blockPosition())); if (!entities.isEmpty()) { return entities.get(this.random.nextInt(entities.size())).blockPosition(); } if (center.getY() == this.getMinY() - 1) { center = center.above(2); } return center; } public boolean isHandlingTick() { return this.handlingTick; } public boolean canSleepThroughNights() { return this.getGameRules().get(GameRules.PLAYERS_SLEEPING_PERCENTAGE) <= 100; } private void announceSleepStatus() { if (!this.canSleepThroughNights()) { return; } if (this.getServer().isSingleplayer() && !this.getServer().isPublished()) { return; } int percentage = this.getGameRules().get(GameRules.PLAYERS_SLEEPING_PERCENTAGE); MutableComponent message = this.sleepStatus.areEnoughSleeping(percentage) ? Component.translatable("sleep.skipping_night") : Component.translatable("sleep.players_sleeping", this.sleepStatus.amountSleeping(), this.sleepStatus.sleepersNeeded(percentage)); for (ServerPlayer player : this.players) { player.displayClientMessage(message, true); } } public void updateSleepingPlayerList() { if (!this.players.isEmpty() && this.sleepStatus.update(this.players)) { this.announceSleepStatus(); } } @Override public ServerScoreboard getScoreboard() { return this.server.getScoreboard(); } public ServerWaypointManager getWaypointManager() { return this.waypointManager; } @Override public DifficultyInstance getCurrentDifficultyAt(BlockPos pos) { long localTime = 0L; float moonBrightness = 0.0f; ChunkAccess chunk = this.getChunk(SectionPos.blockToSectionCoord(pos.getX()), SectionPos.blockToSectionCoord(pos.getZ()), ChunkStatus.FULL, false); if (chunk != null) { localTime = chunk.getInhabitedTime(); moonBrightness = this.getMoonBrightness(pos); } return new DifficultyInstance(this.getDifficulty(), this.getDayTime(), localTime, moonBrightness); } public float getMoonBrightness(BlockPos pos) { MoonPhase moonPhase = this.environmentAttributes.getValue(EnvironmentAttributes.MOON_PHASE, pos); return DimensionType.MOON_BRIGHTNESS_PER_PHASE[moonPhase.index()]; } private void advanceWeatherCycle() { boolean wasRaining = this.isRaining(); if (this.canHaveWeather()) { if (this.getGameRules().get(GameRules.ADVANCE_WEATHER).booleanValue()) { int clearWeatherTime = this.serverLevelData.getClearWeatherTime(); int thunderTime = this.serverLevelData.getThunderTime(); int rainTime = this.serverLevelData.getRainTime(); boolean thundering = this.levelData.isThundering(); boolean raining = this.levelData.isRaining(); if (clearWeatherTime > 0) { --clearWeatherTime; thunderTime = thundering ? 0 : 1; rainTime = raining ? 0 : 1; thundering = false; raining = false; } else { if (thunderTime > 0) { if (--thunderTime == 0) { thundering = !thundering; } } else { thunderTime = thundering ? THUNDER_DURATION.sample(this.random) : THUNDER_DELAY.sample(this.random); } if (rainTime > 0) { if (--rainTime == 0) { raining = !raining; } } else { rainTime = raining ? RAIN_DURATION.sample(this.random) : RAIN_DELAY.sample(this.random); } } this.serverLevelData.setThunderTime(thunderTime); this.serverLevelData.setRainTime(rainTime); this.serverLevelData.setClearWeatherTime(clearWeatherTime); this.serverLevelData.setThundering(thundering); this.serverLevelData.setRaining(raining); } this.oThunderLevel = this.thunderLevel; this.thunderLevel = this.levelData.isThundering() ? (this.thunderLevel += 0.01f) : (this.thunderLevel -= 0.01f); this.thunderLevel = Mth.clamp(this.thunderLevel, 0.0f, 1.0f); this.oRainLevel = this.rainLevel; this.rainLevel = this.levelData.isRaining() ? (this.rainLevel += 0.01f) : (this.rainLevel -= 0.01f); this.rainLevel = Mth.clamp(this.rainLevel, 0.0f, 1.0f); } if (this.oRainLevel != this.rainLevel) { this.server.getPlayerList().broadcastAll(new ClientboundGameEventPacket(ClientboundGameEventPacket.RAIN_LEVEL_CHANGE, this.rainLevel), this.dimension()); } if (this.oThunderLevel != this.thunderLevel) { this.server.getPlayerList().broadcastAll(new ClientboundGameEventPacket(ClientboundGameEventPacket.THUNDER_LEVEL_CHANGE, this.thunderLevel), this.dimension()); } if (wasRaining != this.isRaining()) { if (wasRaining) { this.server.getPlayerList().broadcastAll(new ClientboundGameEventPacket(ClientboundGameEventPacket.STOP_RAINING, 0.0f)); } else { this.server.getPlayerList().broadcastAll(new ClientboundGameEventPacket(ClientboundGameEventPacket.START_RAINING, 0.0f)); } this.server.getPlayerList().broadcastAll(new ClientboundGameEventPacket(ClientboundGameEventPacket.RAIN_LEVEL_CHANGE, this.rainLevel)); this.server.getPlayerList().broadcastAll(new ClientboundGameEventPacket(ClientboundGameEventPacket.THUNDER_LEVEL_CHANGE, this.thunderLevel)); } } @VisibleForTesting public void resetWeatherCycle() { this.serverLevelData.setRainTime(0); this.serverLevelData.setRaining(false); this.serverLevelData.setThunderTime(0); this.serverLevelData.setThundering(false); } public void resetEmptyTime() { this.emptyTime = 0; } private void tickFluid(BlockPos pos, Fluid type) { BlockState blockState = this.getBlockState(pos); FluidState fluidState = blockState.getFluidState(); if (fluidState.is(type)) { fluidState.tick(this, pos, blockState); } } private void tickBlock(BlockPos pos, Block type) { BlockState state = this.getBlockState(pos); if (state.is(type)) { state.tick(this, pos, this.random); } } public void tickNonPassenger(Entity entity) { entity.setOldPosAndRot(); ProfilerFiller profiler = Profiler.get(); ++entity.tickCount; profiler.push(() -> BuiltInRegistries.ENTITY_TYPE.getKey(entity.getType()).toString()); profiler.incrementCounter("tickNonPassenger"); entity.tick(); profiler.pop(); for (Entity passenger : entity.getPassengers()) { this.tickPassenger(entity, passenger); } } private void tickPassenger(Entity vehicle, Entity entity) { if (entity.isRemoved() || entity.getVehicle() != vehicle) { entity.stopRiding(); return; } if (!(entity instanceof Player) && !this.entityTickList.contains(entity)) { return; } entity.setOldPosAndRot(); ++entity.tickCount; ProfilerFiller profiler = Profiler.get(); profiler.push(() -> BuiltInRegistries.ENTITY_TYPE.getKey(entity.getType()).toString()); profiler.incrementCounter("tickPassenger"); entity.rideTick(); profiler.pop(); for (Entity passenger : entity.getPassengers()) { this.tickPassenger(entity, passenger); } } public void updateNeighboursOnBlockSet(BlockPos pos, BlockState oldState) { boolean blockChanged; BlockState blockState = this.getBlockState(pos); Block newBlock = blockState.getBlock(); boolean bl = blockChanged = !oldState.is(newBlock); if (blockChanged) { oldState.affectNeighborsAfterRemoval(this, pos, false); } this.updateNeighborsAt(pos, blockState.getBlock()); if (blockState.hasAnalogOutputSignal()) { this.updateNeighbourForOutputSignal(pos, newBlock); } } @Override public boolean mayInteract(Entity entity, BlockPos pos) { Player player; return !(entity instanceof Player) || !this.server.isUnderSpawnProtection(this, pos, player = (Player)entity) && this.getWorldBorder().isWithinBounds(pos); } public void save(@Nullable ProgressListener progressListener, boolean flush, boolean noSave) { ServerChunkCache chunkSource = this.getChunkSource(); if (noSave) { return; } if (progressListener != null) { progressListener.progressStartNoAbort(Component.translatable("menu.savingLevel")); } this.saveLevelData(flush); if (progressListener != null) { progressListener.progressStage(Component.translatable("menu.savingChunks")); } chunkSource.save(flush); if (flush) { this.entityManager.saveAll(); } else { this.entityManager.autoSave(); } } private void saveLevelData(boolean sync) { if (this.dragonFight != null) { this.server.getWorldData().setEndDragonFightData(this.dragonFight.saveData()); } DimensionDataStorage dataStorage = this.getChunkSource().getDataStorage(); if (sync) { dataStorage.saveAndJoin(); } else { dataStorage.scheduleSave(); } } public List getEntities(EntityTypeTest type, Predicate selector) { ArrayList result = Lists.newArrayList(); this.getEntities(type, selector, result); return result; } public void getEntities(EntityTypeTest type, Predicate selector, List result) { this.getEntities(type, selector, result, Integer.MAX_VALUE); } public void getEntities(EntityTypeTest type, Predicate selector, List result, int maxResults) { this.getEntities().get(type, entity -> { if (selector.test(entity)) { result.add((Object)entity); if (result.size() >= maxResults) { return AbortableIterationConsumer.Continuation.ABORT; } } return AbortableIterationConsumer.Continuation.CONTINUE; }); } public List getDragons() { return this.getEntities(EntityType.ENDER_DRAGON, LivingEntity::isAlive); } public List getPlayers(Predicate selector) { return this.getPlayers(selector, Integer.MAX_VALUE); } public List getPlayers(Predicate selector, int maxResults) { ArrayList result = Lists.newArrayList(); for (ServerPlayer player : this.players) { if (!selector.test(player)) continue; result.add(player); if (result.size() < maxResults) continue; return result; } return result; } public @Nullable ServerPlayer getRandomPlayer() { List players = this.getPlayers(LivingEntity::isAlive); if (players.isEmpty()) { return null; } return players.get(this.random.nextInt(players.size())); } @Override public boolean addFreshEntity(Entity entity) { return this.addEntity(entity); } public boolean addWithUUID(Entity entity) { return this.addEntity(entity); } public void addDuringTeleport(Entity entity) { if (entity instanceof ServerPlayer) { ServerPlayer player = (ServerPlayer)entity; this.addPlayer(player); } else { this.addEntity(entity); } } public void addNewPlayer(ServerPlayer player) { this.addPlayer(player); } public void addRespawnedPlayer(ServerPlayer player) { this.addPlayer(player); } private void addPlayer(ServerPlayer player) { Entity existing = this.getEntity(player.getUUID()); if (existing != null) { LOGGER.warn("Force-added player with duplicate UUID {}", (Object)player.getUUID()); existing.unRide(); this.removePlayerImmediately((ServerPlayer)existing, Entity.RemovalReason.DISCARDED); } this.entityManager.addNewEntity(player); } private boolean addEntity(Entity entity) { if (entity.isRemoved()) { LOGGER.warn("Tried to add entity {} but it was marked as removed already", (Object)EntityType.getKey(entity.getType())); return false; } return this.entityManager.addNewEntity(entity); } public boolean tryAddFreshEntityWithPassengers(Entity entity) { if (entity.getSelfAndPassengers().map(Entity::getUUID).anyMatch(this.entityManager::isLoaded)) { return false; } this.addFreshEntityWithPassengers(entity); return true; } public void unload(LevelChunk levelChunk) { levelChunk.clearAllBlockEntities(); levelChunk.unregisterTickContainerFromLevel(this); this.debugSynchronizers.dropChunk(levelChunk.getPos()); } public void removePlayerImmediately(ServerPlayer player, Entity.RemovalReason reason) { player.remove(reason); } @Override public void destroyBlockProgress(int id, BlockPos blockPos, int progress) { for (ServerPlayer player : this.server.getPlayerList().getPlayers()) { double zd; double yd; double xd; if (player.level() != this || player.getId() == id || !((xd = (double)blockPos.getX() - player.getX()) * xd + (yd = (double)blockPos.getY() - player.getY()) * yd + (zd = (double)blockPos.getZ() - player.getZ()) * zd < 1024.0)) continue; player.connection.send(new ClientboundBlockDestructionPacket(id, blockPos, progress)); } } @Override public void playSeededSound(@Nullable Entity except, double x, double y, double z, Holder sound, SoundSource source, float volume, float pitch, long seed) { Player player; this.server.getPlayerList().broadcast(except instanceof Player ? (player = (Player)except) : null, x, y, z, sound.value().getRange(volume), this.dimension(), new ClientboundSoundPacket(sound, source, x, y, z, volume, pitch, seed)); } @Override public void playSeededSound(@Nullable Entity except, Entity sourceEntity, Holder sound, SoundSource source, float volume, float pitch, long seed) { Player player; this.server.getPlayerList().broadcast(except instanceof Player ? (player = (Player)except) : null, sourceEntity.getX(), sourceEntity.getY(), sourceEntity.getZ(), sound.value().getRange(volume), this.dimension(), new ClientboundSoundEntityPacket(sound, source, sourceEntity, volume, pitch, seed)); } @Override public void globalLevelEvent(int type, BlockPos pos, int data) { if (this.getGameRules().get(GameRules.GLOBAL_SOUND_EVENTS).booleanValue()) { this.server.getPlayerList().getPlayers().forEach(player -> { Vec3 soundPos; if (player.level() == this) { Vec3 centerOfBlock = Vec3.atCenterOf(pos); if (player.distanceToSqr(centerOfBlock) < (double)Mth.square(32)) { soundPos = centerOfBlock; } else { Vec3 directionToEvent = centerOfBlock.subtract(player.position()).normalize(); soundPos = player.position().add(directionToEvent.scale(32.0)); } } else { soundPos = player.position(); } player.connection.send(new ClientboundLevelEventPacket(type, BlockPos.containing(soundPos), data, true)); }); } else { this.levelEvent(null, type, pos, data); } } @Override public void levelEvent(@Nullable Entity source, int type, BlockPos pos, int data) { Player player; this.server.getPlayerList().broadcast(source instanceof Player ? (player = (Player)source) : null, pos.getX(), pos.getY(), pos.getZ(), 64.0, this.dimension(), new ClientboundLevelEventPacket(type, pos, data, false)); } public int getLogicalHeight() { return this.dimensionType().logicalHeight(); } @Override public void gameEvent(Holder gameEvent, Vec3 position, GameEvent.Context context) { this.gameEventDispatcher.post(gameEvent, position, context); } /* * WARNING - Removed try catching itself - possible behaviour change. */ @Override public void sendBlockUpdated(BlockPos pos, BlockState old, BlockState current, int updateFlags) { if (this.isUpdatingNavigations) { String message = "recursive call to sendBlockUpdated"; Util.logAndPauseIfInIde("recursive call to sendBlockUpdated", new IllegalStateException("recursive call to sendBlockUpdated")); } this.getChunkSource().blockChanged(pos); this.pathTypesByPosCache.invalidate(pos); VoxelShape oldShape = old.getCollisionShape(this, pos); VoxelShape newShape = current.getCollisionShape(this, pos); if (!Shapes.joinIsNotEmpty(oldShape, newShape, BooleanOp.NOT_SAME)) { return; } ObjectArrayList navigationsToUpdate = new ObjectArrayList(); for (Mob navigatingMob : this.navigatingMobs) { PathNavigation pathNavigation = navigatingMob.getNavigation(); if (!pathNavigation.shouldRecomputePath(pos)) continue; navigationsToUpdate.add(pathNavigation); } try { this.isUpdatingNavigations = true; for (PathNavigation navigation : navigationsToUpdate) { navigation.recomputePath(); } } finally { this.isUpdatingNavigations = false; } } @Override public void updateNeighborsAt(BlockPos pos, Block sourceBlock) { this.updateNeighborsAt(pos, sourceBlock, ExperimentalRedstoneUtils.initialOrientation(this, null, null)); } @Override public void updateNeighborsAt(BlockPos pos, Block sourceBlock, @Nullable Orientation orientation) { this.neighborUpdater.updateNeighborsAtExceptFromFacing(pos, sourceBlock, null, orientation); } @Override public void updateNeighborsAtExceptFromFacing(BlockPos pos, Block blockObject, Direction skipDirection, @Nullable Orientation orientation) { this.neighborUpdater.updateNeighborsAtExceptFromFacing(pos, blockObject, skipDirection, orientation); } @Override public void neighborChanged(BlockPos pos, Block changedBlock, @Nullable Orientation orientation) { this.neighborUpdater.neighborChanged(pos, changedBlock, orientation); } @Override public void neighborChanged(BlockState state, BlockPos pos, Block changedBlock, @Nullable Orientation orientation, boolean movedByPiston) { this.neighborUpdater.neighborChanged(state, pos, changedBlock, orientation, movedByPiston); } @Override public void broadcastEntityEvent(Entity entity, byte event) { this.getChunkSource().sendToTrackingPlayersAndSelf(entity, new ClientboundEntityEventPacket(entity, event)); } @Override public void broadcastDamageEvent(Entity entity, DamageSource source) { this.getChunkSource().sendToTrackingPlayersAndSelf(entity, new ClientboundDamageEventPacket(entity, source)); } @Override public ServerChunkCache getChunkSource() { return this.chunkSource; } @Override public void explode(@Nullable Entity source, @Nullable DamageSource damageSource, @Nullable ExplosionDamageCalculator damageCalculator, double x, double y, double z, float r, boolean fire, Level.ExplosionInteraction interactionType, ParticleOptions smallExplosionParticles, ParticleOptions largeExplosionParticles, WeightedList blockParticles, Holder explosionSound) { Explosion.BlockInteraction blockInteraction = switch (interactionType) { default -> throw new MatchException(null, null); case Level.ExplosionInteraction.NONE -> Explosion.BlockInteraction.KEEP; case Level.ExplosionInteraction.BLOCK -> this.getDestroyType(GameRules.BLOCK_EXPLOSION_DROP_DECAY); case Level.ExplosionInteraction.MOB -> { if (this.getGameRules().get(GameRules.MOB_GRIEFING).booleanValue()) { yield this.getDestroyType(GameRules.MOB_EXPLOSION_DROP_DECAY); } yield Explosion.BlockInteraction.KEEP; } case Level.ExplosionInteraction.TNT -> this.getDestroyType(GameRules.TNT_EXPLOSION_DROP_DECAY); case Level.ExplosionInteraction.TRIGGER -> Explosion.BlockInteraction.TRIGGER_BLOCK; }; Vec3 center = new Vec3(x, y, z); ServerExplosion explosion = new ServerExplosion(this, source, damageSource, damageCalculator, center, r, fire, blockInteraction); int blockCount = explosion.explode(); ParticleOptions explosionParticle = explosion.isSmall() ? smallExplosionParticles : largeExplosionParticles; for (ServerPlayer player : this.players) { if (!(player.distanceToSqr(center) < 4096.0)) continue; Optional playerKnockback = Optional.ofNullable(explosion.getHitPlayers().get(player)); player.connection.send(new ClientboundExplodePacket(center, r, blockCount, playerKnockback, explosionParticle, explosionSound, blockParticles)); } } private Explosion.BlockInteraction getDestroyType(GameRule gameRule) { return this.getGameRules().get(gameRule) != false ? Explosion.BlockInteraction.DESTROY_WITH_DECAY : Explosion.BlockInteraction.DESTROY; } @Override public void blockEvent(BlockPos pos, Block block, int b0, int b1) { this.blockEvents.add((Object)new BlockEventData(pos, block, b0, b1)); } private void runBlockEvents() { this.blockEventsToReschedule.clear(); while (!this.blockEvents.isEmpty()) { BlockEventData eventData = (BlockEventData)this.blockEvents.removeFirst(); if (this.shouldTickBlocksAt(eventData.pos())) { if (!this.doBlockEvent(eventData)) continue; this.server.getPlayerList().broadcast(null, eventData.pos().getX(), eventData.pos().getY(), eventData.pos().getZ(), 64.0, this.dimension(), new ClientboundBlockEventPacket(eventData.pos(), eventData.block(), eventData.paramA(), eventData.paramB())); continue; } this.blockEventsToReschedule.add(eventData); } this.blockEvents.addAll(this.blockEventsToReschedule); } private boolean doBlockEvent(BlockEventData eventData) { BlockState state = this.getBlockState(eventData.pos()); if (state.is(eventData.block())) { return state.triggerEvent(this, eventData.pos(), eventData.paramA(), eventData.paramB()); } return false; } public LevelTicks getBlockTicks() { return this.blockTicks; } public LevelTicks getFluidTicks() { return this.fluidTicks; } @Override public MinecraftServer getServer() { return this.server; } public PortalForcer getPortalForcer() { return this.portalForcer; } public StructureTemplateManager getStructureManager() { return this.server.getStructureManager(); } public int sendParticles(T particle, double x, double y, double z, int count, double xDist, double yDist, double zDist, double speed) { return this.sendParticles(particle, false, false, x, y, z, count, xDist, yDist, zDist, speed); } public int sendParticles(T particle, boolean overrideLimiter, boolean alwaysShow, double x, double y, double z, int count, double xDist, double yDist, double zDist, double speed) { ClientboundLevelParticlesPacket packet = new ClientboundLevelParticlesPacket(particle, overrideLimiter, alwaysShow, x, y, z, (float)xDist, (float)yDist, (float)zDist, (float)speed, count); int result = 0; for (int i = 0; i < this.players.size(); ++i) { ServerPlayer player = this.players.get(i); if (!this.sendParticles(player, overrideLimiter, x, y, z, packet)) continue; ++result; } return result; } public boolean sendParticles(ServerPlayer player, T particle, boolean overrideLimiter, boolean alwaysShow, double x, double y, double z, int count, double xDist, double yDist, double zDist, double speed) { ClientboundLevelParticlesPacket packet = new ClientboundLevelParticlesPacket(particle, overrideLimiter, alwaysShow, x, y, z, (float)xDist, (float)yDist, (float)zDist, (float)speed, count); return this.sendParticles(player, overrideLimiter, x, y, z, packet); } private boolean sendParticles(ServerPlayer player, boolean overrideLimiter, double x, double y, double z, Packet packet) { if (player.level() != this) { return false; } BlockPos pos = player.blockPosition(); if (pos.closerToCenterThan(new Vec3(x, y, z), overrideLimiter ? 512.0 : 32.0)) { player.connection.send(packet); return true; } return false; } @Override public @Nullable Entity getEntity(int id) { return this.getEntities().get(id); } @Override public @Nullable Entity getEntityInAnyDimension(UUID uuid) { Entity entity = this.getEntity(uuid); if (entity != null) { return entity; } for (ServerLevel otherLevel : this.getServer().getAllLevels()) { Entity otherEntity; if (otherLevel == this || (otherEntity = otherLevel.getEntity(uuid)) == null) continue; return otherEntity; } return null; } @Override public @Nullable Player getPlayerInAnyDimension(UUID uuid) { return this.getServer().getPlayerList().getPlayer(uuid); } @Deprecated public @Nullable Entity getEntityOrPart(int id) { Entity entity = this.getEntities().get(id); if (entity != null) { return entity; } return (Entity)this.dragonParts.get(id); } @Override public Collection dragonParts() { return this.dragonParts.values(); } public @Nullable BlockPos findNearestMapStructure(TagKey structureTag, BlockPos origin, int maxSearchRadius, boolean createReference) { if (!this.server.getWorldData().worldGenOptions().generateStructures()) { return null; } Optional tag = this.registryAccess().lookupOrThrow(Registries.STRUCTURE).get(structureTag); if (tag.isEmpty()) { return null; } Pair> result = this.getChunkSource().getGenerator().findNearestMapStructure(this, (HolderSet)tag.get(), origin, maxSearchRadius, createReference); return result != null ? (BlockPos)result.getFirst() : null; } public @Nullable Pair> findClosestBiome3d(Predicate> biomeTest, BlockPos origin, int maxSearchRadius, int sampleResolutionHorizontal, int sampleResolutionVertical) { return this.getChunkSource().getGenerator().getBiomeSource().findClosestBiome3d(origin, maxSearchRadius, sampleResolutionHorizontal, sampleResolutionVertical, biomeTest, this.getChunkSource().randomState().sampler(), this); } @Override public WorldBorder getWorldBorder() { WorldBorder worldBorder = this.getDataStorage().computeIfAbsent(WorldBorder.TYPE); worldBorder.applyInitialSettings(this.levelData.getGameTime()); return worldBorder; } @Override public RecipeManager recipeAccess() { return this.server.getRecipeManager(); } @Override public TickRateManager tickRateManager() { return this.server.tickRateManager(); } @Override public boolean noSave() { return this.noSave; } public DimensionDataStorage getDataStorage() { return this.getChunkSource().getDataStorage(); } @Override public @Nullable MapItemSavedData getMapData(MapId id) { return this.getServer().overworld().getDataStorage().get(MapItemSavedData.type(id)); } public void setMapData(MapId id, MapItemSavedData data) { this.getServer().overworld().getDataStorage().set(MapItemSavedData.type(id), data); } public MapId getFreeMapId() { return this.getServer().overworld().getDataStorage().computeIfAbsent(MapIndex.TYPE).getNextMapId(); } @Override public void setRespawnData(LevelData.RespawnData respawnData) { this.getServer().setRespawnData(respawnData); } @Override public LevelData.RespawnData getRespawnData() { return this.getServer().getRespawnData(); } public LongSet getForceLoadedChunks() { return this.chunkSource.getForceLoadedChunks(); } public boolean setChunkForced(int chunkX, int chunkZ, boolean forced) { boolean updated = this.chunkSource.updateChunkForced(new ChunkPos(chunkX, chunkZ), forced); if (forced && updated) { this.getChunk(chunkX, chunkZ); } return updated; } public List players() { return this.players; } @Override public void updatePOIOnBlockStateChange(BlockPos pos, BlockState oldState, BlockState newState) { Optional> newType; Optional> oldType = PoiTypes.forState(oldState); if (Objects.equals(oldType, newType = PoiTypes.forState(newState))) { return; } BlockPos immutable = pos.immutable(); oldType.ifPresent(poiType -> this.getServer().execute(() -> { this.getPoiManager().remove(immutable); this.debugSynchronizers.dropPoi(immutable); })); newType.ifPresent(poiType -> this.getServer().execute(() -> { PoiRecord record = this.getPoiManager().add(immutable, (Holder)poiType); if (record != null) { this.debugSynchronizers.registerPoi(record); } })); } public PoiManager getPoiManager() { return this.getChunkSource().getPoiManager(); } public boolean isVillage(BlockPos pos) { return this.isCloseToVillage(pos, 1); } public boolean isVillage(SectionPos sectionPos) { return this.isVillage(sectionPos.center()); } public boolean isCloseToVillage(BlockPos pos, int sectionDistance) { if (sectionDistance > 6) { return false; } return this.sectionsToVillage(SectionPos.of(pos)) <= sectionDistance; } public int sectionsToVillage(SectionPos pos) { return this.getPoiManager().sectionsToVillage(pos); } public Raids getRaids() { return this.raids; } public @Nullable Raid getRaidAt(BlockPos pos) { return this.raids.getNearbyRaid(pos, 9216); } public boolean isRaided(BlockPos pos) { return this.getRaidAt(pos) != null; } public void onReputationEvent(ReputationEventType type, Entity source, ReputationEventHandler target) { target.onReputationEventFrom(type, source); } public void saveDebugReport(Path rootDir) throws IOException { ChunkMap chunkMap = this.getChunkSource().chunkMap; try (BufferedWriter output = Files.newBufferedWriter(rootDir.resolve("stats.txt"), new OpenOption[0]);){ output.write(String.format(Locale.ROOT, "spawning_chunks: %d\n", chunkMap.getDistanceManager().getNaturalSpawnChunkCount())); NaturalSpawner.SpawnState lastSpawnState = this.getChunkSource().getLastSpawnState(); if (lastSpawnState != null) { for (Object2IntMap.Entry entry : lastSpawnState.getMobCategoryCounts().object2IntEntrySet()) { output.write(String.format(Locale.ROOT, "spawn_count.%s: %d\n", ((MobCategory)entry.getKey()).getName(), entry.getIntValue())); } } output.write(String.format(Locale.ROOT, "entities: %s\n", this.entityManager.gatherStats())); output.write(String.format(Locale.ROOT, "block_entity_tickers: %d\n", this.blockEntityTickers.size())); output.write(String.format(Locale.ROOT, "block_ticks: %d\n", ((LevelTicks)this.getBlockTicks()).count())); output.write(String.format(Locale.ROOT, "fluid_ticks: %d\n", ((LevelTicks)this.getFluidTicks()).count())); output.write("distance_manager: " + chunkMap.getDistanceManager().getDebugStatus() + "\n"); output.write(String.format(Locale.ROOT, "pending_tasks: %d\n", this.getChunkSource().getPendingTasksCount())); } CrashReport test = new CrashReport("Level dump", new Exception("dummy")); this.fillReportDetails(test); try (BufferedWriter output = Files.newBufferedWriter(rootDir.resolve("example_crash.txt"), new OpenOption[0]);){ output.write(test.getFriendlyReport(ReportType.TEST)); } Path chunks = rootDir.resolve("chunks.csv"); try (BufferedWriter output = Files.newBufferedWriter(chunks, new OpenOption[0]);){ chunkMap.dumpChunks(output); } Path entityChunks = rootDir.resolve("entity_chunks.csv"); try (BufferedWriter output = Files.newBufferedWriter(entityChunks, new OpenOption[0]);){ this.entityManager.dumpSections(output); } Path entities = rootDir.resolve("entities.csv"); try (BufferedWriter output = Files.newBufferedWriter(entities, new OpenOption[0]);){ ServerLevel.dumpEntities(output, this.getEntities().getAll()); } Path blockEntities = rootDir.resolve("block_entities.csv"); try (BufferedWriter output = Files.newBufferedWriter(blockEntities, new OpenOption[0]);){ this.dumpBlockEntityTickers(output); } } private static void dumpEntities(Writer output, Iterable entities) throws IOException { CsvOutput csvOutput = CsvOutput.builder().addColumn("x").addColumn("y").addColumn("z").addColumn("uuid").addColumn("type").addColumn("alive").addColumn("display_name").addColumn("custom_name").build(output); for (Entity entity : entities) { Component customName = entity.getCustomName(); Component displayName = entity.getDisplayName(); csvOutput.writeRow(entity.getX(), entity.getY(), entity.getZ(), entity.getUUID(), BuiltInRegistries.ENTITY_TYPE.getKey(entity.getType()), entity.isAlive(), displayName.getString(), customName != null ? customName.getString() : null); } } private void dumpBlockEntityTickers(Writer output) throws IOException { CsvOutput csvOutput = CsvOutput.builder().addColumn("x").addColumn("y").addColumn("z").addColumn("type").build(output); for (TickingBlockEntity ticker : this.blockEntityTickers) { BlockPos blockPos = ticker.getPos(); csvOutput.writeRow(blockPos.getX(), blockPos.getY(), blockPos.getZ(), ticker.getType()); } } @VisibleForTesting public void clearBlockEvents(BoundingBox bb) { this.blockEvents.removeIf(e -> bb.isInside(e.pos())); } @Override public float getShade(Direction direction, boolean shade) { return 1.0f; } public Iterable getAllEntities() { return this.getEntities().getAll(); } public String toString() { return "ServerLevel[" + this.serverLevelData.getLevelName() + "]"; } public boolean isFlat() { return this.server.getWorldData().isFlatWorld(); } @Override public long getSeed() { return this.server.getWorldData().worldGenOptions().seed(); } public @Nullable EndDragonFight getDragonFight() { return this.dragonFight; } @Override public ServerLevel getLevel() { return this; } @VisibleForTesting public String getWatchdogStats() { return String.format(Locale.ROOT, "players: %s, entities: %s [%s], block_entities: %d [%s], block_ticks: %d, fluid_ticks: %d, chunk_source: %s", this.players.size(), this.entityManager.gatherStats(), ServerLevel.getTypeCount(this.entityManager.getEntityGetter().getAll(), e -> BuiltInRegistries.ENTITY_TYPE.getKey(e.getType()).toString()), this.blockEntityTickers.size(), ServerLevel.getTypeCount(this.blockEntityTickers, TickingBlockEntity::getType), ((LevelTicks)this.getBlockTicks()).count(), ((LevelTicks)this.getFluidTicks()).count(), this.gatherChunkSourceStats()); } private static String getTypeCount(Iterable values, Function typeGetter) { try { Object2IntOpenHashMap countByType = new Object2IntOpenHashMap(); for (T e2 : values) { String type = typeGetter.apply(e2); countByType.addTo((Object)type, 1); } return countByType.object2IntEntrySet().stream().sorted(Comparator.comparing(Object2IntMap.Entry::getIntValue).reversed()).limit(5L).map(e -> (String)e.getKey() + ":" + e.getIntValue()).collect(Collectors.joining(",")); } catch (Exception e3) { return ""; } } @Override protected LevelEntityGetter getEntities() { return this.entityManager.getEntityGetter(); } public void addLegacyChunkEntities(Stream loaded) { this.entityManager.addLegacyChunkEntities(loaded); } public void addWorldGenChunkEntities(Stream loaded) { this.entityManager.addWorldGenChunkEntities(loaded); } public void startTickingChunk(LevelChunk levelChunk) { levelChunk.unpackTicks(this.getGameTime()); } public void onStructureStartsAvailable(ChunkAccess chunk) { this.server.execute(() -> this.structureCheck.onStructureLoad(chunk.getPos(), chunk.getAllStarts())); } public PathTypeCache getPathTypeCache() { return this.pathTypesByPosCache; } public void waitForEntities(ChunkPos centerChunk, int radius) { List chunks = ChunkPos.rangeClosed(centerChunk, radius).toList(); this.server.managedBlock(() -> { this.entityManager.processPendingLoads(); for (ChunkPos chunk : chunks) { if (this.areEntitiesLoaded(chunk.toLong())) continue; return false; } return true; }); } public boolean isSpawningMonsters() { return this.getLevelData().getDifficulty() != Difficulty.PEACEFUL && this.getGameRules().get(GameRules.SPAWN_MOBS) != false && this.getGameRules().get(GameRules.SPAWN_MONSTERS) != false; } @Override public void close() throws IOException { super.close(); this.entityManager.close(); } @Override public String gatherChunkSourceStats() { return "Chunks[S] W: " + this.chunkSource.gatherStats() + " E: " + this.entityManager.gatherStats(); } public boolean areEntitiesLoaded(long chunkKey) { return this.entityManager.areEntitiesLoaded(chunkKey); } public boolean isPositionTickingWithEntitiesLoaded(long key) { return this.areEntitiesLoaded(key) && this.chunkSource.isPositionTicking(key); } public boolean isPositionEntityTicking(BlockPos pos) { return this.entityManager.canPositionTick(pos) && this.chunkSource.chunkMap.getDistanceManager().inEntityTickingRange(ChunkPos.asLong(pos)); } public boolean areEntitiesActuallyLoadedAndTicking(ChunkPos pos) { return this.entityManager.isTicking(pos) && this.entityManager.areEntitiesLoaded(pos.toLong()); } public boolean anyPlayerCloseEnoughForSpawning(BlockPos pos) { return this.anyPlayerCloseEnoughForSpawning(new ChunkPos(pos)); } public boolean anyPlayerCloseEnoughForSpawning(ChunkPos pos) { return this.chunkSource.chunkMap.anyPlayerCloseEnoughForSpawning(pos); } public boolean canSpreadFireAround(BlockPos pos) { int spreadRadius = this.getGameRules().get(GameRules.FIRE_SPREAD_RADIUS_AROUND_PLAYER); return spreadRadius == -1 || this.chunkSource.chunkMap.anyPlayerCloseEnoughTo(pos, spreadRadius); } public boolean canSpawnEntitiesInChunk(ChunkPos pos) { return this.entityManager.canPositionTick(pos) && this.getWorldBorder().isWithinBounds(pos); } @Override public FeatureFlagSet enabledFeatures() { return this.server.getWorldData().enabledFeatures(); } @Override public PotionBrewing potionBrewing() { return this.server.potionBrewing(); } @Override public FuelValues fuelValues() { return this.server.fuelValues(); } public RandomSource getRandomSequence(Identifier key) { return this.randomSequences.get(key, this.getSeed()); } public RandomSequences getRandomSequences() { return this.randomSequences; } public GameRules getGameRules() { return this.serverLevelData.getGameRules(); } @Override public CrashReportCategory fillReportDetails(CrashReport report) { CrashReportCategory category = super.fillReportDetails(report); category.setDetail("Loaded entity count", () -> String.valueOf(this.entityManager.count())); return category; } @Override public int getSeaLevel() { return this.chunkSource.getGenerator().getSeaLevel(); } @Override public void onBlockEntityAdded(BlockEntity blockEntity) { super.onBlockEntityAdded(blockEntity); this.debugSynchronizers.registerBlockEntity(blockEntity); } public LevelDebugSynchronizers debugSynchronizers() { return this.debugSynchronizers; } public boolean isAllowedToEnterPortal(Level toLevel) { if (toLevel.dimension() == Level.NETHER) { return this.getGameRules().get(GameRules.ALLOW_ENTERING_NETHER_USING_PORTALS); } return true; } public boolean isPvpAllowed() { return this.getGameRules().get(GameRules.PVP); } public boolean isCommandBlockEnabled() { return this.getGameRules().get(GameRules.COMMAND_BLOCKS_WORK); } public boolean isSpawnerBlockEnabled() { return this.getGameRules().get(GameRules.SPAWNER_BLOCKS_WORK); } private final class EntityCallbacks implements LevelCallback { private EntityCallbacks() { } @Override public void onCreated(Entity entity) { WaypointTransmitter waypoint; if (entity instanceof WaypointTransmitter && (waypoint = (WaypointTransmitter)((Object)entity)).isTransmittingWaypoint()) { ServerLevel.this.getWaypointManager().trackWaypoint(waypoint); } } @Override public void onDestroyed(Entity entity) { if (entity instanceof WaypointTransmitter) { WaypointTransmitter waypoint = (WaypointTransmitter)((Object)entity); ServerLevel.this.getWaypointManager().untrackWaypoint(waypoint); } ServerLevel.this.getScoreboard().entityRemoved(entity); } @Override public void onTickingStart(Entity entity) { ServerLevel.this.entityTickList.add(entity); } @Override public void onTickingEnd(Entity entity) { ServerLevel.this.entityTickList.remove(entity); } @Override public void onTrackingStart(Entity entity) { WaypointTransmitter waypoint; ServerLevel.this.getChunkSource().addEntity(entity); if (entity instanceof ServerPlayer) { ServerPlayer player = (ServerPlayer)entity; ServerLevel.this.players.add(player); if (player.isReceivingWaypoints()) { ServerLevel.this.getWaypointManager().addPlayer(player); } ServerLevel.this.updateSleepingPlayerList(); } if (entity instanceof WaypointTransmitter && (waypoint = (WaypointTransmitter)((Object)entity)).isTransmittingWaypoint()) { ServerLevel.this.getWaypointManager().trackWaypoint(waypoint); } if (entity instanceof Mob) { Mob mob = (Mob)entity; if (ServerLevel.this.isUpdatingNavigations) { String message = "onTrackingStart called during navigation iteration"; Util.logAndPauseIfInIde("onTrackingStart called during navigation iteration", new IllegalStateException("onTrackingStart called during navigation iteration")); } ServerLevel.this.navigatingMobs.add(mob); } if (entity instanceof EnderDragon) { EnderDragon dragon = (EnderDragon)entity; for (EnderDragonPart subEntity : dragon.getSubEntities()) { ServerLevel.this.dragonParts.put(subEntity.getId(), (Object)subEntity); } } entity.updateDynamicGameEventListener(DynamicGameEventListener::add); } @Override public void onTrackingEnd(Entity entity) { ServerLevel.this.getChunkSource().removeEntity(entity); if (entity instanceof ServerPlayer) { ServerPlayer player = (ServerPlayer)entity; ServerLevel.this.players.remove(player); ServerLevel.this.getWaypointManager().removePlayer(player); ServerLevel.this.updateSleepingPlayerList(); } if (entity instanceof Mob) { Mob mob = (Mob)entity; if (ServerLevel.this.isUpdatingNavigations) { String message = "onTrackingStart called during navigation iteration"; Util.logAndPauseIfInIde("onTrackingStart called during navigation iteration", new IllegalStateException("onTrackingStart called during navigation iteration")); } ServerLevel.this.navigatingMobs.remove(mob); } if (entity instanceof EnderDragon) { EnderDragon dragon = (EnderDragon)entity; for (EnderDragonPart subEntity : dragon.getSubEntities()) { ServerLevel.this.dragonParts.remove(subEntity.getId()); } } entity.updateDynamicGameEventListener(DynamicGameEventListener::remove); ServerLevel.this.debugSynchronizers.dropEntity(entity); } @Override public void onSectionChange(Entity entity) { entity.updateDynamicGameEventListener(DynamicGameEventListener::move); } } }