aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--GuiTest/src/main/java/io/github/cottonmc/test/TestDescription.java20
-rw-r--r--build.gradle2
-rw-r--r--gradle.properties10
-rw-r--r--src/main/java/io/github/cottonmc/cotton/gui/SyncedGuiDescription.java53
-rw-r--r--src/main/java/io/github/cottonmc/cotton/gui/client/LibGuiClient.java2
-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/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
11 files changed, 341 insertions, 18 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/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<wx+focus.getWidth() && y>=wy && y<wy+focus.getHeight()) {
//Do nothing, focus will get the click soon
} else {
@@ -322,22 +328,22 @@ public class SyncedGuiDescription extends ScreenHandler implements GuiDescriptio
lastFocus.onFocusLost();
}
}
-
+
//if (rootPanel!=null) rootPanel.onClick(x, y, button);
}
-
+
public void doCharType(char ch) {
if (focus!=null) focus.onCharTyped(ch);
}
-
+
//public void doKeyPress(int key) {
// if (focus!=null) focus.onKeyPressed(key);
//}
-
+
//public void doKeyRelease(int key) {
// if (focus!=null) focus.onKeyReleased(key);
//}
-
+
@Nullable
@Override
public PropertyDelegate getPropertyDelegate() {
@@ -559,4 +565,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/client/LibGuiClient.java b/src/main/java/io/github/cottonmc/cotton/gui/client/LibGuiClient.java
index a5fbb2d..d216106 100644
--- a/src/main/java/io/github/cottonmc/cotton/gui/client/LibGuiClient.java
+++ b/src/main/java/io/github/cottonmc/cotton/gui/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(NinePatch.MetadataLoader.INSTANCE);
}
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/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 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": "*"