diff --git a/fabric/build.gradle b/fabric/build.gradle index 92d6255..cf2e8dc 100644 --- a/fabric/build.gradle +++ b/fabric/build.gradle @@ -10,7 +10,8 @@ java.toolchain.languageVersion = JavaLanguageVersion.of(project.modJavaVersion) repositories { maven { - url = "https://maven.terraformersmc.com/releases" + // url = "https://maven.terraformersmc.com/releases" + url = "https://maven.gnomecraft.net/releases" // Official mirror from Terraformers while their system is down } } diff --git a/src/main/java/yalter/mousetweaks/Main.java b/src/main/java/yalter/mousetweaks/Main.java index f16c2fb..d6c05fa 100644 --- a/src/main/java/yalter/mousetweaks/Main.java +++ b/src/main/java/yalter/mousetweaks/Main.java @@ -100,7 +100,7 @@ private static void updateScreen(Screen newScreen) { */ public static boolean onMouseClicked(Screen screen, double x, double y, MouseButton button) { updateScreen(screen); - if (handler == null) + if (handler == null || ServerFeatureControl.isMouseDisabled()) return false; // Store the currently selected slot. @@ -181,7 +181,7 @@ private static void rmbTweakMaybeClickSlot(Slot slot, ItemStack stackOnMouse) { */ public static boolean onMouseReleased(Screen screen, double x, double y, MouseButton button) { updateScreen(screen); - if (handler == null) + if (handler == null || ServerFeatureControl.isMouseDisabled()) return false; // Reset the flags. @@ -204,7 +204,7 @@ else if (button == MouseButton.RIGHT) */ public static boolean onMouseDrag(Screen screen, double x, double y, MouseButton button) { updateScreen(screen); - if (handler == null) + if (handler == null || ServerFeatureControl.isMouseDisabled()) return false; Slot selectedSlot = handler.getSlotUnderMouse(x, y); diff --git a/src/main/java/yalter/mousetweaks/ServerFeatureControl.java b/src/main/java/yalter/mousetweaks/ServerFeatureControl.java new file mode 100644 index 0000000..b3b8336 --- /dev/null +++ b/src/main/java/yalter/mousetweaks/ServerFeatureControl.java @@ -0,0 +1,78 @@ +package yalter.mousetweaks; + +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.resources.Identifier; +import org.jspecify.annotations.NonNull; + +import java.nio.charset.StandardCharsets; + +public class ServerFeatureControl { + + private static boolean mouseDisabled, wheelDisabled; + + public static boolean isMouseDisabled() { + return mouseDisabled; + } + + public static boolean isWheelDisabled() { + return wheelDisabled; + } + + public static void apply(ServerFeatureControlPayload payload) { + for (String token : payload.value().strip().split("[\\s,]+")) { + switch (token.toLowerCase()) { + case "mouse" -> mouseDisabled = false; + case "!mouse" -> mouseDisabled = true; + + case "wheel" -> wheelDisabled = false; + case "!wheel" -> wheelDisabled = true; + + case "" -> { + mouseDisabled = true; + wheelDisabled = true; + } + + default -> { } + } + } + } + + public static void reset() { + mouseDisabled = false; + wheelDisabled = false; + } + + public record ServerFeatureControlPayload(String value) implements CustomPacketPayload { + + public static final Identifier ID = Identifier.fromNamespaceAndPath(Constants.MOD_ID, "feature_control"); + public static final Type TYPE = new Type<>(ID); + + @Override + public @NonNull Type type() { + return TYPE; + } + + public static final StreamCodec CODEC = + new StreamCodec<>() { + @Override + public ServerFeatureControlPayload decode(RegistryFriendlyByteBuf input) { + int length = input.readableBytes(); + + byte[] bytes = new byte[length]; + input.readBytes(bytes); + + return new ServerFeatureControlPayload(new String(bytes, StandardCharsets.UTF_8)); + } + + // Codecs require both methods, and even though we don't send it, it's helpful in tests + @Override + public void encode(RegistryFriendlyByteBuf output, ServerFeatureControlPayload value) { + byte[] bytes = value.value().getBytes(StandardCharsets.UTF_8); + output.writeBytes(bytes); + } + }; + } + +} diff --git a/src/main/java/yalter/mousetweaks/fabric/MouseTweaksFabric.java b/src/main/java/yalter/mousetweaks/fabric/MouseTweaksFabric.java index 5e5f6f3..9ad8757 100644 --- a/src/main/java/yalter/mousetweaks/fabric/MouseTweaksFabric.java +++ b/src/main/java/yalter/mousetweaks/fabric/MouseTweaksFabric.java @@ -1,10 +1,15 @@ package yalter.mousetweaks.fabric; import net.fabricmc.api.ClientModInitializer; +import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents; +import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; import net.fabricmc.fabric.api.client.screen.v1.ScreenEvents; import net.fabricmc.fabric.api.client.screen.v1.ScreenMouseEvents; +import net.fabricmc.fabric.api.networking.v1.PayloadTypeRegistry; import yalter.mousetweaks.Main; import yalter.mousetweaks.MouseButton; +import yalter.mousetweaks.ServerFeatureControl; +import yalter.mousetweaks.ServerFeatureControl.ServerFeatureControlPayload; public class MouseTweaksFabric implements ClientModInitializer { @Override @@ -32,5 +37,12 @@ public void onInitializeClient() { return Main.onMouseScrolled(screen, x, y, vert); }); }); + + PayloadTypeRegistry.clientboundPlay().register(ServerFeatureControlPayload.TYPE, ServerFeatureControlPayload.CODEC); + ClientPlayNetworking.registerGlobalReceiver( + ServerFeatureControlPayload.TYPE, + (payload, _) -> ServerFeatureControl.apply(payload) + ); + ClientPlayConnectionEvents.DISCONNECT.register((_, _) -> ServerFeatureControl.reset()); } } diff --git a/src/main/java/yalter/mousetweaks/forge/MouseTweaksForge.java b/src/main/java/yalter/mousetweaks/forge/MouseTweaksForge.java index f5c4c56..ea585b0 100644 --- a/src/main/java/yalter/mousetweaks/forge/MouseTweaksForge.java +++ b/src/main/java/yalter/mousetweaks/forge/MouseTweaksForge.java @@ -1,6 +1,9 @@ package yalter.mousetweaks.forge; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.resources.Identifier; import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.client.event.ClientPlayerNetworkEvent; import net.minecraftforge.client.event.ScreenEvent.MouseButtonPressed; import net.minecraftforge.client.event.ScreenEvent.MouseButtonReleased; import net.minecraftforge.client.event.ScreenEvent.MouseDragged; @@ -13,7 +16,10 @@ import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent; import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; import net.minecraftforge.fml.loading.FMLEnvironment; +import net.minecraftforge.network.Channel; +import net.minecraftforge.network.ChannelBuilder; import yalter.mousetweaks.*; +import yalter.mousetweaks.ServerFeatureControl.ServerFeatureControlPayload; import java.lang.invoke.MethodHandles; @@ -25,6 +31,7 @@ public MouseTweaksForge() { return; } + registerChannel(); BusGroup.DEFAULT.register(MethodHandles.lookup(), this); MinecraftForge.registerConfigScreen(ConfigScreen::new); @@ -90,4 +97,21 @@ public boolean onGuiMouseDragPre(MouseDragged.Pre event) { return false; } + + @SubscribeEvent + public void onClientLogout(ClientPlayerNetworkEvent.LoggingOut event) { + ServerFeatureControl.reset(); + } + + private void registerChannel() { + ChannelBuilder + .named(Identifier.fromNamespaceAndPath(Constants.MOD_ID, "network")) + .optional() + .payloadChannel() + .play() + .clientbound() + .addMain(ServerFeatureControlPayload.TYPE, ServerFeatureControlPayload.CODEC, + (payload, context) -> ServerFeatureControl.apply(payload)) + .build(); + } } diff --git a/src/main/java/yalter/mousetweaks/handlers/GuiContainerHandler.java b/src/main/java/yalter/mousetweaks/handlers/GuiContainerHandler.java index acf92cb..f52c7ef 100644 --- a/src/main/java/yalter/mousetweaks/handlers/GuiContainerHandler.java +++ b/src/main/java/yalter/mousetweaks/handlers/GuiContainerHandler.java @@ -5,6 +5,7 @@ import net.minecraft.world.inventory.*; import yalter.mousetweaks.IGuiScreenHandler; import yalter.mousetweaks.MouseButton; +import yalter.mousetweaks.ServerFeatureControl; import yalter.mousetweaks.api.MouseTweaksDisableWheelTweak; import yalter.mousetweaks.api.MouseTweaksIgnore; import yalter.mousetweaks.mixin.AbstractContainerScreenAccessor; @@ -29,7 +30,7 @@ public boolean isMouseTweaksDisabled() { @Override public boolean isWheelTweakDisabled() { - return screen.getClass().isAnnotationPresent(MouseTweaksDisableWheelTweak.class); + return screen.getClass().isAnnotationPresent(MouseTweaksDisableWheelTweak.class) || ServerFeatureControl.isWheelDisabled(); } @Override diff --git a/src/main/java/yalter/mousetweaks/handlers/IMTModGuiContainer3ExHandler.java b/src/main/java/yalter/mousetweaks/handlers/IMTModGuiContainer3ExHandler.java index ee7f65c..a690695 100644 --- a/src/main/java/yalter/mousetweaks/handlers/IMTModGuiContainer3ExHandler.java +++ b/src/main/java/yalter/mousetweaks/handlers/IMTModGuiContainer3ExHandler.java @@ -4,6 +4,7 @@ import net.minecraft.world.inventory.Slot; import yalter.mousetweaks.IGuiScreenHandler; import yalter.mousetweaks.MouseButton; +import yalter.mousetweaks.ServerFeatureControl; import yalter.mousetweaks.api.IMTModGuiContainer3Ex; import java.util.List; @@ -22,7 +23,7 @@ public boolean isMouseTweaksDisabled() { @Override public boolean isWheelTweakDisabled() { - return modGuiContainer.MT_isWheelTweakDisabled(); + return modGuiContainer.MT_isWheelTweakDisabled() || ServerFeatureControl.isWheelDisabled(); } @Override diff --git a/src/main/java/yalter/mousetweaks/neoforge/MouseTweaksNeo.java b/src/main/java/yalter/mousetweaks/neoforge/MouseTweaksNeo.java index 066bf5f..2d244ec 100644 --- a/src/main/java/yalter/mousetweaks/neoforge/MouseTweaksNeo.java +++ b/src/main/java/yalter/mousetweaks/neoforge/MouseTweaksNeo.java @@ -1,11 +1,14 @@ package yalter.mousetweaks.neoforge; +import net.neoforged.neoforge.client.event.ClientPlayerNetworkEvent; import net.neoforged.neoforge.client.event.ScreenEvent.MouseButtonPressed; import net.neoforged.neoforge.client.event.ScreenEvent.MouseButtonReleased; import net.neoforged.neoforge.client.event.ScreenEvent.MouseDragged; import net.neoforged.neoforge.client.event.ScreenEvent.MouseScrolled; import net.neoforged.neoforge.client.gui.IConfigScreenFactory; +import net.neoforged.neoforge.client.network.event.RegisterClientPayloadHandlersEvent; import net.neoforged.neoforge.common.NeoForge; +import net.neoforged.neoforge.network.event.RegisterPayloadHandlersEvent; import net.neoforged.bus.api.IEventBus; import net.neoforged.bus.api.SubscribeEvent; import net.neoforged.fml.ModLoadingContext; @@ -16,6 +19,8 @@ import yalter.mousetweaks.Logger; import yalter.mousetweaks.Main; import yalter.mousetweaks.MouseButton; +import yalter.mousetweaks.ServerFeatureControl; +import yalter.mousetweaks.ServerFeatureControl.ServerFeatureControlPayload; @Mod(Constants.MOD_ID) public class MouseTweaksNeo { @@ -26,6 +31,8 @@ public MouseTweaksNeo(IEventBus modBus) { } modBus.addListener(this::onClientSetup); + modBus.addListener(this::registerPayloads); + modBus.addListener(this::registerClientHandlers); NeoForge.EVENT_BUS.register(this); ModLoadingContext.get().registerExtensionPoint(IConfigScreenFactory.class, ClientHelper::new); @@ -81,4 +88,17 @@ public void onGuiMouseDragPre(MouseDragged.Pre event) { event.setCanceled(true); } } + + public void registerPayloads(RegisterPayloadHandlersEvent event) { + event.registrar("1").optional().playToClient(ServerFeatureControlPayload.TYPE, ServerFeatureControlPayload.CODEC); + } + + public void registerClientHandlers(RegisterClientPayloadHandlersEvent event) { + event.register(ServerFeatureControlPayload.TYPE, (payload, context) -> ServerFeatureControl.apply(payload)); + } + + @SubscribeEvent + public void onClientLogout(ClientPlayerNetworkEvent.LoggingOut event) { + ServerFeatureControl.reset(); + } } diff --git a/src/testmodClient/java/yalter/mousetweaks/test/InventoryTests.java b/src/testmodClient/java/yalter/mousetweaks/test/InventoryTests.java index 936a969..679cf85 100644 --- a/src/testmodClient/java/yalter/mousetweaks/test/InventoryTests.java +++ b/src/testmodClient/java/yalter/mousetweaks/test/InventoryTests.java @@ -3,12 +3,15 @@ import net.fabricmc.fabric.api.client.gametest.v1.TestInput; import net.fabricmc.fabric.api.client.gametest.v1.context.ClientGameTestContext; import net.fabricmc.fabric.api.client.gametest.v1.context.TestSingleplayerContext; +import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; +import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.inventory.Slot; import net.minecraft.world.item.Item; import net.minecraft.world.item.Items; import org.lwjgl.glfw.GLFW; import yalter.mousetweaks.mixin.AbstractContainerScreenAccessor; +import yalter.mousetweaks.ServerFeatureControl.ServerFeatureControlPayload; import static yalter.mousetweaks.test.InventoryTestHelper.*; @@ -86,6 +89,21 @@ void closeScreen() { context.waitTick(); } + void sendServerFeatures(String options) { + world.getServer().runOnServer(server -> { + ServerPlayer player = server.getPlayerList().getPlayers().getFirst(); + + if (!ServerPlayNetworking.canSend(player, ServerFeatureControlPayload.TYPE)) { + throw new AssertionError("Client did not register " + ServerFeatureControlPayload.TYPE.id()); + } + + ServerPlayNetworking.send(player, new ServerFeatureControlPayload(options)); + }); + + // Client processes messages at end of tick + context.waitTick(); + } + /** * Asserts that the carried item matches expectations. */ @@ -797,4 +815,87 @@ void testFurnaceSmeltingPreserved() { closeScreen(); } + + void testServerFeatureControl() { + testServerDisablesWheel(); + testServerDisablesMouse(); + + // Restore normal behavior + sendServerFeatures("mouse wheel"); + } + + private void testServerDisablesWheel() { + world.getServer().runCommand("clear @p"); + + sendServerFeatures("mouse !wheel"); + + placeAndOpenBlock("chest"); + + world.getServer().runCommand("item replace block 0 ~ 1 container.0 with minecraft:gold_ingot 8"); + context.waitTick(); + + Slot chestSlot = getSlot(0); + Slot playerSlot = getSlot(27); + + assertSlotContains(chestSlot, Items.GOLD_INGOT, 8); + assertSlotEmpty(playerSlot); + + setCursorTo(chestSlot); + context.getInput().scroll(-1); + context.waitTick(); + + assertSlotContains(chestSlot, Items.GOLD_INGOT, 8); + assertSlotEmpty(playerSlot); + + closeScreen(); + } + + private void testServerDisablesMouse() { + world.getServer().runCommand("clear @p"); + + world.getServer().runCommand("item replace entity @p inventory.0 with minecraft:dirt 16"); + world.getServer().runCommand("item replace entity @p inventory.1 with minecraft:cobblestone 16"); + world.getServer().runCommand("item replace entity @p inventory.2 with minecraft:dirt 16"); + context.waitTick(); + + sendServerFeatures("!mouse wheel"); + + openPlayerInventory(); + + Slot invSlot0 = getSlot(9); + Slot invSlot1 = getSlot(10); + Slot invSlot2 = getSlot(11); + + Slot hotbarSlot0 = getSlot(36); + Slot hotbarSlot1 = getSlot(37); + + TestInput input = context.getInput(); + + input.holdShift(); + + // Begin on an empty slot so vanilla does not shift-click anything. + setCursorTo(hotbarSlot0); + input.holdMouse(GLFW.GLFW_MOUSE_BUTTON_LEFT); + context.waitTick(); + + setCursorTo(invSlot0); + setCursorTo(invSlot1); + setCursorTo(invSlot2); + + input.releaseMouse(GLFW.GLFW_MOUSE_BUTTON_LEFT); + input.releaseShift(); + context.waitTick(); + + // Mouse Tweaks was disabled, so the drag should do nothing. + assertSlotContains(invSlot0, Items.DIRT, 16); + assertSlotContains(invSlot1, Items.COBBLESTONE, 16); + assertSlotContains(invSlot2, Items.DIRT, 16); + + assertSlotEmpty(hotbarSlot0); + assertSlotEmpty(hotbarSlot1); + assertCarriedEmpty(); + + closeScreen(); + } + } diff --git a/src/testmodClient/java/yalter/mousetweaks/test/MouseTweaksClientGameTest.java b/src/testmodClient/java/yalter/mousetweaks/test/MouseTweaksClientGameTest.java index 2f8d852..37208f7 100644 --- a/src/testmodClient/java/yalter/mousetweaks/test/MouseTweaksClientGameTest.java +++ b/src/testmodClient/java/yalter/mousetweaks/test/MouseTweaksClientGameTest.java @@ -41,6 +41,7 @@ public void runTest(ClientGameTestContext context) { tests.testWheelTweakWithBundle(); tests.testCraftingOutputSlot(); tests.testFurnaceSmeltingPreserved(); + tests.testServerFeatureControl(); } }