Line 31: |
Line 31: |
| * The first parameter is the discriminator for the packet. This is a per-channel unique ID for the packet. We recommend you use a local variable to hold the ID, and then call registerMessage using <code><nowiki>id++</nowiki></code>. This will guarantee 100% unique IDs. | | * The first parameter is the discriminator for the packet. This is a per-channel unique ID for the packet. We recommend you use a local variable to hold the ID, and then call registerMessage using <code><nowiki>id++</nowiki></code>. This will guarantee 100% unique IDs. |
| * The second parameter is the actual packet class <code><nowiki>MSG</nowiki></code>. | | * The second parameter is the actual packet class <code><nowiki>MSG</nowiki></code>. |
− | * The third parameter is a <code><nowiki>BiConsumer<MSG, PacketBuffer></nowiki></code> responsible for encoding the message into the provided <code><nowiki>PacketBuffer</nowiki></code> | + | * The third parameter is a <code><nowiki>BiConsumer<MSG, FriendlyByteBuf></nowiki></code> responsible for encoding the message into the provided <code><nowiki>FriendlyByteBuf</nowiki></code> |
− | * The fourth parameter is a <code><nowiki>Function<PacketBuffer, MSG></nowiki></code> responsible for decoding the message from the provided <code><nowiki>PacketBuffer</nowiki></code> | + | * The fourth parameter is a <code><nowiki>Function<FriendlyByteBuf, MSG></nowiki></code> responsible for decoding the message from the provided <code><nowiki>FriendlyByteBuf</nowiki></code> |
| * The final parameter is a <code><nowiki>BiConsumer<MSG, Supplier<NetworkEvent.Context>></nowiki></code> responsible for handling the message itself | | * The final parameter is a <code><nowiki>BiConsumer<MSG, Supplier<NetworkEvent.Context>></nowiki></code> responsible for handling the message itself |
| | | |
− | The last three parameters can be method references to either static or instance methods in Java. Remember that an instance method <code><nowiki>MSG.encode(PacketBuffer)</nowiki></code> still satisfies <code><nowiki>BiConsumer<MSG, PacketBuffer></nowiki></code>, the <code><nowiki>MSG</nowiki></code> simply becomes the implicit first argument. | + | The last three parameters can be method references to either static or instance methods in Java. Remember that an instance method <code><nowiki>MSG.encode(FriendlyByteBuf)</nowiki></code> still satisfies <code><nowiki>BiConsumer<MSG, FriendlyByteBuf></nowiki></code>, the <code><nowiki>MSG</nowiki></code> simply becomes the implicit first argument. |
| | | |
| == Handling Packets == | | == Handling Packets == |
Line 45: |
Line 45: |
| ctx.get().enqueueWork(() -> { | | ctx.get().enqueueWork(() -> { |
| // Work that needs to be threadsafe (most work) | | // Work that needs to be threadsafe (most work) |
− | ServerPlayerEntity sender = ctx.get().getSender(); // the client that sent this packet | + | ServerPlayer sender = ctx.get().getSender(); // the client that sent this packet |
| // do stuff | | // do stuff |
| }); | | }); |
Line 52: |
Line 52: |
| </syntaxhighlight> | | </syntaxhighlight> |
| | | |
− | Note the presence of <code><nowiki>setPacketHandled</nowiki></code>, which used to tell the network system that the packet has successfully completed handling. | + | Packets sent from the server to the client should be handled in another class and wrapped via <code>DistExecutor#unsafeRunWhenOn</code>. |
| + | |
| + | <syntaxhighlight lang="java"> |
| + | // In Packet class |
| + | public static void handle(MyClientMessage msg, Supplier<NetworkEvent.Context> ctx) { |
| + | ctx.get().enqueueWork(() -> |
| + | // Make sure it's only executed on the physical client |
| + | DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> () -> ClientPacketHandlerClass.handlePacket(msg, ctx)) |
| + | ); |
| + | ctx.get().setPacketHandled(true); |
| + | } |
| + | |
| + | // In ClientPacketHandlerClass |
| + | public static void handlePacket(MyClientMessage msg, Supplier<NetworkEvent.Context> ctx) { |
| + | // Do stuff |
| + | } |
| + | </syntaxhighlight> |
| + | |
| + | Note the presence of <code><nowiki>#setPacketHandled</nowiki></code>, which used to tell the network system that the packet has successfully completed handling. |
| | | |
| {{Colored box|title=Alert|content=Packets are by default handled on the network thread. | | {{Colored box|title=Alert|content=Packets are by default handled on the network thread. |
| <br><br> | | <br><br> |
| That means that your handler can _not_ interact with most game objects directly. | | That means that your handler can _not_ interact with most game objects directly. |
− | Forge provides a convenient way to make your code execute on the main thread instead using <code><nowiki>IThreadListener.addScheduledTask</nowiki></code>. | + | Forge provides a convenient way to make your code execute on the main thread through the supplied <code><nowiki>NetworkEvent$Context</nowiki></code>. |
− | Simply call <code><nowiki>ctx.get().enqueueWork(Runnable)</nowiki></code>, which will call the given <code><nowiki>Runnable</nowiki></code> on the main thread at the next opportunity.}} | + | Simply call <code><nowiki>NetworkEvent$Context#enqueueWork(Runnable)</nowiki></code>, which will call the given <code><nowiki>Runnable</nowiki></code> on the main thread at the next opportunity.}} |
| | | |
| {{Colored box|title=Alert|content=Be defensive when handling packets on the server. A client could attempt to exploit the packet handling by sending unexpected data. | | {{Colored box|title=Alert|content=Be defensive when handling packets on the server. A client could attempt to exploit the packet handling by sending unexpected data. |
| <br><br> | | <br><br> |
− | A common problem is vulnerability to <code>arbitrary chunk generation</code>. This typically happens when the server is trusting a block position sent by a client to access blocks and tile entities. When accessing blocks and tile entities in unloaded areas of the world, the server will either generate or load this area from disk, then promply write it to disk. This can be exploited to cause <code>catastrophic damage</code> to a server's performance and storage space without leaving a trace. | + | A common problem is vulnerability to <code>arbitrary chunk generation</code>. This typically happens when the server is trusting a block position sent by a client to access blocks and block entities. When accessing blocks and block entities in unloaded areas of the level, the server will either generate or load this area from disk, then promply write it to disk. This can be exploited to cause <code>catastrophic damage</code> to a server's performance and storage space without leaving a trace. |
| <br> | | <br> |
− | To avoid this problem, a general rule of thumb is to only access blocks and tile entities if <code><nowiki>world.isBlockLoaded(pos)</nowiki></code> is true.}} | + | To avoid this problem, a general rule of thumb is to only access blocks and block entities if <code><nowiki>Level#hasChunkAt</nowiki></code> is true.}} |
| | | |
| == Sending Packets == | | == Sending Packets == |
Line 74: |
Line 92: |
| === Sending to Clients === | | === Sending to Clients === |
| | | |
− | Packets can be sent directly to a client using the <code><nowiki>SimpleChannel</nowiki></code>: <code><nowiki>HANDLER.sendTo(MSG, entityPlayerMP.connection.getNetworkManager(), NetworkDirection.PLAY_TO_CLIENT)</nowiki></code>. However, this can be quite inconvenient. Forge has some convenience functions that can be used: | + | Packets can be sent directly to a client using the <code><nowiki>SimpleChannel</nowiki></code>: <code><nowiki>HANDLER.sendTo(MSG, serverPlayer.connection.getConnection(), NetworkDirection.PLAY_TO_CLIENT)</nowiki></code>. However, this can be quite inconvenient. Forge has some convenience functions that can be used: |
| | | |
| <syntaxhighlight lang="java"> | | <syntaxhighlight lang="java"> |
| // Sending to one player | | // Sending to one player |
− | INSTANCE.send(PacketDistributor.PLAYER.with(playerMP), new MyMessage()); | + | INSTANCE.send(PacketDistributor.PLAYER.with(serverPlayer), new MyMessage()); |
| | | |
− | // Send to all players tracking this chunk | + | // Send to all players tracking this level chunk |
− | INSTANCE.send(PacketDistributor.TRACKING_CHUNK.with(chunk), new MyMessage()); | + | INSTANCE.send(PacketDistributor.TRACKING_CHUNK.with(levelChunk), new MyMessage()); |
| | | |
| // Sending to all connected players | | // Sending to all connected players |