From 788176dcaa49ced35e9f441a820410bcd4f09cfe Mon Sep 17 00:00:00 2001 From: Juuxel <6596629+Juuxel@users.noreply.github.com> Date: Thu, 17 Dec 2020 20:08:01 +0200 Subject: Add screen networking API, update to 1.16.4 --- .../io/github/cottonmc/test/TestDescription.java | 20 ++- build.gradle | 2 +- gradle.properties | 10 +- .../cottonmc/cotton/gui/SyncedGuiDescription.java | 53 ++++++-- .../cottonmc/cotton/gui/client/LibGuiClient.java | 2 + .../cottonmc/cotton/gui/impl/LibGuiCommon.java | 10 ++ .../cotton/gui/impl/ScreenNetworkingImpl.java | 142 +++++++++++++++++++++ .../cotton/gui/networking/NetworkSide.java | 13 ++ .../cotton/gui/networking/ScreenNetworking.java | 96 ++++++++++++++ .../cotton/gui/networking/package-info.java | 7 + src/main/resources/fabric.mod.json | 4 +- 11 files changed, 341 insertions(+), 18 deletions(-) create mode 100644 src/main/java/io/github/cottonmc/cotton/gui/impl/LibGuiCommon.java create mode 100644 src/main/java/io/github/cottonmc/cotton/gui/impl/ScreenNetworkingImpl.java create mode 100644 src/main/java/io/github/cottonmc/cotton/gui/networking/NetworkSide.java create mode 100644 src/main/java/io/github/cottonmc/cotton/gui/networking/ScreenNetworking.java create mode 100644 src/main/java/io/github/cottonmc/cotton/gui/networking/package-info.java diff --git a/GuiTest/src/main/java/io/github/cottonmc/test/TestDescription.java b/GuiTest/src/main/java/io/github/cottonmc/test/TestDescription.java index b3e0c80..e0602be 100644 --- a/GuiTest/src/main/java/io/github/cottonmc/test/TestDescription.java +++ b/GuiTest/src/main/java/io/github/cottonmc/test/TestDescription.java @@ -1,14 +1,19 @@ package io.github.cottonmc.test; import io.github.cottonmc.cotton.gui.SyncedGuiDescription; +import io.github.cottonmc.cotton.gui.networking.NetworkSide; +import io.github.cottonmc.cotton.gui.networking.ScreenNetworking; import io.github.cottonmc.cotton.gui.widget.*; import net.minecraft.entity.player.PlayerInventory; import net.minecraft.screen.ScreenHandlerContext; import net.minecraft.screen.ScreenHandlerType; import net.minecraft.text.LiteralText; +import net.minecraft.util.Identifier; public class TestDescription extends SyncedGuiDescription { - + private static final Identifier TEST_MESSAGE = new Identifier("libgui", "test"); + private static final Identifier UNREGISTERED_ON_SERVER = new Identifier("libgui", "unregistered_on_server"); + public TestDescription(ScreenHandlerType type, int syncId, PlayerInventory playerInventory, ScreenHandlerContext context) { super(type, syncId, playerInventory, getBlockInventory(context, GuiBlockEntity.INVENTORY_SIZE), null); @@ -16,7 +21,14 @@ public class TestDescription extends SyncedGuiDescription { root.add(WItemSlot.of(blockInventory, 0, 4, 1), 0, 1); - root.add(new WButton(new LiteralText("Button A")), 0, 3, 4, 1); + WButton buttonA = new WButton(new LiteralText("Button A")); + + buttonA.setOnClick(() -> { + ScreenNetworking.of(this, NetworkSide.CLIENT).send(TEST_MESSAGE, buf -> {}); + ScreenNetworking.of(this, NetworkSide.CLIENT).send(UNREGISTERED_ON_SERVER, buf -> {}); + }); + + root.add(buttonA, 0, 3, 4, 1); root.add(new WButton(new LiteralText("Button B")), 5, 3, 4, 1); root.add(new WButton(new LiteralText("Button C")), 0, 5, 4, 1); root.add(new WButton(new LiteralText("Button D")), 5, 5, 4, 1); @@ -29,5 +41,9 @@ public class TestDescription extends SyncedGuiDescription { System.out.println(root.toString()); this.getRootPanel().validate(this); + + ScreenNetworking.of(this, NetworkSide.SERVER).receive(TEST_MESSAGE, buf -> { + System.out.println("Received on the server!"); + }); } } diff --git a/build.gradle b/build.gradle index 074acc2..acb42bf 100644 --- a/build.gradle +++ b/build.gradle @@ -133,4 +133,4 @@ artifactory { } else { println "Cannot configure artifactory; please define ext.artifactoryUsername and ext.artifactoryPassword before running artifactoryPublish" } -} \ No newline at end of file +} diff --git a/gradle.properties b/gradle.properties index 0bf317a..20c3ff5 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,16 +3,16 @@ org.gradle.jvmargs=-Xmx1G # Fabric Properties # check these on https://fabricmc.net/use - minecraft_version=1.16.3 - yarn_mappings=1.16.3+build.47 - loader_version=0.10.2+build.210 + minecraft_version=1.16.4 + yarn_mappings=1.16.4+build.7 + loader_version=0.10.8 # Mod Properties - mod_version = 3.2.2 + mod_version = 3.3.0 maven_group = io.github.cottonmc archives_base_name = LibGui # Dependencies - fabric_version=0.24.0+build.411-1.16 + fabric_version=0.28.3+1.16 jankson_version=3.0.1+j1.2.0 modmenu_version=1.14.6+build.31 diff --git a/src/main/java/io/github/cottonmc/cotton/gui/SyncedGuiDescription.java b/src/main/java/io/github/cottonmc/cotton/gui/SyncedGuiDescription.java index f7803f6..ce0dd4b 100644 --- a/src/main/java/io/github/cottonmc/cotton/gui/SyncedGuiDescription.java +++ b/src/main/java/io/github/cottonmc/cotton/gui/SyncedGuiDescription.java @@ -2,6 +2,9 @@ package io.github.cottonmc.cotton.gui; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; +import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; +import net.fabricmc.fabric.api.networking.v1.PacketSender; +import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; import net.minecraft.block.Block; import net.minecraft.block.BlockState; import net.minecraft.block.InventoryProvider; @@ -18,10 +21,13 @@ import net.minecraft.screen.ScreenHandlerContext; import net.minecraft.screen.ScreenHandlerType; import net.minecraft.screen.slot.Slot; import net.minecraft.screen.slot.SlotActionType; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.server.world.ServerWorld; import net.minecraft.world.World; import io.github.cottonmc.cotton.gui.client.BackgroundPainter; import io.github.cottonmc.cotton.gui.client.LibGuiClient; +import io.github.cottonmc.cotton.gui.networking.NetworkSide; import io.github.cottonmc.cotton.gui.widget.WGridPanel; import io.github.cottonmc.cotton.gui.widget.WLabel; import io.github.cottonmc.cotton.gui.widget.WPanel; @@ -297,22 +303,22 @@ public class SyncedGuiDescription extends ScreenHandler implements GuiDescriptio if (rootPanel!=null) return rootPanel.onMouseUp(x, y, state); return null; } - + @Nullable public WWidget doMouseDown(int x, int y, int button) { if (rootPanel!=null) return rootPanel.onMouseDown(x, y, button); return null; } - + public void doMouseDrag(int x, int y, int button, double deltaX, double deltaY) { if (rootPanel!=null) rootPanel.onMouseDrag(x, y, button, deltaX, deltaY); } - + public void doClick(int x, int y, int button) { if (focus!=null) { int wx = focus.getAbsoluteX(); int wy = focus.getAbsoluteY(); - + if (x>=wx && x=wy && y instanceCache = new WeakHashMap<>(); + + private final Map messages = new HashMap<>(); + private SyncedGuiDescription description; + private final NetworkSide side; + + private ScreenNetworkingImpl(SyncedGuiDescription description, NetworkSide side) { + this.description = description; + this.side = side; + } + + public void receive(Identifier message, MessageReceiver receiver) { + Objects.requireNonNull(message, "message"); + Objects.requireNonNull(receiver, "receiver"); + + if (!messages.containsKey(message)) { + messages.put(message, receiver); + } else { + throw new IllegalStateException("Message " + message + " on side " + side + " already registered"); + } + } + + @Override + public void send(Identifier message, Consumer writer) { + Objects.requireNonNull(message, "message"); + Objects.requireNonNull(writer, "writer"); + + PacketByteBuf buf = PacketByteBufs.create(); + buf.writeVarInt(description.syncId); + buf.writeIdentifier(message); + writer.accept(buf); + description.getPacketSender().sendPacket(side == NetworkSide.SERVER ? SCREEN_MESSAGE_S2C : SCREEN_MESSAGE_C2S, buf); + } + + public static void init() { + ServerPlayNetworking.registerGlobalReceiver(SCREEN_MESSAGE_C2S, (server, player, networkHandler, buf, responseSender) -> { + handle(server, player, buf); + }); + } + + @Environment(EnvType.CLIENT) + public static void initClient() { + ClientPlayNetworking.registerGlobalReceiver(SCREEN_MESSAGE_S2C, (client, networkHandler, buf, responseSender) -> { + handle(client, client.player, buf); + }); + } + + private static void handle(Executor executor, PlayerEntity player, PacketByteBuf buf) { + ScreenHandler screenHandler = player.currentScreenHandler; + + // Packet data + int syncId = buf.readVarInt(); + Identifier messageId = buf.readIdentifier(); + + if (!(screenHandler instanceof SyncedGuiDescription)) { + LOGGER.error("Received message packet for screen handler {} which is not a SyncedGuiDescription", screenHandler); + return; + } else if (syncId != screenHandler.syncId) { + LOGGER.error("Received message for sync ID {}, current sync ID: {}", syncId, screenHandler.syncId); + return; + } + + ScreenNetworkingImpl networking = instanceCache.get(screenHandler); + + if (networking != null) { + MessageReceiver receiver = networking.messages.get(messageId); + + if (receiver != null) { + buf.retain(); + executor.execute(() -> receiver.onMessage(buf)); + } else { + LOGGER.warn("Message {} not registered for {} on side {}", messageId, screenHandler, networking.side); + } + } else { + LOGGER.warn("GUI description {} does not use networking", screenHandler); + } + } + + public static ScreenNetworking of(SyncedGuiDescription description, NetworkSide networkSide) { + Objects.requireNonNull(description, "description"); + Objects.requireNonNull(networkSide, "networkSide"); + + if (description.getNetworkSide() == networkSide) { + return instanceCache.computeIfAbsent(description, it -> new ScreenNetworkingImpl(description, networkSide)); + } else { + return DummyNetworking.INSTANCE; + } + } + + private static final class DummyNetworking extends ScreenNetworkingImpl { + static final DummyNetworking INSTANCE = new DummyNetworking(); + + private DummyNetworking() { + super(null, null); + } + + @Override + public void receive(Identifier message, MessageReceiver receiver) { + // NO-OP + } + + @Override + public void send(Identifier message, Consumer writer) { + // NO-OP + } + } +} diff --git a/src/main/java/io/github/cottonmc/cotton/gui/networking/NetworkSide.java b/src/main/java/io/github/cottonmc/cotton/gui/networking/NetworkSide.java new file mode 100644 index 0000000..e3816ac --- /dev/null +++ b/src/main/java/io/github/cottonmc/cotton/gui/networking/NetworkSide.java @@ -0,0 +1,13 @@ +package io.github.cottonmc.cotton.gui.networking; + +/** + * The sides of a network connection. + * + * @since 3.3.0 + */ +public enum NetworkSide { + /** The logical client. */ + CLIENT, + /** The logical server. */ + SERVER +} diff --git a/src/main/java/io/github/cottonmc/cotton/gui/networking/ScreenNetworking.java b/src/main/java/io/github/cottonmc/cotton/gui/networking/ScreenNetworking.java new file mode 100644 index 0000000..2a41b29 --- /dev/null +++ b/src/main/java/io/github/cottonmc/cotton/gui/networking/ScreenNetworking.java @@ -0,0 +1,96 @@ +package io.github.cottonmc.cotton.gui.networking; + +import net.minecraft.network.PacketByteBuf; +import net.minecraft.util.Identifier; + +import io.github.cottonmc.cotton.gui.SyncedGuiDescription; +import io.github.cottonmc.cotton.gui.impl.ScreenNetworkingImpl; + +import java.util.function.Consumer; + +/** + * {@code ScreenNetworking} handles screen-related network messages sent between the server and the client. + * + *

