1942 lines
		
	
	
	
		
			64 KiB
		
	
	
	
		
			Java
		
	
	
	
	
	
			
		
		
	
	
			1942 lines
		
	
	
	
		
			64 KiB
		
	
	
	
		
			Java
		
	
	
	
	
	
| 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.Object2IntOpenHashMap;
 | |
| import it.unimi.dsi.fastutil.objects.ObjectArrayList;
 | |
| import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet;
 | |
| import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
 | |
| import it.unimi.dsi.fastutil.objects.Object2IntMap.Entry;
 | |
| import java.io.IOException;
 | |
| import java.io.Writer;
 | |
| import java.nio.file.Files;
 | |
| 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.concurrent.Executor;
 | |
| import java.util.function.BooleanSupplier;
 | |
| import java.util.function.Function;
 | |
| import java.util.function.Predicate;
 | |
| import java.util.function.Supplier;
 | |
| import java.util.stream.Collectors;
 | |
| import java.util.stream.Stream;
 | |
| import net.minecraft.CrashReport;
 | |
| import net.minecraft.CrashReportCategory;
 | |
| import net.minecraft.CrashReportDetail;
 | |
| import net.minecraft.ReportType;
 | |
| import net.minecraft.Util;
 | |
| 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.ParticleOptions;
 | |
| import net.minecraft.core.registries.BuiltInRegistries;
 | |
| import net.minecraft.core.registries.Registries;
 | |
| import net.minecraft.network.chat.Component;
 | |
| 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.ClientboundSetDefaultSpawnPositionPacket;
 | |
| import net.minecraft.network.protocol.game.ClientboundSoundEntityPacket;
 | |
| import net.minecraft.network.protocol.game.ClientboundSoundPacket;
 | |
| import net.minecraft.network.protocol.game.DebugPackets;
 | |
| import net.minecraft.resources.ResourceKey;
 | |
| import net.minecraft.resources.ResourceLocation;
 | |
| import net.minecraft.server.MinecraftServer;
 | |
| import net.minecraft.server.ServerScoreboard;
 | |
| import net.minecraft.server.level.progress.ChunkProgressListener;
 | |
| 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.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.datafix.DataFixTypes;
 | |
| import net.minecraft.util.profiling.Profiler;
 | |
| import net.minecraft.util.profiling.ProfilerFiller;
 | |
| import net.minecraft.util.valueproviders.IntProvider;
 | |
| import net.minecraft.util.valueproviders.UniformInt;
 | |
| import net.minecraft.world.DifficultyInstance;
 | |
| import net.minecraft.world.RandomSequences;
 | |
| import net.minecraft.world.TickRateManager;
 | |
| 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.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.GameRules;
 | |
| import net.minecraft.world.level.Level;
 | |
| 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.FuelValues;
 | |
| import net.minecraft.world.level.block.entity.TickingBlockEntity;
 | |
| import net.minecraft.world.level.block.state.BlockState;
 | |
| 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.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.LevelStem;
 | |
| import net.minecraft.world.level.dimension.end.EndDragonFight;
 | |
| import net.minecraft.world.level.entity.EntityPersistentStorage;
 | |
| 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.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.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.jetbrains.annotations.NotNull;
 | |
| import org.jetbrains.annotations.Nullable;
 | |
| import org.slf4j.Logger;
 | |
| 
 | |
