diff options
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": "*" |