Registering a message receiver

+ * {@linkplain MessageReceiver Message receivers} can be registered by calling {@link #receive(Identifier, MessageReceiver)} + * on a {@code ScreenNetworking} for the receiving side. The {@code message} ID is a unique ID that matches between + * the sender and the receiver. + * + *

Message receivers should be registered in the constructor of a {@link SyncedGuiDescription}. + * + *

Sending messages

+ * Messages can be sent by calling {@link #send(Identifier, Consumer)} on a {@code ScreenNetworking} + * for the sending side. The {@code message} ID should match up with a receiver registered on the opposite + * side. + * + *

Example

+ *
+ * {@code
+ * private static final Identifier MESSAGE_ID = new Identifier("my_mod", "some_message");
+ *
+ * // Receiver
+ * ScreenNetworking.of(this, NetworkSide.SERVER).receive(MESSAGE_ID, buf -> {
+ * 	   // Example data: a lucky number as an int
+ *     System.out.println("Your lucky number is " + buf.readInt() + "!");
+ * });
+ *
+ * // Sending
+ *
+ * // We're sending from a button. The packet data is our lucky number, 123.
+ * WButton button = ...;
+ * button.setOnClick(() -> {
+ *     ScreenNetworking.of(this, NetworkSide.CLIENT).send(MESSAGE_ID, buf -> buf.writeInt(123));
+ * });
+ * }
+ * 
+ * + * @since 3.3.0 + */ +public interface ScreenNetworking { + /** + * Gets a networking handler for the GUI description that is active on the specified side. + * + * @param description the GUI description + * @param networkSide the network side + * @return the network handler + * @throws NullPointerException if either parameter is null + */ + static ScreenNetworking of(SyncedGuiDescription description, NetworkSide networkSide) { + return ScreenNetworkingImpl.of(description, networkSide); + } + + /** + * Registers a message receiver for the message. + * + * @param message the screen message ID + * @param receiver the message receiver + * @throws IllegalStateException if the message has already been registered + * @throws NullPointerException if either parameter is null + */ + void receive(Identifier message, MessageReceiver receiver); + + /** + * Sends a screen message to the other side of the connection. + * + * @param message the screen message ID + * @param writer a writer that writes the message contents to a packet buffer; + * should not read the buffer + * @throws NullPointerException if either parameter is null + */ + void send(Identifier message, Consumer writer); + + /** + * A handler for received screen messages. + */ + @FunctionalInterface + interface MessageReceiver { + /** + * Handles a received screen message. + * + *

This method should only read from the buffer, not write to it. + * + * @param buf the message packet buffer + */ + void onMessage(PacketByteBuf buf); + } +} diff --git a/src/main/java/io/github/cottonmc/cotton/gui/networking/package-info.java b/src/main/java/io/github/cottonmc/cotton/gui/networking/package-info.java new file mode 100644 index 0000000..e38e08d --- /dev/null +++ b/src/main/java/io/github/cottonmc/cotton/gui/networking/package-info.java @@ -0,0 +1,7 @@ +/** + * Screen networking helpers. + * + * @see io.github.cottonmc.cotton.gui.networking.ScreenNetworking + * @since 3.3.0 + */ +package io.github.cottonmc.cotton.gui.networking; diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index df75753..b540edf 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -18,6 +18,7 @@ "environment": "*", "entrypoints": { + "main": ["io.github.cottonmc.cotton.gui.impl.LibGuiCommon"], "client": ["io.github.cottonmc.cotton.gui.client.LibGuiClient"], "modmenu": ["io.github.cottonmc.cotton.gui.client.modmenu.ModMenuSupport"] }, @@ -28,7 +29,8 @@ "fabricloader": ">=0.8.8", "fabric": "*", "minecraft": ">=1.16.1", - "jankson": "^3.0.0" + "jankson": "^3.0.0", + "fabric-networking-api-v1": "^1.0.0" }, "suggests": { "flamingo": "*" -- cgit