/* * Decompiled with CFR 0.152. * * Could not load the following classes: * it.unimi.dsi.fastutil.objects.ObjectOpenHashSet * org.jspecify.annotations.Nullable */ package net.minecraft.util.debug; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import java.util.HashMap; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.UUID; import java.util.function.BiConsumer; import net.minecraft.core.BlockPos; import net.minecraft.core.SectionPos; import net.minecraft.network.protocol.Packet; import net.minecraft.network.protocol.game.ClientGamePacketListener; import net.minecraft.network.protocol.game.ClientboundDebugBlockValuePacket; import net.minecraft.network.protocol.game.ClientboundDebugChunkValuePacket; import net.minecraft.network.protocol.game.ClientboundDebugEntityValuePacket; import net.minecraft.server.level.ChunkMap; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; import net.minecraft.util.Unit; import net.minecraft.util.debug.DebugPoiInfo; import net.minecraft.util.debug.DebugSubscription; import net.minecraft.util.debug.DebugSubscriptions; import net.minecraft.util.debug.DebugValueSource; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.ai.village.poi.PoiManager; import net.minecraft.world.entity.ai.village.poi.PoiRecord; import net.minecraft.world.entity.player.Player; import net.minecraft.world.level.ChunkPos; import org.jspecify.annotations.Nullable; public abstract class TrackingDebugSynchronizer { protected final DebugSubscription subscription; private final Set subscribedPlayers = new ObjectOpenHashSet(); public TrackingDebugSynchronizer(DebugSubscription subscription) { this.subscription = subscription; } public final void tick(ServerLevel level) { for (ServerPlayer player : level.players()) { boolean wasSubscribed = this.subscribedPlayers.contains(player.getUUID()); boolean isSubscribed = player.debugSubscriptions().contains(this.subscription); if (isSubscribed == wasSubscribed) continue; if (isSubscribed) { this.addSubscriber(player); continue; } this.subscribedPlayers.remove(player.getUUID()); } this.subscribedPlayers.removeIf(id -> level.getPlayerByUUID((UUID)id) == null); if (!this.subscribedPlayers.isEmpty()) { this.pollAndSendUpdates(level); } } private void addSubscriber(ServerPlayer player) { this.subscribedPlayers.add(player.getUUID()); player.getChunkTrackingView().forEach(chunkPos -> { if (!player.connection.chunkSender.isPending(chunkPos.toLong())) { this.startTrackingChunk(player, (ChunkPos)chunkPos); } }); player.level().getChunkSource().chunkMap.forEachEntityTrackedBy(player, entity -> this.startTrackingEntity(player, (Entity)entity)); } protected final void sendToPlayersTrackingChunk(ServerLevel level, ChunkPos trackedChunk, Packet packet) { ChunkMap chunkMap = level.getChunkSource().chunkMap; for (UUID playerId : this.subscribedPlayers) { ServerPlayer player; Player player2 = level.getPlayerByUUID(playerId); if (!(player2 instanceof ServerPlayer) || !chunkMap.isChunkTracked(player = (ServerPlayer)player2, trackedChunk.x, trackedChunk.z)) continue; player.connection.send(packet); } } protected final void sendToPlayersTrackingEntity(ServerLevel level, Entity trackedEntity, Packet packet) { ChunkMap chunkMap = level.getChunkSource().chunkMap; chunkMap.sendToTrackingPlayersFiltered(trackedEntity, packet, player -> this.subscribedPlayers.contains(player.getUUID())); } public final void startTrackingChunk(ServerPlayer player, ChunkPos chunkPos) { if (this.subscribedPlayers.contains(player.getUUID())) { this.sendInitialChunk(player, chunkPos); } } public final void startTrackingEntity(ServerPlayer player, Entity entity) { if (this.subscribedPlayers.contains(player.getUUID())) { this.sendInitialEntity(player, entity); } } protected void clear() { } protected void pollAndSendUpdates(ServerLevel level) { } protected void sendInitialChunk(ServerPlayer player, ChunkPos chunkPos) { } protected void sendInitialEntity(ServerPlayer player, Entity entity) { } public static class VillageSectionSynchronizer extends TrackingDebugSynchronizer { public VillageSectionSynchronizer() { super(DebugSubscriptions.VILLAGE_SECTIONS); } @Override protected void sendInitialChunk(ServerPlayer player, ChunkPos chunkPos) { ServerLevel level = player.level(); PoiManager poiManager = level.getPoiManager(); poiManager.getInChunk(t -> true, chunkPos, PoiManager.Occupancy.ANY).forEach(record -> { SectionPos centerSection = SectionPos.of(record.getPos()); VillageSectionSynchronizer.forEachVillageSectionUpdate(level, centerSection, (sectionPos, isVillage) -> { BlockPos sectionBlockPos = sectionPos.center(); player.connection.send(new ClientboundDebugBlockValuePacket(sectionBlockPos, this.subscription.packUpdate(isVillage != false ? Unit.INSTANCE : null))); }); }); } public void onPoiAdded(ServerLevel level, PoiRecord record) { this.sendVillageSectionsPacket(level, record.getPos()); } public void onPoiRemoved(ServerLevel level, BlockPos poiPos) { this.sendVillageSectionsPacket(level, poiPos); } private void sendVillageSectionsPacket(ServerLevel level, BlockPos poiPos) { VillageSectionSynchronizer.forEachVillageSectionUpdate(level, SectionPos.of(poiPos), (sectionPos, isVillage) -> { BlockPos sectionBlockPos = sectionPos.center(); if (isVillage.booleanValue()) { this.sendToPlayersTrackingChunk(level, new ChunkPos(sectionBlockPos), new ClientboundDebugBlockValuePacket(sectionBlockPos, this.subscription.packUpdate(Unit.INSTANCE))); } else { this.sendToPlayersTrackingChunk(level, new ChunkPos(sectionBlockPos), new ClientboundDebugBlockValuePacket(sectionBlockPos, this.subscription.emptyUpdate())); } }); } private static void forEachVillageSectionUpdate(ServerLevel level, SectionPos centerSection, BiConsumer consumer) { for (int offsetZ = -1; offsetZ <= 1; ++offsetZ) { for (int offsetX = -1; offsetX <= 1; ++offsetX) { for (int offsetY = -1; offsetY <= 1; ++offsetY) { SectionPos sectionPos = centerSection.offset(offsetX, offsetY, offsetZ); if (level.isVillage(sectionPos.center())) { consumer.accept(sectionPos, true); continue; } consumer.accept(sectionPos, false); } } } } } public static class PoiSynchronizer extends TrackingDebugSynchronizer { public PoiSynchronizer() { super(DebugSubscriptions.POIS); } @Override protected void sendInitialChunk(ServerPlayer player, ChunkPos chunkPos) { ServerLevel level = player.level(); PoiManager poiManager = level.getPoiManager(); poiManager.getInChunk(t -> true, chunkPos, PoiManager.Occupancy.ANY).forEach(record -> player.connection.send(new ClientboundDebugBlockValuePacket(record.getPos(), this.subscription.packUpdate(new DebugPoiInfo((PoiRecord)record))))); } public void onPoiAdded(ServerLevel level, PoiRecord record) { this.sendToPlayersTrackingChunk(level, new ChunkPos(record.getPos()), new ClientboundDebugBlockValuePacket(record.getPos(), this.subscription.packUpdate(new DebugPoiInfo(record)))); } public void onPoiRemoved(ServerLevel level, BlockPos poiPos) { this.sendToPlayersTrackingChunk(level, new ChunkPos(poiPos), new ClientboundDebugBlockValuePacket(poiPos, this.subscription.emptyUpdate())); } public void onPoiTicketCountChanged(ServerLevel level, BlockPos poiPos) { this.sendToPlayersTrackingChunk(level, new ChunkPos(poiPos), new ClientboundDebugBlockValuePacket(poiPos, this.subscription.packUpdate(level.getPoiManager().getDebugPoiInfo(poiPos)))); } } private static class ValueSource { private final DebugValueSource.ValueGetter getter; private @Nullable T lastSyncedValue; private ValueSource(DebugValueSource.ValueGetter getter) { this.getter = getter; } public @Nullable DebugSubscription.Update pollUpdate(DebugSubscription subscription) { T newValue = this.getter.get(); if (!Objects.equals(newValue, this.lastSyncedValue)) { this.lastSyncedValue = newValue; return subscription.packUpdate(newValue); } return null; } } public static class SourceSynchronizer extends TrackingDebugSynchronizer { private final Map> chunkSources = new HashMap>(); private final Map> blockEntitySources = new HashMap>(); private final Map> entitySources = new HashMap>(); public SourceSynchronizer(DebugSubscription subscription) { super(subscription); } @Override protected void clear() { this.chunkSources.clear(); this.blockEntitySources.clear(); this.entitySources.clear(); } @Override protected void pollAndSendUpdates(ServerLevel level) { DebugSubscription.Update update; for (Map.Entry> entry : this.chunkSources.entrySet()) { update = entry.getValue().pollUpdate(this.subscription); if (update == null) continue; ChunkPos chunkPos = entry.getKey(); this.sendToPlayersTrackingChunk(level, chunkPos, new ClientboundDebugChunkValuePacket(chunkPos, update)); } for (Map.Entry> entry : this.blockEntitySources.entrySet()) { update = entry.getValue().pollUpdate(this.subscription); if (update == null) continue; BlockPos blockPos = (BlockPos)entry.getKey(); ChunkPos chunkPos = new ChunkPos(blockPos); this.sendToPlayersTrackingChunk(level, chunkPos, new ClientboundDebugBlockValuePacket(blockPos, update)); } for (Map.Entry> entry : this.entitySources.entrySet()) { update = entry.getValue().pollUpdate(this.subscription); if (update == null) continue; Entity entity = Objects.requireNonNull(level.getEntity((UUID)entry.getKey())); this.sendToPlayersTrackingEntity(level, entity, new ClientboundDebugEntityValuePacket(entity.getId(), update)); } } public void registerChunk(ChunkPos chunkPos, DebugValueSource.ValueGetter getter) { this.chunkSources.put(chunkPos, new ValueSource(getter)); } public void registerBlockEntity(BlockPos blockPos, DebugValueSource.ValueGetter getter) { this.blockEntitySources.put(blockPos, new ValueSource(getter)); } public void registerEntity(UUID entityId, DebugValueSource.ValueGetter getter) { this.entitySources.put(entityId, new ValueSource(getter)); } public void dropChunk(ChunkPos chunkPos) { this.chunkSources.remove(chunkPos); this.blockEntitySources.keySet().removeIf(chunkPos::contains); } public void dropBlockEntity(ServerLevel level, BlockPos blockPos) { ValueSource source = this.blockEntitySources.remove(blockPos); if (source != null) { ChunkPos chunkPos = new ChunkPos(blockPos); this.sendToPlayersTrackingChunk(level, chunkPos, new ClientboundDebugBlockValuePacket(blockPos, this.subscription.emptyUpdate())); } } public void dropEntity(Entity entity) { this.entitySources.remove(entity.getUUID()); } @Override protected void sendInitialChunk(ServerPlayer player, ChunkPos chunkPos) { ValueSource chunkSource = this.chunkSources.get(chunkPos); if (chunkSource != null && chunkSource.lastSyncedValue != null) { player.connection.send(new ClientboundDebugChunkValuePacket(chunkPos, this.subscription.packUpdate(chunkSource.lastSyncedValue))); } for (Map.Entry> entry : this.blockEntitySources.entrySet()) { BlockPos blockPos; Object lastValue = entry.getValue().lastSyncedValue; if (lastValue == null || !chunkPos.contains(blockPos = entry.getKey())) continue; player.connection.send(new ClientboundDebugBlockValuePacket(blockPos, this.subscription.packUpdate(lastValue))); } } @Override protected void sendInitialEntity(ServerPlayer player, Entity entity) { ValueSource source = this.entitySources.get(entity.getUUID()); if (source != null && source.lastSyncedValue != null) { player.connection.send(new ClientboundDebugEntityValuePacket(entity.getId(), this.subscription.packUpdate(source.lastSyncedValue))); } } } }