aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--GuiTest/src/main/java/io/github/cottonmc/test/TestDescription.java20
-rw-r--r--src/main/java/io/github/cottonmc/cotton/gui/SyncedGuiDescription.java35
-rw-r--r--src/main/java/io/github/cottonmc/cotton/gui/impl/LibGuiCommon.java10
-rw-r--r--src/main/java/io/github/cottonmc/cotton/gui/impl/ScreenNetworkingImpl.java142
-rw-r--r--src/main/java/io/github/cottonmc/cotton/gui/impl/client/LibGuiClient.java2
-rw-r--r--src/main/java/io/github/cottonmc/cotton/gui/networking/NetworkSide.java13
-rw-r--r--src/main/java/io/github/cottonmc/cotton/gui/networking/ScreenNetworking.java96
-rw-r--r--src/main/java/io/github/cottonmc/cotton/gui/networking/package-info.java7
-rw-r--r--src/main/resources/fabric.mod.json4
9 files changed, 326 insertions, 3 deletions
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/src/main/java/io/github/cottonmc/cotton/gui/SyncedGuiDescription.java b/src/main/java/io/github/cottonmc/cotton/gui/SyncedGuiDescription.java
index ec779a6..fae723e 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.LibGui;
+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;
@@ -513,4 +519,33 @@ public class SyncedGuiDescription extends ScreenHandler implements GuiDescriptio
public void setTitleAlignment(HorizontalAlignment titleAlignment) {
this.titleAlignment = titleAlignment;
}
+
+ /**
+ * Gets the network side this GUI description runs on.
+ *
+ * @return this GUI's network side
+ * @since 3.3.0
+ */
+ public final NetworkSide getNetworkSide() {
+ return world instanceof ServerWorld ? NetworkSide.SERVER : NetworkSide.CLIENT;
+ }
+
+ /**
+ * Gets the packet sender corresponding to this GUI's network side.
+ *
+ * @return the packet sender
+ * @since 3.3.0
+ */
+ public final PacketSender getPacketSender() {
+ if (getNetworkSide() == NetworkSide.SERVER) {
+ return ServerPlayNetworking.getSender((ServerPlayerEntity) playerInventory.player);
+ } else {
+ return getClientPacketSender();
+ }
+ }
+
+ @Environment(EnvType.CLIENT)
+ private PacketSender getClientPacketSender() {
+ return ClientPlayNetworking.getSender();
+ }
}
diff --git a/src/main/java/io/github/cottonmc/cotton/gui/impl/LibGuiCommon.java b/src/main/java/io/github/cottonmc/cotton/gui/impl/LibGuiCommon.java
new file mode 100644
index 0000000..5e87287
--- /dev/null
+++ b/src/main/java/io/github/cottonmc/cotton/gui/impl/LibGuiCommon.java
@@ -0,0 +1,10 @@
+package io.github.cottonmc.cotton.gui.impl;
+
+import net.fabricmc.api.ModInitializer;
+
+public final class LibGuiCommon implements ModInitializer {
+ @Override
+ public void onInitialize() {
+ ScreenNetworkingImpl.init();
+ }
+}
diff --git a/src/main/java/io/github/cottonmc/cotton/gui/impl/ScreenNetworkingImpl.java b/src/main/java/io/github/cottonmc/cotton/gui/impl/ScreenNetworkingImpl.java
new file mode 100644
index 0000000..a8d8921
--- /dev/null
+++ b/src/main/java/io/github/cottonmc/cotton/gui/impl/ScreenNetworkingImpl.java
@@ -0,0 +1,142 @@
+package io.github.cottonmc.cotton.gui.impl;
+
+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.PacketByteBufs;
+import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking;
+import net.minecraft.entity.player.PlayerEntity;
+import net.minecraft.network.PacketByteBuf;
+import net.minecraft.screen.ScreenHandler;
+import net.minecraft.util.Identifier;
+
+import io.github.cottonmc.cotton.gui.SyncedGuiDescription;
+import io.github.cottonmc.cotton.gui.networking.NetworkSide;
+import io.github.cottonmc.cotton.gui.networking.ScreenNetworking;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.WeakHashMap;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+
+public class ScreenNetworkingImpl implements ScreenNetworking {
+ // Packet structure:
+ // syncId: int
+ // message: identifier
+ // rest: buf
+
+ public static final Identifier SCREEN_MESSAGE_S2C = new Identifier("libgui", "screen_message_s2c");
+ public static final Identifier SCREEN_MESSAGE_C2S = new Identifier("libgui", "screen_message_c2s");
+
+ private static final Logger LOGGER = LogManager.getLogger();
+ private static final Map<SyncedGuiDescription, ScreenNetworkingImpl> instanceCache = new WeakHashMap<>();
+
+ private final Map<Identifier, MessageReceiver> 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<PacketByteBuf> 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<PacketByteBuf> writer) {
+ // NO-OP
+ }
+ }
+}
diff --git a/src/main/java/io/github/cottonmc/cotton/gui/impl/client/LibGuiClient.java b/src/main/java/io/github/cottonmc/cotton/gui/impl/client/LibGuiClient.java
index c9e0010..91ce7b7 100644
--- a/src/main/java/io/github/cottonmc/cotton/gui/impl/client/LibGuiClient.java
+++ b/src/main/java/io/github/cottonmc/cotton/gui/impl/client/LibGuiClient.java
@@ -8,6 +8,7 @@ import net.minecraft.resource.ResourceType;
import blue.endless.jankson.Jankson;
import blue.endless.jankson.JsonElement;
import blue.endless.jankson.JsonObject;
+import io.github.cottonmc.cotton.gui.impl.ScreenNetworkingImpl;
import io.github.cottonmc.jankson.JanksonFactory;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@@ -27,6 +28,7 @@ public class LibGuiClient implements ClientModInitializer {
public void onInitializeClient() {
config = loadConfig();
+ ScreenNetworkingImpl.initClient();
ResourceManagerHelper.get(ResourceType.CLIENT_RESOURCES).registerReloadListener(NinePatchInternals.MetadataLoader.INSTANCE);
}
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.
+ *
+ * <h2>Registering a message receiver</h2>
+ * {@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.
+ *
+ * <p>Message receivers should be registered in the constructor of a {@link SyncedGuiDescription}.
+ *
+ * <h2>Sending messages</h2>
+ * 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 <i>opposite</i>
+ * side.
+ *
+ * <h2>Example</h2>
+ * <pre>
+ * {@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));
+ * });
+ * }
+ * </pre>
+ *
+ * @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<PacketByteBuf> writer);
+
+ /**
+ * A handler for received screen messages.
+ */
+ @FunctionalInterface
+ interface MessageReceiver {
+ /**
+ * Handles a received screen message.
+ *
+ * <p>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 c8f0ffe..ca3cde8 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.impl.client.LibGuiClient"],
"modmenu": ["io.github.cottonmc.cotton.gui.impl.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": "*"