package net.minecraft.server.level; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Queues; import com.google.common.collect.Sets; import com.google.common.collect.ImmutableList.Builder; import com.mojang.datafixers.DataFixer; 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.Long2ByteMap; import it.unimi.dsi.fastutil.longs.Long2ByteOpenHashMap; import it.unimi.dsi.fastutil.longs.Long2LongMap; import it.unimi.dsi.fastutil.longs.Long2LongOpenHashMap; import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap; import it.unimi.dsi.fastutil.longs.LongIterator; import it.unimi.dsi.fastutil.longs.LongLinkedOpenHashSet; import it.unimi.dsi.fastutil.longs.LongOpenHashSet; import it.unimi.dsi.fastutil.longs.LongSet; import it.unimi.dsi.fastutil.longs.Long2ObjectMap.Entry; import java.io.IOException; import java.io.Writer; import java.nio.file.Path; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.Queue; import java.util.Set; import java.util.UUID; import java.util.concurrent.CancellationException; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.BooleanSupplier; import java.util.function.Consumer; import java.util.function.IntConsumer; import java.util.function.IntFunction; import java.util.function.IntSupplier; import java.util.function.Supplier; import net.minecraft.CrashReport; import net.minecraft.CrashReportCategory; import net.minecraft.CrashReportDetail; import net.minecraft.ReportedException; import net.minecraft.Util; import net.minecraft.core.RegistryAccess; import net.minecraft.core.SectionPos; import net.minecraft.core.registries.Registries; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.NbtException; import net.minecraft.network.protocol.Packet; import net.minecraft.network.protocol.game.ClientboundChunksBiomesPacket; import net.minecraft.network.protocol.game.ClientboundSetChunkCacheCenterPacket; import net.minecraft.server.level.ChunkHolder.PlayerProvider; import net.minecraft.server.level.ChunkTrackingView.Positioned; import net.minecraft.server.level.progress.ChunkProgressListener; import net.minecraft.server.network.ServerPlayerConnection; import net.minecraft.util.CsvOutput; import net.minecraft.util.Mth; import net.minecraft.util.StaticCache2D; import net.minecraft.util.TriState; import net.minecraft.util.profiling.Profiler; import net.minecraft.util.profiling.ProfilerFiller; import net.minecraft.util.thread.BlockableEventLoop; import net.minecraft.util.thread.ConsecutiveExecutor; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.EntityType; import net.minecraft.world.entity.ai.village.poi.PoiManager; import net.minecraft.world.entity.boss.EnderDragonPart; import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.GameRules; import net.minecraft.world.level.TicketStorage; import net.minecraft.world.level.chunk.ChunkAccess; import net.minecraft.world.level.chunk.ChunkGenerator; import net.minecraft.world.level.chunk.ChunkGeneratorStructureState; import net.minecraft.world.level.chunk.ImposterProtoChunk; import net.minecraft.world.level.chunk.LevelChunk; import net.minecraft.world.level.chunk.LightChunkGetter; import net.minecraft.world.level.chunk.ProtoChunk; import net.minecraft.world.level.chunk.UpgradeData; import net.minecraft.world.level.chunk.status.ChunkStatus; import net.minecraft.world.level.chunk.status.ChunkStep; import net.minecraft.world.level.chunk.status.ChunkType; import net.minecraft.world.level.chunk.status.WorldGenContext; import net.minecraft.world.level.chunk.storage.ChunkStorage; import net.minecraft.world.level.chunk.storage.RegionStorageInfo; import net.minecraft.world.level.chunk.storage.SerializableChunkData; import net.minecraft.world.level.entity.ChunkStatusUpdateListener; import net.minecraft.world.level.levelgen.NoiseBasedChunkGenerator; import net.minecraft.world.level.levelgen.NoiseGeneratorSettings; import net.minecraft.world.level.levelgen.RandomState; import net.minecraft.world.level.levelgen.structure.StructureStart; import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager; import net.minecraft.world.level.storage.DimensionDataStorage; import net.minecraft.world.level.storage.LevelStorageSource; import net.minecraft.world.phys.Vec3; import org.apache.commons.lang3.mutable.MutableBoolean; import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; public class ChunkMap extends ChunkStorage implements PlayerProvider, GeneratingChunkMap { private static final ChunkResult> UNLOADED_CHUNK_LIST_RESULT = ChunkResult.error("Unloaded chunks found in range"); private static final CompletableFuture>> UNLOADED_CHUNK_LIST_FUTURE = CompletableFuture.completedFuture( UNLOADED_CHUNK_LIST_RESULT ); private static final byte CHUNK_TYPE_REPLACEABLE = -1; private static final byte CHUNK_TYPE_UNKNOWN = 0; private static final byte CHUNK_TYPE_FULL = 1; private static final Logger LOGGER = LogUtils.getLogger(); private static final int CHUNK_SAVED_PER_TICK = 200; private static final int CHUNK_SAVED_EAGERLY_PER_TICK = 20; private static final int EAGER_CHUNK_SAVE_COOLDOWN_IN_MILLIS = 10000; private static final int MAX_ACTIVE_CHUNK_WRITES = 128; public static final int MIN_VIEW_DISTANCE = 2; public static final int MAX_VIEW_DISTANCE = 32; public static final int FORCED_TICKET_LEVEL = ChunkLevel.byStatus(FullChunkStatus.ENTITY_TICKING); /** * Chunks in memory. This should only ever be manipulated by the main thread. */ private final Long2ObjectLinkedOpenHashMap updatingChunkMap = new Long2ObjectLinkedOpenHashMap<>(); /** * Same as {@link #loadedChunks}, but immutable for access from other threads. This should never be mutated. */ private volatile Long2ObjectLinkedOpenHashMap visibleChunkMap = this.updatingChunkMap.clone(); private final Long2ObjectLinkedOpenHashMap pendingUnloads = new Long2ObjectLinkedOpenHashMap<>(); private final List pendingGenerationTasks = new ArrayList(); final ServerLevel level; private final ThreadedLevelLightEngine lightEngine; private final BlockableEventLoop mainThreadExecutor; private final RandomState randomState; private final ChunkGeneratorStructureState chunkGeneratorState; private final Supplier overworldDataStorage; private final TicketStorage ticketStorage; private final PoiManager poiManager; /** * Chunks that have been requested to be unloaded, but haven't been unloaded yet. */ final LongSet toDrop = new LongOpenHashSet(); /** * True if changes have been made to {@link #loadedChunks} and thus a new copy of the collection has to be made into {@link #immutableLoadedChunks}. */ private boolean modified; private final ChunkTaskDispatcher worldgenTaskDispatcher; private final ChunkTaskDispatcher lightTaskDispatcher; private final ChunkProgressListener progressListener; private final ChunkStatusUpdateListener chunkStatusListener; private final ChunkMap.DistanceManager distanceManager; private final AtomicInteger tickingGenerated = new AtomicInteger(); private final String storageName; private final PlayerMap playerMap = new PlayerMap(); private final Int2ObjectMap entityMap = new Int2ObjectOpenHashMap<>(); private final Long2ByteMap chunkTypeCache = new Long2ByteOpenHashMap(); private final Long2LongMap nextChunkSaveTime = new Long2LongOpenHashMap(); private final LongSet chunksToEagerlySave = new LongLinkedOpenHashSet(); private final Queue unloadQueue = Queues.newConcurrentLinkedQueue(); private final AtomicInteger activeChunkWrites = new AtomicInteger(); private int serverViewDistance; private final WorldGenContext worldGenContext; public ChunkMap( ServerLevel level, LevelStorageSource.LevelStorageAccess levelStorageAccess, DataFixer fixerUpper, StructureTemplateManager structureManager, Executor dispatcher, BlockableEventLoop mainThreadExecutor, LightChunkGetter lightChunk, ChunkGenerator generator, ChunkProgressListener progressListener, ChunkStatusUpdateListener chunkStatusListener, Supplier overworldDataStorage, TicketStorage ticketStorage, int serverViewDistance, boolean sync ) { super( new RegionStorageInfo(levelStorageAccess.getLevelId(), level.dimension(), "chunk"), levelStorageAccess.getDimensionPath(level.dimension()).resolve("region"), fixerUpper, sync ); Path path = levelStorageAccess.getDimensionPath(level.dimension()); this.storageName = path.getFileName().toString(); this.level = level; RegistryAccess registryAccess = level.registryAccess(); long l = level.getSeed(); if (generator instanceof NoiseBasedChunkGenerator noiseBasedChunkGenerator) { this.randomState = RandomState.create(noiseBasedChunkGenerator.generatorSettings().value(), registryAccess.lookupOrThrow(Registries.NOISE), l); } else { this.randomState = RandomState.create(NoiseGeneratorSettings.dummy(), registryAccess.lookupOrThrow(Registries.NOISE), l); } this.chunkGeneratorState = generator.createState(registryAccess.lookupOrThrow(Registries.STRUCTURE_SET), this.randomState, l); this.mainThreadExecutor = mainThreadExecutor; ConsecutiveExecutor consecutiveExecutor = new ConsecutiveExecutor(dispatcher, "worldgen"); this.progressListener = progressListener; this.chunkStatusListener = chunkStatusListener; ConsecutiveExecutor consecutiveExecutor2 = new ConsecutiveExecutor(dispatcher, "light"); this.worldgenTaskDispatcher = new ChunkTaskDispatcher(consecutiveExecutor, dispatcher); this.lightTaskDispatcher = new ChunkTaskDispatcher(consecutiveExecutor2, dispatcher); this.lightEngine = new ThreadedLevelLightEngine(lightChunk, this, this.level.dimensionType().hasSkyLight(), consecutiveExecutor2, this.lightTaskDispatcher); this.distanceManager = new ChunkMap.DistanceManager(ticketStorage, dispatcher, mainThreadExecutor); this.overworldDataStorage = overworldDataStorage; this.ticketStorage = ticketStorage; this.poiManager = new PoiManager( new RegionStorageInfo(levelStorageAccess.getLevelId(), level.dimension(), "poi"), path.resolve("poi"), fixerUpper, sync, registryAccess, level.getServer(), level ); this.setServerViewDistance(serverViewDistance); this.worldGenContext = new WorldGenContext(level, generator, structureManager, this.lightEngine, mainThreadExecutor, this::setChunkUnsaved); } private void setChunkUnsaved(ChunkPos chunkPos) { this.chunksToEagerlySave.add(chunkPos.toLong()); } protected ChunkGenerator generator() { return this.worldGenContext.generator(); } protected ChunkGeneratorStructureState generatorState() { return this.chunkGeneratorState; } protected RandomState randomState() { return this.randomState; } /** * Checks if a chunk is within a player's view distance. */ boolean isChunkTracked(ServerPlayer player, int x, int z) { return player.getChunkTrackingView().contains(x, z) && !player.connection.chunkSender.isPending(ChunkPos.asLong(x, z)); } /** * Checks if a chunk is on the edge of the player's view distance. */ private boolean isChunkOnTrackedBorder(ServerPlayer player, int x, int z) { if (!this.isChunkTracked(player, x, z)) { return false; } else { for (int i = -1; i <= 1; i++) { for (int j = -1; j <= 1; j++) { if ((i != 0 || j != 0) && !this.isChunkTracked(player, x + i, z + j)) { return true; } } } return false; } } protected ThreadedLevelLightEngine getLightEngine() { return this.lightEngine; } @Nullable protected ChunkHolder getUpdatingChunkIfPresent(long chunkPos) { return this.updatingChunkMap.get(chunkPos); } @Nullable protected ChunkHolder getVisibleChunkIfPresent(long chunkPos) { return this.visibleChunkMap.get(chunkPos); } protected IntSupplier getChunkQueueLevel(long chunkPos) { return () -> { ChunkHolder chunkHolder = this.getVisibleChunkIfPresent(chunkPos); return chunkHolder == null ? ChunkTaskPriorityQueue.PRIORITY_LEVEL_COUNT - 1 : Math.min(chunkHolder.getQueueLevel(), ChunkTaskPriorityQueue.PRIORITY_LEVEL_COUNT - 1); }; } public String getChunkDebugData(ChunkPos pos) { ChunkHolder chunkHolder = this.getVisibleChunkIfPresent(pos.toLong()); if (chunkHolder == null) { return "null"; } else { String string = chunkHolder.getTicketLevel() + "\n"; ChunkStatus chunkStatus = chunkHolder.getLatestStatus(); ChunkAccess chunkAccess = chunkHolder.getLatestChunk(); if (chunkStatus != null) { string = string + "St: §" + chunkStatus.getIndex() + chunkStatus + "§r\n"; } if (chunkAccess != null) { string = string + "Ch: §" + chunkAccess.getPersistedStatus().getIndex() + chunkAccess.getPersistedStatus() + "§r\n"; } FullChunkStatus fullChunkStatus = chunkHolder.getFullStatus(); string = string + '§' + fullChunkStatus.ordinal() + fullChunkStatus; return string + "§r"; } } private CompletableFuture>> getChunkRangeFuture(ChunkHolder chunkHolder, int range, IntFunction statusGetter) { if (range == 0) { ChunkStatus chunkStatus = (ChunkStatus)statusGetter.apply(0); return chunkHolder.scheduleChunkGenerationTask(chunkStatus, this).thenApply(chunkResult -> chunkResult.map(List::of)); } else { int i = Mth.square(range * 2 + 1); List>> list = new ArrayList(i); ChunkPos chunkPos = chunkHolder.getPos(); for (int j = -range; j <= range; j++) { for (int k = -range; k <= range; k++) { int l = Math.max(Math.abs(k), Math.abs(j)); long m = ChunkPos.asLong(chunkPos.x + k, chunkPos.z + j); ChunkHolder chunkHolder2 = this.getUpdatingChunkIfPresent(m); if (chunkHolder2 == null) { return UNLOADED_CHUNK_LIST_FUTURE; } ChunkStatus chunkStatus2 = (ChunkStatus)statusGetter.apply(l); list.add(chunkHolder2.scheduleChunkGenerationTask(chunkStatus2, this)); } } return Util.sequence(list).thenApply(listx -> { List list2 = new ArrayList(listx.size()); for (ChunkResult chunkResult : listx) { if (chunkResult == null) { throw this.debugFuturesAndCreateReportedException(new IllegalStateException("At least one of the chunk futures were null"), "n/a"); } ChunkAccess chunkAccess = chunkResult.orElse(null); if (chunkAccess == null) { return UNLOADED_CHUNK_LIST_RESULT; } list2.add(chunkAccess); } return ChunkResult.of(list2); }); } } public ReportedException debugFuturesAndCreateReportedException(IllegalStateException exception, String details) { StringBuilder stringBuilder = new StringBuilder(); Consumer consumer = chunkHolder -> chunkHolder.getAllFutures() .forEach( pair -> { ChunkStatus chunkStatus = (ChunkStatus)pair.getFirst(); CompletableFuture> completableFuture = (CompletableFuture>)pair.getSecond(); if (completableFuture != null && completableFuture.isDone() && completableFuture.join() == null) { stringBuilder.append(chunkHolder.getPos()) .append(" - status: ") .append(chunkStatus) .append(" future: ") .append(completableFuture) .append(System.lineSeparator()); } } ); stringBuilder.append("Updating:").append(System.lineSeparator()); this.updatingChunkMap.values().forEach(consumer); stringBuilder.append("Visible:").append(System.lineSeparator()); this.visibleChunkMap.values().forEach(consumer); CrashReport crashReport = CrashReport.forThrowable(exception, "Chunk loading"); CrashReportCategory crashReportCategory = crashReport.addCategory("Chunk loading"); crashReportCategory.setDetail("Details", details); crashReportCategory.setDetail("Futures", stringBuilder); return new ReportedException(crashReport); } public CompletableFuture> prepareEntityTickingChunk(ChunkHolder chunk) { return this.getChunkRangeFuture(chunk, 2, i -> ChunkStatus.FULL).thenApply(chunkResult -> chunkResult.map(list -> (LevelChunk)list.get(list.size() / 2))); } /** * Sets level and loads/unloads chunk. * * @param holder The {@link net.minecraft.server.level.ChunkHolder} of the chunk if it is loaded, and null otherwise. */ @Nullable ChunkHolder updateChunkScheduling(long chunkPos, int newLevel, @Nullable ChunkHolder holder, int oldLevel) { if (!ChunkLevel.isLoaded(oldLevel) && !ChunkLevel.isLoaded(newLevel)) { return holder; } else { if (holder != null) { holder.setTicketLevel(newLevel); } if (holder != null) { if (!ChunkLevel.isLoaded(newLevel)) { this.toDrop.add(chunkPos); } else { this.toDrop.remove(chunkPos); } } if (ChunkLevel.isLoaded(newLevel) && holder == null) { holder = this.pendingUnloads.remove(chunkPos); if (holder != null) { holder.setTicketLevel(newLevel); } else { holder = new ChunkHolder(new ChunkPos(chunkPos), newLevel, this.level, this.lightEngine, this::onLevelChange, this); } this.updatingChunkMap.put(chunkPos, holder); this.modified = true; } return holder; } } private void onLevelChange(ChunkPos chunkPos, IntSupplier queueLevelGetter, int ticketLevel, IntConsumer queueLevelSetter) { this.worldgenTaskDispatcher.onLevelChange(chunkPos, queueLevelGetter, ticketLevel, queueLevelSetter); this.lightTaskDispatcher.onLevelChange(chunkPos, queueLevelGetter, ticketLevel, queueLevelSetter); } @Override public void close() throws IOException { try { this.worldgenTaskDispatcher.close(); this.lightTaskDispatcher.close(); this.poiManager.close(); } finally { super.close(); } } protected void saveAllChunks(boolean flush) { if (flush) { List list = this.visibleChunkMap .values() .stream() .filter(ChunkHolder::wasAccessibleSinceLastSave) .peek(ChunkHolder::refreshAccessibility) .toList(); MutableBoolean mutableBoolean = new MutableBoolean(); do { mutableBoolean.setFalse(); list.stream() .map(chunkHolderx -> { this.mainThreadExecutor.managedBlock(chunkHolderx::isReadyForSaving); return chunkHolderx.getLatestChunk(); }) .filter(chunkAccess -> chunkAccess instanceof ImposterProtoChunk || chunkAccess instanceof LevelChunk) .filter(this::save) .forEach(chunkAccess -> mutableBoolean.setTrue()); } while (mutableBoolean.isTrue()); this.poiManager.flushAll(); this.processUnloads(() -> true); this.flushWorker(); } else { this.nextChunkSaveTime.clear(); long l = Util.getMillis(); for (ChunkHolder chunkHolder : this.visibleChunkMap.values()) { this.saveChunkIfNeeded(chunkHolder, l); } } } protected void tick(BooleanSupplier hasMoreTime) { ProfilerFiller profilerFiller = Profiler.get(); profilerFiller.push("poi"); this.poiManager.tick(hasMoreTime); profilerFiller.popPush("chunk_unload"); if (!this.level.noSave()) { this.processUnloads(hasMoreTime); } profilerFiller.pop(); } public boolean hasWork() { return this.lightEngine.hasLightWork() || !this.pendingUnloads.isEmpty() || !this.updatingChunkMap.isEmpty() || this.poiManager.hasWork() || !this.toDrop.isEmpty() || !this.unloadQueue.isEmpty() || this.worldgenTaskDispatcher.hasWork() || this.lightTaskDispatcher.hasWork() || this.distanceManager.hasTickets(); } private void processUnloads(BooleanSupplier hasMoreTime) { for (LongIterator longIterator = this.toDrop.iterator(); longIterator.hasNext(); longIterator.remove()) { long l = longIterator.nextLong(); ChunkHolder chunkHolder = this.updatingChunkMap.get(l); if (chunkHolder != null) { this.updatingChunkMap.remove(l); this.pendingUnloads.put(l, chunkHolder); this.modified = true; this.scheduleUnload(l, chunkHolder); } } int i = Math.max(0, this.unloadQueue.size() - 2000); Runnable runnable; while ((i > 0 || hasMoreTime.getAsBoolean()) && (runnable = (Runnable)this.unloadQueue.poll()) != null) { i--; runnable.run(); } this.saveChunksEagerly(hasMoreTime); } private void saveChunksEagerly(BooleanSupplier hasMoreTime) { long l = Util.getMillis(); int i = 0; LongIterator longIterator = this.chunksToEagerlySave.iterator(); while (i < 20 && this.activeChunkWrites.get() < 128 && hasMoreTime.getAsBoolean() && longIterator.hasNext()) { long m = longIterator.nextLong(); ChunkHolder chunkHolder = this.visibleChunkMap.get(m); ChunkAccess chunkAccess = chunkHolder != null ? chunkHolder.getLatestChunk() : null; if (chunkAccess == null || !chunkAccess.isUnsaved()) { longIterator.remove(); } else if (this.saveChunkIfNeeded(chunkHolder, l)) { i++; longIterator.remove(); } } } private void scheduleUnload(long chunkPos, ChunkHolder chunkHolder) { CompletableFuture completableFuture = chunkHolder.getSaveSyncFuture(); completableFuture.thenRunAsync(() -> { CompletableFuture completableFuture2 = chunkHolder.getSaveSyncFuture(); if (completableFuture2 != completableFuture) { this.scheduleUnload(chunkPos, chunkHolder); } else { ChunkAccess chunkAccess = chunkHolder.getLatestChunk(); if (this.pendingUnloads.remove(chunkPos, chunkHolder) && chunkAccess != null) { if (chunkAccess instanceof LevelChunk levelChunk) { levelChunk.setLoaded(false); } this.save(chunkAccess); if (chunkAccess instanceof LevelChunk levelChunk) { this.level.unload(levelChunk); } this.lightEngine.updateChunkStatus(chunkAccess.getPos()); this.lightEngine.tryScheduleUpdate(); this.progressListener.onStatusChange(chunkAccess.getPos(), null); this.nextChunkSaveTime.remove(chunkAccess.getPos().toLong()); } } }, this.unloadQueue::add).whenComplete((void_, throwable) -> { if (throwable != null) { LOGGER.error("Failed to save chunk {}", chunkHolder.getPos(), throwable); } }); } protected boolean promoteChunkMap() { if (!this.modified) { return false; } else { this.visibleChunkMap = this.updatingChunkMap.clone(); this.modified = false; return true; } } private CompletableFuture scheduleChunkLoad(ChunkPos chunkPos) { CompletableFuture> completableFuture = this.readChunk(chunkPos).thenApplyAsync(optional -> optional.map(compoundTag -> { SerializableChunkData serializableChunkData = SerializableChunkData.parse(this.level, this.level.registryAccess(), compoundTag); if (serializableChunkData == null) { LOGGER.error("Chunk file at {} is missing level data, skipping", chunkPos); } return serializableChunkData; }), Util.backgroundExecutor().forName("parseChunk")); CompletableFuture completableFuture2 = this.poiManager.prefetch(chunkPos); return completableFuture.thenCombine(completableFuture2, (optional, object) -> optional).thenApplyAsync(optional -> { Profiler.get().incrementCounter("chunkLoad"); if (optional.isPresent()) { ChunkAccess chunkAccess = ((SerializableChunkData)optional.get()).read(this.level, this.poiManager, this.storageInfo(), chunkPos); this.markPosition(chunkPos, chunkAccess.getPersistedStatus().getChunkType()); return chunkAccess; } else { return this.createEmptyChunk(chunkPos); } }, this.mainThreadExecutor).exceptionallyAsync(throwable -> this.handleChunkLoadFailure(throwable, chunkPos), this.mainThreadExecutor); } private ChunkAccess handleChunkLoadFailure(Throwable exception, ChunkPos chunkPos) { Throwable throwable = exception instanceof CompletionException completionException ? completionException.getCause() : exception; Throwable throwable2 = throwable instanceof ReportedException reportedException ? reportedException.getCause() : throwable; boolean bl = throwable2 instanceof Error; boolean bl2 = throwable2 instanceof IOException || throwable2 instanceof NbtException; if (!bl) { if (!bl2) { } this.level.getServer().reportChunkLoadFailure(throwable2, this.storageInfo(), chunkPos); return this.createEmptyChunk(chunkPos); } else { CrashReport crashReport = CrashReport.forThrowable(exception, "Exception loading chunk"); CrashReportCategory crashReportCategory = crashReport.addCategory("Chunk being loaded"); crashReportCategory.setDetail("pos", chunkPos); this.markPositionReplaceable(chunkPos); throw new ReportedException(crashReport); } } private ChunkAccess createEmptyChunk(ChunkPos chunkPos) { this.markPositionReplaceable(chunkPos); return new ProtoChunk(chunkPos, UpgradeData.EMPTY, this.level, this.level.registryAccess().lookupOrThrow(Registries.BIOME), null); } private void markPositionReplaceable(ChunkPos chunkPos) { this.chunkTypeCache.put(chunkPos.toLong(), (byte)-1); } private byte markPosition(ChunkPos chunkPos, ChunkType chunkType) { return this.chunkTypeCache.put(chunkPos.toLong(), (byte)(chunkType == ChunkType.PROTOCHUNK ? -1 : 1)); } @Override public GenerationChunkHolder acquireGeneration(long chunkPos) { ChunkHolder chunkHolder = this.updatingChunkMap.get(chunkPos); chunkHolder.increaseGenerationRefCount(); return chunkHolder; } @Override public void releaseGeneration(GenerationChunkHolder chunk) { chunk.decreaseGenerationRefCount(); } @Override public CompletableFuture applyStep(GenerationChunkHolder chunk, ChunkStep step, StaticCache2D cache) { ChunkPos chunkPos = chunk.getPos(); if (step.targetStatus() == ChunkStatus.EMPTY) { return this.scheduleChunkLoad(chunkPos); } else { try { GenerationChunkHolder generationChunkHolder = cache.get(chunkPos.x, chunkPos.z); ChunkAccess chunkAccess = generationChunkHolder.getChunkIfPresentUnchecked(step.targetStatus().getParent()); if (chunkAccess == null) { throw new IllegalStateException("Parent chunk missing"); } else { CompletableFuture completableFuture = step.apply(this.worldGenContext, cache, chunkAccess); this.progressListener.onStatusChange(chunkPos, step.targetStatus()); return completableFuture; } } catch (Exception var8) { var8.getStackTrace(); CrashReport crashReport = CrashReport.forThrowable(var8, "Exception generating new chunk"); CrashReportCategory crashReportCategory = crashReport.addCategory("Chunk to be generated"); crashReportCategory.setDetail("Status being generated", (CrashReportDetail)(() -> step.targetStatus().getName())); crashReportCategory.setDetail("Location", String.format(Locale.ROOT, "%d,%d", chunkPos.x, chunkPos.z)); crashReportCategory.setDetail("Position hash", ChunkPos.asLong(chunkPos.x, chunkPos.z)); crashReportCategory.setDetail("Generator", this.generator()); this.mainThreadExecutor.execute(() -> { throw new ReportedException(crashReport); }); throw new ReportedException(crashReport); } } } @Override public ChunkGenerationTask scheduleGenerationTask(ChunkStatus targetStatus, ChunkPos pos) { ChunkGenerationTask chunkGenerationTask = ChunkGenerationTask.create(this, targetStatus, pos); this.pendingGenerationTasks.add(chunkGenerationTask); return chunkGenerationTask; } private void runGenerationTask(ChunkGenerationTask task) { GenerationChunkHolder generationChunkHolder = task.getCenter(); this.worldgenTaskDispatcher.submit(() -> { CompletableFuture completableFuture = task.runUntilWait(); if (completableFuture != null) { completableFuture.thenRun(() -> this.runGenerationTask(task)); } }, generationChunkHolder.getPos().toLong(), generationChunkHolder::getQueueLevel); } @Override public void runGenerationTasks() { this.pendingGenerationTasks.forEach(this::runGenerationTask); this.pendingGenerationTasks.clear(); } public CompletableFuture> prepareTickingChunk(ChunkHolder holder) { CompletableFuture>> completableFuture = this.getChunkRangeFuture(holder, 1, i -> ChunkStatus.FULL); CompletableFuture> completableFuture2 = completableFuture.thenApplyAsync(chunkResult -> chunkResult.map(list -> { LevelChunk levelChunk = (LevelChunk)list.get(list.size() / 2); levelChunk.postProcessGeneration(this.level); this.level.startTickingChunk(levelChunk); CompletableFuture completableFuturex = holder.getSendSyncFuture(); if (completableFuturex.isDone()) { this.onChunkReadyToSend(holder, levelChunk); } else { completableFuturex.thenAcceptAsync(object -> this.onChunkReadyToSend(holder, levelChunk), this.mainThreadExecutor); } return levelChunk; }), this.mainThreadExecutor); completableFuture2.handle((chunkResult, throwable) -> { this.tickingGenerated.getAndIncrement(); return null; }); return completableFuture2; } private void onChunkReadyToSend(ChunkHolder chunkHolder, LevelChunk chunk) { ChunkPos chunkPos = chunk.getPos(); for (ServerPlayer serverPlayer : this.playerMap.getAllPlayers()) { if (serverPlayer.getChunkTrackingView().contains(chunkPos)) { markChunkPendingToSend(serverPlayer, chunk); } } this.level.getChunkSource().onChunkReadyToSend(chunkHolder); } public CompletableFuture> prepareAccessibleChunk(ChunkHolder chunk) { return this.getChunkRangeFuture(chunk, 1, ChunkLevel::getStatusAroundFullChunk) .thenApply(chunkResult -> chunkResult.map(list -> (LevelChunk)list.get(list.size() / 2))); } public int getTickingGenerated() { return this.tickingGenerated.get(); } private boolean saveChunkIfNeeded(ChunkHolder chunk, long gametime) { if (chunk.wasAccessibleSinceLastSave() && chunk.isReadyForSaving()) { ChunkAccess chunkAccess = chunk.getLatestChunk(); if (!(chunkAccess instanceof ImposterProtoChunk) && !(chunkAccess instanceof LevelChunk)) { return false; } else if (!chunkAccess.isUnsaved()) { return false; } else { long l = chunkAccess.getPos().toLong(); long m = this.nextChunkSaveTime.getOrDefault(l, -1L); if (gametime < m) { return false; } else { boolean bl = this.save(chunkAccess); chunk.refreshAccessibility(); if (bl) { this.nextChunkSaveTime.put(l, gametime + 10000L); } return bl; } } } else { return false; } } private boolean save(ChunkAccess chunk) { this.poiManager.flush(chunk.getPos()); if (!chunk.tryMarkSaved()) { return false; } else { ChunkPos chunkPos = chunk.getPos(); try { ChunkStatus chunkStatus = chunk.getPersistedStatus(); if (chunkStatus.getChunkType() != ChunkType.LEVELCHUNK) { if (this.isExistingChunkFull(chunkPos)) { return false; } if (chunkStatus == ChunkStatus.EMPTY && chunk.getAllStarts().values().stream().noneMatch(StructureStart::isValid)) { return false; } } Profiler.get().incrementCounter("chunkSave"); this.activeChunkWrites.incrementAndGet(); SerializableChunkData serializableChunkData = SerializableChunkData.copyOf(this.level, chunk); CompletableFuture completableFuture = CompletableFuture.supplyAsync(serializableChunkData::write, Util.backgroundExecutor()); this.write(chunkPos, completableFuture::join).handle((void_, throwable) -> { if (throwable != null) { this.level.getServer().reportChunkSaveFailure(throwable, this.storageInfo(), chunkPos); } this.activeChunkWrites.decrementAndGet(); return null; }); this.markPosition(chunkPos, chunkStatus.getChunkType()); return true; } catch (Exception var6) { this.level.getServer().reportChunkSaveFailure(var6, this.storageInfo(), chunkPos); return false; } } } private boolean isExistingChunkFull(ChunkPos chunkPos) { byte b = this.chunkTypeCache.get(chunkPos.toLong()); if (b != 0) { return b == 1; } else { CompoundTag compoundTag; try { compoundTag = (CompoundTag)((Optional)this.readChunk(chunkPos).join()).orElse(null); if (compoundTag == null) { this.markPositionReplaceable(chunkPos); return false; } } catch (Exception var5) { LOGGER.error("Failed to read chunk {}", chunkPos, var5); this.markPositionReplaceable(chunkPos); return false; } ChunkType chunkType = SerializableChunkData.getChunkStatusFromTag(compoundTag).getChunkType(); return this.markPosition(chunkPos, chunkType) == 1; } } protected void setServerViewDistance(int viewDistance) { int i = Mth.clamp(viewDistance, 2, 32); if (i != this.serverViewDistance) { this.serverViewDistance = i; this.distanceManager.updatePlayerTickets(this.serverViewDistance); for (ServerPlayer serverPlayer : this.playerMap.getAllPlayers()) { this.updateChunkTracking(serverPlayer); } } } int getPlayerViewDistance(ServerPlayer player) { return Mth.clamp(player.requestedViewDistance(), 2, this.serverViewDistance); } private void markChunkPendingToSend(ServerPlayer player, ChunkPos chunkPos) { LevelChunk levelChunk = this.getChunkToSend(chunkPos.toLong()); if (levelChunk != null) { markChunkPendingToSend(player, levelChunk); } } private static void markChunkPendingToSend(ServerPlayer player, LevelChunk chunk) { player.connection.chunkSender.markChunkPendingToSend(chunk); } private static void dropChunk(ServerPlayer player, ChunkPos chunkPos) { player.connection.chunkSender.dropChunk(player, chunkPos); } @Nullable public LevelChunk getChunkToSend(long chunkPos) { ChunkHolder chunkHolder = this.getVisibleChunkIfPresent(chunkPos); return chunkHolder == null ? null : chunkHolder.getChunkToSend(); } public int size() { return this.visibleChunkMap.size(); } public net.minecraft.server.level.DistanceManager getDistanceManager() { return this.distanceManager; } /** * Gets an unmodifiable iterable of all loaded chunks in the chunk manager */ protected Iterable getChunks() { return Iterables.unmodifiableIterable(this.visibleChunkMap.values()); } void dumpChunks(Writer writer) throws IOException { CsvOutput csvOutput = CsvOutput.builder() .addColumn("x") .addColumn("z") .addColumn("level") .addColumn("in_memory") .addColumn("status") .addColumn("full_status") .addColumn("accessible_ready") .addColumn("ticking_ready") .addColumn("entity_ticking_ready") .addColumn("ticket") .addColumn("spawning") .addColumn("block_entity_count") .addColumn("ticking_ticket") .addColumn("ticking_level") .addColumn("block_ticks") .addColumn("fluid_ticks") .build(writer); for (Entry entry : this.visibleChunkMap.long2ObjectEntrySet()) { long l = entry.getLongKey(); ChunkPos chunkPos = new ChunkPos(l); ChunkHolder chunkHolder = (ChunkHolder)entry.getValue(); Optional optional = Optional.ofNullable(chunkHolder.getLatestChunk()); Optional optional2 = optional.flatMap(chunkAccess -> chunkAccess instanceof LevelChunk ? Optional.of((LevelChunk)chunkAccess) : Optional.empty()); csvOutput.writeRow( chunkPos.x, chunkPos.z, chunkHolder.getTicketLevel(), optional.isPresent(), optional.map(ChunkAccess::getPersistedStatus).orElse(null), optional2.map(LevelChunk::getFullStatus).orElse(null), printFuture(chunkHolder.getFullChunkFuture()), printFuture(chunkHolder.getTickingChunkFuture()), printFuture(chunkHolder.getEntityTickingChunkFuture()), this.ticketStorage.getTicketDebugString(l, false), this.anyPlayerCloseEnoughForSpawning(chunkPos), optional2.map(levelChunk -> levelChunk.getBlockEntities().size()).orElse(0), this.ticketStorage.getTicketDebugString(l, true), this.distanceManager.getChunkLevel(l, true), optional2.map(levelChunk -> levelChunk.getBlockTicks().count()).orElse(0), optional2.map(levelChunk -> levelChunk.getFluidTicks().count()).orElse(0) ); } } private static String printFuture(CompletableFuture> future) { try { ChunkResult chunkResult = (ChunkResult)future.getNow(null); if (chunkResult != null) { return chunkResult.isSuccess() ? "done" : "unloaded"; } else { return "not completed"; } } catch (CompletionException var2) { return "failed " + var2.getCause().getMessage(); } catch (CancellationException var3) { return "cancelled"; } } private CompletableFuture> readChunk(ChunkPos pos) { return this.read(pos).thenApplyAsync(optional -> optional.map(this::upgradeChunkTag), Util.backgroundExecutor().forName("upgradeChunk")); } private CompoundTag upgradeChunkTag(CompoundTag tag) { return this.upgradeChunkTag(this.level.dimension(), this.overworldDataStorage, tag, this.generator().getTypeNameForDataFixer()); } void collectSpawningChunks(List output) { LongIterator longIterator = this.distanceManager.getSpawnCandidateChunks(); while (longIterator.hasNext()) { ChunkHolder chunkHolder = this.visibleChunkMap.get(longIterator.nextLong()); if (chunkHolder != null) { LevelChunk levelChunk = chunkHolder.getTickingChunk(); if (levelChunk != null && this.anyPlayerCloseEnoughForSpawningInternal(chunkHolder.getPos())) { output.add(levelChunk); } } } } void forEachBlockTickingChunk(Consumer action) { this.distanceManager.forEachEntityTickingChunk(l -> { ChunkHolder chunkHolder = this.visibleChunkMap.get(l); if (chunkHolder != null) { LevelChunk levelChunk = chunkHolder.getTickingChunk(); if (levelChunk != null) { action.accept(levelChunk); } } }); } boolean anyPlayerCloseEnoughForSpawning(ChunkPos chunkPos) { TriState triState = this.distanceManager.hasPlayersNearby(chunkPos.toLong()); return triState == TriState.DEFAULT ? this.anyPlayerCloseEnoughForSpawningInternal(chunkPos) : triState.toBoolean(true); } private boolean anyPlayerCloseEnoughForSpawningInternal(ChunkPos chunkPos) { for (ServerPlayer serverPlayer : this.playerMap.getAllPlayers()) { if (this.playerIsCloseEnoughForSpawning(serverPlayer, chunkPos)) { return true; } } return false; } public List getPlayersCloseForSpawning(ChunkPos chunkPos) { long l = chunkPos.toLong(); if (!this.distanceManager.hasPlayersNearby(l).toBoolean(true)) { return List.of(); } else { Builder builder = ImmutableList.builder(); for (ServerPlayer serverPlayer : this.playerMap.getAllPlayers()) { if (this.playerIsCloseEnoughForSpawning(serverPlayer, chunkPos)) { builder.add(serverPlayer); } } return builder.build(); } } private boolean playerIsCloseEnoughForSpawning(ServerPlayer player, ChunkPos chunkPos) { if (player.isSpectator()) { return false; } else { double d = euclideanDistanceSquared(chunkPos, player.position()); return d < 16384.0; } } private static double euclideanDistanceSquared(ChunkPos chunkPos, Vec3 pos) { double d = SectionPos.sectionToBlockCoord(chunkPos.x, 8); double e = SectionPos.sectionToBlockCoord(chunkPos.z, 8); double f = d - pos.x; double g = e - pos.z; return f * f + g * g; } private boolean skipPlayer(ServerPlayer player) { return player.isSpectator() && !this.level.getGameRules().getBoolean(GameRules.RULE_SPECTATORSGENERATECHUNKS); } void updatePlayerStatus(ServerPlayer player, boolean track) { boolean bl = this.skipPlayer(player); boolean bl2 = this.playerMap.ignoredOrUnknown(player); if (track) { this.playerMap.addPlayer(player, bl); this.updatePlayerPos(player); if (!bl) { this.distanceManager.addPlayer(SectionPos.of(player), player); } player.setChunkTrackingView(ChunkTrackingView.EMPTY); this.updateChunkTracking(player); } else { SectionPos sectionPos = player.getLastSectionPos(); this.playerMap.removePlayer(player); if (!bl2) { this.distanceManager.removePlayer(sectionPos, player); } this.applyChunkTrackingView(player, ChunkTrackingView.EMPTY); } } private void updatePlayerPos(ServerPlayer player) { SectionPos sectionPos = SectionPos.of(player); player.setLastSectionPos(sectionPos); } public void move(ServerPlayer player) { for (ChunkMap.TrackedEntity trackedEntity : this.entityMap.values()) { if (trackedEntity.entity == player) { trackedEntity.updatePlayers(this.level.players()); } else { trackedEntity.updatePlayer(player); } } SectionPos sectionPos = player.getLastSectionPos(); SectionPos sectionPos2 = SectionPos.of(player); boolean bl = this.playerMap.ignored(player); boolean bl2 = this.skipPlayer(player); boolean bl3 = sectionPos.asLong() != sectionPos2.asLong(); if (bl3 || bl != bl2) { this.updatePlayerPos(player); if (!bl) { this.distanceManager.removePlayer(sectionPos, player); } if (!bl2) { this.distanceManager.addPlayer(sectionPos2, player); } if (!bl && bl2) { this.playerMap.ignorePlayer(player); } if (bl && !bl2) { this.playerMap.unIgnorePlayer(player); } this.updateChunkTracking(player); } } private void updateChunkTracking(ServerPlayer player) { ChunkPos chunkPos = player.chunkPosition(); int i = this.getPlayerViewDistance(player); if (!(player.getChunkTrackingView() instanceof Positioned positioned && positioned.center().equals(chunkPos) && positioned.viewDistance() == i)) { this.applyChunkTrackingView(player, ChunkTrackingView.of(chunkPos, i)); } } private void applyChunkTrackingView(ServerPlayer player, ChunkTrackingView chunkTrackingView) { if (player.level() == this.level) { ChunkTrackingView chunkTrackingView2 = player.getChunkTrackingView(); if (chunkTrackingView instanceof Positioned positioned && !(chunkTrackingView2 instanceof Positioned positioned2 && positioned2.center().equals(positioned.center()))) { player.connection.send(new ClientboundSetChunkCacheCenterPacket(positioned.center().x, positioned.center().z)); } ChunkTrackingView.difference( chunkTrackingView2, chunkTrackingView, chunkPos -> this.markChunkPendingToSend(player, chunkPos), chunkPos -> dropChunk(player, chunkPos) ); player.setChunkTrackingView(chunkTrackingView); } } @Override public List getPlayers(ChunkPos pos, boolean boundaryOnly) { Set set = this.playerMap.getAllPlayers(); Builder builder = ImmutableList.builder(); for (ServerPlayer serverPlayer : set) { if (boundaryOnly && this.isChunkOnTrackedBorder(serverPlayer, pos.x, pos.z) || !boundaryOnly && this.isChunkTracked(serverPlayer, pos.x, pos.z)) { builder.add(serverPlayer); } } return builder.build(); } protected void addEntity(Entity entity) { if (!(entity instanceof EnderDragonPart)) { EntityType entityType = entity.getType(); int i = entityType.clientTrackingRange() * 16; if (i != 0) { int j = entityType.updateInterval(); if (this.entityMap.containsKey(entity.getId())) { throw (IllegalStateException)Util.pauseInIde(new IllegalStateException("Entity is already tracked!")); } else { ChunkMap.TrackedEntity trackedEntity = new ChunkMap.TrackedEntity(entity, i, j, entityType.trackDeltas()); this.entityMap.put(entity.getId(), trackedEntity); trackedEntity.updatePlayers(this.level.players()); if (entity instanceof ServerPlayer serverPlayer) { this.updatePlayerStatus(serverPlayer, true); for (ChunkMap.TrackedEntity trackedEntity2 : this.entityMap.values()) { if (trackedEntity2.entity != serverPlayer) { trackedEntity2.updatePlayer(serverPlayer); } } } } } } } protected void removeEntity(Entity entity) { if (entity instanceof ServerPlayer serverPlayer) { this.updatePlayerStatus(serverPlayer, false); for (ChunkMap.TrackedEntity trackedEntity : this.entityMap.values()) { trackedEntity.removePlayer(serverPlayer); } } ChunkMap.TrackedEntity trackedEntity2 = this.entityMap.remove(entity.getId()); if (trackedEntity2 != null) { trackedEntity2.broadcastRemoved(); } } protected void tick() { for (ServerPlayer serverPlayer : this.playerMap.getAllPlayers()) { this.updateChunkTracking(serverPlayer); } List list = Lists.newArrayList(); List list2 = this.level.players(); for (ChunkMap.TrackedEntity trackedEntity : this.entityMap.values()) { SectionPos sectionPos = trackedEntity.lastSectionPos; SectionPos sectionPos2 = SectionPos.of(trackedEntity.entity); boolean bl = !Objects.equals(sectionPos, sectionPos2); if (bl) { trackedEntity.updatePlayers(list2); Entity entity = trackedEntity.entity; if (entity instanceof ServerPlayer) { list.add((ServerPlayer)entity); } trackedEntity.lastSectionPos = sectionPos2; } if (bl || this.distanceManager.inEntityTickingRange(sectionPos2.chunk().toLong())) { trackedEntity.serverEntity.sendChanges(); } } if (!list.isEmpty()) { for (ChunkMap.TrackedEntity trackedEntity : this.entityMap.values()) { trackedEntity.updatePlayers(list); } } } public void broadcast(Entity entity, Packet packet) { ChunkMap.TrackedEntity trackedEntity = this.entityMap.get(entity.getId()); if (trackedEntity != null) { trackedEntity.broadcast(packet); } } protected void broadcastAndSend(Entity entity, Packet packet) { ChunkMap.TrackedEntity trackedEntity = this.entityMap.get(entity.getId()); if (trackedEntity != null) { trackedEntity.broadcastAndSend(packet); } } public void resendBiomesForChunks(List chunks) { Map> map = new HashMap(); for (ChunkAccess chunkAccess : chunks) { ChunkPos chunkPos = chunkAccess.getPos(); LevelChunk levelChunk2; if (chunkAccess instanceof LevelChunk levelChunk) { levelChunk2 = levelChunk; } else { levelChunk2 = this.level.getChunk(chunkPos.x, chunkPos.z); } for (ServerPlayer serverPlayer : this.getPlayers(chunkPos, false)) { ((List)map.computeIfAbsent(serverPlayer, serverPlayerx -> new ArrayList())).add(levelChunk2); } } map.forEach((serverPlayerx, list) -> serverPlayerx.connection.send(ClientboundChunksBiomesPacket.forChunks(list))); } protected PoiManager getPoiManager() { return this.poiManager; } public String getStorageName() { return this.storageName; } void onFullChunkStatusChange(ChunkPos chunkPos, FullChunkStatus fullChunkStatus) { this.chunkStatusListener.onChunkStatusChange(chunkPos, fullChunkStatus); } public void waitForLightBeforeSending(ChunkPos chunkPos, int range) { int i = range + 1; ChunkPos.rangeClosed(chunkPos, i).forEach(chunkPosx -> { ChunkHolder chunkHolder = this.getVisibleChunkIfPresent(chunkPosx.toLong()); if (chunkHolder != null) { chunkHolder.addSendDependency(this.lightEngine.waitForPendingTasks(chunkPosx.x, chunkPosx.z)); } }); } class DistanceManager extends net.minecraft.server.level.DistanceManager { protected DistanceManager(final TicketStorage ticketStorage, final Executor dispatcher, final Executor mainThreadExecutor) { super(ticketStorage, dispatcher, mainThreadExecutor); } @Override protected boolean isChunkToRemove(long chunkPos) { return ChunkMap.this.toDrop.contains(chunkPos); } @Nullable @Override protected ChunkHolder getChunk(long chunkPos) { return ChunkMap.this.getUpdatingChunkIfPresent(chunkPos); } @Nullable @Override protected ChunkHolder updateChunkScheduling(long chunkPos, int newLevel, @Nullable ChunkHolder holder, int oldLevel) { return ChunkMap.this.updateChunkScheduling(chunkPos, newLevel, holder, oldLevel); } } class TrackedEntity { final ServerEntity serverEntity; final Entity entity; private final int range; SectionPos lastSectionPos; private final Set seenBy = Sets.newIdentityHashSet(); public TrackedEntity(final Entity entity, final int range, final int updateInterval, final boolean trackDelta) { this.serverEntity = new ServerEntity(ChunkMap.this.level, entity, updateInterval, trackDelta, this::broadcast, this::broadcastIgnorePlayers); this.entity = entity; this.range = range; this.lastSectionPos = SectionPos.of(entity); } public boolean equals(Object object) { return object instanceof ChunkMap.TrackedEntity ? ((ChunkMap.TrackedEntity)object).entity.getId() == this.entity.getId() : false; } public int hashCode() { return this.entity.getId(); } public void broadcast(Packet packet) { for (ServerPlayerConnection serverPlayerConnection : this.seenBy) { serverPlayerConnection.send(packet); } } public void broadcastIgnorePlayers(Packet packet, List ignoredPlayers) { for (ServerPlayerConnection serverPlayerConnection : this.seenBy) { if (!ignoredPlayers.contains(serverPlayerConnection.getPlayer().getUUID())) { serverPlayerConnection.send(packet); } } } public void broadcastAndSend(Packet packet) { this.broadcast(packet); if (this.entity instanceof ServerPlayer) { ((ServerPlayer)this.entity).connection.send(packet); } } public void broadcastRemoved() { for (ServerPlayerConnection serverPlayerConnection : this.seenBy) { this.serverEntity.removePairing(serverPlayerConnection.getPlayer()); } } public void removePlayer(ServerPlayer player) { if (this.seenBy.remove(player.connection)) { this.serverEntity.removePairing(player); } } public void updatePlayer(ServerPlayer player) { if (player != this.entity) { Vec3 vec3 = player.position().subtract(this.entity.position()); int i = ChunkMap.this.getPlayerViewDistance(player); double d = Math.min(this.getEffectiveRange(), i * 16); double e = vec3.x * vec3.x + vec3.z * vec3.z; double f = d * d; boolean bl = e <= f && this.entity.broadcastToPlayer(player) && ChunkMap.this.isChunkTracked(player, this.entity.chunkPosition().x, this.entity.chunkPosition().z); if (bl) { if (this.seenBy.add(player.connection)) { this.serverEntity.addPairing(player); } } else if (this.seenBy.remove(player.connection)) { this.serverEntity.removePairing(player); } } } private int scaledRange(int trackingDistance) { return ChunkMap.this.level.getServer().getScaledTrackingDistance(trackingDistance); } private int getEffectiveRange() { int i = this.range; for (Entity entity : this.entity.getIndirectPassengers()) { int j = entity.getType().clientTrackingRange() * 16; if (j > i) { i = j; } } return this.scaledRange(i); } public void updatePlayers(List playersList) { for (ServerPlayer serverPlayer : playersList) { this.updatePlayer(serverPlayer); } } } }