2025-11-24 22:52:51 +03:00

1696 lines
74 KiB
Java

/*
* 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<ServerPlayer> 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<Entity> entityManager;
private final GameEventDispatcher gameEventDispatcher;
public boolean noSave;
private final SleepStatus sleepStatus;
private int emptyTime;
private final PortalForcer portalForcer;
private final LevelTicks<Block> blockTicks = new LevelTicks(this::isPositionTickingWithEntitiesLoaded);
private final LevelTicks<Fluid> fluidTicks = new LevelTicks(this::isPositionTickingWithEntitiesLoaded);
private final PathTypeCache pathTypesByPosCache = new PathTypeCache();
private final Set<Mob> navigatingMobs = new ObjectOpenHashSet();
private volatile boolean isUpdatingNavigations;
protected final Raids raids;
private final ObjectLinkedOpenHashSet<BlockEventData> blockEvents = new ObjectLinkedOpenHashSet();
private final List<BlockEventData> blockEventsToReschedule = new ArrayList<BlockEventData>(64);
private boolean handlingTick;
private final List<CustomSpawner> customSpawners;
private @Nullable EndDragonFight dragonFight;
private final Int2ObjectMap<EnderDragonPart> 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<Level> dimension, LevelStem levelStem, boolean isDebug, long biomeZoomSeed, List<CustomSpawner> 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>(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<Biome> 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<BlockPos> findLightningRod(BlockPos center) {
Optional<BlockPos> 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<BlockPos> lightningRodTarget = this.findLightningRod(center);
if (lightningRodTarget.isPresent()) {
return lightningRodTarget.get();
}
AABB search = AABB.encapsulatingFullBlocks(center, center.atY(this.getMaxY() + 1)).inflate(3.0);
List<LivingEntity> 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 <T extends Entity> List<? extends T> getEntities(EntityTypeTest<Entity, T> type, Predicate<? super T> selector) {
ArrayList result = Lists.newArrayList();
this.getEntities(type, selector, result);
return result;
}
public <T extends Entity> void getEntities(EntityTypeTest<Entity, T> type, Predicate<? super T> selector, List<? super T> result) {
this.getEntities(type, selector, result, Integer.MAX_VALUE);
}
public <T extends Entity> void getEntities(EntityTypeTest<Entity, T> type, Predicate<? super T> selector, List<? super T> 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<? extends EnderDragon> getDragons() {
return this.getEntities(EntityType.ENDER_DRAGON, LivingEntity::isAlive);
}
public List<ServerPlayer> getPlayers(Predicate<? super ServerPlayer> selector) {
return this.getPlayers(selector, Integer.MAX_VALUE);
}
public List<ServerPlayer> getPlayers(Predicate<? super ServerPlayer> 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<ServerPlayer> 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<SoundEvent> 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<SoundEvent> 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> 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<ExplosionParticleInfo> blockParticles, Holder<SoundEvent> 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<Vec3> playerKnockback = Optional.ofNullable(explosion.getHitPlayers().get(player));
player.connection.send(new ClientboundExplodePacket(center, r, blockCount, playerKnockback, explosionParticle, explosionSound, blockParticles));
}
}
private Explosion.BlockInteraction getDestroyType(GameRule<Boolean> 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<Block> getBlockTicks() {
return this.blockTicks;
}
public LevelTicks<Fluid> 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 <T extends ParticleOptions> 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 <T extends ParticleOptions> 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 <T extends ParticleOptions> 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<EnderDragonPart> dragonParts() {
return this.dragonParts.values();
}
public @Nullable BlockPos findNearestMapStructure(TagKey<Structure> 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<BlockPos, Holder<Structure>> result = this.getChunkSource().getGenerator().findNearestMapStructure(this, (HolderSet)tag.get(), origin, maxSearchRadius, createReference);
return result != null ? (BlockPos)result.getFirst() : null;
}
public @Nullable Pair<BlockPos, Holder<Biome>> findClosestBiome3d(Predicate<Holder<Biome>> 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<ServerPlayer> players() {
return this.players;
}
@Override
public void updatePOIOnBlockStateChange(BlockPos pos, BlockState oldState, BlockState newState) {
Optional<Holder<PoiType>> newType;
Optional<Holder<PoiType>> 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>)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<Entity> 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<Entity> 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 <T> String getTypeCount(Iterable<T> values, Function<T, String> 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<Entity> getEntities() {
return this.entityManager.getEntityGetter();
}
public void addLegacyChunkEntities(Stream<Entity> loaded) {
this.entityManager.addLegacyChunkEntities(loaded);
}
public void addWorldGenChunkEntities(Stream<Entity> 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<ChunkPos> 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<Entity> {
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);
}
}
}