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 --- .../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 + 7 files changed, 314 insertions(+), 9 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 (limited to 'src/main/java') 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; -- cgit