diff --git a/.vitepress/sidebars/develop.ts b/.vitepress/sidebars/develop.ts index fbe2f1525..9d4bafaa7 100644 --- a/.vitepress/sidebars/develop.ts +++ b/.vitepress/sidebars/develop.ts @@ -247,6 +247,10 @@ export default [ text: "develop.misc.events", link: "/develop/events", }, + { + text: "develop.misc.networking", + link: "/develop/networking", + }, { text: "develop.misc.text-and-translations", link: "/develop/text-and-translations", diff --git a/develop/data-generation/advancements.md b/develop/data-generation/advancements.md index 1afb9fe7e..e46b1ec3c 100644 --- a/develop/data-generation/advancements.md +++ b/develop/data-generation/advancements.md @@ -24,7 +24,7 @@ First, we need to make our provider. Create a class that `extends FabricAdvancem To finish setup, add this provider to your `DataGeneratorEntrypoint` within the `onInitializeDataGenerator` method. -@[code lang=java transclude={25-25}](@/reference/latest/src/client/java/com/example/docs/datagen/FabricDocsReferenceDataGenerator.java) +@[code lang=java transclude={26-26}](@/reference/latest/src/client/java/com/example/docs/datagen/FabricDocsReferenceDataGenerator.java) ## Advancement Structure {#advancement-structure} diff --git a/develop/data-generation/loot-tables.md b/develop/data-generation/loot-tables.md index 693251430..40e216f45 100644 --- a/develop/data-generation/loot-tables.md +++ b/develop/data-generation/loot-tables.md @@ -20,7 +20,7 @@ Make sure you've completed the [datagen setup](./setup) process first. You will need different providers (classes) for blocks, chests, and entities. Remember to add them all to your pack in your `DataGeneratorEntrypoint` within the `onInitializeDataGenerator` method. -@[code lang=java transclude={32-33}](@/reference/latest/src/client/java/com/example/docs/datagen/FabricDocsReferenceDataGenerator.java) +@[code lang=java transclude={35-36}](@/reference/latest/src/client/java/com/example/docs/datagen/FabricDocsReferenceDataGenerator.java) ## Loot Tables Explained {#loot-tables-explained} diff --git a/develop/data-generation/recipes.md b/develop/data-generation/recipes.md index 7409685d2..1f6c05c72 100644 --- a/develop/data-generation/recipes.md +++ b/develop/data-generation/recipes.md @@ -23,7 +23,7 @@ First, we'll need our provider. Make a class that `extends FabricRecipeProvider` To finish setup, add this provider to your `DataGeneratorEntrypoint` within the `onInitializeDataGenerator` method. -@[code lang=java transclude={30-30}](@/reference/latest/src/client/java/com/example/docs/datagen/FabricDocsReferenceDataGenerator.java) +@[code lang=java transclude={32-32}](@/reference/latest/src/client/java/com/example/docs/datagen/FabricDocsReferenceDataGenerator.java) ## Shapeless Recipes {#shapeless-recipes} diff --git a/develop/data-generation/tags.md b/develop/data-generation/tags.md index 2e28a781b..869c7125f 100644 --- a/develop/data-generation/tags.md +++ b/develop/data-generation/tags.md @@ -27,7 +27,7 @@ You will need a different provider for each type of tag (eg. one `FabricTagProvi To finish setup, add this provider to your `DataGeneratorEntrypoint` within the `onInitializeDataGenerator` method. -@[code lang=java transclude={29-29}](@/reference/latest/src/client/java/com/example/docs/datagen/FabricDocsReferenceDataGenerator.java) +@[code lang=java transclude={30-30}](@/reference/latest/src/client/java/com/example/docs/datagen/FabricDocsReferenceDataGenerator.java) ## Creating a Tag {#creating-a-tag} diff --git a/develop/data-generation/translations.md b/develop/data-generation/translations.md index ebb408732..823af9bb0 100644 --- a/develop/data-generation/translations.md +++ b/develop/data-generation/translations.md @@ -30,7 +30,7 @@ You will need a different provider for each language you want to generate (eg. o To finish setup, add this provider to your `DataGeneratorEntrypoint` within the `onInitializeDataGenerator` method. -@[code lang=java transclude={27-27}](@/reference/latest/src/client/java/com/example/docs/datagen/FabricDocsReferenceDataGenerator.java) +@[code lang=java transclude={28-28}](@/reference/latest/src/client/java/com/example/docs/datagen/FabricDocsReferenceDataGenerator.java) ## Creating Translations {#creating-translations} diff --git a/develop/entities/damage-types.md b/develop/entities/damage-types.md index 90dd926de..3c7928dba 100644 --- a/develop/entities/damage-types.md +++ b/develop/entities/damage-types.md @@ -72,13 +72,7 @@ Now whenever a living entity steps on our custom block, it'll take 5 damage (2.5 You can define a death message for the damage type in the format of `death.attack.` in our mod's `en_us.json` file. -```json -{ - // ... - "death.attack.tater": "%1$s died from Tater damage!", - // ... -} -``` +@[code lang=json transclude={5-5}](@/reference/latest/src/main/generated/assets/fabric-docs-reference/lang/en_us.json) Upon death from our damage type, you'll see the following death message: diff --git a/develop/networking.md b/develop/networking.md new file mode 100644 index 000000000..aa8da3e3a --- /dev/null +++ b/develop/networking.md @@ -0,0 +1,279 @@ +--- +title: Networking +description: A general guide on networking using Fabric API. +authors: + - dicedpixels + - FlooferLand + - skycatminepokie + - fxmorin + - netuserget + - wxffel + - daomephsta + - solidblock + - voleil + - daomephsta + - YTG123-Mods + - zulrang + - i509VCB + - nshak + - earthcomputer + - natanfudge + - modmuss50 +--- + +# Networking {#networking} + +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. + +## Why Is Networking Important? {#why-is-networking-important} + +The importance of networking can be shown by a simple code example. + +::: warning +Below code is for demonstration purposes only. +::: + +Say you had a Wand which highlights the block you're looking, which will be visible to all +nearby players: + +```java +public class HighlightingWandItem extends Item { + public HighlightingWandItem(Item.Settings settings) { + super(settings) + } + + public TypedActionResult use(World world, PlayerEntity user, Hand hand) { + // Raycast and find the block the user is facing at + BlockPos target = ... + + // BAD CODE: DON'T EVER DO THIS! + ClientBlockHighlighting.highlightBlock(MinecraftClient.getInstance(), target); + return super.use(world, user, hand); + } +} +``` + +Upon testing, you will see a lightning bolt being summoned and nothing crashes. Now you want to show the mod to your +friend, you boot up a dedicated server and invite your friend on with the mod installed. + +You use the item and the server crashes. You will probably notice in the crash log an error similar to this: + +```text +[Server thread/FATAL]: Error executing task on Server +java.lang.RuntimeException: Cannot load class net.minecraft.client.MinecraftClient in environment type SERVER +``` + +### Why Does the Server Crash? {#why-does-the-server-crash} + +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 Server JAR file. There isn't really a reason to include an +entire rendering engine when your own machine will render the world. + +In a development environment, client-only classes are indicated by the `@Environment(EnvType.CLIENT)` annotation. + +### How Do We Fix the Crash? {#how-do-we-fix-the-crash} + +To fix this issue, you need to understand how Minecraft communicates between the game client and dedicated +server. + +![Sides](/assets/develop/networking/sides.png) + +The diagram above shows that the game client and dedicated server are separate systems, bridged together using +_packets_. Packets can contain data which we refer to as the _payload_. + +This packet bridge does not only exist between a game client and dedicated server, but also between your client and +another client connected over LAN. The packet bridge is also present even in single-player. This is because the game +client will spin up a special integrated server instance to run the game on. + +The key difference between the three types of connections that are shown in the table below: + +| Connection Type | Access to Game Client | +|-------------------------------|----------------------------| +| Connected to dedicated Server | None (Server crash) | +| Connected over LAN | Yes (Not host game client) | +| Single-player (or LAN host) | Yes (Full access) | + +It may seem complicated to have 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're always running on a dedicated +server. + +Connection to a server over LAN or single-player can also be treated like the server is a remote, dedicated server; so +your game client can't directly access the server instance. + +## An Introduction to Networking {#an-introduction-to-networking} + +### Defining a Payload {#defining-a-payload} + +First, you need to define an `Identifier` used to identify our packet's payload. For this example our identifier will be +`fabric-docs-reference:summon_lightning`. + +We can define this identifier in our **common initializer** for easy access. + +@[code lang=java transclude={21-21}](@/reference/latest/src/main/java/com/example/docs/networking/basic/FabricDocsReferenceNetworkingBasic.java) + +Now, let's define the payload that will be sent with the packet. This payload will contain the position (a `BlockPos`) +of the item user. + +This can be done by creating a Java `Record` with a `BlockPos` parameter that implements `CustomPayload`. + +@[code lang=java transcludeWith=:::summon_Lightning_payload](@/reference/latest/src/main/java/com/example/docs/networking/basic/SummonLightningPayload.java) + +At the same time, we've defined: + +- A public static instance of `CustomPayload.Id` to uniquely identify this custom payload. We will be referencing this + ID in both our common and client code. + + @[code lang=java transclude={10-10}](@/reference/latest/src/main/java/com/example/docs/networking/basic/SummonLightningPayload.java) + +- A public static instance of a `PacketCodec` so that the game knows how to serialize/deserialize the contents of the + packet. + + @[code lang=java transclude={11-11}](@/reference/latest/src/main/java/com/example/docs/networking/basic/SummonLightningPayload.java) + +We have also overridden `getId` to return our payload ID. + +### Registering a Payload {#registering-a-payload} + +Before we send a packet with our custom payload, we need to register it. + +This can be done in our **common initializer** by `PayloadTypeRegistry.playS2C().register` which takes in a +`CustomPayload.Id` and a `PacketCodec`. + +@[code lang=java transclude={28-28}](@/reference/latest/src/main/java/com/example/docs/networking/basic/FabricDocsReferenceNetworkingBasic.java) + +A similar method exists to register client-to-server payloads: `PayloadTypeRegistry.playC2S().register`. + +`S2C` and `C2S` are two common suffixes that mean _Server-to-Client_ and _Client-to-Server_ respectively. + +### Sending a Packet to the Client {#sending-a-packet-to-the-client} + +To send a packet with our custom payload, we can use `ServerPlayNetworking.send` which takes in a `ServerPlayerEntity` +and a `CustomPayload`. + +Let's start by creating our Lightning Tater item. You can override `use` to add trigger an action when the item is used. +In this case, let's send packets to all the players in the server world. + +@[code lang=java transcludeWith=:::lightning_tater_item](@/reference/latest/src/main/java/com/example/docs/networking/basic/LightningTaterItem.java) + +Let's examine the code above. + +We only send packets when the action is initiated on the server, by returning early with a `isClient` check: + +@[code lang=java transclude={22-24}](@/reference/latest/src/main/java/com/example/docs/networking/basic/LightningTaterItem.java) + +We create an instance of the payload with the user's position: + +@[code lang=java transclude={26-26}](@/reference/latest/src/main/java/com/example/docs/networking/basic/LightningTaterItem.java) + +Finally, we get all the players in the server world through `PlayerLookup` and send a packet to each player. + +@[code lang=java transclude={28-30}](@/reference/latest/src/main/java/com/example/docs/networking/basic/LightningTaterItem.java) + +::: info +Fabric API provides `PlayerLookup`, a collection of helper functions that will look up players in a server. + +A term frequently used to describe the functionality of these methods is "_tracking_". It means that an entity or a +chunk +on the server is known to a player's client (within in view distance) and the entity or block entity should notify +tracking clients of changes. + +Tracking is an important concept for efficient networking, so that only the necessary players are notified of changes by +sending packets. +::: + +Remember to register the item in our **common initializer**. + +@[code lang=java transclude={24-25}](@/reference/latest/src/main/java/com/example/docs/networking/basic/FabricDocsReferenceNetworkingBasic.java) + +### Receiving a Packet on the Client {#receiving-a-packet-on-the-client} + +To receive a packet sent from a server on the client, you need to specify how you will handle the incoming packet. + +This can be done in the **client initializer**, by calling `ClientPlayNetworking.registerGlobalReceiver` and passing a +`CustomPayload.Id` and a `PlayPayloadHandler`, which is a Functional Interface. + +In this case, we'll define the action to trigger within the implementation of `PlayPayloadHandler` implementation (as a +lambda expression). + +@[code lang=java transcludeWith=:::client_global_receiver](@/reference/latest/src/client/java/com/example/docs/network/basic/FabricDocsReferenceNetworkingBasicClient.java) + +Let's examine the code above. + +We can access the data from our payload by calling the Record's getter methods. In this case `payload.pos()`. Which then +can be used to get the `x`, `y` and `z` positions. + +@[code lang=java transclude={32-32}](@/reference/latest/src/client/java/com/example/docs/network/basic/FabricDocsReferenceNetworkingBasicClient.java) + +Finally, we create a `LightningEntity` and add it to the world. + +@[code lang=java transclude={34-39}](@/reference/latest/src/client/java/com/example/docs/network/basic/FabricDocsReferenceNetworkingBasicClient.java) + +Now, if you add this mod to a server and when a player uses our Lightning Tater item, every player will see lightning +striking at the user's position. + + + +### Sending a Packet to the Server {#sending-a-packet-to-the-server} + +Just like sending a packet to the client, we start by creating a custom payload. This time, when a player uses a +Poisonous Potato on a living entity, we will apply the Glowing effect to it. + +@[code lang=java transcludeWith=:::give_glowing_effect_payload](@/reference/latest/src/main/java/com/example/docs/networking/basic/GiveGlowingEffectPayload.java) + +We pass in the appropriate codec along with a method reference to get the value from the Record to build this codec. + +`GIVE_GLOWING_EFFECT_PAYLOAD_ID` is defined in the **common initializer** for easy access. + +@[code lang=java transclude={22-22}](@/reference/latest/src/main/java/com/example/docs/networking/basic/FabricDocsReferenceNetworkingBasic.java) + +Then we register our payload in our **common initializer**. However, this time as _Client-to-Server_ payload by using +`PayloadTypeRegistry.playC2S().register`. + +@[code lang=java transclude={29-29}](@/reference/latest/src/main/java/com/example/docs/networking/basic/FabricDocsReferenceNetworkingBasic.java) + +To send a packet, let's add an action when the player uses a Poisonous Potato. We'll be using the `UseItemCallback` +event to +keep things concise. + +We register the event in our **client initializer**, and we use `isClient()` to ensure that the action is only triggered +on the logical client. + +We get the cross-hair target, verify if it's targeting an entity. + +@[code lang=java transcludeWith=:::use_item_callback](@/reference/latest/src/client/java/com/example/docs/network/basic/FabricDocsReferenceNetworkingBasicClient.java) + +We create and instance of our `GiveGlowingEffectPayload` with the necessary arguments. In this case, the network ID of +the targeted entity. + +@[code lang=java transclude={54-54}](@/reference/latest/src/client/java/com/example/docs/network/basic/FabricDocsReferenceNetworkingBasicClient.java) + +Finally, we send a packet to the server by calling `ClientPlayNetworking.send` with the instance of our +`UsePoisonousPotatoPayload`. + +@[code lang=java transclude={55-55}](@/reference/latest/src/client/java/com/example/docs/network/basic/FabricDocsReferenceNetworkingBasicClient.java) + +### Receiving a Packet on the Server {#receiving-a-packet-on-the-server} + +This can be done in the **common initializer**, by calling `ServerPlayNetworking.registerGlobalReceiver` and passing a +`CustomPayload.Id` and a `PlayPayloadHandler`. + +@[code lang=java transcludeWith=:::server_global_receiver](@/reference/latest/src/main/java/com/example/docs/networking/basic/FabricDocsReferenceNetworkingBasic.java) + +::: info +It is important that you validate the content of the packet on the server side. + +In this case, we validate if the entity exists based on its network ID. + +@[code lang=java transclude={33-33}](@/reference/latest/src/main/java/com/example/docs/networking/basic/FabricDocsReferenceNetworkingBasic.java) + +Additionally, the targeted entity has to be a living entity, and we restrict the range of the target entity from the +player to 5. + +@[code lang=java transclude={35-35}](@/reference/latest/src/main/java/com/example/docs/networking/basic/FabricDocsReferenceNetworkingBasic.java) +::: + +Now when any player tries to use a Poisonous Potato on a living entity, the glowing effect will be applied to it. + + diff --git a/public/assets/develop/networking/sides.png b/public/assets/develop/networking/sides.png new file mode 100644 index 000000000..118425043 Binary files /dev/null and b/public/assets/develop/networking/sides.png differ diff --git a/public/assets/develop/networking/summon-lightning.webm b/public/assets/develop/networking/summon-lightning.webm new file mode 100644 index 000000000..a3afc8ccc Binary files /dev/null and b/public/assets/develop/networking/summon-lightning.webm differ diff --git a/public/assets/develop/networking/use-poisonous-potato.webm b/public/assets/develop/networking/use-poisonous-potato.webm new file mode 100644 index 000000000..f5de84d33 Binary files /dev/null and b/public/assets/develop/networking/use-poisonous-potato.webm differ diff --git a/reference/latest/src/client/java/com/example/docs/datagen/FabricDocsReferenceDataGenerator.java b/reference/latest/src/client/java/com/example/docs/datagen/FabricDocsReferenceDataGenerator.java index d92bb3c3f..b8eab2a0b 100644 --- a/reference/latest/src/client/java/com/example/docs/datagen/FabricDocsReferenceDataGenerator.java +++ b/reference/latest/src/client/java/com/example/docs/datagen/FabricDocsReferenceDataGenerator.java @@ -10,6 +10,7 @@ import com.example.docs.damage.FabricDocsReferenceDamageTypes; import com.example.docs.datagen.internal.FabricDocsReferenceInternalModelProvider; +import com.example.docs.network.basic.FabricDocsReferenceNetworkingBasicModelProvider; // :::datagen-setup:generator public class FabricDocsReferenceDataGenerator implements DataGeneratorEntrypoint { @@ -39,6 +40,8 @@ public void onInitializeDataGenerator(FabricDataGenerator fabricDataGenerator) { pack.addProvider(FabricDocsReferenceInternalModelProvider::new); // :::datagen-setup:generator + + pack.addProvider(FabricDocsReferenceNetworkingBasicModelProvider::new); } // :::datagen-setup:generator diff --git a/reference/latest/src/client/java/com/example/docs/network/basic/FabricDocsReferenceNetworkingBasicClient.java b/reference/latest/src/client/java/com/example/docs/network/basic/FabricDocsReferenceNetworkingBasicClient.java new file mode 100644 index 000000000..a774a609a --- /dev/null +++ b/reference/latest/src/client/java/com/example/docs/network/basic/FabricDocsReferenceNetworkingBasicClient.java @@ -0,0 +1,66 @@ +package com.example.docs.network.basic; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.world.ClientWorld; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityType; +import net.minecraft.entity.LightningEntity; +import net.minecraft.entity.SpawnReason; +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; +import net.minecraft.util.ActionResult; +import net.minecraft.util.Hand; +import net.minecraft.util.hit.EntityHitResult; +import net.minecraft.util.hit.HitResult; +import net.minecraft.util.math.BlockPos; + +import net.fabricmc.api.ClientModInitializer; +import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; +import net.fabricmc.fabric.api.event.player.UseItemCallback; + +import com.example.docs.networking.basic.GiveGlowingEffectPayload; +import com.example.docs.networking.basic.SummonLightningPayload; + +public class FabricDocsReferenceNetworkingBasicClient implements ClientModInitializer { + @Override + public void onInitializeClient() { + // :::client_global_receiver + ClientPlayNetworking.registerGlobalReceiver(SummonLightningPayload.ID, (payload, context) -> { + ClientWorld world = context.client().world; + + if (world != null) { + BlockPos entityPos = payload.pos(); + + LightningEntity entity = EntityType.LIGHTNING_BOLT.create(world, SpawnReason.TRIGGERED); + + if (entity != null) { + entity.setPosition(entityPos.getX(), entityPos.getY(), entityPos.getZ()); + world.addEntity(entity); + } + } + }); + // :::client_global_receiver + + // :::use_item_callback + UseItemCallback.EVENT.register((playerEntity, world, hand) -> { + if (world.isClient()) { + ItemStack usedItemStack = playerEntity.getStackInHand(hand); + + if (usedItemStack.isOf(Items.POISONOUS_POTATO) && hand == Hand.MAIN_HAND) { + HitResult target = MinecraftClient.getInstance().crosshairTarget; + + if (target != null && target.getType() == HitResult.Type.ENTITY) { + Entity targettedEntity = ((EntityHitResult) target).getEntity(); + GiveGlowingEffectPayload payload = new GiveGlowingEffectPayload(targettedEntity.getId()); + ClientPlayNetworking.send(payload); + + return ActionResult.SUCCESS; + } + } + } + + return ActionResult.PASS; + }); + // :::use_item_callback + } +} diff --git a/reference/latest/src/client/java/com/example/docs/network/basic/FabricDocsReferenceNetworkingBasicModelProvider.java b/reference/latest/src/client/java/com/example/docs/network/basic/FabricDocsReferenceNetworkingBasicModelProvider.java new file mode 100644 index 000000000..bf12229de --- /dev/null +++ b/reference/latest/src/client/java/com/example/docs/network/basic/FabricDocsReferenceNetworkingBasicModelProvider.java @@ -0,0 +1,25 @@ +package com.example.docs.network.basic; + +import net.minecraft.client.data.BlockStateModelGenerator; +import net.minecraft.client.data.ItemModelGenerator; +import net.minecraft.client.data.Models; + +import net.fabricmc.fabric.api.client.datagen.v1.provider.FabricModelProvider; +import net.fabricmc.fabric.api.datagen.v1.FabricDataOutput; + +import com.example.docs.networking.basic.FabricDocsReferenceNetworkingBasic; + +public class FabricDocsReferenceNetworkingBasicModelProvider extends FabricModelProvider { + public FabricDocsReferenceNetworkingBasicModelProvider(FabricDataOutput output) { + super(output); + } + + @Override + public void generateBlockStateModels(BlockStateModelGenerator blockStateModelGenerator) { + } + + @Override + public void generateItemModels(ItemModelGenerator itemModelGenerator) { + itemModelGenerator.register(FabricDocsReferenceNetworkingBasic.LIGHTNING_TATER, Models.HANDHELD); + } +} diff --git a/reference/latest/src/main/generated/assets/fabric-docs-reference/items/lightning_tater.json b/reference/latest/src/main/generated/assets/fabric-docs-reference/items/lightning_tater.json new file mode 100644 index 000000000..e2c46e49f --- /dev/null +++ b/reference/latest/src/main/generated/assets/fabric-docs-reference/items/lightning_tater.json @@ -0,0 +1,6 @@ +{ + "model": { + "type": "minecraft:model", + "model": "fabric-docs-reference:item/lightning_tater" + } +} \ No newline at end of file diff --git a/reference/latest/src/main/generated/assets/fabric-docs-reference/models/item/lightning_tater.json b/reference/latest/src/main/generated/assets/fabric-docs-reference/models/item/lightning_tater.json new file mode 100644 index 000000000..6b99f07ae --- /dev/null +++ b/reference/latest/src/main/generated/assets/fabric-docs-reference/models/item/lightning_tater.json @@ -0,0 +1,6 @@ +{ + "parent": "minecraft:item/handheld", + "textures": { + "layer0": "fabric-docs-reference:item/lightning_tater" + } +} \ No newline at end of file diff --git a/reference/latest/src/main/java/com/example/docs/networking/basic/FabricDocsReferenceNetworkingBasic.java b/reference/latest/src/main/java/com/example/docs/networking/basic/FabricDocsReferenceNetworkingBasic.java new file mode 100644 index 000000000..5adc54443 --- /dev/null +++ b/reference/latest/src/main/java/com/example/docs/networking/basic/FabricDocsReferenceNetworkingBasic.java @@ -0,0 +1,41 @@ +package com.example.docs.networking.basic; + +import net.minecraft.entity.Entity; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.effect.StatusEffectInstance; +import net.minecraft.entity.effect.StatusEffects; +import net.minecraft.item.Item; +import net.minecraft.registry.Registries; +import net.minecraft.registry.Registry; +import net.minecraft.registry.RegistryKey; +import net.minecraft.registry.RegistryKeys; +import net.minecraft.util.Identifier; + +import net.fabricmc.api.ModInitializer; +import net.fabricmc.fabric.api.networking.v1.PayloadTypeRegistry; +import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; + +import com.example.docs.FabricDocsReference; + +public class FabricDocsReferenceNetworkingBasic implements ModInitializer { + public static final Identifier SUMMON_LIGHTNING_PAYLOAD_ID = Identifier.of(FabricDocsReference.MOD_ID, "summon_lightning"); + public static final Identifier GIVE_GLOWING_EFFECT_PAYLOAD_ID = Identifier.of(FabricDocsReference.MOD_ID, "give_glowing_effect"); + + public static final RegistryKey LIGHTNING_TATER_REGISTRY_KEY = RegistryKey.of(RegistryKeys.ITEM, Identifier.of(FabricDocsReference.MOD_ID, "lightning_tater")); + public static final Item LIGHTNING_TATER = Registry.register(Registries.ITEM, Identifier.of(FabricDocsReference.MOD_ID, "lightning_tater"), new LightningTaterItem(new Item.Settings().registryKey(LIGHTNING_TATER_REGISTRY_KEY))); + + public void onInitialize() { + PayloadTypeRegistry.playS2C().register(SummonLightningPayload.ID, SummonLightningPayload.CODEC); + PayloadTypeRegistry.playC2S().register(GiveGlowingEffectPayload.ID, GiveGlowingEffectPayload.CODEC); + + // :::server_global_receiver + ServerPlayNetworking.registerGlobalReceiver(GiveGlowingEffectPayload.ID, (payload, context) -> { + Entity entity = context.player().getWorld().getEntityById(payload.entityId()); + + if (entity != null && entity.isInRange(context.player(), 5) && entity instanceof LivingEntity livingEntity) { + livingEntity.addStatusEffect(new StatusEffectInstance(StatusEffects.GLOWING, 100)); + } + }); + // :::server_global_receiver + } +} diff --git a/reference/latest/src/main/java/com/example/docs/networking/basic/GiveGlowingEffectPayload.java b/reference/latest/src/main/java/com/example/docs/networking/basic/GiveGlowingEffectPayload.java new file mode 100644 index 000000000..2bc37ad11 --- /dev/null +++ b/reference/latest/src/main/java/com/example/docs/networking/basic/GiveGlowingEffectPayload.java @@ -0,0 +1,18 @@ +package com.example.docs.networking.basic; + +import net.minecraft.network.RegistryByteBuf; +import net.minecraft.network.codec.PacketCodec; +import net.minecraft.network.codec.PacketCodecs; +import net.minecraft.network.packet.CustomPayload; + +// :::give_glowing_effect_payload +public record GiveGlowingEffectPayload(int entityId) implements CustomPayload { + public static final CustomPayload.Id ID = new CustomPayload.Id<>(FabricDocsReferenceNetworkingBasic.GIVE_GLOWING_EFFECT_PAYLOAD_ID); + public static final PacketCodec CODEC = PacketCodec.tuple(PacketCodecs.INTEGER, GiveGlowingEffectPayload::entityId, GiveGlowingEffectPayload::new); + + @Override + public Id getId() { + return ID; + } +} +// :::give_glowing_effect_payload diff --git a/reference/latest/src/main/java/com/example/docs/networking/basic/LightningTaterItem.java b/reference/latest/src/main/java/com/example/docs/networking/basic/LightningTaterItem.java new file mode 100644 index 000000000..547fddd72 --- /dev/null +++ b/reference/latest/src/main/java/com/example/docs/networking/basic/LightningTaterItem.java @@ -0,0 +1,35 @@ +package com.example.docs.networking.basic; + +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.Item; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.util.ActionResult; +import net.minecraft.util.Hand; +import net.minecraft.world.World; + +import net.fabricmc.fabric.api.networking.v1.PlayerLookup; +import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; + +// :::lightning_tater_item +public class LightningTaterItem extends Item { + public LightningTaterItem(Settings settings) { + super(settings); + } + + @Override + public ActionResult use(World world, PlayerEntity user, Hand hand) { + if (world.isClient()) { + return ActionResult.PASS; + } + + SummonLightningPayload payload = new SummonLightningPayload(user.getBlockPos()); + + for (ServerPlayerEntity player : PlayerLookup.world((ServerWorld) world)) { + ServerPlayNetworking.send(player, payload); + } + + return ActionResult.SUCCESS; + } +} +// :::lightning_tater_item diff --git a/reference/latest/src/main/java/com/example/docs/networking/basic/SummonLightningPayload.java b/reference/latest/src/main/java/com/example/docs/networking/basic/SummonLightningPayload.java new file mode 100644 index 000000000..2c57f4371 --- /dev/null +++ b/reference/latest/src/main/java/com/example/docs/networking/basic/SummonLightningPayload.java @@ -0,0 +1,18 @@ +package com.example.docs.networking.basic; + +import net.minecraft.network.RegistryByteBuf; +import net.minecraft.network.codec.PacketCodec; +import net.minecraft.network.packet.CustomPayload; +import net.minecraft.util.math.BlockPos; + +// :::summon_Lightning_payload +public record SummonLightningPayload(BlockPos pos) implements CustomPayload { + public static final CustomPayload.Id ID = new CustomPayload.Id<>(FabricDocsReferenceNetworkingBasic.SUMMON_LIGHTNING_PAYLOAD_ID); + public static final PacketCodec CODEC = PacketCodec.tuple(BlockPos.PACKET_CODEC, SummonLightningPayload::pos, SummonLightningPayload::new); + + @Override + public Id getId() { + return ID; + } +} +// :::summon_Lightning_payload diff --git a/reference/latest/src/main/resources/assets/fabric-docs-reference/textures/item/lightning_tater.png b/reference/latest/src/main/resources/assets/fabric-docs-reference/textures/item/lightning_tater.png new file mode 100644 index 000000000..b32c8a9ca Binary files /dev/null and b/reference/latest/src/main/resources/assets/fabric-docs-reference/textures/item/lightning_tater.png differ diff --git a/reference/latest/src/main/resources/fabric.mod.json b/reference/latest/src/main/resources/fabric.mod.json index ca1657485..6cd508942 100644 --- a/reference/latest/src/main/resources/fabric.mod.json +++ b/reference/latest/src/main/resources/fabric.mod.json @@ -20,13 +20,15 @@ "com.example.docs.block.entity.FabricDocsReferenceBlockEntities", "com.example.docs.component.FabricDocsReferenceComponents", "com.example.docs.advancement.FabricDocsReferenceDatagenAdvancement", - "com.example.docs.networking.FabricDocsReferenceNetworking" + "com.example.docs.networking.FabricDocsReferenceNetworking", + "com.example.docs.networking.basic.FabricDocsReferenceNetworkingBasic" ], "client": [ "com.example.docs.FabricDocsReferenceClient", "com.example.docs.client.command.FabricDocsReferenceClientCommands", "com.example.docs.FabricDocsDynamicSound", - "com.example.docs.FabricDocsBlockEntityRenderer" + "com.example.docs.FabricDocsBlockEntityRenderer", + "com.example.docs.network.basic.FabricDocsReferenceNetworkingBasicClient" ], "fabric-datagen": [ "com.example.docs.datagen.FabricDocsReferenceDataGenerator" diff --git a/sidebar_translations.json b/sidebar_translations.json index 23dd41fc7..dec10293e 100644 --- a/sidebar_translations.json +++ b/sidebar_translations.json @@ -61,6 +61,7 @@ "develop.misc": "Miscellaneous Pages", "develop.misc.codecs": "Codecs", "develop.misc.events": "Events", + "develop.misc.networking": "Networking", "develop.misc.text-and-translations": "Text and Translations", "develop.misc.ideTipsAndTricks": "IDE Tips and Tricks", "develop.misc.automatic-testing": "Automated Testing",