| public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLevel {
 | |
| 	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;
 | |
| 	final List<ServerPlayer> players = Lists.<ServerPlayer>newArrayList();
 | |
| 	private final ServerChunkCache chunkSource;
 | |
| 	private final MinecraftServer server;
 | |
| 	private final ServerLevelData serverLevelData;
 | |
| 	private int lastSpawnChunkRadius;
 | |
| 	final EntityTickList entityTickList = new EntityTickList();
 | |
| 	private final ServerWaypointManager waypointManager;
 | |
| 	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();
 | |
| 	final Set<Mob> navigatingMobs = new ObjectOpenHashSet<>();
 | |
| 	volatile boolean isUpdatingNavigations;
 | |
| 	protected final Raids raids;
 | |
| 	private final ObjectLinkedOpenHashSet<BlockEventData> blockEvents = new ObjectLinkedOpenHashSet<>();
 | |
| 	private final List<BlockEventData> blockEventsToReschedule = new ArrayList(64);
 | |
| 	private boolean handlingTick;
 | |
| 	private final List<CustomSpawner> customSpawners;
 | |
| 	@Nullable
 | |
| 	private EndDragonFight dragonFight;
 | |
| 	final Int2ObjectMap<EnderDragonPart> dragonParts = new Int2ObjectOpenHashMap<>();
 | |
| 	private final StructureManager structureManager;
 | |
| 	private final StructureCheck structureCheck;
 | |
| 	private final boolean tickTime;
 | |
| 	private final RandomSequences randomSequences;
 | |
| 
 | |
| 	public ServerLevel(
 | |
| 		MinecraftServer server,
 | |
| 		Executor dispatcher,
 | |
| 		LevelStorageSource.LevelStorageAccess levelStorageAccess,
 | |
| 		ServerLevelData serverLevelData,
 | |
| 		ResourceKey<Level> dimension,
 | |
| 		LevelStem levelStem,
 | |
| 		ChunkProgressListener progressListener,
 | |
| 		boolean isDebug,
 | |
| 		long biomeZoomSeed,
 | |
| 		List<CustomSpawner> customSpawners,
 | |
| 		boolean tickTime,
 | |
| 		@Nullable RandomSequences randomSequences
 | |
| 	) {
 | |
| 		super(serverLevelData, dimension, server.registryAccess(), levelStem.type(), false, isDebug, biomeZoomSeed, server.getMaxChainedNeighborUpdates());
 | |
| 		this.tickTime = tickTime;
 | |
| 		this.server = server;
 | |
| 		this.customSpawners = customSpawners;
 | |
| 		this.serverLevelData = serverLevelData;
 | |
| 		ChunkGenerator chunkGenerator = levelStem.generator();
 | |
| 		boolean bl = server.forceSynchronousWrites();
 | |
| 		DataFixer dataFixer = server.getFixerUpper();
 | |
| 		EntityPersistentStorage<Entity> entityPersistentStorage = new EntityStorage(
 | |
| 			new SimpleRegionStorage(
 | |
| 				new RegionStorageInfo(levelStorageAccess.getLevelId(), dimension, "entities"),
 | |
| 				levelStorageAccess.getDimensionPath(dimension).resolve("entities"),
 | |
| 				dataFixer,
 | |
| 				bl,
 | |
| 				DataFixTypes.ENTITY_CHUNK
 | |
| 			),
 | |
| 			this,
 | |
| 			server
 | |
| 		);
 | |
| 		this.entityManager = new PersistentEntitySectionManager<>(Entity.class, new ServerLevel.EntityCallbacks(), entityPersistentStorage);
 | |
| 		this.chunkSource = new ServerChunkCache(
 | |
| 			this,
 | |
| 			levelStorageAccess,
 | |
| 			dataFixer,
 | |
| 			server.getStructureManager(),
 | |
| 			dispatcher,
 | |
| 			chunkGenerator,
 | |
| 			server.getPlayerList().getViewDistance(),
 | |
| 			server.getPlayerList().getSimulationDistance(),
 | |
| 			bl,
 | |
| 			progressListener,
 | |
| 			this.entityManager::updateChunkStatus,
 | |
| 			() -> server.overworld().getDataStorage()
 | |
| 		);
 | |
| 		this.chunkSource.getGeneratorState().ensureStructuresGenerated();
 | |
| 		this.portalForcer = new PortalForcer(this);
 | |
| 		this.updateSkyBrightness();
 | |
| 		this.prepareWeather();
 | |
| 		this.getWorldBorder().setAbsoluteMaxSize(server.getAbsoluteMaxWorldSize());
 | |
| 		this.raids = this.getDataStorage().computeIfAbsent(Raids.getType(this.dimensionTypeRegistration()));
 | |
| 		if (!server.isSingleplayer()) {
 | |
| 			serverLevelData.setGameType(server.getDefaultGameType());
 | |
| 		}
 | |
| 
 | |
| 		long l = server.getWorldData().worldGenOptions().seed();
 | |
| 		this.structureCheck = new StructureCheck(
 | |
| 			this.chunkSource.chunkScanner(),
 | |
| 			this.registryAccess(),
 | |
| 			server.getStructureManager(),
 | |
| 			dimension,
 | |
| 			chunkGenerator,
 | |
| 			this.chunkSource.randomState(),
 | |
| 			this,
 | |
| 			chunkGenerator.getBiomeSource(),
 | |
| 			l,
 | |
| 			dataFixer
 | |
| 		);
 | |
| 		this.structureManager = new StructureManager(this, server.getWorldData().worldGenOptions(), this.structureCheck);
 | |
| 		if (this.dimension() == Level.END && this.dimensionTypeRegistration().is(BuiltinDimensionTypes.END)) {
 | |
| 			this.dragonFight = new EndDragonFight(this, l, server.getWorldData().endDragonFightData());
 | |
| 		} else {
 | |
| 			this.dragonFight = null;
 | |
| 		}
 | |
| 
 | |
| 		this.sleepStatus = new SleepStatus();
 | |
| 		this.gameEventDispatcher = new GameEventDispatcher(this);
 | |
| 		this.randomSequences = (RandomSequences)Objects.requireNonNullElseGet(randomSequences, () -> this.getDataStorage().computeIfAbsent(RandomSequences.TYPE));
 | |
| 		this.waypointManager = new ServerWaypointManager();
 | |
| 	}
 | |
| 
 | |
| 	@Deprecated
 | |
| 	@VisibleForTesting
 | |
| 	public void setDragonFight(@Nullable EndDragonFight dragonFight) {
 | |
| 		this.dragonFight = dragonFight;
 | |
| 	}
 | |
| 
 | |
| 	public void setWeatherParameters(int clearTime, int weatherTime, boolean isRaining, boolean isThundering) {
 | |
| 		this.serverLevelData.setClearWeatherTime(clearTime);
 | |
| 		this.serverLevelData.setRainTime(weatherTime);
 | |
| 		this.serverLevelData.setThunderTime(weatherTime);
 | |
| 		this.serverLevelData.setRaining(isRaining);
 | |
| 		this.serverLevelData.setThundering(isThundering);
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public Holder<Biome> getUncachedNoiseBiome(int x, int y, int z) {
 | |
| 		return this.getChunkSource().getGenerator().getBiomeSource().getNoiseBiome(x, y, z, this.getChunkSource().randomState().sampler());
 | |
| 	}
 | |
| 
 | |
| 	public StructureManager structureManager() {
 | |
| 		return this.structureManager;
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Runs a single tick for the world
 | |
| 	 */
 | |
| 	public void tick(BooleanSupplier hasTimeLeft) {
 | |
| 		ProfilerFiller profilerFiller = Profiler.get();
 | |
| 		this.handlingTick = true;
 | |
| 		TickRateManager tickRateManager = this.tickRateManager();
 | |
| 		boolean bl = tickRateManager.runsNormally();
 | |
| 		if (bl) {
 | |
| 			profilerFiller.push("world border");
 | |
| 			this.getWorldBorder().tick();
 | |
| 			profilerFiller.popPush("weather");
 | |
| 			this.advanceWeatherCycle();
 | |
| 			profilerFiller.pop();
 | |
| 		}
 | |
| 
 | |
| 		int i = this.getGameRules().getInt(GameRules.RULE_PLAYERS_SLEEPING_PERCENTAGE);
 | |
| 		if (this.sleepStatus.areEnoughSleeping(i) && this.sleepStatus.areEnoughDeepSleeping(i, this.players)) {
 | |
| 			if (this.getGameRules().getBoolean(GameRules.RULE_DAYLIGHT)) {
 | |
| 				long l = this.levelData.getDayTime() + 24000L;
 | |
| 				this.setDayTime(l - l % 24000L);
 | |
| 			}
 | |
| 
 | |
| 			this.wakeUpAllPlayers();
 | |
| 			if (this.getGameRules().getBoolean(GameRules.RULE_WEATHER_CYCLE) && this.isRaining()) {
 | |
| 				this.resetWeatherCycle();
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		this.updateSkyBrightness();
 | |
| 		if (bl) {
 | |
| 			this.tickTime();
 | |
| 		}
 | |
| 
 | |
| 		profilerFiller.push("tickPending");
 | |
| 		if (!this.isDebug() && bl) {
 | |
| 			long l = this.getGameTime();
 | |
| 			profilerFiller.push("blockTicks");
 | |
| 			this.blockTicks.tick(l, 65536, this::tickBlock);
 | |
| 			profilerFiller.popPush("fluidTicks");
 | |
| 			this.fluidTicks.tick(l, 65536, this::tickFluid);
 | |
| 			profilerFiller.pop();
 | |
| 		}
 | |
| 
 | |
| 		profilerFiller.popPush("raid");
 | |
| 		if (bl) {
 | |
| 			this.raids.tick(this);
 | |
| 		}
 | |
| 
 | |
| 		profilerFiller.popPush("chunkSource");
 | |
| 		this.getChunkSource().tick(hasTimeLeft, true);
 | |
| 		profilerFiller.popPush("blockEvents");
 | |
| 		if (bl) {
 | |
| 			this.runBlockEvents();
 | |
| 		}
 | |
| 
 | |
| 		this.handlingTick = false;
 | |
| 		profilerFiller.pop();
 | |
| 		boolean bl2 = !this.players.isEmpty() || !this.getForceLoadedChunks().isEmpty();
 | |
| 		if (bl2) {
 | |
| 			this.resetEmptyTime();
 | |
| 		}
 | |
| 
 | |
| 		if (bl2 || this.emptyTime++ < 300) {
 | |
| 			profilerFiller.push("entities");
 | |
| 			if (this.dragonFight != null && bl) {
 | |
| 				profilerFiller.push("dragonFight");
 | |
| 				this.dragonFight.tick();
 | |
| 				profilerFiller.pop();
 | |
| 			}
 | |
| 
 | |
| 			this.entityTickList.forEach(entity -> {
 | |
| 				if (!entity.isRemoved()) {
 | |
| 					if (!tickRateManager.isEntityFrozen(entity)) {
 | |
| 						profilerFiller.push("checkDespawn");
 | |
| 						entity.checkDespawn();
 | |
| 						profilerFiller.pop();
 | |
| 						if (entity instanceof ServerPlayer || this.chunkSource.chunkMap.getDistanceManager().inEntityTickingRange(entity.chunkPosition().toLong())) {
 | |
| 							Entity entity2 = entity.getVehicle();
 | |
| 							if (entity2 != null) {
 | |
| 								if (!entity2.isRemoved() && entity2.hasPassenger(entity)) {
 | |
| 									return;
 | |
| 								}
 | |
| 
 | |
| 								entity.stopRiding();
 | |
| 							}
 | |
| 
 | |
| 							profilerFiller.push("tick");
 | |
| 							this.guardEntityTick(this::tickNonPassenger, entity);
 | |
| 							profilerFiller.pop();
 | |
| 						}
 | |
| 					}
 | |
| 				}
 | |
| 			});
 | |
| 			profilerFiller.pop();
 | |
| 			this.tickBlockEntities();
 | |
| 		}
 | |
| 
 | |
| 		profilerFiller.push("entityManagement");
 | |
| 		this.entityManager.tick();
 | |
| 		profilerFiller.pop();
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public boolean shouldTickBlocksAt(long chunkPos) {
 | |
| 		return this.chunkSource.chunkMap.getDistanceManager().inBlockTickingRange(chunkPos);
 | |
| 	}
 | |
| 
 | |
| 	protected void tickTime() {
 | |
| 		if (this.tickTime) {
 | |
| 			long l = this.levelData.getGameTime() + 1L;
 | |
| 			this.serverLevelData.setGameTime(l);
 | |
| 			Profiler.get().push("scheduledFunctions");
 | |
| 			this.serverLevelData.getScheduledEvents().tick(this.server, l);
 | |
| 			Profiler.get().pop();
 | |
| 			if (this.serverLevelData.getGameRules().getBoolean(GameRules.RULE_DAYLIGHT)) {
 | |
| 				this.setDayTime(this.levelData.getDayTime() + 1L);
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	public void setDayTime(long time) {
 | |
| 		this.serverLevelData.setDayTime(time);
 | |
| 	}
 | |
| 
 | |
| 	public void tickCustomSpawners(boolean spawnEnemies, boolean spawnFriendlies) {
 | |
| 		for (CustomSpawner customSpawner : this.customSpawners) {
 | |
| 			customSpawner.tick(this, spawnEnemies, spawnFriendlies);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	private void wakeUpAllPlayers() {
 | |
| 		this.sleepStatus.removeAllSleepers();
 | |
| 		((List)this.players.stream().filter(LivingEntity::isSleeping).collect(Collectors.toList()))
 | |
| 			.forEach(serverPlayer -> serverPlayer.stopSleepInBed(false, false));
 | |
| 	}
 | |
| 
 | |
| 	public void tickChunk(LevelChunk chunk, int randomTickSpeed) {
 | |
| 		ChunkPos chunkPos = chunk.getPos();
 | |
| 		int i = chunkPos.getMinBlockX();
 | |
| 		int j = chunkPos.getMinBlockZ();
 | |
| 		ProfilerFiller profilerFiller = Profiler.get();
 | |
| 		profilerFiller.push("iceandsnow");
 | |
| 
 | |
| 		for (int k = 0; k < randomTickSpeed; k++) {
 | |
| 			if (this.random.nextInt(48) == 0) {
 | |
| 				this.tickPrecipitation(this.getBlockRandomPos(i, 0, j, 15));
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		profilerFiller.popPush("tickBlocks");
 | |
| 		if (randomTickSpeed > 0) {
 | |
| 			LevelChunkSection[] levelChunkSections = chunk.getSections();
 | |
| 
 | |
| 			for (int l = 0; l < levelChunkSections.length; l++) {
 | |
| 				LevelChunkSection levelChunkSection = levelChunkSections[l];
 | |
| 				if (levelChunkSection.isRandomlyTicking()) {
 | |
| 					int m = chunk.getSectionYFromSectionIndex(l);
 | |
| 					int n = SectionPos.sectionToBlockCoord(m);
 | |
| 
 | |
| 					for (int o = 0; o < randomTickSpeed; o++) {
 | |
| 						BlockPos blockPos = this.getBlockRandomPos(i, n, j, 15);
 | |
| 						profilerFiller.push("randomTick");
 | |
| 						BlockState blockState = levelChunkSection.getBlockState(blockPos.getX() - i, blockPos.getY() - n, blockPos.getZ() - j);
 | |
| 						if (blockState.isRandomlyTicking()) {
 | |
| 							blockState.randomTick(this, blockPos, this.random);
 | |
| 						}
 | |
| 
 | |
| 						FluidState fluidState = blockState.getFluidState();
 | |
| 						if (fluidState.isRandomlyTicking()) {
 | |
| 							fluidState.randomTick(this, blockPos, this.random);
 | |
| 						}
 | |
| 
 | |
| 						profilerFiller.pop();
 | |
| 					}
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		profilerFiller.pop();
 | |
| 	}
 | |
| 
 | |
| 	public void tickThunder(LevelChunk chunk) {
 | |
| 		ChunkPos chunkPos = chunk.getPos();
 | |
| 		boolean bl = this.isRaining();
 | |
| 		int i = chunkPos.getMinBlockX();
 | |
| 		int j = chunkPos.getMinBlockZ();
 | |
| 		ProfilerFiller profilerFiller = Profiler.get();
 | |
| 		profilerFiller.push("thunder");
 | |
| 		if (bl && this.isThundering() && this.random.nextInt(100000) == 0) {
 | |
| 			BlockPos blockPos = this.findLightningTargetAround(this.getBlockRandomPos(i, 0, j, 15));
 | |
| 			if (this.isRainingAt(blockPos)) {
 | |
| 				DifficultyInstance difficultyInstance = this.getCurrentDifficultyAt(blockPos);
 | |
| 				boolean bl2 = this.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING)
 | |
| 					&& this.random.nextDouble() < difficultyInstance.getEffectiveDifficulty() * 0.01
 | |
| 					&& !this.getBlockState(blockPos.below()).is(Blocks.LIGHTNING_ROD);
 | |
| 				if (bl2) {
 | |
| 					SkeletonHorse skeletonHorse = EntityType.SKELETON_HORSE.create(this, EntitySpawnReason.EVENT);
 | |
| 					if (skeletonHorse != null) {
 | |
| 						skeletonHorse.setTrap(true);
 | |
| 						skeletonHorse.setAge(0);
 | |
| 						skeletonHorse.setPos(blockPos.getX(), blockPos.getY(), blockPos.getZ());
 | |
| 						this.addFreshEntity(skeletonHorse);
 | |
| 					}
 | |
| 				}
 | |
| 
 | |
| 				LightningBolt lightningBolt = EntityType.LIGHTNING_BOLT.create(this, EntitySpawnReason.EVENT);
 | |
| 				if (lightningBolt != null) {
 | |
| 					lightningBolt.snapTo(Vec3.atBottomCenterOf(blockPos));
 | |
| 					lightningBolt.setVisualOnly(bl2);
 | |
| 					this.addFreshEntity(lightningBolt);
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		profilerFiller.pop();
 | |
| 	}
 | |
| 
 | |
| 	@VisibleForTesting
 | |
| 	public void tickPrecipitation(BlockPos blockPos) {
 | |
| 		BlockPos blockPos2 = this.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, blockPos);
 | |
| 		BlockPos blockPos3 = blockPos2.below();
 | |
| 		Biome biome = this.getBiome(blockPos2).value();
 | |
| 		if (biome.shouldFreeze(this, blockPos3)) {
 | |
| 			this.setBlockAndUpdate(blockPos3, Blocks.ICE.defaultBlockState());
 | |
| 		}
 | |
| 
 | |
| 		if (this.isRaining()) {
 | |
| 			int i = this.getGameRules().getInt(GameRules.RULE_SNOW_ACCUMULATION_HEIGHT);
 | |
| 			if (i > 0 && biome.shouldSnow(this, blockPos2)) {
 | |
| 				BlockState blockState = this.getBlockState(blockPos2);
 | |
| 				if (blockState.is(Blocks.SNOW)) {
 | |
| 					int j = (Integer)blockState.getValue(SnowLayerBlock.LAYERS);
 | |
| 					if (j < Math.min(i, 8)) {
 | |
| 						BlockState blockState2 = blockState.setValue(SnowLayerBlock.LAYERS, j + 1);
 | |
| 						Block.pushEntitiesUp(blockState, blockState2, this, blockPos2);
 | |
| 						this.setBlockAndUpdate(blockPos2, blockState2);
 | |
| 					}
 | |
| 				} else {
 | |
| 					this.setBlockAndUpdate(blockPos2, Blocks.SNOW.defaultBlockState());
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			Biome.Precipitation precipitation = biome.getPrecipitationAt(blockPos3, this.getSeaLevel());
 | |
| 			if (precipitation != Biome.Precipitation.NONE) {
 | |
| 				BlockState blockState3 = this.getBlockState(blockPos3);
 | |
| 				blockState3.getBlock().handlePrecipitation(blockState3, this, blockPos3, precipitation);
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	private Optional<BlockPos> findLightningRod(BlockPos pos) {
 | |
| 		Optional<BlockPos> optional = this.getPoiManager()
 | |
| 			.findClosest(
 | |
| 				holder -> holder.is(PoiTypes.LIGHTNING_ROD),
 | |
| 				blockPos -> blockPos.getY() == this.getHeight(Heightmap.Types.WORLD_SURFACE, blockPos.getX(), blockPos.getZ()) - 1,
 | |
| 				pos,
 | |
| 				128,
 | |
| 				PoiManager.Occupancy.ANY
 | |
| 			);
 | |
| 		return optional.map(blockPos -> blockPos.above(1));
 | |
| 	}
 | |
| 
 | |
| 	protected BlockPos findLightningTargetAround(BlockPos pos) {
 | |
| 		BlockPos blockPos = this.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, pos);
 | |
| 		Optional<BlockPos> optional = this.findLightningRod(blockPos);
 | |
| 		if (optional.isPresent()) {
 | |
| 			return (BlockPos)optional.get();
 | |
| 		} else {
 | |
| 			AABB aABB = AABB.encapsulatingFullBlocks(blockPos, blockPos.atY(this.getMaxY() + 1)).inflate(3.0);
 | |
| 			List<LivingEntity> list = this.getEntitiesOfClass(
 | |
| 				LivingEntity.class, aABB, livingEntity -> livingEntity != null && livingEntity.isAlive() && this.canSeeSky(livingEntity.blockPosition())
 | |
| 			);
 | |
| 			if (!list.isEmpty()) {
 | |
| 				return ((LivingEntity)list.get(this.random.nextInt(list.size()))).blockPosition();
 | |
| 			} else {
 | |
| 				if (blockPos.getY() == this.getMinY() - 1) {
 | |
| 					blockPos = blockPos.above(2);
 | |
| 				}
 | |
| 
 | |
| 				return blockPos;
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	public boolean isHandlingTick() {
 | |
| 		return this.handlingTick;
 | |
| 	}
 | |
| 
 | |
| 	public boolean canSleepThroughNights() {
 | |
| 		return this.getGameRules().getInt(GameRules.RULE_PLAYERS_SLEEPING_PERCENTAGE) <= 100;
 | |
| 	}
 | |
| 
 | |
| 	private void announceSleepStatus() {
 | |
| 		if (this.canSleepThroughNights()) {
 | |
| 			if (!this.getServer().isSingleplayer() || this.getServer().isPublished()) {
 | |
| 				int i = this.getGameRules().getInt(GameRules.RULE_PLAYERS_SLEEPING_PERCENTAGE);
 | |
| 				Component component;
 | |
| 				if (this.sleepStatus.areEnoughSleeping(i)) {
 | |
| 					component = Component.translatable("sleep.skipping_night");
 | |
| 				} else {
 | |
| 					component = Component.translatable("sleep.players_sleeping", this.sleepStatus.amountSleeping(), this.sleepStatus.sleepersNeeded(i));
 | |
| 				}
 | |
| 
 | |
| 				for (ServerPlayer serverPlayer : this.players) {
 | |
| 					serverPlayer.displayClientMessage(component, true);
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Updates the flag that indicates whether all players in the world are sleeping.
 | |
| 	 */
 | |
| 	public void updateSleepingPlayerList() {
 | |
| 		if (!this.players.isEmpty() && this.sleepStatus.update(this.players)) {
 | |
| 			this.announceSleepStatus();
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	public ServerScoreboard getScoreboard() {
 | |
| 		return this.server.getScoreboard();
 | |
| 	}
 | |
| 
 | |
| 	public ServerWaypointManager getWaypointManager() {
 | |
| 		return this.waypointManager;
 | |
| 	}
 | |
| 
 | |
| 	private void advanceWeatherCycle() {
 | |
| 		boolean bl = this.isRaining();
 | |
| 		if (this.dimensionType().hasSkyLight()) {
 | |
| 			if (this.getGameRules().getBoolean(GameRules.RULE_WEATHER_CYCLE)) {
 | |
| 				int i = this.serverLevelData.getClearWeatherTime();
 | |
| 				int j = this.serverLevelData.getThunderTime();
 | |
| 				int k = this.serverLevelData.getRainTime();
 | |
| 				boolean bl2 = this.levelData.isThundering();
 | |
| 				boolean bl3 = this.levelData.isRaining();
 | |
| 				if (i > 0) {
 | |
| 					i--;
 | |
| 					j = bl2 ? 0 : 1;
 | |
| 					k = bl3 ? 0 : 1;
 | |
| 					bl2 = false;
 | |
| 					bl3 = false;
 | |
| 				} else {
 | |
| 					if (j > 0) {
 | |
| 						if (--j == 0) {
 | |
| 							bl2 = !bl2;
 | |
| 						}
 | |
| 					} else if (bl2) {
 | |
| 						j = THUNDER_DURATION.sample(this.random);
 | |
| 					} else {
 | |
| 						j = THUNDER_DELAY.sample(this.random);
 | |
| 					}
 | |
| 
 | |
| 					if (k > 0) {
 | |
| 						if (--k == 0) {
 | |
| 							bl3 = !bl3;
 | |
| 						}
 | |
| 					} else if (bl3) {
 | |
| 						k = RAIN_DURATION.sample(this.random);
 | |
| 					} else {
 | |
| 						k = RAIN_DELAY.sample(this.random);
 | |
| 					}
 | |
| 				}
 | |
| 
 | |
| 				this.serverLevelData.setThunderTime(j);
 | |
| 				this.serverLevelData.setRainTime(k);
 | |
| 				this.serverLevelData.setClearWeatherTime(i);
 | |
| 				this.serverLevelData.setThundering(bl2);
 | |
| 				this.serverLevelData.setRaining(bl3);
 | |
| 			}
 | |
| 
 | |
| 			this.oThunderLevel = this.thunderLevel;
 | |
| 			if (this.levelData.isThundering()) {
 | |
| 				this.thunderLevel += 0.01F;
 | |
| 			} else {
 | |
| 				this.thunderLevel -= 0.01F;
 | |
| 			}
 | |
| 
 | |
| 			this.thunderLevel = Mth.clamp(this.thunderLevel, 0.0F, 1.0F);
 | |
| 			this.oRainLevel = this.rainLevel;
 | |
| 			if (this.levelData.isRaining()) {
 | |
| 				this.rainLevel += 0.01F;
 | |
| 			} else {
 | |
| 				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 (bl != this.isRaining()) {
 | |
| 			if (bl) {
 | |
| 				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);
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Resets the updateEntityTick field to 0
 | |
| 	 */
 | |
| 	public void resetEmptyTime() {
 | |
| 		this.emptyTime = 0;
 | |
| 	}
 | |
| 
 | |
| 	private void tickFluid(BlockPos pos, Fluid fluid) {
 | |
| 		BlockState blockState = this.getBlockState(pos);
 | |
| 		FluidState fluidState = blockState.getFluidState();
 | |
| 		if (fluidState.is(fluid)) {
 | |
| 			fluidState.tick(this, pos, blockState);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	private void tickBlock(BlockPos pos, Block block) {
 | |
| 		BlockState blockState = this.getBlockState(pos);
 | |
| 		if (blockState.is(block)) {
 | |
| 			blockState.tick(this, pos, this.random);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	public void tickNonPassenger(Entity entity) {
 | |
| 		entity.setOldPosAndRot();
 | |
| 		ProfilerFiller profilerFiller = Profiler.get();
 | |
| 		entity.tickCount++;
 | |
| 		profilerFiller.push((Supplier<String>)(() -> BuiltInRegistries.ENTITY_TYPE.getKey(entity.getType()).toString()));
 | |
| 		profilerFiller.incrementCounter("tickNonPassenger");
 | |
| 		entity.tick();
 | |
| 		profilerFiller.pop();
 | |
| 
 | |
| 		for (Entity entity2 : entity.getPassengers()) {
 | |
| 			this.tickPassenger(entity, entity2);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	private void tickPassenger(Entity ridingEntity, Entity passengerEntity) {
 | |
| 		if (passengerEntity.isRemoved() || passengerEntity.getVehicle() != ridingEntity) {
 | |
| 			passengerEntity.stopRiding();
 | |
| 		} else if (passengerEntity instanceof Player || this.entityTickList.contains(passengerEntity)) {
 | |
| 			passengerEntity.setOldPosAndRot();
 | |
| 			passengerEntity.tickCount++;
 | |
| 			ProfilerFiller profilerFiller = Profiler.get();
 | |
| 			profilerFiller.push((Supplier<String>)(() -> BuiltInRegistries.ENTITY_TYPE.getKey(passengerEntity.getType()).toString()));
 | |
| 			profilerFiller.incrementCounter("tickPassenger");
 | |
| 			passengerEntity.rideTick();
 | |
| 			profilerFiller.pop();
 | |
| 
 | |
| 			for (Entity entity : passengerEntity.getPassengers()) {
 | |
| 				this.tickPassenger(passengerEntity, entity);
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	public void updateNeighboursOnBlockSet(BlockPos pos, BlockState state) {
 | |
| 		BlockState blockState = this.getBlockState(pos);
 | |
| 		Block block = blockState.getBlock();
 | |
| 		boolean bl = !state.is(block);
 | |
| 		if (bl) {
 | |
| 			state.affectNeighborsAfterRemoval(this, pos, false);
 | |
| 		}
 | |
| 
 | |
| 		this.updateNeighborsAt(pos, blockState.getBlock());
 | |
| 		if (blockState.hasAnalogOutputSignal()) {
 | |
| 			this.updateNeighbourForOutputSignal(pos, block);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public boolean mayInteract(Entity entity, BlockPos pos) {
 | |
| 		return !(entity instanceof Player player && (this.server.isUnderSpawnProtection(this, pos, player) || !this.getWorldBorder().isWithinBounds(pos)));
 | |
| 	}
 | |
| 
 | |
| 	public void save(@Nullable ProgressListener progress, boolean flush, boolean skipSave) {
 | |
| 		ServerChunkCache serverChunkCache = this.getChunkSource();
 | |
| 		if (!skipSave) {
 | |
| 			if (progress != null) {
 | |
| 				progress.progressStartNoAbort(Component.translatable("menu.savingLevel"));
 | |
| 			}
 | |
| 
 | |
| 			this.saveLevelData(flush);
 | |
| 			if (progress != null) {
 | |
| 				progress.progressStage(Component.translatable("menu.savingChunks"));
 | |
| 			}
 | |
| 
 | |
| 			serverChunkCache.save(flush);
 | |
| 			if (flush) {
 | |
| 				this.entityManager.saveAll();
 | |
| 			} else {
 | |
| 				this.entityManager.autoSave();
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	private void saveLevelData(boolean join) {
 | |
| 		if (this.dragonFight != null) {
 | |
| 			this.server.getWorldData().setEndDragonFightData(this.dragonFight.saveData());
 | |
| 		}
 | |
| 
 | |
| 		DimensionDataStorage dimensionDataStorage = this.getChunkSource().getDataStorage();
 | |
| 		if (join) {
 | |
| 			dimensionDataStorage.saveAndJoin();
 | |
| 		} else {
 | |
| 			dimensionDataStorage.scheduleSave();
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	public <T extends Entity> List<? extends T> getEntities(EntityTypeTest<Entity, T> typeTest, Predicate<? super T> predicate) {
 | |
| 		List<T> list = Lists.<T>newArrayList();
 | |
| 		this.getEntities(typeTest, predicate, list);
 | |
| 		return list;
 | |
| 	}
 | |
| 
 | |
| 	public <T extends Entity> void getEntities(EntityTypeTest<Entity, T> typeTest, Predicate<? super T> predicate, List<? super T> output) {
 | |
| 		this.getEntities(typeTest, predicate, output, Integer.MAX_VALUE);
 | |
| 	}
 | |
| 
 | |
| 	public <T extends Entity> void getEntities(EntityTypeTest<Entity, T> typeTest, Predicate<? super T> predicate, List<? super T> output, int maxResults) {
 | |
| 		this.getEntities().get(typeTest, entity -> {
 | |
| 			if (predicate.test(entity)) {
 | |
| 				output.add(entity);
 | |
| 				if (output.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> predicate) {
 | |
| 		return this.getPlayers(predicate, Integer.MAX_VALUE);
 | |
| 	}
 | |
| 
 | |
| 	public List<ServerPlayer> getPlayers(Predicate<? super ServerPlayer> predicate, int maxResults) {
 | |
| 		List<ServerPlayer> list = Lists.<ServerPlayer>newArrayList();
 | |
| 
 | |
| 		for (ServerPlayer serverPlayer : this.players) {
 | |
| 			if (predicate.test(serverPlayer)) {
 | |
| 				list.add(serverPlayer);
 | |
| 				if (list.size() >= maxResults) {
 | |
| 					return list;
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		return list;
 | |
| 	}
 | |
| 
 | |
| 	@Nullable
 | |
| 	public ServerPlayer getRandomPlayer() {
 | |
| 		List<ServerPlayer> list = this.getPlayers(LivingEntity::isAlive);
 | |
| 		return list.isEmpty() ? null : (ServerPlayer)list.get(this.random.nextInt(list.size()));
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public boolean addFreshEntity(Entity entity) {
 | |
| 		return this.addEntity(entity);
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Used for "unnatural" ways of entities appearing in the world, e.g. summon command, interdimensional teleports
 | |
| 	 */
 | |
| 	public boolean addWithUUID(Entity entity) {
 | |
| 		return this.addEntity(entity);
 | |
| 	}
 | |
| 
 | |
| 	public void addDuringTeleport(Entity entity) {
 | |
| 		if (entity instanceof ServerPlayer serverPlayer) {
 | |
| 			this.addPlayer(serverPlayer);
 | |
| 		} 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 entity = this.getEntity(player.getUUID());
 | |
| 		if (entity != null) {
 | |
| 			LOGGER.warn("Force-added player with duplicate UUID {}", player.getUUID());
 | |
| 			entity.unRide();
 | |
| 			this.removePlayerImmediately((ServerPlayer)entity, Entity.RemovalReason.DISCARDED);
 | |
| 		}
 | |
| 
 | |
| 		this.entityManager.addNewEntity(player);
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Called when an entity is spawned in the world. This includes players.
 | |
| 	 */
 | |
| 	private boolean addEntity(Entity entity) {
 | |
| 		if (entity.isRemoved()) {
 | |
| 			LOGGER.warn("Tried to add entity {} but it was marked as removed already", EntityType.getKey(entity.getType()));
 | |
| 			return false;
 | |
| 		} else {
 | |
| 			return this.entityManager.addNewEntity(entity);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Attempts to summon an entity and it's passengers. They will only be summoned if all entities are unique and not already in queue to be summoned.
 | |
| 	 */
 | |
| 	public boolean tryAddFreshEntityWithPassengers(Entity entity) {
 | |
| 		if (entity.getSelfAndPassengers().map(Entity::getUUID).anyMatch(this.entityManager::isLoaded)) {
 | |
| 			return false;
 | |
| 		} else {
 | |
| 			this.addFreshEntityWithPassengers(entity);
 | |
| 			return true;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	public void unload(LevelChunk chunk) {
 | |
| 		chunk.clearAllBlockEntities();
 | |
| 		chunk.unregisterTickContainerFromLevel(this);
 | |
| 	}
 | |
| 
 | |
| 	public void removePlayerImmediately(ServerPlayer player, Entity.RemovalReason reason) {
 | |
| 		player.remove(reason);
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public void destroyBlockProgress(int breakerId, BlockPos pos, int progress) {
 | |
| 		for (ServerPlayer serverPlayer : this.server.getPlayerList().getPlayers()) {
 | |
| 			if (serverPlayer != null && serverPlayer.level() == this && serverPlayer.getId() != breakerId) {
 | |
| 				double d = pos.getX() - serverPlayer.getX();
 | |
| 				double e = pos.getY() - serverPlayer.getY();
 | |
| 				double f = pos.getZ() - serverPlayer.getZ();
 | |
| 				if (d * d + e * e + f * f < 1024.0) {
 | |
| 					serverPlayer.connection.send(new ClientboundBlockDestructionPacket(breakerId, pos, progress));
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public void playSeededSound(
 | |
| 		@Nullable Entity entity, double x, double y, double z, Holder<SoundEvent> sound, SoundSource source, float volume, float pitch, long seed
 | |
| 	) {
 | |
| 		this.server
 | |
| 			.getPlayerList()
 | |
| 			.broadcast(
 | |
| 				entity instanceof Player player ? player : 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 entity, Entity sourceEntity, Holder<SoundEvent> sound, SoundSource source, float volume, float pitch, long seed) {
 | |
| 		this.server
 | |
| 			.getPlayerList()
 | |
| 			.broadcast(
 | |
| 				entity instanceof Player player ? player : 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 id, BlockPos pos, int data) {
 | |
| 		if (this.getGameRules().getBoolean(GameRules.RULE_GLOBAL_SOUND_EVENTS)) {
 | |
| 			this.server.getPlayerList().getPlayers().forEach(serverPlayer -> {
 | |
| 				Vec3 vec32;
 | |
| 				if (serverPlayer.level() == this) {
 | |
| 					Vec3 vec3 = Vec3.atCenterOf(pos);
 | |
| 					if (serverPlayer.distanceToSqr(vec3) < Mth.square(32)) {
 | |
| 						vec32 = vec3;
 | |
| 					} else {
 | |
| 						Vec3 vec33 = vec3.subtract(serverPlayer.position()).normalize();
 | |
| 						vec32 = serverPlayer.position().add(vec33.scale(32.0));
 | |
| 					}
 | |
| 				} else {
 | |
| 					vec32 = serverPlayer.position();
 | |
| 				}
 | |
| 
 | |
| 				serverPlayer.connection.send(new ClientboundLevelEventPacket(id, BlockPos.containing(vec32), data, true));
 | |
| 			});
 | |
| 		} else {
 | |
| 			this.levelEvent(null, id, pos, data);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public void levelEvent(@Nullable Entity entity, int type, BlockPos pos, int data) {
 | |
| 		this.server
 | |
| 			.getPlayerList()
 | |
| 			.broadcast(
 | |
| 				entity instanceof Player player ? player : 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 pos, GameEvent.Context context) {
 | |
| 		this.gameEventDispatcher.post(gameEvent, pos, context);
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public void sendBlockUpdated(BlockPos pos, BlockState oldState, BlockState newState, int flags) {
 | |
| 		if (this.isUpdatingNavigations) {
 | |
| 			String string = "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 voxelShape = oldState.getCollisionShape(this, pos);
 | |
| 		VoxelShape voxelShape2 = newState.getCollisionShape(this, pos);
 | |
| 		if (Shapes.joinIsNotEmpty(voxelShape, voxelShape2, BooleanOp.NOT_SAME)) {
 | |
| 			List<PathNavigation> list = new ObjectArrayList<>();
 | |
| 
 | |
| 			for (Mob mob : this.navigatingMobs) {
 | |
| 				PathNavigation pathNavigation = mob.getNavigation();
 | |
| 				if (pathNavigation.shouldRecomputePath(pos)) {
 | |
| 					list.add(pathNavigation);
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			try {
 | |
| 				this.isUpdatingNavigations = true;
 | |
| 
 | |
| 				for (PathNavigation pathNavigation2 : list) {
 | |
| 					pathNavigation2.recomputePath();
 | |
| 				}
 | |
| 			} finally {
 | |
| 				this.isUpdatingNavigations = false;
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public void updateNeighborsAt(BlockPos pos, Block block) {
 | |
| 		this.updateNeighborsAt(pos, block, ExperimentalRedstoneUtils.initialOrientation(this, null, null));
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public void updateNeighborsAt(BlockPos pos, Block block, @Nullable Orientation orientation) {
 | |
| 		this.neighborUpdater.updateNeighborsAtExceptFromFacing(pos, block, null, orientation);
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public void updateNeighborsAtExceptFromFacing(BlockPos pos, Block block, Direction facing, @Nullable Orientation orientation) {
 | |
| 		this.neighborUpdater.updateNeighborsAtExceptFromFacing(pos, block, facing, orientation);
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public void neighborChanged(BlockPos pos, Block block, @Nullable Orientation orientation) {
 | |
| 		this.neighborUpdater.neighborChanged(pos, block, orientation);
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public void neighborChanged(BlockState state, BlockPos pos, Block block, @Nullable Orientation orientation, boolean movedByPiston) {
 | |
| 		this.neighborUpdater.neighborChanged(state, pos, block, orientation, movedByPiston);
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public void broadcastEntityEvent(Entity entity, byte state) {
 | |
| 		this.getChunkSource().broadcastAndSend(entity, new ClientboundEntityEventPacket(entity, state));
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public void broadcastDamageEvent(Entity entity, DamageSource damageSource) {
 | |
| 		this.getChunkSource().broadcastAndSend(entity, new ClientboundDamageEventPacket(entity, damageSource));
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Gets the world's chunk provider
 | |
| 	 */
 | |
| 	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 radius,
 | |
| 		boolean fire,
 | |
| 		Level.ExplosionInteraction explosionInteraction,
 | |
| 		ParticleOptions smallExplosionParticles,
 | |
| 		ParticleOptions largeExplosionParticles,
 | |
| 		Holder<SoundEvent> explosionSound
 | |
| 	) {
 | |
| 		Explosion.BlockInteraction blockInteraction = switch (explosionInteraction) {
 | |
| 			case NONE -> Explosion.BlockInteraction.KEEP;
 | |
| 			case BLOCK -> this.getDestroyType(GameRules.RULE_BLOCK_EXPLOSION_DROP_DECAY);
 | |
| 			case MOB -> this.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)
 | |
| 				? this.getDestroyType(GameRules.RULE_MOB_EXPLOSION_DROP_DECAY)
 | |
| 				: Explosion.BlockInteraction.KEEP;
 | |
| 			case TNT -> this.getDestroyType(GameRules.RULE_TNT_EXPLOSION_DROP_DECAY);
 | |
| 			case TRIGGER -> Explosion.BlockInteraction.TRIGGER_BLOCK;
 | |
| 		};
 | |
| 		Vec3 vec3 = new Vec3(x, y, z);
 | |
| 		ServerExplosion serverExplosion = new ServerExplosion(this, source, damageSource, damageCalculator, vec3, radius, fire, blockInteraction);
 | |
| 		serverExplosion.explode();
 | |
| 		ParticleOptions particleOptions = serverExplosion.isSmall() ? smallExplosionParticles : largeExplosionParticles;
 | |
| 
 | |
| 		for (ServerPlayer serverPlayer : this.players) {
 | |
| 			if (serverPlayer.distanceToSqr(vec3) < 4096.0) {
 | |
| 				Optional<Vec3> optional = Optional.ofNullable((Vec3)serverExplosion.getHitPlayers().get(serverPlayer));
 | |
| 				serverPlayer.connection.send(new ClientboundExplodePacket(vec3, optional, particleOptions, explosionSound));
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	private Explosion.BlockInteraction getDestroyType(GameRules.Key<GameRules.BooleanValue> decayGameRule) {
 | |
| 		return this.getGameRules().getBoolean(decayGameRule) ? Explosion.BlockInteraction.DESTROY_WITH_DECAY : Explosion.BlockInteraction.DESTROY;
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public void blockEvent(BlockPos pos, Block block, int eventID, int eventParam) {
 | |
| 		this.blockEvents.add(new BlockEventData(pos, block, eventID, eventParam));
 | |
| 	}
 | |
| 
 | |
| 	private void runBlockEvents() {
 | |
| 		this.blockEventsToReschedule.clear();
 | |
| 
 | |
| 		while (!this.blockEvents.isEmpty()) {
 | |
| 			BlockEventData blockEventData = this.blockEvents.removeFirst();
 | |
| 			if (this.shouldTickBlocksAt(blockEventData.pos())) {
 | |
| 				if (this.doBlockEvent(blockEventData)) {
 | |
| 					this.server
 | |
| 						.getPlayerList()
 | |
| 						.broadcast(
 | |
| 							null,
 | |
| 							blockEventData.pos().getX(),
 | |
| 							blockEventData.pos().getY(),
 | |
| 							blockEventData.pos().getZ(),
 | |
| 							64.0,
 | |
| 							this.dimension(),
 | |
| 							new ClientboundBlockEventPacket(blockEventData.pos(), blockEventData.block(), blockEventData.paramA(), blockEventData.paramB())
 | |
| 						);
 | |
| 				}
 | |
| 			} else {
 | |
| 				this.blockEventsToReschedule.add(blockEventData);
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		this.blockEvents.addAll(this.blockEventsToReschedule);
 | |
| 	}
 | |
| 
 | |
| 	private boolean doBlockEvent(BlockEventData event) {
 | |
| 		BlockState blockState = this.getBlockState(event.pos());
 | |
| 		return blockState.is(event.block()) ? blockState.triggerEvent(this, event.pos(), event.paramA(), event.paramB()) : false;
 | |
| 	}
 | |
| 
 | |
| 	public LevelTicks<Block> getBlockTicks() {
 | |
| 		return this.blockTicks;
 | |
| 	}
 | |
| 
 | |
| 	public LevelTicks<Fluid> getFluidTicks() {
 | |
| 		return this.fluidTicks;
 | |
| 	}
 | |
| 
 | |
| 	@NotNull
 | |
| 	@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 type, double posX, double posY, double posZ, int particleCount, double xOffset, double yOffset, double zOffset, double speed
 | |
| 	) {
 | |
| 		return this.sendParticles(type, false, false, posX, posY, posZ, particleCount, xOffset, yOffset, zOffset, speed);
 | |
| 	}
 | |
| 
 | |
| 	public <T extends ParticleOptions> int sendParticles(
 | |
| 		T type,
 | |
| 		boolean overrideLimiter,
 | |
| 		boolean alwaysShow,
 | |
| 		double posX,
 | |
| 		double posY,
 | |
| 		double posZ,
 | |
| 		int particleCount,
 | |
| 		double xOffset,
 | |
| 		double yOffset,
 | |
| 		double zOffset,
 | |
| 		double speed
 | |
| 	) {
 | |
| 		ClientboundLevelParticlesPacket clientboundLevelParticlesPacket = new ClientboundLevelParticlesPacket(
 | |
| 			type, overrideLimiter, alwaysShow, posX, posY, posZ, (float)xOffset, (float)yOffset, (float)zOffset, (float)speed, particleCount
 | |
| 		);
 | |
| 		int i = 0;
 | |
| 
 | |
| 		for (int j = 0; j < this.players.size(); j++) {
 | |
| 			ServerPlayer serverPlayer = (ServerPlayer)this.players.get(j);
 | |
| 			if (this.sendParticles(serverPlayer, overrideLimiter, posX, posY, posZ, clientboundLevelParticlesPacket)) {
 | |
| 				i++;
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		return i;
 | |
| 	}
 | |
| 
 | |
| 	public <T extends ParticleOptions> boolean sendParticles(
 | |
| 		ServerPlayer player,
 | |
| 		T particle,
 | |
| 		boolean overrideLimiter,
 | |
| 		boolean alwaysShow,
 | |
| 		double posX,
 | |
| 		double posY,
 | |
| 		double posZ,
 | |
| 		int count,
 | |
| 		double xDist,
 | |
| 		double yDist,
 | |
| 		double zDist,
 | |
| 		double maxSpeed
 | |
| 	) {
 | |
| 		Packet<?> packet = new ClientboundLevelParticlesPacket(
 | |
| 			particle, overrideLimiter, alwaysShow, posX, posY, posZ, (float)xDist, (float)yDist, (float)zDist, (float)maxSpeed, count
 | |
| 		);
 | |
| 		return this.sendParticles(player, overrideLimiter, posX, posY, posZ, packet);
 | |
| 	}
 | |
| 
 | |
| 	private boolean sendParticles(ServerPlayer player, boolean longDistance, double posX, double posY, double posZ, Packet<?> packet) {
 | |
| 		if (player.level() != this) {
 | |
| 			return false;
 | |
| 		} else {
 | |
| 			BlockPos blockPos = player.blockPosition();
 | |
| 			if (blockPos.closerToCenterThan(new Vec3(posX, posY, posZ), longDistance ? 512.0 : 32.0)) {
 | |
| 				player.connection.send(packet);
 | |
| 				return true;
 | |
| 			} else {
 | |
| 				return false;
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	@Nullable
 | |
| 	@Override
 | |
| 	public Entity getEntity(int id) {
 | |
| 		return this.getEntities().get(id);
 | |
| 	}
 | |
| 
 | |
| 	@Deprecated
 | |
| 	@Nullable
 | |
| 	public Entity getEntityOrPart(int id) {
 | |
| 		Entity entity = this.getEntities().get(id);
 | |
| 		return entity != null ? entity : this.dragonParts.get(id);
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public Collection<EnderDragonPart> dragonParts() {
 | |
| 		return this.dragonParts.values();
 | |
| 	}
 | |
| 
 | |
| 	@Nullable
 | |
| 	public BlockPos findNearestMapStructure(TagKey<Structure> structureTag, BlockPos pos, int radius, boolean skipExistingChunks) {
 | |
| 		if (!this.server.getWorldData().worldGenOptions().generateStructures()) {
 | |
| 			return null;
 | |
| 		} else {
 | |
| 			Optional<HolderSet.Named<Structure>> optional = this.registryAccess().lookupOrThrow(Registries.STRUCTURE).get(structureTag);
 | |
| 			if (optional.isEmpty()) {
 | |
| 				return null;
 | |
| 			} else {
 | |
| 				Pair<BlockPos, Holder<Structure>> pair = this.getChunkSource()
 | |
| 					.getGenerator()
 | |
| 					.findNearestMapStructure(this, (HolderSet<Structure>)optional.get(), pos, radius, skipExistingChunks);
 | |
| 				return pair != null ? pair.getFirst() : null;
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	@Nullable
 | |
| 	public Pair<BlockPos, Holder<Biome>> findClosestBiome3d(
 | |
| 		Predicate<Holder<Biome>> biomePredicate, BlockPos pos, int radius, int horizontalStep, int verticalStep
 | |
| 	) {
 | |
| 		return this.getChunkSource()
 | |
| 			.getGenerator()
 | |
| 			.getBiomeSource()
 | |
| 			.findClosestBiome3d(pos, radius, horizontalStep, verticalStep, biomePredicate, this.getChunkSource().randomState().sampler(), this);
 | |
| 	}
 | |
| 
 | |
| 	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();
 | |
| 	}
 | |
| 
 | |
| 	@Nullable
 | |
| 	@Override
 | |
| 	public MapItemSavedData getMapData(MapId mapId) {
 | |
| 		return this.getServer().overworld().getDataStorage().get(MapItemSavedData.type(mapId));
 | |
| 	}
 | |
| 
 | |
| 	public void setMapData(MapId mapId, MapItemSavedData data) {
 | |
| 		this.getServer().overworld().getDataStorage().set(MapItemSavedData.type(mapId), data);
 | |
| 	}
 | |
| 
 | |
| 	public MapId getFreeMapId() {
 | |
| 		return this.getServer().overworld().getDataStorage().computeIfAbsent(MapIndex.TYPE).getNextMapId();
 | |
| 	}
 | |
| 
 | |
| 	public void setDefaultSpawnPos(BlockPos pos, float angle) {
 | |
| 		BlockPos blockPos = this.levelData.getSpawnPos();
 | |
| 		float f = this.levelData.getSpawnAngle();
 | |
| 		if (!blockPos.equals(pos) || f != angle) {
 | |
| 			this.levelData.setSpawn(pos, angle);
 | |
| 			this.getServer().getPlayerList().broadcastAll(new ClientboundSetDefaultSpawnPositionPacket(pos, angle));
 | |
| 		}
 | |
| 
 | |
| 		if (this.lastSpawnChunkRadius > 1) {
 | |
| 			this.getChunkSource().removeTicketWithRadius(TicketType.START, new ChunkPos(blockPos), this.lastSpawnChunkRadius);
 | |
| 		}
 | |
| 
 | |
| 		int i = this.getGameRules().getInt(GameRules.RULE_SPAWN_CHUNK_RADIUS) + 1;
 | |
| 		if (i > 1) {
 | |
| 			this.getChunkSource().addTicketWithRadius(TicketType.START, new ChunkPos(pos), i);
 | |
| 		}
 | |
| 
 | |
| 		this.lastSpawnChunkRadius = i;
 | |
| 	}
 | |
| 
 | |
| 	public LongSet getForceLoadedChunks() {
 | |
| 		return this.chunkSource.getForceLoadedChunks();
 | |
| 	}
 | |
| 
 | |
| 	public boolean setChunkForced(int chunkX, int chunkZ, boolean add) {
 | |
| 		boolean bl = this.chunkSource.updateChunkForced(new ChunkPos(chunkX, chunkZ), add);
 | |
| 		if (add && bl) {
 | |
| 			this.getChunk(chunkX, chunkZ);
 | |
| 		}
 | |
| 
 | |
| 		return bl;
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public List<ServerPlayer> players() {
 | |
| 		return this.players;
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public void updatePOIOnBlockStateChange(BlockPos pos, BlockState oldState, BlockState newState) {
 | |
| 		Optional<Holder<PoiType>> optional = PoiTypes.forState(oldState);
 | |
| 		Optional<Holder<PoiType>> optional2 = PoiTypes.forState(newState);
 | |
| 		if (!Objects.equals(optional, optional2)) {
 | |
| 			BlockPos blockPos = pos.immutable();
 | |
| 			optional.ifPresent(holder -> this.getServer().execute(() -> {
 | |
| 				this.getPoiManager().remove(blockPos);
 | |
| 				DebugPackets.sendPoiRemovedPacket(this, blockPos);
 | |
| 			}));
 | |
| 			optional2.ifPresent(holder -> this.getServer().execute(() -> {
 | |
| 				this.getPoiManager().add(blockPos, holder);
 | |
| 				DebugPackets.sendPoiAddedPacket(this, blockPos);
 | |
| 			}));
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	public PoiManager getPoiManager() {
 | |
| 		return this.getChunkSource().getPoiManager();
 | |
| 	}
 | |
| 
 | |
| 	public boolean isVillage(BlockPos pos) {
 | |
| 		return this.isCloseToVillage(pos, 1);
 | |
| 	}
 | |
| 
 | |
| 	public boolean isVillage(SectionPos pos) {
 | |
| 		return this.isVillage(pos.center());
 | |
| 	}
 | |
| 
 | |
| 	public boolean isCloseToVillage(BlockPos pos, int sections) {
 | |
| 		return sections > 6 ? false : this.sectionsToVillage(SectionPos.of(pos)) <= sections;
 | |
| 	}
 | |
| 
 | |
| 	public int sectionsToVillage(SectionPos pos) {
 | |
| 		return this.getPoiManager().sectionsToVillage(pos);
 | |
| 	}
 | |
| 
 | |
| 	public Raids getRaids() {
 | |
| 		return this.raids;
 | |
| 	}
 | |
| 
 | |
| 	@Nullable
 | |
| 	public 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 target, ReputationEventHandler host) {
 | |
| 		host.onReputationEventFrom(type, target);
 | |
| 	}
 | |
| 
 | |
| 	public void saveDebugReport(Path path) throws IOException {
 | |
| 		ChunkMap chunkMap = this.getChunkSource().chunkMap;
 | |
| 		Writer writer = Files.newBufferedWriter(path.resolve("stats.txt"));
 | |
| 
 | |
| 		try {
 | |
| 			writer.write(String.format(Locale.ROOT, "spawning_chunks: %d\n", chunkMap.getDistanceManager().getNaturalSpawnChunkCount()));
 | |
| 			NaturalSpawner.SpawnState spawnState = this.getChunkSource().getLastSpawnState();
 | |
| 			if (spawnState != null) {
 | |
| 				for (Entry<MobCategory> entry : spawnState.getMobCategoryCounts().object2IntEntrySet()) {
 | |
| 					writer.write(String.format(Locale.ROOT, "spawn_count.%s: %d\n", ((MobCategory)entry.getKey()).getName(), entry.getIntValue()));
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			writer.write(String.format(Locale.ROOT, "entities: %s\n", this.entityManager.gatherStats()));
 | |
| 			writer.write(String.format(Locale.ROOT, "block_entity_tickers: %d\n", this.blockEntityTickers.size()));
 | |
| 			writer.write(String.format(Locale.ROOT, "block_ticks: %d\n", this.getBlockTicks().count()));
 | |
| 			writer.write(String.format(Locale.ROOT, "fluid_ticks: %d\n", this.getFluidTicks().count()));
 | |
| 			writer.write("distance_manager: " + chunkMap.getDistanceManager().getDebugStatus() + "\n");
 | |
| 			writer.write(String.format(Locale.ROOT, "pending_tasks: %d\n", this.getChunkSource().getPendingTasksCount()));
 | |
| 		} catch (Throwable var22) {
 | |
| 			if (writer != null) {
 | |
| 				try {
 | |
| 					writer.close();
 | |
| 				} catch (Throwable var16) {
 | |
| 					var22.addSuppressed(var16);
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			throw var22;
 | |
| 		}
 | |
| 
 | |
| 		if (writer != null) {
 | |
| 			writer.close();
 | |
| 		}
 | |
| 
 | |
| 		CrashReport crashReport = new CrashReport("Level dump", new Exception("dummy"));
 | |
| 		this.fillReportDetails(crashReport);
 | |
| 		Writer writer2 = Files.newBufferedWriter(path.resolve("example_crash.txt"));
 | |
| 
 | |
| 		try {
 | |
| 			writer2.write(crashReport.getFriendlyReport(ReportType.TEST));
 | |
| 		} catch (Throwable var21) {
 | |
| 			if (writer2 != null) {
 | |
| 				try {
 | |
| 					writer2.close();
 | |
| 				} catch (Throwable var15) {
 | |
| 					var21.addSuppressed(var15);
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			throw var21;
 | |
| 		}
 | |
| 
 | |
| 		if (writer2 != null) {
 | |
| 			writer2.close();
 | |
| 		}
 | |
| 
 | |
| 		Path path2 = path.resolve("chunks.csv");
 | |
| 		Writer writer3 = Files.newBufferedWriter(path2);
 | |
| 
 | |
| 		try {
 | |
| 			chunkMap.dumpChunks(writer3);
 | |
| 		} catch (Throwable var20) {
 | |
| 			if (writer3 != null) {
 | |
| 				try {
 | |
| 					writer3.close();
 | |
| 				} catch (Throwable var14) {
 | |
| 					var20.addSuppressed(var14);
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			throw var20;
 | |
| 		}
 | |
| 
 | |
| 		if (writer3 != null) {
 | |
| 			writer3.close();
 | |
| 		}
 | |
| 
 | |
| 		Path path3 = path.resolve("entity_chunks.csv");
 | |
| 		Writer writer4 = Files.newBufferedWriter(path3);
 | |
| 
 | |
| 		try {
 | |
| 			this.entityManager.dumpSections(writer4);
 | |
| 		} catch (Throwable var19) {
 | |
| 			if (writer4 != null) {
 | |
| 				try {
 | |
| 					writer4.close();
 | |
| 				} catch (Throwable var13) {
 | |
| 					var19.addSuppressed(var13);
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			throw var19;
 | |
| 		}
 | |
| 
 | |
| 		if (writer4 != null) {
 | |
| 			writer4.close();
 | |
| 		}
 | |
| 
 | |
| 		Path path4 = path.resolve("entities.csv");
 | |
| 		Writer writer5 = Files.newBufferedWriter(path4);
 | |
| 
 | |
| 		try {
 | |
| 			dumpEntities(writer5, this.getEntities().getAll());
 | |
| 		} catch (Throwable var18) {
 | |
| 			if (writer5 != null) {
 | |
| 				try {
 | |
| 					writer5.close();
 | |
| 				} catch (Throwable var12) {
 | |
| 					var18.addSuppressed(var12);
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			throw var18;
 | |
| 		}
 | |
| 
 | |
| 		if (writer5 != null) {
 | |
| 			writer5.close();
 | |
| 		}
 | |
| 
 | |
| 		Path path5 = path.resolve("block_entities.csv");
 | |
| 		Writer writer6 = Files.newBufferedWriter(path5);
 | |
| 
 | |
| 		try {
 | |
| 			this.dumpBlockEntityTickers(writer6);
 | |
| 		} catch (Throwable var17) {
 | |
| 			if (writer6 != null) {
 | |
| 				try {
 | |
| 					writer6.close();
 | |
| 				} catch (Throwable var11) {
 | |
| 					var17.addSuppressed(var11);
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			throw var17;
 | |
| 		}
 | |
| 
 | |
| 		if (writer6 != null) {
 | |
| 			writer6.close();
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	private static void dumpEntities(Writer writer, 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(writer);
 | |
| 
 | |
| 		for (Entity entity : entities) {
 | |
| 			Component component = entity.getCustomName();
 | |
| 			Component component2 = entity.getDisplayName();
 | |
| 			csvOutput.writeRow(
 | |
| 				entity.getX(),
 | |
| 				entity.getY(),
 | |
| 				entity.getZ(),
 | |
| 				entity.getUUID(),
 | |
| 				BuiltInRegistries.ENTITY_TYPE.getKey(entity.getType()),
 | |
| 				entity.isAlive(),
 | |
| 				component2.getString(),
 | |
| 				component != null ? component.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 tickingBlockEntity : this.blockEntityTickers) {
 | |
| 			BlockPos blockPos = tickingBlockEntity.getPos();
 | |
| 			csvOutput.writeRow(blockPos.getX(), blockPos.getY(), blockPos.getZ(), tickingBlockEntity.getType());
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	@VisibleForTesting
 | |
| 	public void clearBlockEvents(BoundingBox boundingBox) {
 | |
| 		this.blockEvents.removeIf(blockEventData -> boundingBox.isInside(blockEventData.pos()));
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public float getShade(Direction direction, boolean shade) {
 | |
| 		return 1.0F;
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Gets an unmodifiable iterator of all loaded entities in the world.
 | |
| 	 */
 | |
| 	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();
 | |
| 	}
 | |
| 
 | |
| 	@Nullable
 | |
| 	public 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(),
 | |
| 			getTypeCount(this.entityManager.getEntityGetter().getAll(), entity -> BuiltInRegistries.ENTITY_TYPE.getKey(entity.getType()).toString()),
 | |
| 			this.blockEntityTickers.size(),
 | |
| 			getTypeCount(this.blockEntityTickers, TickingBlockEntity::getType),
 | |
| 			this.getBlockTicks().count(),
 | |
| 			this.getFluidTicks().count(),
 | |
| 			this.gatherChunkSourceStats()
 | |
| 		);
 | |
| 	}
 | |
| 
 | |
| 	private static <T> String getTypeCount(Iterable<T> objects, Function<T, String> typeGetter) {
 | |
| 		try {
 | |
| 			Object2IntOpenHashMap<String> object2IntOpenHashMap = new Object2IntOpenHashMap<>();
 | |
| 
 | |
| 			for (T object : objects) {
 | |
| 				String string = (String)typeGetter.apply(object);
 | |
| 				object2IntOpenHashMap.addTo(string, 1);
 | |
| 			}
 | |
| 
 | |
| 			return (String)object2IntOpenHashMap.object2IntEntrySet()
 | |
| 				.stream()
 | |
| 				.sorted(Comparator.comparing(Entry::getIntValue).reversed())
 | |
| 				.limit(5L)
 | |
| 				.map(entry -> (String)entry.getKey() + ":" + entry.getIntValue())
 | |
| 				.collect(Collectors.joining(","));
 | |
| 		} catch (Exception var6) {
 | |
| 			return "";
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	protected LevelEntityGetter<Entity> getEntities() {
 | |
| 		return this.entityManager.getEntityGetter();
 | |
| 	}
 | |
| 
 | |
| 	public void addLegacyChunkEntities(Stream<Entity> entities) {
 | |
| 		this.entityManager.addLegacyChunkEntities(entities);
 | |
| 	}
 | |
| 
 | |
| 	public void addWorldGenChunkEntities(Stream<Entity> entities) {
 | |
| 		this.entityManager.addWorldGenChunkEntities(entities);
 | |
| 	}
 | |
| 
 | |
| 	public void startTickingChunk(LevelChunk chunk) {
 | |
| 		chunk.unpackTicks(this.getLevelData().getGameTime());
 | |
| 	}
 | |
| 
 | |
| 	public void onStructureStartsAvailable(ChunkAccess chunk) {
 | |
| 		this.server.execute(() -> this.structureCheck.onStructureLoad(chunk.getPos(), chunk.getAllStarts()));
 | |
| 	}
 | |
| 
 | |
| 	public PathTypeCache getPathTypeCache() {
 | |
| 		return this.pathTypesByPosCache;
 | |
| 	}
 | |
| 
 | |
| 	public void waitForChunkAndEntities(ChunkPos chunkPos, int radius) {
 | |
| 		List<ChunkPos> list = ChunkPos.rangeClosed(chunkPos, radius).toList();
 | |
| 		this.chunkSource.addTicketWithRadius(TicketType.UNKNOWN, chunkPos, radius);
 | |
| 		list.forEach(chunkPosx -> this.getChunk(chunkPosx.x, chunkPosx.z));
 | |
| 		this.server.managedBlock(() -> {
 | |
| 			this.entityManager.processPendingLoads();
 | |
| 
 | |
| 			for (ChunkPos chunkPosx : list) {
 | |
| 				if (!this.areEntitiesLoaded(chunkPosx.toLong())) {
 | |
| 					return false;
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			return true;
 | |
| 		});
 | |
| 	}
 | |
| 
 | |
| 	@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 chunkPos) {
 | |
| 		return this.entityManager.areEntitiesLoaded(chunkPos);
 | |
| 	}
 | |
| 
 | |
| 	public boolean isPositionTickingWithEntitiesLoaded(long chunkPos) {
 | |
| 		return this.areEntitiesLoaded(chunkPos) && this.chunkSource.isPositionTicking(chunkPos);
 | |
| 	}
 | |
| 
 | |
| 	public boolean isPositionEntityTicking(BlockPos pos) {
 | |
| 		return this.entityManager.canPositionTick(pos) && this.chunkSource.chunkMap.getDistanceManager().inEntityTickingRange(ChunkPos.asLong(pos));
 | |
| 	}
 | |
| 
 | |
| 	public boolean areEntitiesActuallyLoadedAndTicking(ChunkPos chunkPos) {
 | |
| 		return this.entityManager.isTicking(chunkPos) && this.entityManager.areEntitiesLoaded(chunkPos.toLong());
 | |
| 	}
 | |
| 
 | |
| 	public boolean anyPlayerCloseEnoughForSpawning(BlockPos pos) {
 | |
| 		return this.anyPlayerCloseEnoughForSpawning(new ChunkPos(pos));
 | |
| 	}
 | |
| 
 | |
| 	public boolean anyPlayerCloseEnoughForSpawning(ChunkPos chunkPos) {
 | |
| 		return this.chunkSource.chunkMap.anyPlayerCloseEnoughForSpawning(chunkPos);
 | |
| 	}
 | |
| 
 | |
| 	public boolean canSpawnEntitiesInChunk(ChunkPos chunkPos) {
 | |
| 		return this.entityManager.canPositionTick(chunkPos) && this.getWorldBorder().isWithinBounds(chunkPos);
 | |
| 	}
 | |
| 
 | |
| 	@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(ResourceLocation location) {
 | |
| 		return this.randomSequences.get(location);
 | |
| 	}
 | |
| 
 | |
| 	public RandomSequences getRandomSequences() {
 | |
| 		return this.randomSequences;
 | |
| 	}
 | |
| 
 | |
| 	public GameRules getGameRules() {
 | |
| 		return this.serverLevelData.getGameRules();
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public CrashReportCategory fillReportDetails(CrashReport report) {
 | |
| 		CrashReportCategory crashReportCategory = super.fillReportDetails(report);
 | |
| 		crashReportCategory.setDetail("Loaded entity count", (CrashReportDetail<String>)(() -> String.valueOf(this.entityManager.count())));
 | |
| 		return crashReportCategory;
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public int getSeaLevel() {
 | |
| 		return this.chunkSource.getGenerator().getSeaLevel();
 | |
| 	}
 | |
| 
 | |
| 	final class EntityCallbacks implements LevelCallback<Entity> {
 | |
| 		public void onCreated(Entity entity) {
 | |
| 			if (entity instanceof WaypointTransmitter waypointTransmitter && waypointTransmitter.isTransmittingWaypoint()) {
 | |
| 				ServerLevel.this.getWaypointManager().trackWaypoint(waypointTransmitter);
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		public void onDestroyed(Entity entity) {
 | |
| 			if (entity instanceof WaypointTransmitter waypointTransmitter) {
 | |
| 				ServerLevel.this.getWaypointManager().untrackWaypoint(waypointTransmitter);
 | |
| 			}
 | |
| 
 | |
| 			ServerLevel.this.getScoreboard().entityRemoved(entity);
 | |
| 		}
 | |
| 
 | |
| 		public void onTickingStart(Entity entity) {
 | |
| 			ServerLevel.this.entityTickList.add(entity);
 | |
| 		}
 | |
| 
 | |
| 		public void onTickingEnd(Entity entity) {
 | |
| 			ServerLevel.this.entityTickList.remove(entity);
 | |
| 		}
 | |
| 
 | |
| 		public void onTrackingStart(Entity entity) {
 | |
| 			ServerLevel.this.getChunkSource().addEntity(entity);
 | |
| 			if (entity instanceof ServerPlayer serverPlayer) {
 | |
| 				ServerLevel.this.players.add(serverPlayer);
 | |
| 				if (serverPlayer.isReceivingWaypoints()) {
 | |
| 					ServerLevel.this.getWaypointManager().addPlayer(serverPlayer);
 | |
| 				}
 | |
| 
 | |
| 				ServerLevel.this.updateSleepingPlayerList();
 | |
| 			}
 | |
| 
 | |
| 			if (entity instanceof WaypointTransmitter waypointTransmitter && waypointTransmitter.isTransmittingWaypoint()) {
 | |
| 				ServerLevel.this.getWaypointManager().trackWaypoint(waypointTransmitter);
 | |
| 			}
 | |
| 
 | |
| 			if (entity instanceof Mob mob) {
 | |
| 				if (ServerLevel.this.isUpdatingNavigations) {
 | |
| 					String string = "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) {
 | |
| 				for (EnderDragonPart enderDragonPart : enderDragon.getSubEntities()) {
 | |
| 					ServerLevel.this.dragonParts.put(enderDragonPart.getId(), enderDragonPart);
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			entity.updateDynamicGameEventListener(DynamicGameEventListener::add);
 | |
| 		}
 | |
| 
 | |
| 		public void onTrackingEnd(Entity entity) {
 | |
| 			ServerLevel.this.getChunkSource().removeEntity(entity);
 | |
| 			if (entity instanceof ServerPlayer serverPlayer) {
 | |
| 				ServerLevel.this.players.remove(serverPlayer);
 | |
| 				ServerLevel.this.getWaypointManager().removePlayer(serverPlayer);
 | |
| 				ServerLevel.this.updateSleepingPlayerList();
 | |
| 			}
 | |
| 
 | |
| 			if (entity instanceof Mob mob) {
 | |
| 				if (ServerLevel.this.isUpdatingNavigations) {
 | |
| 					String string = "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) {
 | |
| 				for (EnderDragonPart enderDragonPart : enderDragon.getSubEntities()) {
 | |
| 					ServerLevel.this.dragonParts.remove(enderDragonPart.getId());
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			entity.updateDynamicGameEventListener(DynamicGameEventListener::remove);
 | |
| 		}
 | |
| 
 | |
| 		public void onSectionChange(Entity entity) {
 | |
| 			entity.updateDynamicGameEventListener(DynamicGameEventListener::move);
 | |
| 		}
 | |
| 	}
 | |
| }
 |