tutorial:networking
Differences
This shows you the differences between two versions of the page.
Both sides previous revisionPrevious revisionNext revision | Previous revisionNext revisionBoth sides next revision | ||
tutorial:networking [2020/03/17 15:53] – [Client To Server (C2S) Packets] Update mappings earthcomputer | tutorial:networking [2020/12/10 19:13] – Rewrite for networking v1 - v0 has been moved i509vcb | ||
---|---|---|---|
Line 1: | Line 1: | ||
- | ====== | + | ====== |
- | Say you wanted to emit an explosion particle whenever your block is destroyed. Emitting particles requires access | + | This page has replaced the old networking page. |
+ | It is recommended | ||
+ | The old page can be [[tutorial:legacy: | ||
- | <code java> | + | ====== Networking ====== |
- | public | + | |
- | | + | Networking in Minecraft is used so the client and server can communicate with each other. Networking is a broad topic so this page is split up into a few categories. |
- | public | + | |
- | | + | ===== Example: Why is networking important? ===== |
- | // WARNING: | + | |
- | MinecraftClient.getInstance().particleManager.addParticle( | + | The importance of networking can be shown by a simple code example. This code should **NOT** be used and is here to explain why networking is important. |
- | ParticleTypes.EXPLOSION, pos.getX(), pos.getY(), pos.getZ(), | + | |
- | 0.0D, 0.0D, 0.0D | + | Say you had a wand which highlights the block you are looking at to all nearby players. |
- | | + | <code java [enable_line_numbers=" |
- | } | + | class HighlightingWandItem |
+ | | ||
+ | super(settings) | ||
+ | } | ||
+ | |||
+ | public | ||
+ | | ||
+ | BlockPos target | ||
+ | |||
+ | | ||
+ | | ||
+ | return super.use(world, user, hand); | ||
} | } | ||
} | } | ||
</ | </ | ||
- | If you go into try to break your block, you will see your particle works perfectly fine. Job's done, right? Well, try to [[testing-on-a-dedicated-server.md|run it on a dedicated server]]. You break your block, | + | |
+ | Upon testing, you will see the block you are facing at is highlighted and nothing crashes. | ||
+ | Now you want to show the mod to your friend, you boot up a dedicated server | ||
+ | You will probably notice in the crash log an error similar to this: | ||
< | < | ||
Line 23: | Line 39: | ||
java.lang.RuntimeException: | java.lang.RuntimeException: | ||
</ | </ | ||
- | ==== What went wrong? ==== | ||
- | There are 2 types of Minecraft jars. The //server// jar, which is run by the server that hosts a multiplayer game. And there is the //client// jar, which is run by every player who joins the server. Some classes, such as the '' | + | ==== Why does the server |
- | '' | + | The code calls logic only present on the client distribution of the Minecraft. |
+ | The reason for Mojang distributing the game in this way is to cut down on the size of the Minecraft | ||
- | * in this case, playing particles. Since '' | + | ==== How do I fix the crash? ==== |
- | we will need to somehow tell the clients they need to play the particles. | + | In order to fix this issue, you need to understand how Minecraft communicates between |
- | In a single player settings this is easy, the client runs in the same process as the server, | + | [[Minecraft' |
- | so we can just access | + | The diagram above shows that the game client |
- | In multiplayer we will need to communicate between entirely different computers. This is where packets come in. | + | ^ Connection Type ^ Access |
+ | | Connected to Dedicated Server | None -> Server Crash | | ||
+ | | Connected over LAN | Yes -> Not host game client | | ||
+ | | Singleplayer (or LAN host) | Yes -> Full access | ||
- | ===== Server To Client (S2C) Packets ===== | + | It may seem complicated to have to communication with the server in three different ways. However you don't need to communicate in three different ways with the game client. Since all three connection types communicate with the game client using packets, you only need to communicate with the game client like you are always running on a dedicated server. Connection to server over LAN or Singleplayer can be also be treated like the server is a remote dedicated server; so your game client cannot directly access the server instance. |
- | Server To Client, or S2C packets for short, allow the server | + | ===== An introduction |
- | (Note that the identifier | + | To begin, we need to fix an issue with the example code shown above. Since we are using packets to communicate with the client, |
- | <code java> | + | ==== Sending a packet to the game client ==== |
- | public class ExampleMod implements ModInitializer { | + | |
- | // Save the id of the packet so we can reference it later | + | <code java [enable_line_numbers="true", |
- | public static final Identifier PLAY_PARTICLE_PACKET_ID | + | public |
- | } | + | // Verify we are processing |
- | </code> | + | |
- | <code java> | + | |
- | public class ExampleClientInit implements ClientModInitializer { | + | |
- | @Override | + | |
- | public | + | |
- | // We associate PLAY_PARTICLE_PACKET_ID with this callback, so the server can then use that id to execute | + | |
- | | + | |
- | | + | return TypedActionResult.success(user.getHandStack(hand)); |
- | // Client sided code | + | |
- | // Caution: don't execute code here just yet | + | |
- | }); | + | |
} | } | ||
- | } | ||
</ | </ | ||
- | One thing we must make sure is that **we execute | + | |
+ | Next we need to send the packet to the game client. First you need to define an '' | ||
+ | |||
+ | To send the packet | ||
<code java> | <code java> | ||
- | // [...] | + | public static void send(ServerPlayerEntity player, Identifier channelName, PacketByteBuf buf) { |
- | ClientSidePacketRegistry.INSTANCE.register(ExampleMod.PLAY_PARTICLE_PACKET_ID, | + | ... |
- | (packetContext, attachedData) -> packetContext.getTaskQueue().execute(() -> { | + | |
- | // For now we will use the player' | + | |
- | // in the onBlockRemoved callback. This is explained in " | + | |
- | Vec3d pos = packetContext.getPlayer().getPos(); | + | |
- | MinecraftClient.getInstance().particleManager.addParticle( | + | |
- | ParticleTypes.EXPLOSION, | + | |
- | 0.0D, 0.0D, 0.0D | + | |
- | ); | + | |
- | })); | + | |
</ | </ | ||
- | The only thing that is left is for the server to actually send the packet to clients. | ||
- | <code java> | + | The player in this method is the player |
- | public class MyBlock extends Block { | + | |
- | @Override | + | |
- | public void onBlockRemoved(BlockState before, World world, BlockPos pos, BlockState after, boolean bool) { | + | |
- | // First we need to actually get hold of the players that we want to send the packets | + | |
- | // A simple way is to obtain all players watching this position: | + | |
- | Stream< | + | |
- | // Look at the other methods of `PlayerStream` | + | |
- | // We'll get to this later | + | Since we are not writing any data to the packet for now, we will send the packet |
- | PacketByteBuf passedData = new PacketByteBuf(Unpooled.buffer()); | + | |
- | + | ||
- | // Then we' | + | |
- | watchingPlayers.forEach(player -> | + | |
- | ServerSidePacketRegistry.INSTANCE.sendToPlayer(player, | + | |
- | // This will work in both multiplayer and singleplayer! | + | |
- | } | + | |
- | // [...] | + | <code java [enable_line_numbers=" |
+ | | ||
+ | ServerPlayNetworking.send((ServerPlayerEntity) user, ModNetworkingConstants.HIGHLIGHT_PACKET_ID, | ||
+ | return TypedActionResult.success(user.getHandStack(hand)); | ||
} | } | ||
</ | </ | ||
- | If you join a dedicated server, go into first person and break the block, you will see an explosion particle was indeed emitted in your location. | ||
- | ===== Attaching data to packets ===== | + | Though you have sent a packet |
- | But wait, we wanted the explosion to occur at the block' | + | ==== Receiving a packet |
- | <code java> | + | To receive a packet from a server on the game client, your mod needs to specify how it will handle the incoming packet. |
- | Stream< | + | In your client entrypoint, you will register the receiver for your packet using '' |
- | // Pass the `BlockPos` information | + | The '' |
- | PacketByteBuf passedData = new PacketByteBuf(Unpooled.buffer()); | + | |
- | passedData.writeBlockPos(pos); | + | |
- | watchingPlayers.forEach(player | + | The example below implements the play channel handler as a lambda: |
+ | |||
+ | <code java [enable_line_numbers=" | ||
+ | ClientPlayNetworking.registerGlobalReceiver(ModNetworkingConstants.HIGHLIGHT_PACKET_ID, | ||
+ | | ||
+ | }); | ||
</ | </ | ||
- | And then retrieve it in the packet callback, **but make sure you do it in the network thread**. | ||
- | <code java> | + | However you cannot draw the highlight box immediately. This is because the receiver is called on the netty event loop. The event loop runs on another thread, and you must draw the highlight box on the render thread. |
- | ClientSidePacketRegistry.INSTANCE.register(ExampleMod.PLAY_PARTICLE_PACKET_ID, | + | |
- | | + | In order to draw the highlight box, you need to schedule the task on the game client. This may be done with the '' |
- | // Get the BlockPos we put earlier, in the networking thread | + | |
- | BlockPos pos = attachedData.readBlockPos(); | + | <code java [enable_line_numbers=" |
- | packetContext.getTaskQueue().execute(() -> { | + | ClientPlayNetworking.registerGlobalReceiver(ModNetworkingConstants.HIGHLIGHT_PACKET_ID, (client, handler, buf, responseSender) -> { |
- | // Use the pos in the main thread | + | |
- | | + | // Everything |
- | ParticleTypes.EXPLOSION, pos.getX(), pos.getY(), pos.getZ(), | + | |
- | 0.0D, 0.0D, 0.0D | + | }); |
- | | + | }); |
- | }); | + | |
- | }); | + | |
</ | </ | ||
- | Try it out in a dedicated server environment. | ||
- | If you want to send additional | + | You may have noticed |
- | <code java> | + | <code java [enable_line_numbers=" |
- | // Write | + | PacketByteBuf buf = PacketByteBufs.create(); |
- | passedData.writeBlockPos(pos); | + | |
- | passedData.writeInt(123); | + | |
- | passedData.writeString(" | + | |
</ | </ | ||
- | <code java> | + | |
- | // Read | + | Next you need to write the data to the packet byte buf. It should be noted that you must read data in the same order you write it. |
- | (packetContext, attachedData) -> { | + | |
- | // Read in the correct, networking thread | + | <code java [enable_line_numbers=" |
- | BlockPos | + | PacketByteBuf buf = PacketByteBufs.create(); |
- | int num = attachedData.readInt(); | + | |
- | | + | buf.writeBlockPos(target); |
- | packetContext.getTaskQueue().execute(() -> { | + | </code> |
- | /* | + | |
- | * Use num and str in the correct, main thread | + | Afterwards you will send the '' |
- | */ | + | |
- | | + | To read this block position on the game client, you can use '' |
- | System.out.println(str); | + | |
+ | You should read all data from the packet on the network thread before scheduling a task to occur on the client thread. You will get errors related to the ref count if you try to read data on the client thread. If you must read data on the client thread, you need to '' | ||
+ | |||
+ | In the end, the client' | ||
+ | <code java [enable_line_numbers=" | ||
+ | ClientPlayNetworking.registerGlobalReceiver(ModNetworkingConstants.HIGHLIGHT_PACKET_ID, | ||
+ | // Read packet data on the event loop | ||
+ | BlockPos | ||
+ | |||
+ | | ||
+ | // Everything | ||
+ | | ||
}); | }); | ||
}); | }); | ||
</ | </ | ||
- | ===== Client To Server (C2S) Packets ===== | ||
- | Client | + | ==== Sending packets |
- | In this example we will replace | + | Sending packets to a server and receiving |
- | As before we'll define an identifier | + | Firstly sending a packet to the server is done through '' |
- | <code java> | + | ===== The concept of tracking |
- | public class ExampleMod implements ModInitializer { | + | |
- | public static final Identifier TURN_TO_DIAMOND_PACKET_ID | + | |
- | } | + | |
- | </ | + | |
- | Now we will send the packet when the block is right-clicked | + | |
- | <code java> | + | Now that the highlighting wand properly uses networking so the dedicated server does not crash, you invite your friend back on the server to show off the highlighting wand. You use the wand and the block is highlighted on your client |
- | public class MyBlock extends Block { | + | |
- | @Override | + | |
- | public boolean activate(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockHitResult result) { | + | |
- | if (world.isClient()) { | + | |
- | // We are in the client | + | |
- | // See the keybindings tutorial for information about this if statement (the keybindings tutorial calls this variable | + | <code java [enable_line_numbers="true"]> |
- | if(ExampleClientInit.EXAMPLE_KEYBINDING.isPressed()){ | + | |
- | // Pass the `BlockPos` information | + | // Verify we are processing |
- | | + | |
- | passedData.writeBlockPos(pos); | + | |
- | // Send packet to server to change the block for us | + | |
- | ClientSidePacketRegistry.INSTANCE.sendToServer(ExampleMod.TURN_TO_DIAMOND_PACKET_ID, passedData); | + | |
- | } | + | |
- | | + | |
- | return | + | BlockPos target = ... |
+ | PacketByteBuf buf = PacketByteBufs.create(); | ||
+ | buf.writeBlockPos(target); | ||
+ | |||
+ | ServerPlayNetworking.send((ServerPlayerEntity) user, ModNetworkingConstants.HIGHLIGHT_PACKET_ID, | ||
+ | return | ||
} | } | ||
- | } | ||
</ | </ | ||
- | And then we receive the packet on the server side by registering it in the common mod initializer. Make sure to **take data in the IO thread and use it in the main thread**, and to **validate the received data**: | ||
- | <code java> | + | You may notice the item will only send the packet to the player who used the item. To fix this, we can use the utility methods in '' |
- | public class ExampleMod implements ModInitializer { | + | |
- | public static final Identifier TURN_TO_DIAMOND_PACKET_ID = new Identifier(" | + | |
- | @Override | + | Since we know where the highlight will occur, we can use '' |
- | public void onInitialize() { | + | |
- | ServerSidePacketRegistry.INSTANCE.register(TURN_TO_DIAMOND_PACKET_ID, (packetContext, | + | |
- | // Get the BlockPos we put earlier | + | |
- | BlockPos pos = attachedData.readBlockPos(); | + | |
- | packetContext.getTaskQueue().execute(() -> { | + | |
- | // Execute on the main thread | + | |
- | | + | <code java [enable_line_numbers=" |
- | if(packetContext.getPlayer().world.canSetBlock(pos)){ | + | public TypedActionResult< |
- | // Turn to diamond | + | |
- | packetContext.getPlayer().world.setBlockState(pos, Blocks.DIAMOND_BLOCK.getDefaultState()); | + | if (world.isClient()) return super.use(world, |
- | } | + | |
- | }); | + | // Raycast and find the block the user is facing at |
- | }); | + | BlockPos target = ... |
+ | PacketByteBuf buf = PacketByteBufs.create(); | ||
+ | buf.writeBlockPos(target); | ||
+ | |||
+ | // Iterate over all players tracking a position in the world and send the packet to each player | ||
+ | for (ServerPlayerEntity player : PlayerLookup.tracking((ServerWorld) world, target)) { | ||
+ | ServerPlayNetworking.send(player, | ||
+ | } | ||
+ | |||
+ | return TypedActionResult.success(user.getHandStack(hand)); | ||
} | } | ||
- | } | ||
</ | </ | ||
+ | |||
+ | After this change, when you use the wand, you friend should also see the highlighted block on their own client. | ||
+ | |||
+ | ====== Advanced Networking topics ====== | ||
+ | |||
+ | The Networking system Fabric API supplies is very flexible and supports additional features other than just sending and receiving simple packets. As some of these more advanced topics are long, here are links to their specific pages: | ||
+ | |||
+ | ^ Networking Topic ^ Description ^ | ||
+ | | [[tutorial: | ||
+ | | [[tutorial: | ||
+ | | [[tutorial: | ||
+ | | [[tutorial: | ||
+ |
tutorial/networking.txt · Last modified: 2024/05/04 19:51 by bluemeanial