diff --git a/src/main/java/dan200/computercraft/client/proxy/ComputerCraftProxyClient.java b/src/main/java/dan200/computercraft/client/proxy/ComputerCraftProxyClient.java index a0f21fa59..ca7f86112 100644 --- a/src/main/java/dan200/computercraft/client/proxy/ComputerCraftProxyClient.java +++ b/src/main/java/dan200/computercraft/client/proxy/ComputerCraftProxyClient.java @@ -14,7 +14,7 @@ import dan200.computercraft.client.render.TileEntityMonitorRenderer; import dan200.computercraft.client.render.TileEntityTurtleRenderer; import dan200.computercraft.client.render.TurtleModelLoader; import dan200.computercraft.client.render.TurtlePlayerRenderer; -import dan200.computercraft.fabric.events.ClientUnloadWorldEvent; +import dan200.computercraft.fabric.events.ComputerCraftCustomEvents; import dan200.computercraft.shared.Registry; import dan200.computercraft.shared.common.ContainerHeldItem; import dan200.computercraft.shared.common.IColouredItem; @@ -23,6 +23,7 @@ import dan200.computercraft.shared.computer.inventory.ContainerComputerBase; import dan200.computercraft.shared.computer.inventory.ContainerViewComputer; import dan200.computercraft.shared.peripheral.diskdrive.ContainerDiskDrive; import dan200.computercraft.shared.peripheral.monitor.ClientMonitor; +import dan200.computercraft.shared.peripheral.monitor.MonitorWatcher; import dan200.computercraft.shared.peripheral.printer.ContainerPrinter; import dan200.computercraft.shared.pocket.items.ItemPocketComputer; import dan200.computercraft.shared.turtle.inventory.ContainerTurtle; @@ -60,7 +61,7 @@ public final class ComputerCraftProxyClient implements ClientModInitializer } } ); - ClientUnloadWorldEvent.EVENT.register( () -> ClientMonitor.destroyAll() ); + ComputerCraftCustomEvents.CLIENT_UNLOAD_WORLD_EVENT.register( () -> ClientMonitor.destroyAll() ); // Config ClientLifecycleEvents.CLIENT_STARTED.register( Config::clientStarted ); @@ -70,6 +71,7 @@ public final class ComputerCraftProxyClient implements ClientModInitializer public void onInitializeClient() { FrameInfo.init(); + MonitorWatcher.init(); registerContainers(); // While turtles themselves are not transparent, their upgrades may be. diff --git a/src/main/java/dan200/computercraft/fabric/events/ClientUnloadWorldEvent.java b/src/main/java/dan200/computercraft/fabric/events/ClientUnloadWorldEvent.java deleted file mode 100644 index ce687d947..000000000 --- a/src/main/java/dan200/computercraft/fabric/events/ClientUnloadWorldEvent.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * This file is part of ComputerCraft - http://www.computercraft.info - * Copyright Daniel Ratcliffe, 2011-2021. Do not distribute without permission. - * Send enquiries to dratcliffe@gmail.com - */ -package dan200.computercraft.fabric.events; - -import net.fabricmc.fabric.api.event.Event; -import net.fabricmc.fabric.api.event.EventFactory; - -@FunctionalInterface -public interface ClientUnloadWorldEvent -{ - Event EVENT = EventFactory.createArrayBacked( ClientUnloadWorldEvent.class, - callbacks -> () -> { - for( ClientUnloadWorldEvent callback : callbacks ) - { - callback.onClientUnloadWorld(); - } - } ); - - void onClientUnloadWorld(); -} diff --git a/src/main/java/dan200/computercraft/fabric/events/ComputerCraftCustomEvents.java b/src/main/java/dan200/computercraft/fabric/events/ComputerCraftCustomEvents.java new file mode 100644 index 000000000..623ffde15 --- /dev/null +++ b/src/main/java/dan200/computercraft/fabric/events/ComputerCraftCustomEvents.java @@ -0,0 +1,42 @@ +/* + * This file is part of ComputerCraft - http://www.computercraft.info + * Copyright Daniel Ratcliffe, 2011-2021. Do not distribute without permission. + * Send enquiries to dratcliffe@gmail.com + */ +package dan200.computercraft.fabric.events; + +import net.fabricmc.fabric.api.event.Event; +import net.fabricmc.fabric.api.event.EventFactory; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.level.ChunkPos; + +public final class ComputerCraftCustomEvents +{ + public static final Event CLIENT_UNLOAD_WORLD_EVENT = EventFactory.createArrayBacked( ClientUnloadWorld.class, + callbacks -> () -> { + for( ClientUnloadWorld callback : callbacks ) + { + callback.onClientUnloadWorld(); + } + } ); + + public static final Event SERVER_PLAYER_LOADED_CHUNK_EVENT = EventFactory.createArrayBacked( ServerPlayerLoadedChunk.class, + callbacks -> ( serverPlayer, chunkPos ) -> { + for( ServerPlayerLoadedChunk callback : callbacks ) + { + callback.onServerPlayerLoadedChunk( serverPlayer, chunkPos ); + } + } ); + + @FunctionalInterface + public interface ClientUnloadWorld + { + void onClientUnloadWorld(); + } + + @FunctionalInterface + public interface ServerPlayerLoadedChunk + { + void onServerPlayerLoadedChunk( ServerPlayer player, ChunkPos chunkPos ); + } +} diff --git a/src/main/java/dan200/computercraft/fabric/mixin/MixinChunkMap.java b/src/main/java/dan200/computercraft/fabric/mixin/MixinChunkMap.java new file mode 100644 index 000000000..c8f35a5ec --- /dev/null +++ b/src/main/java/dan200/computercraft/fabric/mixin/MixinChunkMap.java @@ -0,0 +1,52 @@ +/* + * This file is part of ComputerCraft - http://www.computercraft.info + * Copyright Daniel Ratcliffe, 2011-2021. Do not distribute without permission. + * Send enquiries to dratcliffe@gmail.com + */ +package dan200.computercraft.fabric.mixin; + +import dan200.computercraft.shared.peripheral.monitor.MonitorWatcher; +import net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket; +import net.minecraft.server.level.ChunkMap; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.level.chunk.LevelChunk; +import org.apache.commons.lang3.mutable.MutableObject; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin( ChunkMap.class ) +public class MixinChunkMap +{ + @Final + @Shadow + ServerLevel level; + + /* + * This mixin mimics the logic of Forge's ChunkWatchEvent.Watch but I don't believe it behaves as expected. Instead + * of firing once when the player initially come in server view distance of a chunk, it fires every time + * the chunk is checked against the player's view distance. This continually happens every tick that the player + * moves (or even rotates in place). + */ + + // @Inject( method = "updateChunkTracking", at = @At( value = "HEAD" ) ) + // public void updateChunkTracking( ServerPlayer serverPlayer, ChunkPos chunkPos, MutableObject mutableObject, boolean bl, boolean bl2, CallbackInfo ci ) + // { + // if( serverPlayer.level == this.level && bl ) + // { + // ComputerCraftCustomEvents.SERVER_PLAYER_LOADED_CHUNK_EVENT.invoker().onServerPlayerLoadedChunk( serverPlayer, chunkPos ); + // } + // } + + // This version behaves as expected in my testing. + @Inject( method = "playerLoadedChunk", at = @At( value = "HEAD" ) ) + private void playerLoadedChunk( ServerPlayer serverPlayer, MutableObject mutableObject, LevelChunk levelChunk, CallbackInfo ci ) + { + MonitorWatcher.onWatch( serverPlayer, levelChunk.getPos() ); + } + +} diff --git a/src/main/java/dan200/computercraft/fabric/mixin/MixinMinecraft.java b/src/main/java/dan200/computercraft/fabric/mixin/MixinMinecraft.java index dd2e82c0d..1c7c0b892 100644 --- a/src/main/java/dan200/computercraft/fabric/mixin/MixinMinecraft.java +++ b/src/main/java/dan200/computercraft/fabric/mixin/MixinMinecraft.java @@ -6,7 +6,7 @@ package dan200.computercraft.fabric.mixin; import dan200.computercraft.client.FrameInfo; -import dan200.computercraft.fabric.events.ClientUnloadWorldEvent; +import dan200.computercraft.fabric.events.ComputerCraftCustomEvents; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.screens.Screen; import net.minecraft.client.multiplayer.ClientLevel; @@ -27,12 +27,12 @@ public abstract class MixinMinecraft @Inject( method = "clearLevel(Lnet/minecraft/client/gui/screens/Screen;)V", at = @At( "RETURN" ) ) private void disconnectAfter( Screen screen, CallbackInfo info ) { - ClientUnloadWorldEvent.EVENT.invoker().onClientUnloadWorld(); + ComputerCraftCustomEvents.CLIENT_UNLOAD_WORLD_EVENT.invoker().onClientUnloadWorld(); } @Inject( method = "setLevel", at = @At( "RETURN" ) ) private void setLevel( ClientLevel world, CallbackInfo info ) { - ClientUnloadWorldEvent.EVENT.invoker().onClientUnloadWorld(); + ComputerCraftCustomEvents.CLIENT_UNLOAD_WORLD_EVENT.invoker().onClientUnloadWorld(); } } diff --git a/src/main/java/dan200/computercraft/shared/network/NetworkHandler.java b/src/main/java/dan200/computercraft/shared/network/NetworkHandler.java index cde68c6e0..63b8fd672 100644 --- a/src/main/java/dan200/computercraft/shared/network/NetworkHandler.java +++ b/src/main/java/dan200/computercraft/shared/network/NetworkHandler.java @@ -25,6 +25,7 @@ import net.minecraft.network.protocol.game.ClientboundCustomPayloadPacket; import net.minecraft.network.protocol.game.ServerboundCustomPayloadPacket; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerChunkCache; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.entity.player.Player; import net.minecraft.world.level.Level; @@ -32,6 +33,7 @@ import net.minecraft.world.level.chunk.LevelChunk; import net.minecraft.world.phys.Vec3; import java.util.function.BiConsumer; +import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Supplier; @@ -122,13 +124,8 @@ public final class NetworkHandler public static void sendToAllTracking( NetworkMessage packet, LevelChunk chunk ) { - for( Player player : chunk.getLevel().players() ) - { - if( chunk.getLevel().dimension().location() == player.getCommandSenderWorld().dimension().location() && player.chunkPosition().equals( chunk.getPos() ) ) - { - ((ServerPlayer) player).connection.send( new ClientboundCustomPayloadPacket( ID, encode( packet ) ) ); - } - } + Consumer sender = p -> p.connection.send( new ClientboundCustomPayloadPacket( ID, encode( packet ) ) ); + ((ServerChunkCache)chunk.getLevel().getChunkSource()).chunkMap.getPlayers( chunk.getPos(), false ).forEach( sender ); } /** diff --git a/src/main/java/dan200/computercraft/shared/peripheral/monitor/MonitorWatcher.java b/src/main/java/dan200/computercraft/shared/peripheral/monitor/MonitorWatcher.java index 607a9d8fe..76d2e4dc5 100644 --- a/src/main/java/dan200/computercraft/shared/peripheral/monitor/MonitorWatcher.java +++ b/src/main/java/dan200/computercraft/shared/peripheral/monitor/MonitorWatcher.java @@ -6,6 +6,7 @@ package dan200.computercraft.shared.peripheral.monitor; import dan200.computercraft.ComputerCraft; +import dan200.computercraft.fabric.events.ComputerCraftCustomEvents; import dan200.computercraft.shared.network.NetworkHandler; import dan200.computercraft.shared.network.client.MonitorClientMessage; import dan200.computercraft.shared.network.client.TerminalState; @@ -14,7 +15,10 @@ import net.minecraft.core.BlockPos; import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.chunk.ChunkStatus; import net.minecraft.world.level.chunk.LevelChunk; import java.util.ArrayDeque; @@ -31,11 +35,8 @@ public final class MonitorWatcher public static void init() { - ServerTickEvents.END_SERVER_TICK.register( server -> { - onTick( server ); - } ); - - + ServerTickEvents.END_SERVER_TICK.register( MonitorWatcher::onTick ); + ComputerCraftCustomEvents.SERVER_PLAYER_LOADED_CHUNK_EVENT.register( MonitorWatcher::onWatch ); } static void enqueue( TileMonitor monitor ) @@ -47,24 +48,23 @@ public final class MonitorWatcher watching.add( monitor ); } - // public static void onWatch( ChunkWatchEvent.Watch event ) - // { - // ChunkPos chunkPos = event.getPos(); - // LevelChunk chunk = (LevelChunk) event.getWorld().getChunk( chunkPos.x, chunkPos.z, ChunkStatus.FULL, false ); - // if( chunk == null ) return; - // - // for( BlockEntity te : chunk.getBlockEntities().values() ) - // { - // // Find all origin monitors who are not already on the queue. - // if( !(te instanceof TileMonitor monitor) ) continue; - // - // ServerMonitor serverMonitor = getMonitor( monitor ); - // if( serverMonitor == null || monitor.enqueued ) continue; - // - // // The chunk hasn't been sent to the client yet, so we can't send an update. Do it on tick end. - // playerUpdates.add( new PlayerUpdate( event.getPlayer(), monitor ) ); - // } - // } + public static void onWatch( ServerPlayer serverPlayer, ChunkPos chunkPos ) + { + LevelChunk chunk = (LevelChunk) serverPlayer.getLevel().getChunk( chunkPos.x, chunkPos.z, ChunkStatus.FULL, false ); + if( chunk == null ) return; + + for( BlockEntity te : chunk.getBlockEntities().values() ) + { + // Find all origin monitors who are not already on the queue. + if( !(te instanceof TileMonitor monitor) ) continue; + + ServerMonitor serverMonitor = getMonitor( monitor ); + if( serverMonitor == null || monitor.enqueued ) continue; + + // The chunk hasn't been sent to the client yet, so we can't send an update. Do it on tick end. + playerUpdates.add( new PlayerUpdate( serverPlayer, monitor ) ); + } + } public static void onTick( MinecraftServer server ) { diff --git a/src/main/resources/computercraft.mixins.json b/src/main/resources/computercraft.mixins.json index 8acb52d3e..da52bc5ee 100644 --- a/src/main/resources/computercraft.mixins.json +++ b/src/main/resources/computercraft.mixins.json @@ -7,6 +7,7 @@ "MinecraftServerAccess", "MixinBlock", + "MixinChunkMap", "MixinEntity", "MixinLevel", "MixinMatrix4f",