From 0d9321e3ba97b8fce52b12604d5d8f96147e1778 Mon Sep 17 00:00:00 2001 From: isXander Date: Sun, 26 Mar 2023 18:13:02 +0100 Subject: [PATCH] compound joysticks, button guide in screens, improve API --- .../dev/isxander/controlify/Controlify.java | 76 +++++++- .../controlify/api/ControlifyApi.java | 8 + .../api/bind/ControlifyBindingsApi.java | 8 +- .../api/buttonguide/ButtonGuideApi.java | 34 ++++ .../api/buttonguide/ButtonGuidePredicate.java | 13 ++ .../api/buttonguide/ButtonRenderPosition.java | 19 ++ .../buttonguide/GuideActionNameSupplier.java | 25 --- .../api/entrypoint/ControlifyEntrypoint.java | 20 ++ .../api/event/ControlifyEvents.java | 12 +- .../ActionLocation.java | 2 +- .../ActionPriority.java | 2 +- .../ingameguide/GuideActionNameSupplier.java | 16 ++ .../api/ingameguide/IngameGuideContext.java | 14 ++ .../IngameGuideRegistry.java} | 6 +- .../api/vmousesnapping/ISnapBehaviour.java | 1 + .../bindings/ControllerBindings.java | 8 + .../isxander/controlify/bindings/IBind.java | 4 +- .../controlify/bindings/JoystickAxisBind.java | 6 +- .../bindings/JoystickButtonBind.java | 7 +- .../controlify/bindings/JoystickHatBind.java | 7 +- .../controlify/config/ControlifyConfig.java | 26 ++- .../config/gui/JoystickBindController.java | 5 +- .../controlify/config/gui/YACLHelper.java | 37 ++-- .../controller/AbstractController.java | 4 +- .../controlify/controller/Controller.java | 26 ++- .../controller/ControllerConfig.java | 3 +- .../controller/gamepad/GamepadController.java | 5 + .../controller/hid/ControllerHIDService.java | 5 +- .../joystick/CompoundJoystickController.java | 158 ++++++++++++++++ .../joystick/CompoundJoystickInfo.java | 46 +++++ .../controller/joystick/JoystickConfig.java | 8 +- .../joystick/JoystickController.java | 72 +------ .../controller/joystick/JoystickState.java | 47 ++++- .../joystick/SingleJoystickController.java | 83 +++++++++ .../joystick/mapping/JoystickMapping.java | 2 + .../joystick/mapping/RPJoystickMapping.java | 4 +- .../mapping/UnmappedJoystickMapping.java | 7 +- .../controlify/gui/ButtonGuideRenderer.java | 24 +++ .../controlify/ingame/guide/GuideAction.java | 4 +- .../ingame/guide/InGameButtonGuide.java | 49 ++--- .../ClientPacketListenerMixin.java | 2 +- .../feature/guide/{ => ingame}/GuiMixin.java | 2 +- .../guide/screen/AbstractButtonMixin.java | 86 +++++++++ .../guide/screen/AbstractWidgetMixin.java | 32 ++++ .../guide/screen/TabNavigationBarMixin.java | 57 ++++++ .../vanilla/CreateWorldScreenMixin.java | 40 ++++ .../vanilla/SelectWorldScreenMixin.java | 20 ++ .../controlify/screenop/ScreenProcessor.java | 12 +- .../vanilla/CreateWorldScreenProcessor.java | 24 +++ .../vanilla/SelectWorldScreenProcessor.java | 8 + .../assets/controlify/lang/en_us.json | 8 +- ..._a9dbf1cb-246b-3c70-91c9-40dcf2a13889.json | 176 ++++++++++++++++++ .../assets/controlify/mappings/xbox_one.json | 92 --------- src/main/resources/controlify.mixins.json | 9 +- .../controlify/test/ClientTestHelper.java | 4 +- 55 files changed, 1188 insertions(+), 287 deletions(-) create mode 100644 src/main/java/dev/isxander/controlify/api/buttonguide/ButtonGuideApi.java create mode 100644 src/main/java/dev/isxander/controlify/api/buttonguide/ButtonGuidePredicate.java create mode 100644 src/main/java/dev/isxander/controlify/api/buttonguide/ButtonRenderPosition.java delete mode 100644 src/main/java/dev/isxander/controlify/api/buttonguide/GuideActionNameSupplier.java create mode 100644 src/main/java/dev/isxander/controlify/api/entrypoint/ControlifyEntrypoint.java rename src/main/java/dev/isxander/controlify/api/{buttonguide => ingameguide}/ActionLocation.java (70%) rename src/main/java/dev/isxander/controlify/api/{buttonguide => ingameguide}/ActionPriority.java (80%) create mode 100644 src/main/java/dev/isxander/controlify/api/ingameguide/GuideActionNameSupplier.java create mode 100644 src/main/java/dev/isxander/controlify/api/ingameguide/IngameGuideContext.java rename src/main/java/dev/isxander/controlify/api/{buttonguide/ButtonGuideRegistry.java => ingameguide/IngameGuideRegistry.java} (89%) create mode 100644 src/main/java/dev/isxander/controlify/controller/joystick/CompoundJoystickController.java create mode 100644 src/main/java/dev/isxander/controlify/controller/joystick/CompoundJoystickInfo.java create mode 100644 src/main/java/dev/isxander/controlify/controller/joystick/SingleJoystickController.java create mode 100644 src/main/java/dev/isxander/controlify/gui/ButtonGuideRenderer.java rename src/main/java/dev/isxander/controlify/mixins/feature/guide/{ => ingame}/ClientPacketListenerMixin.java (96%) rename src/main/java/dev/isxander/controlify/mixins/feature/guide/{ => ingame}/GuiMixin.java (95%) create mode 100644 src/main/java/dev/isxander/controlify/mixins/feature/guide/screen/AbstractButtonMixin.java create mode 100644 src/main/java/dev/isxander/controlify/mixins/feature/guide/screen/AbstractWidgetMixin.java create mode 100644 src/main/java/dev/isxander/controlify/mixins/feature/guide/screen/TabNavigationBarMixin.java create mode 100644 src/main/java/dev/isxander/controlify/mixins/feature/screenop/vanilla/CreateWorldScreenMixin.java create mode 100644 src/main/java/dev/isxander/controlify/screenop/compat/vanilla/CreateWorldScreenProcessor.java create mode 100644 src/main/resources/assets/controlify/mappings/compound-5be9f119-838b-3afd-92b3-e1069f897a51_a9dbf1cb-246b-3c70-91c9-40dcf2a13889.json delete mode 100644 src/main/resources/assets/controlify/mappings/xbox_one.json diff --git a/src/main/java/dev/isxander/controlify/Controlify.java b/src/main/java/dev/isxander/controlify/Controlify.java index b7ce382..513fa70 100644 --- a/src/main/java/dev/isxander/controlify/Controlify.java +++ b/src/main/java/dev/isxander/controlify/Controlify.java @@ -3,8 +3,10 @@ package dev.isxander.controlify; import com.mojang.blaze3d.Blaze3D; import com.mojang.logging.LogUtils; import dev.isxander.controlify.api.ControlifyApi; +import dev.isxander.controlify.api.entrypoint.ControlifyEntrypoint; import dev.isxander.controlify.controller.Controller; import dev.isxander.controlify.controller.ControllerState; +import dev.isxander.controlify.controller.joystick.CompoundJoystickController; import dev.isxander.controlify.gui.screen.ControllerDeadzoneCalibrationScreen; import dev.isxander.controlify.screenop.ScreenProcessorProvider; import dev.isxander.controlify.config.ControlifyConfig; @@ -16,8 +18,8 @@ import dev.isxander.controlify.mixins.feature.virtualmouse.MouseHandlerAccessor; import dev.isxander.controlify.utils.ToastUtils; import dev.isxander.controlify.virtualmouse.VirtualMouseHandler; import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; +import net.fabricmc.loader.api.FabricLoader; import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.components.toasts.SystemToast; import net.minecraft.client.gui.screens.Screen; import net.minecraft.network.chat.Component; import org.jetbrains.annotations.NotNull; @@ -72,6 +74,8 @@ public class Controlify implements ControlifyApi { } } + checkCompoundJoysticks(); + if (Controller.CONTROLLERS.isEmpty()) { LOGGER.info("No controllers found."); } @@ -82,19 +86,41 @@ public class Controlify implements ControlifyApi { // listen for new controllers GLFW.glfwSetJoystickCallback((jid, event) -> { - if (event == GLFW.GLFW_CONNECTED) { - this.onControllerHotplugged(jid); - } else if (event == GLFW.GLFW_DISCONNECTED) { - this.onControllerDisconnect(jid); + try { + if (event == GLFW.GLFW_CONNECTED) { + this.onControllerHotplugged(jid); + } else if (event == GLFW.GLFW_DISCONNECTED) { + this.onControllerDisconnect(jid); + } + } catch (Exception e) { + e.printStackTrace(); } }); ClientTickEvents.START_CLIENT_TICK.register(this::tick); + + FabricLoader.getInstance().getEntrypoints("controlify", ControlifyEntrypoint.class).forEach(entrypoint -> { + try { + entrypoint.onControllersDiscovered(this); + } catch (Exception e) { + LOGGER.error("Failed to run `onControllersDiscovered` on Controlify entrypoint: " + entrypoint.getClass().getName(), e); + } + }); } public void initializeControlify() { + LOGGER.info("Pre-initializing Controlify..."); + this.inGameInputHandler = new InGameInputHandler(Controller.DUMMY); // initialize with dummy controller before connection in case of no controller this.virtualMouseHandler = new VirtualMouseHandler(); + + FabricLoader.getInstance().getEntrypoints("controlify", ControlifyEntrypoint.class).forEach(entrypoint -> { + try { + entrypoint.onControlifyPreInit(this); + } catch (Exception e) { + LOGGER.error("Failed to run `onControlifyPreInit` on Controlify entrypoint: " + entrypoint.getClass().getName(), e); + } + }); } public void tick(Minecraft client) { @@ -109,8 +135,13 @@ public class Controlify implements ControlifyApi { } } + boolean outOfFocus = !config().globalSettings().outOfFocusInput && !client.isWindowActive(); + for (var controller : Controller.CONTROLLERS.values()) { - controller.updateState(); + if (!outOfFocus) + controller.updateState(); + else + controller.clearState(); } ControllerState state = currentController == null ? ControllerState.EMPTY : currentController.state(); @@ -127,7 +158,7 @@ public class Controlify implements ControlifyApi { } } - if (!config().globalSettings().outOfFocusInput && !client.isWindowActive()) + if (outOfFocus) state = ControllerState.EMPTY; if (state.hasAnyInput()) @@ -171,11 +202,14 @@ public class Controlify implements ControlifyApi { config().loadOrCreateControllerData(currentController); this.askToSwitchController(controller); + + checkCompoundJoysticks(); } private void onControllerDisconnect(int jid) { - var controller = Controller.CONTROLLERS.remove(jid); - if (controller != null) { + Controller.CONTROLLERS.values().stream().filter(controller -> controller.joystickId() == jid).findAny().ifPresent(controller -> { + Controller.CONTROLLERS.remove(controller.uid(), controller); + setCurrentController(Controller.CONTROLLERS.values().stream().findFirst().orElse(null)); LOGGER.info("Controller disconnected: " + controller.name()); this.setInputMode(currentController == null ? InputMode.KEYBOARD_MOUSE : InputMode.CONTROLLER); @@ -185,7 +219,29 @@ public class Controlify implements ControlifyApi { Component.translatable("controlify.toast.controller_disconnected.description", controller.name()), false ); - } + }); + + checkCompoundJoysticks(); + } + + private void checkCompoundJoysticks() { + config().getCompoundJoysticks().values().forEach(info -> { + try { + if (info.isLoaded() && !info.canBeUsed()) { + LOGGER.warn("Unloading compound joystick " + info.friendlyName() + " due to missing controllers."); + Controller.CONTROLLERS.remove(info.type().identifier()); + } + + if (!info.isLoaded() && info.canBeUsed()) { + LOGGER.info("Loading compound joystick " + info.type().identifier() + "."); + CompoundJoystickController controller = info.attemptCreate().orElseThrow(); + Controller.CONTROLLERS.put(info.type().identifier(), controller); + config().loadOrCreateControllerData(controller); + } + } catch (Exception e) { + e.printStackTrace(); + } + }); } private void askToSwitchController(Controller controller) { diff --git a/src/main/java/dev/isxander/controlify/api/ControlifyApi.java b/src/main/java/dev/isxander/controlify/api/ControlifyApi.java index d68289c..f89aabb 100644 --- a/src/main/java/dev/isxander/controlify/api/ControlifyApi.java +++ b/src/main/java/dev/isxander/controlify/api/ControlifyApi.java @@ -9,6 +9,11 @@ import org.jetbrains.annotations.NotNull; /** * Interface with Controlify in a manner where you don't need to worry about updates * breaking! This is the recommended way to interact with Controlify. + *

+ * Alternatively, to use Controlify directly, you can use {@link Controlify#instance()}. Though + * beware, things may break at any time! + *

+ * Anything that is asked for from this API is safe to use, even if it is not in the API package. */ public interface ControlifyApi { /** @@ -16,6 +21,9 @@ public interface ControlifyApi { */ @NotNull Controller currentController(); + /** + * Get the current input mode for the game. + */ @NotNull InputMode currentInputMode(); void setInputMode(@NotNull InputMode mode); diff --git a/src/main/java/dev/isxander/controlify/api/bind/ControlifyBindingsApi.java b/src/main/java/dev/isxander/controlify/api/bind/ControlifyBindingsApi.java index 7492ec8..8f68fd6 100644 --- a/src/main/java/dev/isxander/controlify/api/bind/ControlifyBindingsApi.java +++ b/src/main/java/dev/isxander/controlify/api/bind/ControlifyBindingsApi.java @@ -1,5 +1,6 @@ package dev.isxander.controlify.api.bind; +import dev.isxander.controlify.api.ControlifyApi; import dev.isxander.controlify.bindings.BindingSupplier; import dev.isxander.controlify.bindings.ControllerBindings; import dev.isxander.controlify.bindings.GamepadBinds; @@ -8,12 +9,17 @@ import net.minecraft.resources.ResourceLocation; import java.util.function.BooleanSupplier; +/** + * Handles registering new bindings for controllers. + *

+ * Should be called within {@link dev.isxander.controlify.api.entrypoint.ControlifyEntrypoint#onControlifyPreInit(ControlifyApi)} + */ public interface ControlifyBindingsApi { /** * Registers a custom binding for all available controllers. * If the controller is not a gamepad, the binding with be empty by default. * - * @param bind the default gamepad bind + * @param bind the default gamepad bind - joysticks are unset by default * @param id the identifier for the binding, the namespace should be your modid. * @return the binding supplier to fetch the binding for a specific controller. */ diff --git a/src/main/java/dev/isxander/controlify/api/buttonguide/ButtonGuideApi.java b/src/main/java/dev/isxander/controlify/api/buttonguide/ButtonGuideApi.java new file mode 100644 index 0000000..b28c33c --- /dev/null +++ b/src/main/java/dev/isxander/controlify/api/buttonguide/ButtonGuideApi.java @@ -0,0 +1,34 @@ +package dev.isxander.controlify.api.buttonguide; + +import dev.isxander.controlify.bindings.ControllerBinding; +import dev.isxander.controlify.bindings.ControllerBindings; +import dev.isxander.controlify.gui.ButtonGuideRenderer; +import net.minecraft.client.gui.components.AbstractButton; +import net.minecraft.client.gui.screens.Screen; + +import java.util.function.Function; + +/** + * Adds a guide to a button. This does not invoke the button press on binding trigger, only renders the guide. + * This should be called every time a button is initialised, like in {@link Screen#init()} + */ +public interface ButtonGuideApi { + /** + * Makes the button render the image of the binding specified. + * This does not invoke the button press on binding trigger, only renders the guide. + * Custom behaviour should be handled inside a {@link dev.isxander.controlify.screenop.ScreenProcessor} or {@link dev.isxander.controlify.screenop.ComponentProcessor} + * + * @param button button to render the guide for + * @param binding gets the binding to render + * @param position where the guide should be rendered relative to the button + * @param renderPredicate whether the guide should be rendered + */ + static void addGuideToButton( + T button, + Function, ControllerBinding> binding, + ButtonRenderPosition position, + ButtonGuidePredicate renderPredicate) { + ButtonGuideRenderer.registerBindingForButton(button, binding, position, renderPredicate); + } +} + diff --git a/src/main/java/dev/isxander/controlify/api/buttonguide/ButtonGuidePredicate.java b/src/main/java/dev/isxander/controlify/api/buttonguide/ButtonGuidePredicate.java new file mode 100644 index 0000000..b40163e --- /dev/null +++ b/src/main/java/dev/isxander/controlify/api/buttonguide/ButtonGuidePredicate.java @@ -0,0 +1,13 @@ +package dev.isxander.controlify.api.buttonguide; + +import net.minecraft.client.gui.components.AbstractButton; +import net.minecraft.client.gui.components.AbstractWidget; +import net.minecraft.client.gui.screens.Screen; + +@FunctionalInterface +public interface ButtonGuidePredicate { + boolean shouldDisplay(T button); + + ButtonGuidePredicate FOCUS_ONLY = AbstractWidget::isFocused; + ButtonGuidePredicate ALWAYS = btn -> true; +} diff --git a/src/main/java/dev/isxander/controlify/api/buttonguide/ButtonRenderPosition.java b/src/main/java/dev/isxander/controlify/api/buttonguide/ButtonRenderPosition.java new file mode 100644 index 0000000..2f11374 --- /dev/null +++ b/src/main/java/dev/isxander/controlify/api/buttonguide/ButtonRenderPosition.java @@ -0,0 +1,19 @@ +package dev.isxander.controlify.api.buttonguide; + +/** + * Where the guide should be rendered relative to the button. + */ +public enum ButtonRenderPosition { + /** + * Renders outside the button the left. + */ + LEFT, + /** + * Renders outside the button the right. + */ + RIGHT, + /** + * Renders inside the button on the left of the button text. + */ + TEXT +} diff --git a/src/main/java/dev/isxander/controlify/api/buttonguide/GuideActionNameSupplier.java b/src/main/java/dev/isxander/controlify/api/buttonguide/GuideActionNameSupplier.java deleted file mode 100644 index 2f14a34..0000000 --- a/src/main/java/dev/isxander/controlify/api/buttonguide/GuideActionNameSupplier.java +++ /dev/null @@ -1,25 +0,0 @@ -package dev.isxander.controlify.api.buttonguide; - -import dev.isxander.controlify.controller.Controller; -import net.minecraft.client.Minecraft; -import net.minecraft.client.multiplayer.ClientLevel; -import net.minecraft.client.player.LocalPlayer; -import net.minecraft.network.chat.Component; -import net.minecraft.world.phys.HitResult; - -import java.util.Optional; - -/** - * Supplies the text to display for a guide action based on the current context. - * If return is empty, the action will not be displayed. - */ -@FunctionalInterface -public interface GuideActionNameSupplier { - Optional supply( - Minecraft client, - LocalPlayer player, - ClientLevel level, - HitResult hitResult, - Controller controller - ); -} diff --git a/src/main/java/dev/isxander/controlify/api/entrypoint/ControlifyEntrypoint.java b/src/main/java/dev/isxander/controlify/api/entrypoint/ControlifyEntrypoint.java new file mode 100644 index 0000000..cbf50bd --- /dev/null +++ b/src/main/java/dev/isxander/controlify/api/entrypoint/ControlifyEntrypoint.java @@ -0,0 +1,20 @@ +package dev.isxander.controlify.api.entrypoint; + +import dev.isxander.controlify.api.ControlifyApi; + +public interface ControlifyEntrypoint { + /** + * Called once Controlify has been fully initialised. And all controllers have + * been discovered and loaded. + * Due to the nature of the resource-pack system, this is called + * very late in the game's lifecycle (once the resources have been reloaded). + */ + void onControllersDiscovered(ControlifyApi controlify); + + /** + * Called once Controlify has initialised some systems but controllers + * have not yet been discovered and constructed. This is the ideal + * time to register events in preparation for controller discovery. + */ + void onControlifyPreInit(ControlifyApi controlify); +} diff --git a/src/main/java/dev/isxander/controlify/api/event/ControlifyEvents.java b/src/main/java/dev/isxander/controlify/api/event/ControlifyEvents.java index b56eef4..6ee4e4e 100644 --- a/src/main/java/dev/isxander/controlify/api/event/ControlifyEvents.java +++ b/src/main/java/dev/isxander/controlify/api/event/ControlifyEvents.java @@ -3,7 +3,7 @@ package dev.isxander.controlify.api.event; import dev.isxander.controlify.InputMode; import dev.isxander.controlify.bindings.ControllerBindings; import dev.isxander.controlify.controller.Controller; -import dev.isxander.controlify.api.buttonguide.ButtonGuideRegistry; +import dev.isxander.controlify.api.ingameguide.IngameGuideRegistry; import net.fabricmc.fabric.api.event.Event; import net.fabricmc.fabric.api.event.EventFactory; @@ -29,9 +29,9 @@ public final class ControlifyEvents { /** * Triggers when the button guide entries are being populated, so you can add more of your own. */ - public static final Event BUTTON_GUIDE_REGISTRY = EventFactory.createArrayBacked(ButtonGuideRegistryEvent.class, callbacks -> (bindings, registry) -> { - for (ButtonGuideRegistryEvent callback : callbacks) { - callback.onRegisterButtonGuide(bindings, registry); + public static final Event INGAME_GUIDE_REGISTRY = EventFactory.createArrayBacked(IngameGuideRegistryEvent.class, callbacks -> (bindings, registry) -> { + for (IngameGuideRegistryEvent callback : callbacks) { + callback.onRegisterIngameGuide(bindings, registry); } }); @@ -55,8 +55,8 @@ public final class ControlifyEvents { } @FunctionalInterface - public interface ButtonGuideRegistryEvent { - void onRegisterButtonGuide(ControllerBindings bindings, ButtonGuideRegistry registry); + public interface IngameGuideRegistryEvent { + void onRegisterIngameGuide(ControllerBindings bindings, IngameGuideRegistry registry); } @FunctionalInterface diff --git a/src/main/java/dev/isxander/controlify/api/buttonguide/ActionLocation.java b/src/main/java/dev/isxander/controlify/api/ingameguide/ActionLocation.java similarity index 70% rename from src/main/java/dev/isxander/controlify/api/buttonguide/ActionLocation.java rename to src/main/java/dev/isxander/controlify/api/ingameguide/ActionLocation.java index 11d8b34..2a96751 100644 --- a/src/main/java/dev/isxander/controlify/api/buttonguide/ActionLocation.java +++ b/src/main/java/dev/isxander/controlify/api/ingameguide/ActionLocation.java @@ -1,4 +1,4 @@ -package dev.isxander.controlify.api.buttonguide; +package dev.isxander.controlify.api.ingameguide; /** * Whether the action should be on the left or right list. diff --git a/src/main/java/dev/isxander/controlify/api/buttonguide/ActionPriority.java b/src/main/java/dev/isxander/controlify/api/ingameguide/ActionPriority.java similarity index 80% rename from src/main/java/dev/isxander/controlify/api/buttonguide/ActionPriority.java rename to src/main/java/dev/isxander/controlify/api/ingameguide/ActionPriority.java index ea05914..997198b 100644 --- a/src/main/java/dev/isxander/controlify/api/buttonguide/ActionPriority.java +++ b/src/main/java/dev/isxander/controlify/api/ingameguide/ActionPriority.java @@ -1,4 +1,4 @@ -package dev.isxander.controlify.api.buttonguide; +package dev.isxander.controlify.api.ingameguide; /** * Defines how the action is sorted in the list. All default Controlify actions are {@link #NORMAL}. diff --git a/src/main/java/dev/isxander/controlify/api/ingameguide/GuideActionNameSupplier.java b/src/main/java/dev/isxander/controlify/api/ingameguide/GuideActionNameSupplier.java new file mode 100644 index 0000000..82e3eab --- /dev/null +++ b/src/main/java/dev/isxander/controlify/api/ingameguide/GuideActionNameSupplier.java @@ -0,0 +1,16 @@ +package dev.isxander.controlify.api.ingameguide; + +import net.minecraft.network.chat.Component; + +import java.util.Optional; + +/** + * Supplies the text to display for a guide action based on the current context. + * If return is empty, the action will not be displayed. + *

+ * This is supplied once every tick. + */ +@FunctionalInterface +public interface GuideActionNameSupplier { + Optional supply(IngameGuideContext ctx); +} diff --git a/src/main/java/dev/isxander/controlify/api/ingameguide/IngameGuideContext.java b/src/main/java/dev/isxander/controlify/api/ingameguide/IngameGuideContext.java new file mode 100644 index 0000000..debf423 --- /dev/null +++ b/src/main/java/dev/isxander/controlify/api/ingameguide/IngameGuideContext.java @@ -0,0 +1,14 @@ +package dev.isxander.controlify.api.ingameguide; + +import dev.isxander.controlify.controller.Controller; +import net.minecraft.client.Minecraft; +import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.client.player.LocalPlayer; +import net.minecraft.world.phys.HitResult; + +public record IngameGuideContext(Minecraft client, + LocalPlayer player, + ClientLevel level, + HitResult hitResult, + Controller controller) { +} diff --git a/src/main/java/dev/isxander/controlify/api/buttonguide/ButtonGuideRegistry.java b/src/main/java/dev/isxander/controlify/api/ingameguide/IngameGuideRegistry.java similarity index 89% rename from src/main/java/dev/isxander/controlify/api/buttonguide/ButtonGuideRegistry.java rename to src/main/java/dev/isxander/controlify/api/ingameguide/IngameGuideRegistry.java index 4c3e94b..13dabb1 100644 --- a/src/main/java/dev/isxander/controlify/api/buttonguide/ButtonGuideRegistry.java +++ b/src/main/java/dev/isxander/controlify/api/ingameguide/IngameGuideRegistry.java @@ -1,13 +1,13 @@ -package dev.isxander.controlify.api.buttonguide; +package dev.isxander.controlify.api.ingameguide; import dev.isxander.controlify.bindings.ControllerBinding; /** * Allows you to register your own actions to the button guide. - * This should be called through {@link dev.isxander.controlify.api.event.ControlifyEvents#BUTTON_GUIDE_REGISTRY} as + * This should be called through {@link dev.isxander.controlify.api.event.ControlifyEvents#INGAME_GUIDE_REGISTRY} as * these should be called every time the guide is initialised. */ -public interface ButtonGuideRegistry { +public interface IngameGuideRegistry { /** * Registers a new action to the button guide. * diff --git a/src/main/java/dev/isxander/controlify/api/vmousesnapping/ISnapBehaviour.java b/src/main/java/dev/isxander/controlify/api/vmousesnapping/ISnapBehaviour.java index ac803b6..b9f8a3a 100644 --- a/src/main/java/dev/isxander/controlify/api/vmousesnapping/ISnapBehaviour.java +++ b/src/main/java/dev/isxander/controlify/api/vmousesnapping/ISnapBehaviour.java @@ -4,6 +4,7 @@ import java.util.Set; /** * An interface to implement by gui components to define snap points for virtual mouse snapping. + * Can also be implemented in a mixin to improve compatibility. */ public interface ISnapBehaviour { Set getSnapPoints(); diff --git a/src/main/java/dev/isxander/controlify/bindings/ControllerBindings.java b/src/main/java/dev/isxander/controlify/bindings/ControllerBindings.java index 58a4a2c..d7cda8c 100644 --- a/src/main/java/dev/isxander/controlify/bindings/ControllerBindings.java +++ b/src/main/java/dev/isxander/controlify/bindings/ControllerBindings.java @@ -34,6 +34,7 @@ public class ControllerBindings { OPEN_CHAT, GUI_PRESS, GUI_BACK, GUI_NEXT_TAB, GUI_PREV_TAB, + GUI_ABSTRACT_ACTION_1, GUI_ABSTRACT_ACTION_2, PICK_BLOCK, TOGGLE_HUD_VISIBILITY, SHOW_PLAYER_LIST, @@ -77,6 +78,8 @@ public class ControllerBindings { register(GUI_BACK = new ControllerBinding<>(controller, GamepadBinds.B_BUTTON, new ResourceLocation("controlify", "gui_back"))); register(GUI_NEXT_TAB = new ControllerBinding<>(controller, GamepadBinds.RIGHT_BUMPER, new ResourceLocation("controlify", "gui_next_tab"))); register(GUI_PREV_TAB = new ControllerBinding<>(controller, GamepadBinds.LEFT_BUMPER, new ResourceLocation("controlify", "gui_prev_tab"))); + register(GUI_ABSTRACT_ACTION_1 = new ControllerBinding<>(controller, GamepadBinds.X_BUTTON, new ResourceLocation("controlify", "gui_abstract_action_1"))); + register(GUI_ABSTRACT_ACTION_2 = new ControllerBinding<>(controller, GamepadBinds.Y_BUTTON, new ResourceLocation("controlify", "gui_abstract_action_2"))); register(PICK_BLOCK = new ControllerBinding<>(controller, GamepadBinds.DPAD_LEFT, new ResourceLocation("controlify", "pick_block"), options.keyPickItem, () -> false)); register(TOGGLE_HUD_VISIBILITY = new ControllerBinding<>(controller, new EmptyBind<>(), new ResourceLocation("controlify", "toggle_hud_visibility"))); register(SHOW_PLAYER_LIST = new ControllerBinding<>(controller, GamepadBinds.DPAD_RIGHT, new ResourceLocation("controlify", "show_player_list"), options.keyPlayerList, () -> false)); @@ -138,6 +141,11 @@ public class ControllerBindings { public void fromJson(JsonObject json) { for (var binding : registry().values()) { + if (!json.has(binding.id().toString())) { + Controlify.LOGGER.warn("Missing control: " + binding.id() + " in config file. Skipping!"); + continue; + } + var bind = json.get(binding.id().toString()).getAsJsonObject(); if (bind == null) { Controlify.LOGGER.warn("Unknown control: " + binding.id() + " in config file. Skipping!"); diff --git a/src/main/java/dev/isxander/controlify/bindings/IBind.java b/src/main/java/dev/isxander/controlify/bindings/IBind.java index 716a900..d07ad6f 100644 --- a/src/main/java/dev/isxander/controlify/bindings/IBind.java +++ b/src/main/java/dev/isxander/controlify/bindings/IBind.java @@ -4,7 +4,7 @@ import com.google.gson.JsonObject; import com.mojang.blaze3d.vertex.PoseStack; import dev.isxander.controlify.controller.*; import dev.isxander.controlify.controller.gamepad.GamepadController; -import dev.isxander.controlify.controller.joystick.JoystickController; +import dev.isxander.controlify.controller.joystick.SingleJoystickController; import dev.isxander.controlify.gui.DrawSize; public interface IBind { @@ -28,7 +28,7 @@ public interface IBind { if (controller instanceof GamepadController gamepad && type.equals(GamepadBinds.BIND_ID)) { return GamepadBinds.fromJson(json).map(bind -> (IBind) bind.forGamepad(gamepad)).orElse(new EmptyBind<>()); - } else if (controller instanceof JoystickController joystick) { + } else if (controller instanceof SingleJoystickController joystick) { return (IBind) switch (type) { case JoystickButtonBind.BIND_ID -> JoystickButtonBind.fromJson(json, joystick); case JoystickHatBind.BIND_ID -> JoystickHatBind.fromJson(json, joystick); diff --git a/src/main/java/dev/isxander/controlify/bindings/JoystickAxisBind.java b/src/main/java/dev/isxander/controlify/bindings/JoystickAxisBind.java index da10b76..1cc463b 100644 --- a/src/main/java/dev/isxander/controlify/bindings/JoystickAxisBind.java +++ b/src/main/java/dev/isxander/controlify/bindings/JoystickAxisBind.java @@ -18,11 +18,11 @@ import java.util.Objects; public class JoystickAxisBind implements IBind { public static final String BIND_ID = "joystick_axis"; - private final JoystickController joystick; + private final JoystickController joystick; private final int axisIndex; private final AxisDirection direction; - public JoystickAxisBind(JoystickController joystick, int axisIndex, AxisDirection direction) { + public JoystickAxisBind(JoystickController joystick, int axisIndex, AxisDirection direction) { this.joystick = joystick; this.axisIndex = axisIndex; this.direction = direction; @@ -93,7 +93,7 @@ public class JoystickAxisBind implements IBind { return Objects.hash(axisIndex, direction); } - public static JoystickAxisBind fromJson(JsonObject object, JoystickController joystick) { + public static JoystickAxisBind fromJson(JsonObject object, JoystickController joystick) { var axisIndex = object.get("axis").getAsInt(); var direction = AxisDirection.valueOf(object.get("direction").getAsString()); return new JoystickAxisBind(joystick, axisIndex, direction); diff --git a/src/main/java/dev/isxander/controlify/bindings/JoystickButtonBind.java b/src/main/java/dev/isxander/controlify/bindings/JoystickButtonBind.java index a73abdb..ee88b49 100644 --- a/src/main/java/dev/isxander/controlify/bindings/JoystickButtonBind.java +++ b/src/main/java/dev/isxander/controlify/bindings/JoystickButtonBind.java @@ -7,7 +7,6 @@ import dev.isxander.controlify.controller.Controller; import dev.isxander.controlify.controller.joystick.JoystickController; import dev.isxander.controlify.controller.joystick.JoystickState; import dev.isxander.controlify.gui.DrawSize; -import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiComponent; import net.minecraft.resources.ResourceLocation; @@ -16,10 +15,10 @@ import java.util.Objects; public class JoystickButtonBind implements IBind { public static final String BIND_ID = "joystick_button"; - private final JoystickController joystick; + private final JoystickController joystick; private final int buttonIndex; - public JoystickButtonBind(JoystickController joystick, int buttonIndex) { + public JoystickButtonBind(JoystickController joystick, int buttonIndex) { this.joystick = joystick; this.buttonIndex = buttonIndex; } @@ -71,7 +70,7 @@ public class JoystickButtonBind implements IBind { return Objects.hash(buttonIndex, joystick.uid()); } - public static JoystickButtonBind fromJson(JsonObject object, JoystickController joystick) { + public static JoystickButtonBind fromJson(JsonObject object, JoystickController joystick) { var buttonIndex = object.get("button").getAsInt(); return new JoystickButtonBind(joystick, buttonIndex); } diff --git a/src/main/java/dev/isxander/controlify/bindings/JoystickHatBind.java b/src/main/java/dev/isxander/controlify/bindings/JoystickHatBind.java index e94565e..d635a82 100644 --- a/src/main/java/dev/isxander/controlify/bindings/JoystickHatBind.java +++ b/src/main/java/dev/isxander/controlify/bindings/JoystickHatBind.java @@ -7,7 +7,6 @@ import dev.isxander.controlify.controller.Controller; import dev.isxander.controlify.controller.joystick.JoystickController; import dev.isxander.controlify.controller.joystick.JoystickState; import dev.isxander.controlify.gui.DrawSize; -import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiComponent; import net.minecraft.resources.ResourceLocation; @@ -16,11 +15,11 @@ import java.util.Objects; public class JoystickHatBind implements IBind { public static final String BIND_ID = "joystick_hat"; - private final JoystickController joystick; + private final JoystickController joystick; private final int hatIndex; private final JoystickState.HatState hatState; - public JoystickHatBind(JoystickController joystick, int hatIndex, JoystickState.HatState hatState) { + public JoystickHatBind(JoystickController joystick, int hatIndex, JoystickState.HatState hatState) { this.joystick = joystick; this.hatIndex = hatIndex; this.hatState = hatState; @@ -84,7 +83,7 @@ public class JoystickHatBind implements IBind { return Objects.hash(hatIndex, hatState, joystick.uid()); } - public static JoystickHatBind fromJson(JsonObject object, JoystickController joystick) { + public static JoystickHatBind fromJson(JsonObject object, JoystickController joystick) { var hatIndex = object.get("hat").getAsInt(); var hatState = JoystickState.HatState.valueOf(object.get("state").getAsString()); return new JoystickHatBind(joystick, hatIndex, hatState); diff --git a/src/main/java/dev/isxander/controlify/config/ControlifyConfig.java b/src/main/java/dev/isxander/controlify/config/ControlifyConfig.java index e0a232b..76540c0 100644 --- a/src/main/java/dev/isxander/controlify/config/ControlifyConfig.java +++ b/src/main/java/dev/isxander/controlify/config/ControlifyConfig.java @@ -3,12 +3,17 @@ package dev.isxander.controlify.config; import com.google.gson.*; import dev.isxander.controlify.Controlify; import dev.isxander.controlify.controller.Controller; +import dev.isxander.controlify.controller.joystick.CompoundJoystickInfo; import net.fabricmc.loader.api.FabricLoader; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardOpenOption; +import java.util.Map; +import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Collectors; public class ControlifyConfig { public static final Path CONFIG_PATH = FabricLoader.getInstance().getConfigDir().resolve("controlify.json"); @@ -24,6 +29,7 @@ public class ControlifyConfig { private String currentControllerUid; private JsonObject controllerData = new JsonObject(); + private Map compoundJoysticks = Map.of(); private GlobalSettings globalSettings = new GlobalSettings(); private boolean firstLaunch; @@ -69,9 +75,10 @@ public class ControlifyConfig { } controllerData = newControllerData; - config.add("controllers", controllerData); - config.add("global", GSON.toJsonTree(globalSettings)); config.addProperty("current_controller", currentControllerUid = controlify.currentController().uid()); + config.add("controllers", controllerData); + config.add("compound_joysticks", GSON.toJsonTree(compoundJoysticks.values().toArray(new CompoundJoystickInfo[0]))); + config.add("global", GSON.toJsonTree(globalSettings)); return config; } @@ -97,6 +104,13 @@ public class ControlifyConfig { } } + this.compoundJoysticks = object + .getAsJsonArray("compound_joysticks") + .asList() + .stream() + .map(element -> GSON.fromJson(element, CompoundJoystickInfo.class)) + .collect(Collectors.toMap(info -> info.type().identifier(), Function.identity())); + if (object.has("current_controller")) { currentControllerUid = object.get("current_controller").getAsString(); } else { @@ -128,6 +142,14 @@ public class ControlifyConfig { } } + public Optional getLoadedControllerConfig(String uid) { + return Optional.ofNullable(controllerData.getAsJsonObject(uid)); + } + + public Map getCompoundJoysticks() { + return compoundJoysticks; + } + public GlobalSettings globalSettings() { return globalSettings; } diff --git a/src/main/java/dev/isxander/controlify/config/gui/JoystickBindController.java b/src/main/java/dev/isxander/controlify/config/gui/JoystickBindController.java index de30d2b..837380a 100644 --- a/src/main/java/dev/isxander/controlify/config/gui/JoystickBindController.java +++ b/src/main/java/dev/isxander/controlify/config/gui/JoystickBindController.java @@ -3,6 +3,7 @@ package dev.isxander.controlify.config.gui; import com.mojang.blaze3d.vertex.PoseStack; import dev.isxander.controlify.bindings.*; import dev.isxander.controlify.controller.joystick.JoystickController; +import dev.isxander.controlify.controller.joystick.SingleJoystickController; import dev.isxander.controlify.controller.joystick.JoystickState; import dev.isxander.controlify.screenop.ComponentProcessor; import dev.isxander.controlify.screenop.ScreenProcessor; @@ -18,9 +19,9 @@ import org.lwjgl.glfw.GLFW; public class JoystickBindController implements Controller> { private final Option> option; - private final JoystickController controller; + private final JoystickController controller; - public JoystickBindController(Option> option, JoystickController controller) { + public JoystickBindController(Option> option, JoystickController controller) { this.option = option; this.controller = controller; } diff --git a/src/main/java/dev/isxander/controlify/config/gui/YACLHelper.java b/src/main/java/dev/isxander/controlify/config/gui/YACLHelper.java index 9eddbee..e854b99 100644 --- a/src/main/java/dev/isxander/controlify/config/gui/YACLHelper.java +++ b/src/main/java/dev/isxander/controlify/config/gui/YACLHelper.java @@ -9,6 +9,7 @@ import dev.isxander.controlify.controller.gamepad.GamepadController; import dev.isxander.controlify.controller.gamepad.GamepadState; import dev.isxander.controlify.controller.gamepad.BuiltinGamepadTheme; import dev.isxander.controlify.controller.joystick.JoystickController; +import dev.isxander.controlify.controller.joystick.SingleJoystickController; import dev.isxander.controlify.controller.joystick.JoystickState; import dev.isxander.controlify.gui.screen.ControllerDeadzoneCalibrationScreen; import dev.isxander.yacl.api.*; @@ -79,14 +80,18 @@ public class YACLHelper { } private static ConfigCategory createControllerCategory(Controller controller) { + if (!controller.canBeUsed()) { + return PlaceholderCategory.createBuilder() + .name(Component.literal(controller.name())) + .tooltip(Component.translatable("controlify.gui.controller_unavailable")) + .screen((minecraft, yacl) -> yacl) + .build(); + } + var category = ConfigCategory.createBuilder(); category.name(Component.literal(controller.name())); - if (!controller.canBeUsed()) { - category.tooltip(Component.translatable("controlify.gui.controller_unavailable")); - } - var config = controller.config(); var def = controller.defaultConfig(); @@ -126,9 +131,15 @@ public class YACLHelper { .controller(BooleanController::new) .build()) .option(Option.createBuilder(boolean.class) - .name(Component.translatable("controlify.gui.show_guide")) - .tooltip(Component.translatable("controlify.gui.show_guide.tooltip")) - .binding(def.showGuide, () -> config.showGuide, v -> config.showGuide = v) + .name(Component.translatable("controlify.gui.show_ingame_guide")) + .tooltip(Component.translatable("controlify.gui.show_ingame_guide.tooltip")) + .binding(def.showIngameGuide, () -> config.showIngameGuide, v -> config.showIngameGuide = v) + .controller(TickBoxController::new) + .build()) + .option(Option.createBuilder(boolean.class) + .name(Component.translatable("controlify.gui.show_screen_guide")) + .tooltip(Component.translatable("controlify.gui.show_screen_guide.tooltip")) + .binding(def.showScreenGuide, () -> config.showScreenGuide, v -> config.showScreenGuide = v) .controller(TickBoxController::new) .build()) .option(Option.createBuilder(float.class) @@ -182,8 +193,8 @@ public class YACLHelper { var gpCfgDef = gamepad.defaultConfig(); advancedGroup .option(Option.createBuilder(float.class) - .name(Component.translatable("controlify.gui.left_stick_deadzone")) - .tooltip(Component.translatable("controlify.gui.left_stick_deadzone.tooltip")) + .name(Component.translatable("controlify.gui.axis_deadzone", Component.translatable("controlify.gui.left_stick"))) + .tooltip(Component.translatable("controlify.gui.axis_deadzone.tooltip", Component.translatable("controlify.gui.left_stick"))) .tooltip(Component.translatable("controlify.gui.stickdrift_warning").withStyle(ChatFormatting.RED)) .binding( Math.max(gpCfgDef.leftStickDeadzoneX, gpCfgDef.leftStickDeadzoneY), @@ -193,8 +204,8 @@ public class YACLHelper { .controller(opt -> new FloatSliderController(opt, 0, 1, 0.01f, v -> Component.literal(String.format("%.0f%%", v*100)))) .build()) .option(Option.createBuilder(float.class) - .name(Component.translatable("controlify.gui.right_stick_deadzone")) - .tooltip(Component.translatable("controlify.gui.right_stick_deadzone.tooltip")) + .name(Component.translatable("controlify.gui.axis_deadzone", Component.translatable("controlify.gui.right_stick"))) + .tooltip(Component.translatable("controlify.gui.axis_deadzone.tooltip", Component.translatable("controlify.gui.right_stick"))) .tooltip(Component.translatable("controlify.gui.stickdrift_warning").withStyle(ChatFormatting.RED)) .binding( Math.max(gpCfgDef.rightStickDeadzoneX, gpCfgDef.rightStickDeadzoneY), @@ -203,7 +214,7 @@ public class YACLHelper { ) .controller(opt -> new FloatSliderController(opt, 0, 1, 0.01f, v -> Component.literal(String.format("%.0f%%", v*100)))) .build()); - } else if (controller instanceof JoystickController joystick) { + } else if (controller instanceof SingleJoystickController joystick) { Collection deadzoneAxes = IntStream.range(0, joystick.axisCount()) .filter(i -> joystick.mapping().axis(i).requiresDeadzone()) .boxed() @@ -254,7 +265,7 @@ public class YACLHelper { .tooltip(binding.description()) .build()); } - } else if (controller instanceof JoystickController joystick) { + } else if (controller instanceof JoystickController joystick) { for (var binding : joystick.bindings().registry().values()) { controlsGroup.option(Option.createBuilder((Class>) (Class) IBind.class) .name(binding.name()) diff --git a/src/main/java/dev/isxander/controlify/controller/AbstractController.java b/src/main/java/dev/isxander/controlify/controller/AbstractController.java index 9e36662..17f70ba 100644 --- a/src/main/java/dev/isxander/controlify/controller/AbstractController.java +++ b/src/main/java/dev/isxander/controlify/controller/AbstractController.java @@ -65,8 +65,8 @@ public abstract class AbstractController { String uid(); - String guid(); + int joystickId(); ControllerBindings bindings(); @@ -31,26 +31,27 @@ public interface Controller> CONTROLLERS = new HashMap<>(); + Map> CONTROLLERS = new HashMap<>(); static Controller createOrGet(int joystickId, ControllerHIDService.ControllerHIDInfo hidInfo) { - if (CONTROLLERS.containsKey(joystickId)) { - return CONTROLLERS.get(joystickId); + if (CONTROLLERS.containsKey(hidInfo.createControllerUID())) { + return CONTROLLERS.get(hidInfo.createControllerUID()); } if (GLFW.glfwJoystickIsGamepad(joystickId) && !DebugProperties.FORCE_JOYSTICK) { GamepadController controller = new GamepadController(joystickId, hidInfo); - CONTROLLERS.put(joystickId, controller); + CONTROLLERS.put(controller.uid(), controller); return controller; } - JoystickController controller = new JoystickController(joystickId, hidInfo); - CONTROLLERS.put(joystickId, controller); + SingleJoystickController controller = new SingleJoystickController(joystickId, hidInfo); + CONTROLLERS.put(controller.uid(), controller); return controller; } @@ -74,8 +75,8 @@ public interface Controller path) { + public String createControllerUID() { + return UUID.nameUUIDFromBytes(path().get().getBytes()).toString(); + } } } diff --git a/src/main/java/dev/isxander/controlify/controller/joystick/CompoundJoystickController.java b/src/main/java/dev/isxander/controlify/controller/joystick/CompoundJoystickController.java new file mode 100644 index 0000000..02caeb2 --- /dev/null +++ b/src/main/java/dev/isxander/controlify/controller/joystick/CompoundJoystickController.java @@ -0,0 +1,158 @@ +package dev.isxander.controlify.controller.joystick; + +import com.google.common.collect.ImmutableList; +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import dev.isxander.controlify.Controlify; +import dev.isxander.controlify.bindings.ControllerBindings; +import dev.isxander.controlify.controller.ControllerType; +import dev.isxander.controlify.controller.joystick.mapping.JoystickMapping; +import dev.isxander.controlify.controller.joystick.mapping.RPJoystickMapping; +import org.lwjgl.glfw.GLFW; + +import java.util.List; + +public class CompoundJoystickController implements JoystickController { + private final String uid; + private final List joysticks; + private final int axisCount, buttonCount, hatCount; + private final ControllerType compoundType; + private final ControllerBindings bindings; + private final JoystickMapping mapping; + + private JoystickConfig config; + private final JoystickConfig defaultConfig; + + private JoystickState state = JoystickState.EMPTY, prevState = JoystickState.EMPTY; + + public CompoundJoystickController(List joystickIds, String uid, ControllerType compoundType) { + this.joysticks = ImmutableList.copyOf(joystickIds); + this.uid = uid; + this.compoundType = compoundType; + + this.axisCount = joystickIds.stream().mapToInt(this::getAxisCountForJoystick).sum(); + this.buttonCount = joystickIds.stream().mapToInt(this::getButtonCountForJoystick).sum(); + this.hatCount = joystickIds.stream().mapToInt(this::getHatCountForJoystick).sum(); + + this.mapping = RPJoystickMapping.fromType(type()); + + this.config = new JoystickConfig(this); + this.defaultConfig = new JoystickConfig(this); + + this.bindings = new ControllerBindings<>(this); + } + + + @Override + public String uid() { + return this.uid; + } + + @Override + public ControllerBindings bindings() { + return this.bindings; + } + + @Override + public JoystickState state() { + return this.state; + } + + @Override + public JoystickState prevState() { + return this.prevState; + } + + @Override + public void updateState() { + this.prevState = this.state; + + var states = this.joysticks.stream().map(joystick -> JoystickState.fromJoystick(this, joystick)).toList(); + this.state = JoystickState.merged(mapping(), states); + } + + @Override + public void clearState() { + this.state = JoystickState.empty(this); + } + + @Override + public JoystickConfig config() { + return this.config; + } + + @Override + public JoystickConfig defaultConfig() { + return this.defaultConfig; + } + + @Override + public void resetConfig() { + this.config = new JoystickConfig(this); + } + + @Override + public void setConfig(Gson gson, JsonElement json) { + JoystickConfig newConfig = gson.fromJson(json, JoystickConfig.class); + if (newConfig != null) { + this.config = newConfig; + } else { + Controlify.LOGGER.error("Could not set config for controller " + name() + " (" + uid() + ")! Using default config instead."); + this.config = defaultConfig(); + } + this.config.setup(this); + } + + @Override + public ControllerType type() { + return this.compoundType; + } + + @Override + public String name() { + return type().friendlyName(); + } + + @Override + public JoystickMapping mapping() { + return this.mapping; + } + + @Override + public int axisCount() { + return this.axisCount; + } + + @Override + public int buttonCount() { + return this.buttonCount; + } + + @Override + public int hatCount() { + return this.hatCount; + } + + @Override + public boolean canBeUsed() { + return JoystickController.super.canBeUsed() + && joysticks.stream().allMatch(GLFW::glfwJoystickPresent); + } + + @Override + public int joystickId() { + return -1; + } + + private int getAxisCountForJoystick(int joystick) { + return GLFW.glfwGetJoystickAxes(joystick).capacity(); + } + + private int getButtonCountForJoystick(int joystick) { + return GLFW.glfwGetJoystickButtons(joystick).capacity(); + } + + private int getHatCountForJoystick(int joystick) { + return GLFW.glfwGetJoystickHats(joystick).capacity(); + } +} diff --git a/src/main/java/dev/isxander/controlify/controller/joystick/CompoundJoystickInfo.java b/src/main/java/dev/isxander/controlify/controller/joystick/CompoundJoystickInfo.java new file mode 100644 index 0000000..701e2e3 --- /dev/null +++ b/src/main/java/dev/isxander/controlify/controller/joystick/CompoundJoystickInfo.java @@ -0,0 +1,46 @@ +package dev.isxander.controlify.controller.joystick; + +import dev.isxander.controlify.controller.Controller; +import dev.isxander.controlify.controller.ControllerType; + +import java.util.Collection; +import java.util.List; +import java.util.Optional; + +public record CompoundJoystickInfo(Collection joystickUids, String friendlyName) { + public ControllerType type() { + return new ControllerType(friendlyName, createUID(joystickUids)); + } + + public boolean canBeUsed() { + List> joysticks = Controller.CONTROLLERS.values().stream().filter(c -> joystickUids.contains(c.uid())).toList(); + if (joysticks.size() != joystickUids().size()) { + return false; // not all controllers are connected + } + if (joysticks.stream().anyMatch(c -> !c.canBeUsed())) { + return false; // not all controllers can be used + } + + return true; + } + + public boolean isLoaded() { + return Controller.CONTROLLERS.containsKey(createUID(joystickUids)); + } + + public Optional attemptCreate() { + if (!canBeUsed()) return Optional.empty(); + + List joystickIDs = Controller.CONTROLLERS.values().stream() + .filter(c -> joystickUids.contains(c.uid())) + .map(Controller::joystickId) + .toList(); + + ControllerType type = type(); + return Optional.of(new CompoundJoystickController(joystickIDs, type.identifier(), type)); + } + + public static String createUID(Collection joystickUIDs) { + return "compound-" + String.join("_", joystickUIDs); + } +} diff --git a/src/main/java/dev/isxander/controlify/controller/joystick/JoystickConfig.java b/src/main/java/dev/isxander/controlify/controller/joystick/JoystickConfig.java index 5ebf571..5d56b5b 100644 --- a/src/main/java/dev/isxander/controlify/controller/joystick/JoystickConfig.java +++ b/src/main/java/dev/isxander/controlify/controller/joystick/JoystickConfig.java @@ -1,6 +1,7 @@ package dev.isxander.controlify.controller.joystick; import dev.isxander.controlify.controller.ControllerConfig; +import org.apache.commons.lang3.Validate; import java.util.HashMap; import java.util.Map; @@ -8,9 +9,10 @@ import java.util.Map; public class JoystickConfig extends ControllerConfig { private Map deadzones; - private transient JoystickController controller; + private transient JoystickController controller; - public JoystickConfig(JoystickController controller) { + public JoystickConfig(JoystickController controller) { + Validate.notNull(controller); setup(controller); } @@ -30,7 +32,7 @@ public class JoystickConfig extends ControllerConfig { return deadzones.getOrDefault(controller.mapping().axis(axis).identifier(), 0.2f); } - void setup(JoystickController controller) { + void setup(JoystickController controller) { this.controller = controller; if (this.deadzones == null) { deadzones = new HashMap<>(); diff --git a/src/main/java/dev/isxander/controlify/controller/joystick/JoystickController.java b/src/main/java/dev/isxander/controlify/controller/joystick/JoystickController.java index bb83cc5..da64300 100644 --- a/src/main/java/dev/isxander/controlify/controller/joystick/JoystickController.java +++ b/src/main/java/dev/isxander/controlify/controller/joystick/JoystickController.java @@ -1,74 +1,20 @@ package dev.isxander.controlify.controller.joystick; -import com.google.gson.Gson; -import com.google.gson.JsonElement; -import dev.isxander.controlify.controller.AbstractController; -import dev.isxander.controlify.controller.hid.ControllerHIDService; -import dev.isxander.controlify.controller.joystick.mapping.RPJoystickMapping; +import dev.isxander.controlify.controller.Controller; +import dev.isxander.controlify.controller.joystick.JoystickConfig; +import dev.isxander.controlify.controller.joystick.JoystickState; import dev.isxander.controlify.controller.joystick.mapping.JoystickMapping; import dev.isxander.controlify.controller.joystick.mapping.UnmappedJoystickMapping; -import org.lwjgl.glfw.GLFW; -import java.util.Objects; +public interface JoystickController extends Controller { + JoystickMapping mapping(); -public class JoystickController extends AbstractController { - private JoystickState state = JoystickState.EMPTY, prevState = JoystickState.EMPTY; - private final int axisCount, buttonCount, hatCount; - private final JoystickMapping mapping; - - public JoystickController(int joystickId, ControllerHIDService.ControllerHIDInfo hidInfo) { - super(joystickId, hidInfo); - - this.axisCount = GLFW.glfwGetJoystickAxes(joystickId).capacity(); - this.buttonCount = GLFW.glfwGetJoystickButtons(joystickId).capacity(); - this.hatCount = GLFW.glfwGetJoystickHats(joystickId).capacity(); - - this.mapping = Objects.requireNonNull(RPJoystickMapping.fromType(type())); - - this.config = new JoystickConfig(this); - this.defaultConfig = new JoystickConfig(this); - } + int axisCount(); + int buttonCount(); + int hatCount(); @Override - public JoystickState state() { - return state; - } - - @Override - public JoystickState prevState() { - return prevState; - } - - @Override - public void updateState() { - prevState = state; - state = JoystickState.fromJoystick(this, joystickId); - } - - public JoystickMapping mapping() { - return mapping; - } - - @Override - public boolean canBeUsed() { + default boolean canBeUsed() { return !(mapping() instanceof UnmappedJoystickMapping); } - - public int axisCount() { - return axisCount; - } - - public int buttonCount() { - return buttonCount; - } - - public int hatCount() { - return hatCount; - } - - @Override - public void setConfig(Gson gson, JsonElement json) { - super.setConfig(gson, json); - this.config.setup(this); - } } diff --git a/src/main/java/dev/isxander/controlify/controller/joystick/JoystickState.java b/src/main/java/dev/isxander/controlify/controller/joystick/JoystickState.java index aca3b76..b0ed94d 100644 --- a/src/main/java/dev/isxander/controlify/controller/joystick/JoystickState.java +++ b/src/main/java/dev/isxander/controlify/controller/joystick/JoystickState.java @@ -11,6 +11,7 @@ import org.lwjgl.glfw.GLFW; import java.nio.ByteBuffer; import java.nio.FloatBuffer; import java.util.ArrayList; +import java.util.Collection; import java.util.List; import java.util.stream.IntStream; @@ -58,7 +59,17 @@ public class JoystickState implements ControllerState { || hats().stream().anyMatch(hat -> hat != HatState.CENTERED); } - public static JoystickState fromJoystick(JoystickController joystick, int joystickId) { + @Override + public String toString() { + return "JoystickState{" + + "axes=" + axes + + ", rawAxes=" + rawAxes + + ", buttons=" + buttons + + ", hats=" + hats + + '}'; + } + + public static JoystickState fromJoystick(JoystickController joystick, int joystickId) { FloatBuffer axesBuffer = GLFW.glfwGetJoystickAxes(joystickId); List axes = new ArrayList<>(); List rawAxes = new ArrayList<>(); @@ -107,6 +118,40 @@ public class JoystickState implements ControllerState { return new JoystickState(joystick.mapping(), axes, rawAxes, buttons, hats); } + public static JoystickState empty(JoystickController joystick) { + var axes = new ArrayList(); + var buttons = new ArrayList(); + var hats = new ArrayList(); + + for (int i = 0; i < joystick.axisCount(); i++) { + axes.add(joystick.mapping().axis(i).restingValue()); + } + for (int i = 0; i < joystick.buttonCount(); i++) { + buttons.add(false); + } + for (int i = 0; i < joystick.hatCount(); i++) { + hats.add(HatState.CENTERED); + } + + return new JoystickState(joystick.mapping(), axes, axes, buttons, hats); + } + + public static JoystickState merged(JoystickMapping mapping, Collection states) { + var axes = new ArrayList(); + var rawAxes = new ArrayList(); + var buttons = new ArrayList(); + var hats = new ArrayList(); + + for (var state : states) { + axes.addAll(state.axes); + rawAxes.addAll(state.rawAxes); + buttons.addAll(state.buttons); + hats.addAll(state.hats); + } + + return new JoystickState(mapping, axes, rawAxes, buttons, hats); + } + public enum HatState implements NameableEnum { CENTERED, UP, diff --git a/src/main/java/dev/isxander/controlify/controller/joystick/SingleJoystickController.java b/src/main/java/dev/isxander/controlify/controller/joystick/SingleJoystickController.java new file mode 100644 index 0000000..25caeb3 --- /dev/null +++ b/src/main/java/dev/isxander/controlify/controller/joystick/SingleJoystickController.java @@ -0,0 +1,83 @@ +package dev.isxander.controlify.controller.joystick; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import dev.isxander.controlify.controller.AbstractController; +import dev.isxander.controlify.controller.hid.ControllerHIDService; +import dev.isxander.controlify.controller.joystick.mapping.RPJoystickMapping; +import dev.isxander.controlify.controller.joystick.mapping.JoystickMapping; +import dev.isxander.controlify.controller.joystick.mapping.UnmappedJoystickMapping; +import org.lwjgl.glfw.GLFW; + +import java.util.Objects; + +public class SingleJoystickController extends AbstractController implements JoystickController { + private JoystickState state = JoystickState.EMPTY, prevState = JoystickState.EMPTY; + private final int axisCount, buttonCount, hatCount; + private final JoystickMapping mapping; + + public SingleJoystickController(int joystickId, ControllerHIDService.ControllerHIDInfo hidInfo) { + super(joystickId, hidInfo); + + this.axisCount = GLFW.glfwGetJoystickAxes(joystickId).capacity(); + this.buttonCount = GLFW.glfwGetJoystickButtons(joystickId).capacity(); + this.hatCount = GLFW.glfwGetJoystickHats(joystickId).capacity(); + + this.mapping = Objects.requireNonNull(RPJoystickMapping.fromType(type())); + + this.config = new JoystickConfig(this); + this.defaultConfig = new JoystickConfig(this); + } + + @Override + public JoystickState state() { + return state; + } + + @Override + public JoystickState prevState() { + return prevState; + } + + @Override + public void updateState() { + prevState = state; + state = JoystickState.fromJoystick(this, joystickId); + } + + @Override + public void clearState() { + this.state = JoystickState.empty(this); + } + + @Override + public JoystickMapping mapping() { + return mapping; + } + + @Override + public int axisCount() { + return axisCount; + } + + @Override + public int buttonCount() { + return buttonCount; + } + + @Override + public int hatCount() { + return hatCount; + } + + @Override + public int joystickId() { + return joystickId; + } + + @Override + public void setConfig(Gson gson, JsonElement json) { + super.setConfig(gson, json); + this.config.setup(this); + } +} diff --git a/src/main/java/dev/isxander/controlify/controller/joystick/mapping/JoystickMapping.java b/src/main/java/dev/isxander/controlify/controller/joystick/mapping/JoystickMapping.java index ca2f6d9..db3a335 100644 --- a/src/main/java/dev/isxander/controlify/controller/joystick/mapping/JoystickMapping.java +++ b/src/main/java/dev/isxander/controlify/controller/joystick/mapping/JoystickMapping.java @@ -21,6 +21,8 @@ public interface JoystickMapping { boolean isAxisResting(float value); + float restingValue(); + String getDirectionIdentifier(int axis, JoystickAxisBind.AxisDirection direction); } diff --git a/src/main/java/dev/isxander/controlify/controller/joystick/mapping/RPJoystickMapping.java b/src/main/java/dev/isxander/controlify/controller/joystick/mapping/RPJoystickMapping.java index 737543a..6ae4317 100644 --- a/src/main/java/dev/isxander/controlify/controller/joystick/mapping/RPJoystickMapping.java +++ b/src/main/java/dev/isxander/controlify/controller/joystick/mapping/RPJoystickMapping.java @@ -111,7 +111,7 @@ public class RPJoystickMapping implements JoystickMapping { } } - private record AxisMapping(List ids, String identifier, Vec2 inpRange, Vec2 outRange, float restState, boolean requiresDeadzone, String typeId, List> axisNames) implements Axis { + private record AxisMapping(List ids, String identifier, Vec2 inpRange, Vec2 outRange, float restingValue, boolean requiresDeadzone, String typeId, List> axisNames) implements Axis { @Override public float modifyAxis(float value) { if (inpRange() == null || outRange() == null) @@ -122,7 +122,7 @@ public class RPJoystickMapping implements JoystickMapping { @Override public boolean isAxisResting(float value) { - return value == restState(); + return value == restingValue(); } @Override diff --git a/src/main/java/dev/isxander/controlify/controller/joystick/mapping/UnmappedJoystickMapping.java b/src/main/java/dev/isxander/controlify/controller/joystick/mapping/UnmappedJoystickMapping.java index 79e5b0d..410adc1 100644 --- a/src/main/java/dev/isxander/controlify/controller/joystick/mapping/UnmappedJoystickMapping.java +++ b/src/main/java/dev/isxander/controlify/controller/joystick/mapping/UnmappedJoystickMapping.java @@ -45,7 +45,12 @@ public class UnmappedJoystickMapping implements JoystickMapping { @Override public boolean isAxisResting(float value) { - return value == 0; + return value == restingValue(); + } + + @Override + public float restingValue() { + return 0; } @Override diff --git a/src/main/java/dev/isxander/controlify/gui/ButtonGuideRenderer.java b/src/main/java/dev/isxander/controlify/gui/ButtonGuideRenderer.java new file mode 100644 index 0000000..b1f93da --- /dev/null +++ b/src/main/java/dev/isxander/controlify/gui/ButtonGuideRenderer.java @@ -0,0 +1,24 @@ +package dev.isxander.controlify.gui; + +import dev.isxander.controlify.api.buttonguide.ButtonGuidePredicate; +import dev.isxander.controlify.api.buttonguide.ButtonRenderPosition; +import dev.isxander.controlify.bindings.ControllerBinding; +import dev.isxander.controlify.bindings.ControllerBindings; +import net.minecraft.client.gui.components.AbstractButton; +import net.minecraft.client.gui.screens.Screen; + +import java.util.function.Function; + +/** + * @see dev.isxander.controlify.mixins.feature.guide.screen.AbstractButtonMixin + */ +public interface ButtonGuideRenderer { + void setButtonGuide(RenderData renderData); + + static void registerBindingForButton(T button, Function, ControllerBinding> binding, ButtonRenderPosition position, ButtonGuidePredicate renderPredicate) { + ((ButtonGuideRenderer) button).setButtonGuide(new RenderData<>(binding, position, renderPredicate)); + } + + record RenderData(Function, ControllerBinding> binding, ButtonRenderPosition position, ButtonGuidePredicate renderPredicate) { + } +} diff --git a/src/main/java/dev/isxander/controlify/ingame/guide/GuideAction.java b/src/main/java/dev/isxander/controlify/ingame/guide/GuideAction.java index 520d8f6..ca88671 100644 --- a/src/main/java/dev/isxander/controlify/ingame/guide/GuideAction.java +++ b/src/main/java/dev/isxander/controlify/ingame/guide/GuideAction.java @@ -1,7 +1,7 @@ package dev.isxander.controlify.ingame.guide; -import dev.isxander.controlify.api.buttonguide.ActionLocation; -import dev.isxander.controlify.api.buttonguide.ActionPriority; +import dev.isxander.controlify.api.ingameguide.ActionLocation; +import dev.isxander.controlify.api.ingameguide.ActionPriority; import dev.isxander.controlify.bindings.ControllerBinding; import net.minecraft.network.chat.Component; import org.jetbrains.annotations.NotNull; diff --git a/src/main/java/dev/isxander/controlify/ingame/guide/InGameButtonGuide.java b/src/main/java/dev/isxander/controlify/ingame/guide/InGameButtonGuide.java index 527634b..c89ec05 100644 --- a/src/main/java/dev/isxander/controlify/ingame/guide/InGameButtonGuide.java +++ b/src/main/java/dev/isxander/controlify/ingame/guide/InGameButtonGuide.java @@ -1,10 +1,7 @@ package dev.isxander.controlify.ingame.guide; import com.mojang.blaze3d.vertex.PoseStack; -import dev.isxander.controlify.api.buttonguide.ActionLocation; -import dev.isxander.controlify.api.buttonguide.ActionPriority; -import dev.isxander.controlify.api.buttonguide.ButtonGuideRegistry; -import dev.isxander.controlify.api.buttonguide.GuideActionNameSupplier; +import dev.isxander.controlify.api.ingameguide.*; import dev.isxander.controlify.bindings.ControllerBinding; import dev.isxander.controlify.compatibility.ControlifyCompat; import dev.isxander.controlify.controller.Controller; @@ -23,7 +20,7 @@ import net.minecraft.world.phys.*; import java.util.*; -public class InGameButtonGuide implements ButtonGuideRegistry { +public class InGameButtonGuide implements IngameGuideRegistry { private final Controller controller; private final LocalPlayer player; private final Minecraft minecraft = Minecraft.getInstance(); @@ -38,11 +35,11 @@ public class InGameButtonGuide implements ButtonGuideRegistry { this.player = localPlayer; registerDefaultActions(); - ControlifyEvents.BUTTON_GUIDE_REGISTRY.invoker().onRegisterButtonGuide(controller.bindings(), this); + ControlifyEvents.INGAME_GUIDE_REGISTRY.invoker().onRegisterIngameGuide(controller.bindings(), this); } public void renderHud(PoseStack poseStack, float tickDelta, int width, int height) { - if (!controller.config().showGuide || minecraft.screen != null || minecraft.options.renderDebug) + if (!controller.config().showIngameGuide || minecraft.screen != null || minecraft.options.renderDebug) return; ControlifyCompat.ifBeginHudBatching(); @@ -98,7 +95,7 @@ public class InGameButtonGuide implements ButtonGuideRegistry { leftGuides.clear(); rightGuides.clear(); - if (!controller.config().showGuide || minecraft.screen != null) + if (!controller.config().showIngameGuide || minecraft.screen != null) return; for (var actionPredicate : guidePredicates) { @@ -131,7 +128,8 @@ public class InGameButtonGuide implements ButtonGuideRegistry { private void registerDefaultActions() { var options = Minecraft.getInstance().options; - registerGuideAction(controller.bindings().JUMP, ActionLocation.LEFT, (client, player, level, hitResult, controller) -> { + registerGuideAction(controller.bindings().JUMP, ActionLocation.LEFT, (ctx) -> { + var player = ctx.player(); if (player.getAbilities().flying) return Optional.of(Component.translatable("controlify.guide.fly_up")); @@ -149,14 +147,15 @@ public class InGameButtonGuide implements ButtonGuideRegistry { return Optional.empty(); }); - registerGuideAction(controller.bindings().SNEAK, ActionLocation.LEFT, (client, player, level, hitResult, controller) -> { + registerGuideAction(controller.bindings().SNEAK, ActionLocation.LEFT, (ctx) -> { + var player = ctx.player(); if (player.getVehicle() != null) return Optional.of(Component.translatable("controlify.guide.dismount")); if (player.getAbilities().flying) return Optional.of(Component.translatable("controlify.guide.fly_down")); if (player.isInWater()) return Optional.of(Component.translatable("controlify.guide.swim_down")); - if (controller.config().toggleSneak) { + if (ctx.controller().config().toggleSneak) { if (player.input.shiftKeyDown) return Optional.of(Component.translatable("controlify.guide.stop_sneaking")); else @@ -167,33 +166,37 @@ public class InGameButtonGuide implements ButtonGuideRegistry { } return Optional.empty(); }); - registerGuideAction(controller.bindings().SPRINT, ActionLocation.LEFT, (client, player, level, hitResult, controller) -> { + registerGuideAction(controller.bindings().SPRINT, ActionLocation.LEFT, (ctx) -> { + var player = ctx.player(); if (!options.keySprint.isDown()) { if (!player.input.getMoveVector().equals(Vec2.ZERO)) { if (player.isUnderWater()) return Optional.of(Component.translatable("controlify.guide.start_swimming")); return Optional.of(Component.translatable("controlify.guide.start_sprinting")); } - } else if (controller.config().toggleSprint) { + } else if (ctx.controller().config().toggleSprint) { if (player.isUnderWater()) return Optional.of(Component.translatable("controlify.guide.stop_swimming")); return Optional.of(Component.translatable("controlify.guide.stop_sprinting")); } return Optional.empty(); }); - registerGuideAction(controller.bindings().INVENTORY, ActionLocation.RIGHT, (client, player, level, hitResult, controller) -> { - if (client.screen == null) + registerGuideAction(controller.bindings().INVENTORY, ActionLocation.RIGHT, (ctx) -> { + if (ctx.client().screen == null) return Optional.of(Component.translatable("controlify.guide.inventory")); return Optional.empty(); }); - registerGuideAction(controller.bindings().ATTACK, ActionLocation.RIGHT, (client, player, level, hitResult, controller) -> { + registerGuideAction(controller.bindings().ATTACK, ActionLocation.RIGHT, (ctx) -> { + var hitResult = ctx.hitResult(); if (hitResult.getType() == HitResult.Type.ENTITY) return Optional.of(Component.translatable("controlify.guide.attack")); if (hitResult.getType() == HitResult.Type.BLOCK) return Optional.of(Component.translatable("controlify.guide.break")); return Optional.empty(); }); - registerGuideAction(controller.bindings().USE, ActionLocation.RIGHT, (client, player, level, hitResult, controller) -> { + registerGuideAction(controller.bindings().USE, ActionLocation.RIGHT, (ctx) -> { + var hitResult = ctx.hitResult(); + var player = ctx.player(); if (hitResult.getType() == HitResult.Type.ENTITY) if (player.isSpectator()) return Optional.of(Component.translatable("controlify.guide.spectate")); @@ -203,18 +206,20 @@ public class InGameButtonGuide implements ButtonGuideRegistry { return Optional.of(Component.translatable("controlify.guide.use")); return Optional.empty(); }); - registerGuideAction(controller.bindings().DROP, ActionLocation.RIGHT, (client, player, level, hitResult, controller) -> { + registerGuideAction(controller.bindings().DROP, ActionLocation.RIGHT, (ctx) -> { + var player = ctx.player(); if (player.hasItemInSlot(EquipmentSlot.MAINHAND) || player.hasItemInSlot(EquipmentSlot.OFFHAND)) return Optional.of(Component.translatable("controlify.guide.drop")); return Optional.empty(); }); - registerGuideAction(controller.bindings().SWAP_HANDS, ActionLocation.RIGHT, (client, player, level, hitResult, controller) -> { + registerGuideAction(controller.bindings().SWAP_HANDS, ActionLocation.RIGHT, (ctx) -> { + var player = ctx.player(); if (player.hasItemInSlot(EquipmentSlot.MAINHAND) || player.hasItemInSlot(EquipmentSlot.OFFHAND)) return Optional.of(Component.translatable("controlify.guide.swap_hands")); return Optional.empty(); }); - registerGuideAction(controller.bindings().PICK_BLOCK, ActionLocation.RIGHT, (client, player, level, hitResult, controller) -> { - if (hitResult.getType() == HitResult.Type.BLOCK && player.isCreative()) + registerGuideAction(controller.bindings().PICK_BLOCK, ActionLocation.RIGHT, (ctx) -> { + if (ctx.hitResult().getType() == HitResult.Type.BLOCK && ctx.player().isCreative()) return Optional.of(Component.translatable("controlify.guide.pick_block")); return Optional.empty(); }); @@ -248,7 +253,7 @@ public class InGameButtonGuide implements ButtonGuideRegistry { private record GuideActionSupplier(ControllerBinding binding, ActionLocation location, ActionPriority priority, GuideActionNameSupplier nameSupplier) { public Optional supply(Minecraft client, LocalPlayer player, ClientLevel level, HitResult hitResult, Controller controller) { - return nameSupplier.supply(client, player, level, hitResult, controller) + return nameSupplier.supply(new IngameGuideContext(client, player, level, hitResult, controller)) .map(name -> new GuideAction(binding, name, location, priority)); } } diff --git a/src/main/java/dev/isxander/controlify/mixins/feature/guide/ClientPacketListenerMixin.java b/src/main/java/dev/isxander/controlify/mixins/feature/guide/ingame/ClientPacketListenerMixin.java similarity index 96% rename from src/main/java/dev/isxander/controlify/mixins/feature/guide/ClientPacketListenerMixin.java rename to src/main/java/dev/isxander/controlify/mixins/feature/guide/ingame/ClientPacketListenerMixin.java index 42de244..b876e5f 100644 --- a/src/main/java/dev/isxander/controlify/mixins/feature/guide/ClientPacketListenerMixin.java +++ b/src/main/java/dev/isxander/controlify/mixins/feature/guide/ingame/ClientPacketListenerMixin.java @@ -1,4 +1,4 @@ -package dev.isxander.controlify.mixins.feature.guide; +package dev.isxander.controlify.mixins.feature.guide.ingame; import dev.isxander.controlify.Controlify; import dev.isxander.controlify.InputMode; diff --git a/src/main/java/dev/isxander/controlify/mixins/feature/guide/GuiMixin.java b/src/main/java/dev/isxander/controlify/mixins/feature/guide/ingame/GuiMixin.java similarity index 95% rename from src/main/java/dev/isxander/controlify/mixins/feature/guide/GuiMixin.java rename to src/main/java/dev/isxander/controlify/mixins/feature/guide/ingame/GuiMixin.java index 902bed5..bec29bb 100644 --- a/src/main/java/dev/isxander/controlify/mixins/feature/guide/GuiMixin.java +++ b/src/main/java/dev/isxander/controlify/mixins/feature/guide/ingame/GuiMixin.java @@ -1,4 +1,4 @@ -package dev.isxander.controlify.mixins.feature.guide; +package dev.isxander.controlify.mixins.feature.guide.ingame; import com.mojang.blaze3d.vertex.PoseStack; import dev.isxander.controlify.Controlify; diff --git a/src/main/java/dev/isxander/controlify/mixins/feature/guide/screen/AbstractButtonMixin.java b/src/main/java/dev/isxander/controlify/mixins/feature/guide/screen/AbstractButtonMixin.java new file mode 100644 index 0000000..c6693db --- /dev/null +++ b/src/main/java/dev/isxander/controlify/mixins/feature/guide/screen/AbstractButtonMixin.java @@ -0,0 +1,86 @@ +package dev.isxander.controlify.mixins.feature.guide.screen; + +import com.mojang.blaze3d.vertex.PoseStack; +import dev.isxander.controlify.Controlify; +import dev.isxander.controlify.InputMode; +import dev.isxander.controlify.api.buttonguide.ButtonRenderPosition; +import dev.isxander.controlify.bindings.IBind; +import dev.isxander.controlify.gui.ButtonGuideRenderer; +import dev.isxander.controlify.screenop.ComponentProcessor; +import dev.isxander.controlify.screenop.ComponentProcessorProvider; +import dev.isxander.controlify.screenop.compat.vanilla.AbstractButtonComponentProcessor; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.components.AbstractButton; +import net.minecraft.network.chat.CommonComponents; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.contents.TranslatableContents; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import java.util.Set; + +@Mixin(AbstractButton.class) +public abstract class AbstractButtonMixin extends AbstractWidgetMixin implements ButtonGuideRenderer { + @Unique private RenderData renderData = null; + + @Inject(method = "renderString", at = @At("RETURN")) + private void renderButtonGuide(PoseStack matrices, Font renderer, int color, CallbackInfo ci) { + if (shouldRender()) { + switch (renderData.position()) { + case LEFT -> getBind().draw(matrices, getX() - getBind().drawSize().width() - 1, getY() + getHeight() / 2); + case RIGHT -> getBind().draw(matrices, getX() + getWidth() + 1, getY() + getHeight() / 2); + case TEXT -> { + Font font = Minecraft.getInstance().font; + int x; + if (font.width(getMessage()) > getWidth()) { + x = getX(); + } else { + x = getX() + getWidth() / 2 - font.width(getMessage()) / 2 - getBind().drawSize().width(); + } + + getBind().draw(matrices, x, getY() + getHeight() / 2); + } + } + } + } + + @Inject(method = "renderString", at = @At("HEAD")) + private void shiftXOffset(PoseStack matrices, Font renderer, int color, CallbackInfo ci) { + matrices.pushPose(); + if (!shouldRender() || Minecraft.getInstance().font.width(getMessage()) > getWidth() || renderData.position() != ButtonRenderPosition.TEXT) return; + matrices.translate(getBind().drawSize().width() / 2f, 0, 0); + } + + @Inject(method = "renderString", at = @At("RETURN")) + private void finishShiftXOffset(PoseStack matrices, Font renderer, int color, CallbackInfo ci) { + matrices.popPose(); + } + + @Override + protected int shiftDrawSize(int x) { + if (!shouldRender() || Minecraft.getInstance().font.width(getMessage()) < getWidth() || renderData.position() != ButtonRenderPosition.TEXT) return x; + return x + getBind().drawSize().width(); + } + + @Override + public void setButtonGuide(RenderData renderData) { + this.renderData = renderData; + } + + private boolean shouldRender() { + return renderData != null + && this.isActive() + && Controlify.instance().currentInputMode() == InputMode.CONTROLLER + && Controlify.instance().currentController().config().showScreenGuide + && !renderData.binding().apply(Controlify.instance().currentController().bindings()).unbound() + && renderData.renderPredicate().shouldDisplay((AbstractButton) (Object) this); + } + + private IBind getBind() { + return renderData.binding().apply(Controlify.instance().currentController().bindings()).currentBind(); + } +} diff --git a/src/main/java/dev/isxander/controlify/mixins/feature/guide/screen/AbstractWidgetMixin.java b/src/main/java/dev/isxander/controlify/mixins/feature/guide/screen/AbstractWidgetMixin.java new file mode 100644 index 0000000..0ad13a9 --- /dev/null +++ b/src/main/java/dev/isxander/controlify/mixins/feature/guide/screen/AbstractWidgetMixin.java @@ -0,0 +1,32 @@ +package dev.isxander.controlify.mixins.feature.guide.screen; + +import dev.isxander.controlify.gui.DrawSize; +import net.minecraft.client.gui.GuiComponent; +import net.minecraft.client.gui.components.AbstractWidget; +import net.minecraft.network.chat.Component; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.ModifyArg; +import org.spongepowered.asm.mixin.injection.ModifyVariable; + +@Mixin(AbstractWidget.class) +public abstract class AbstractWidgetMixin extends GuiComponent { + @Shadow public abstract int getX(); + + @Shadow public abstract int getY(); + + @Shadow public abstract int getHeight(); + + @Shadow public abstract Component getMessage(); + + @Shadow public abstract int getWidth(); + + @Shadow public abstract boolean isActive(); + + @ModifyArg(method = "renderScrollingString(Lcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/gui/Font;II)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/components/AbstractWidget;renderScrollingString(Lcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/gui/Font;Lnet/minecraft/network/chat/Component;IIIII)V"), index = 3) + protected int shiftDrawSize(int x) { + return x; + } +} diff --git a/src/main/java/dev/isxander/controlify/mixins/feature/guide/screen/TabNavigationBarMixin.java b/src/main/java/dev/isxander/controlify/mixins/feature/guide/screen/TabNavigationBarMixin.java new file mode 100644 index 0000000..f76ea31 --- /dev/null +++ b/src/main/java/dev/isxander/controlify/mixins/feature/guide/screen/TabNavigationBarMixin.java @@ -0,0 +1,57 @@ +package dev.isxander.controlify.mixins.feature.guide.screen; + +import com.google.common.collect.ImmutableList; +import com.mojang.blaze3d.vertex.PoseStack; +import dev.isxander.controlify.Controlify; +import dev.isxander.controlify.InputMode; +import dev.isxander.controlify.bindings.IBind; +import dev.isxander.controlify.compatibility.ControlifyCompat; +import dev.isxander.controlify.controller.Controller; +import dev.isxander.controlify.gui.DrawSize; +import net.minecraft.client.gui.components.TabButton; +import net.minecraft.client.gui.components.tabs.TabNavigationBar; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(TabNavigationBar.class) +public class TabNavigationBarMixin { + @Shadow @Final private ImmutableList tabButtons; + + @Shadow private int width; + + @Inject(method = "render", at = @At("RETURN")) + private void renderControllerButtonOverlay(PoseStack matrices, int mouseX, int mouseY, float delta, CallbackInfo ci) { + if (Controlify.instance().currentInputMode() == InputMode.CONTROLLER) { + var controller = Controlify.instance().currentController(); + if (controller.config().showScreenGuide) { + this.renderControllerButtonOverlay(matrices, controller); + } + } + } + + private void renderControllerButtonOverlay(PoseStack matrices, Controller controller) { + ControlifyCompat.ifBeginHudBatching(); + + TabButton firstTab = tabButtons.get(0); + TabButton lastTab = tabButtons.get(tabButtons.size() - 1); + + IBind prevBind = controller.bindings().GUI_PREV_TAB.currentBind(); + DrawSize prevBindDrawSize = prevBind.drawSize(); + int firstButtonX = Math.max(firstTab.getX() - 2 - prevBindDrawSize.width(), firstTab.getX() / 2 - prevBindDrawSize.width() / 2); + int firstButtonY = 12; + prevBind.draw(matrices, firstButtonX, firstButtonY); + + IBind nextBind = controller.bindings().GUI_NEXT_TAB.currentBind(); + DrawSize nextBindDrawSize = nextBind.drawSize(); + int lastButtonEnd = lastTab.getX() + lastTab.getWidth(); + int lastButtonX = Math.min(lastTab.getX() + lastTab.getWidth() + 2, lastButtonEnd + (width - lastButtonEnd) / 2 - nextBindDrawSize.width() / 2); + int lastButtonY = 12; + nextBind.draw(matrices, lastButtonX, lastButtonY); + + ControlifyCompat.ifEndHudBatching(); + } +} diff --git a/src/main/java/dev/isxander/controlify/mixins/feature/screenop/vanilla/CreateWorldScreenMixin.java b/src/main/java/dev/isxander/controlify/mixins/feature/screenop/vanilla/CreateWorldScreenMixin.java new file mode 100644 index 0000000..658835c --- /dev/null +++ b/src/main/java/dev/isxander/controlify/mixins/feature/screenop/vanilla/CreateWorldScreenMixin.java @@ -0,0 +1,40 @@ +package dev.isxander.controlify.mixins.feature.screenop.vanilla; + +import dev.isxander.controlify.api.buttonguide.ButtonGuidePredicate; +import dev.isxander.controlify.api.buttonguide.ButtonRenderPosition; +import dev.isxander.controlify.gui.ButtonGuideRenderer; +import dev.isxander.controlify.screenop.ScreenProcessor; +import dev.isxander.controlify.screenop.ScreenProcessorProvider; +import dev.isxander.controlify.screenop.compat.vanilla.CreateWorldScreenProcessor; +import net.minecraft.client.gui.components.AbstractButton; +import net.minecraft.client.gui.layouts.LayoutElement; +import net.minecraft.client.gui.screens.worldselection.CreateWorldScreen; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.ModifyArg; + +@Mixin(CreateWorldScreen.class) +public abstract class CreateWorldScreenMixin implements ScreenProcessorProvider { + @Shadow protected abstract void onCreate(); + + @Unique private final ScreenProcessor processor = new CreateWorldScreenProcessor((CreateWorldScreen) (Object) this, this::onCreate); + + @ModifyArg(method = "init()V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/layouts/GridLayout$RowHelper;addChild(Lnet/minecraft/client/gui/layouts/LayoutElement;)Lnet/minecraft/client/gui/layouts/LayoutElement;", ordinal = 1)) + private LayoutElement modifyCancelButton(LayoutElement button) { + ButtonGuideRenderer.registerBindingForButton((AbstractButton) button, bindings -> bindings.GUI_BACK, ButtonRenderPosition.TEXT, ButtonGuidePredicate.ALWAYS); + return button; + } + + @ModifyArg(method = "init()V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/layouts/GridLayout$RowHelper;addChild(Lnet/minecraft/client/gui/layouts/LayoutElement;)Lnet/minecraft/client/gui/layouts/LayoutElement;", ordinal = 0)) + private LayoutElement modifyCreateButton(LayoutElement button) { + ButtonGuideRenderer.registerBindingForButton((AbstractButton) button, bindings -> bindings.GUI_ABSTRACT_ACTION_1, ButtonRenderPosition.TEXT, ButtonGuidePredicate.ALWAYS); + return button; + } + + @Override + public ScreenProcessor screenProcessor() { + return processor; + } +} diff --git a/src/main/java/dev/isxander/controlify/mixins/feature/screenop/vanilla/SelectWorldScreenMixin.java b/src/main/java/dev/isxander/controlify/mixins/feature/screenop/vanilla/SelectWorldScreenMixin.java index 43ef8e5..9917e29 100644 --- a/src/main/java/dev/isxander/controlify/mixins/feature/screenop/vanilla/SelectWorldScreenMixin.java +++ b/src/main/java/dev/isxander/controlify/mixins/feature/screenop/vanilla/SelectWorldScreenMixin.java @@ -1,10 +1,18 @@ package dev.isxander.controlify.mixins.feature.screenop.vanilla; +import dev.isxander.controlify.api.buttonguide.ButtonGuidePredicate; +import dev.isxander.controlify.api.buttonguide.ButtonRenderPosition; +import dev.isxander.controlify.gui.ButtonGuideRenderer; import dev.isxander.controlify.screenop.ScreenProcessor; import dev.isxander.controlify.screenop.ScreenProcessorProvider; import dev.isxander.controlify.screenop.compat.vanilla.SelectWorldScreenProcessor; +import net.minecraft.client.gui.components.AbstractButton; +import net.minecraft.client.gui.components.Renderable; +import net.minecraft.client.gui.components.events.GuiEventListener; import net.minecraft.client.gui.screens.worldselection.SelectWorldScreen; import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.ModifyArg; @Mixin(SelectWorldScreen.class) public class SelectWorldScreenMixin implements ScreenProcessorProvider { @@ -14,4 +22,16 @@ public class SelectWorldScreenMixin implements ScreenProcessorProvider { public ScreenProcessor screenProcessor() { return controlify$processor; } + + @ModifyArg(method = "init()V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screens/worldselection/SelectWorldScreen;addRenderableWidget(Lnet/minecraft/client/gui/components/events/GuiEventListener;)Lnet/minecraft/client/gui/components/events/GuiEventListener;", ordinal = 5)) + private T modifyCancelButton(T button) { + ButtonGuideRenderer.registerBindingForButton((AbstractButton) button, bindings -> bindings.GUI_BACK, ButtonRenderPosition.TEXT, ButtonGuidePredicate.ALWAYS); + return button; + } + + @ModifyArg(method = "init()V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screens/worldselection/SelectWorldScreen;addRenderableWidget(Lnet/minecraft/client/gui/components/events/GuiEventListener;)Lnet/minecraft/client/gui/components/events/GuiEventListener;", ordinal = 1)) + private T modifyCreateButton(T button) { + ButtonGuideRenderer.registerBindingForButton((AbstractButton) button, bindings -> bindings.GUI_ABSTRACT_ACTION_1, ButtonRenderPosition.TEXT, ButtonGuidePredicate.ALWAYS); + return button; + } } diff --git a/src/main/java/dev/isxander/controlify/screenop/ScreenProcessor.java b/src/main/java/dev/isxander/controlify/screenop/ScreenProcessor.java index 26333a3..9e7e202 100644 --- a/src/main/java/dev/isxander/controlify/screenop/ScreenProcessor.java +++ b/src/main/java/dev/isxander/controlify/screenop/ScreenProcessor.java @@ -6,6 +6,7 @@ import dev.isxander.controlify.controller.Controller; import dev.isxander.controlify.api.event.ControlifyEvents; import dev.isxander.controlify.mixins.feature.screenop.vanilla.ScreenAccessor; import dev.isxander.controlify.mixins.feature.screenop.vanilla.TabNavigationBarAccessor; +import net.minecraft.client.Minecraft; import net.minecraft.client.gui.ComponentPath; import net.minecraft.client.gui.components.events.GuiEventListener; import net.minecraft.client.gui.components.tabs.Tab; @@ -13,6 +14,8 @@ import net.minecraft.client.gui.components.tabs.TabNavigationBar; import net.minecraft.client.gui.navigation.FocusNavigationEvent; import net.minecraft.client.gui.navigation.ScreenDirection; import net.minecraft.client.gui.screens.Screen; +import net.minecraft.client.resources.sounds.SimpleSoundInstance; +import net.minecraft.sounds.SoundEvents; import org.lwjgl.glfw.GLFW; import java.util.*; @@ -20,6 +23,7 @@ import java.util.*; public class ScreenProcessor { public final T screen; protected int lastMoved = 0; + protected final Minecraft minecraft = Minecraft.getInstance(); public ScreenProcessor(T screen) { this.screen = screen; @@ -101,8 +105,10 @@ public class ScreenProcessor { if (controller.bindings().GUI_PRESS.justPressed()) screen.keyPressed(GLFW.GLFW_KEY_ENTER, 0, 0); - if (controller.bindings().GUI_BACK.justPressed()) + if (controller.bindings().GUI_BACK.justPressed()) { + this.playClackSound(); screen.onClose(); + } } protected void handleVMouseNavigation(Controller controller) { @@ -174,4 +180,8 @@ public class ScreenProcessor { return tree; } + + protected void playClackSound() { + minecraft.getSoundManager().play(SimpleSoundInstance.forUI(SoundEvents.UI_BUTTON_CLICK, 1.0F)); + } } diff --git a/src/main/java/dev/isxander/controlify/screenop/compat/vanilla/CreateWorldScreenProcessor.java b/src/main/java/dev/isxander/controlify/screenop/compat/vanilla/CreateWorldScreenProcessor.java new file mode 100644 index 0000000..187b5e3 --- /dev/null +++ b/src/main/java/dev/isxander/controlify/screenop/compat/vanilla/CreateWorldScreenProcessor.java @@ -0,0 +1,24 @@ +package dev.isxander.controlify.screenop.compat.vanilla; + +import dev.isxander.controlify.controller.Controller; +import dev.isxander.controlify.screenop.ScreenProcessor; +import net.minecraft.client.gui.screens.worldselection.CreateWorldScreen; + +public class CreateWorldScreenProcessor extends ScreenProcessor { + private final Runnable onCreateButton; + + public CreateWorldScreenProcessor(CreateWorldScreen screen, Runnable onCreateButton) { + super(screen); + this.onCreateButton = onCreateButton; + } + + @Override + protected void handleButtons(Controller controller) { + if (controller.bindings().GUI_ABSTRACT_ACTION_1.justPressed()) { + this.onCreateButton.run(); + this.playClackSound(); + } + + super.handleButtons(controller); + } +} diff --git a/src/main/java/dev/isxander/controlify/screenop/compat/vanilla/SelectWorldScreenProcessor.java b/src/main/java/dev/isxander/controlify/screenop/compat/vanilla/SelectWorldScreenProcessor.java index af23bd9..3b1145f 100644 --- a/src/main/java/dev/isxander/controlify/screenop/compat/vanilla/SelectWorldScreenProcessor.java +++ b/src/main/java/dev/isxander/controlify/screenop/compat/vanilla/SelectWorldScreenProcessor.java @@ -3,7 +3,9 @@ package dev.isxander.controlify.screenop.compat.vanilla; import dev.isxander.controlify.controller.Controller; import dev.isxander.controlify.screenop.ScreenProcessor; import dev.isxander.controlify.mixins.feature.screenop.vanilla.SelectWorldScreenAccessor; +import net.minecraft.client.Minecraft; import net.minecraft.client.gui.components.Button; +import net.minecraft.client.gui.screens.worldselection.CreateWorldScreen; import net.minecraft.client.gui.screens.worldselection.SelectWorldScreen; public class SelectWorldScreenProcessor extends ScreenProcessor { @@ -13,6 +15,12 @@ public class SelectWorldScreenProcessor extends ScreenProcessor controller) { + if (controller.bindings().GUI_ABSTRACT_ACTION_1.justPressed()) { + this.playClackSound(); + CreateWorldScreen.openFresh(Minecraft.getInstance(), screen); + return; + } + if (screen.getFocused() != null && screen.getFocused() instanceof Button) { if (controller.bindings().GUI_BACK.justPressed()) { screen.setFocused(((SelectWorldScreenAccessor) screen).getList()); diff --git a/src/main/resources/assets/controlify/lang/en_us.json b/src/main/resources/assets/controlify/lang/en_us.json index 704f5cf..1dd5996 100644 --- a/src/main/resources/assets/controlify/lang/en_us.json +++ b/src/main/resources/assets/controlify/lang/en_us.json @@ -20,8 +20,10 @@ "controlify.gui.toggle_sprint.tooltip": "How the state of the sprint button behaves.", "controlify.gui.auto_jump": "Auto Jump", "controlify.gui.auto_jump.tooltip": "If the player should automatically jump when you reach a block.", - "controlify.gui.show_guide": "Show Button Guide", - "controlify.gui.show_guide.tooltip": "Show a HUD in-game displaying actions you can do with controller buttons.", + "controlify.gui.show_ingame_guide": "Show Ingame Button Guide", + "controlify.gui.show_ingame_guide.tooltip": "Show a HUD in-game displaying actions you can do with controller buttons.", + "controlify.gui.show_screen_guide": "Show Screen Button Guide", + "controlify.gui.show_screen_guide.tooltip": "Show various helpers in GUIs to assist in using controller input.", "controlify.gui.vmouse_sensitivity": "Virtual Mouse Sensitivity", "controlify.gui.vmouse_sensitivity.tooltip": "How fast the virtual mouse moves.", "controlify.gui.chat_screen_offset": "On-screen keyboard height", @@ -55,6 +57,8 @@ "controlify.gui.button": "Controller Settings...", + "controlify.gui.controller_unavailable": "Controller unavailable and cannot be edited.", + "controlify.toast.vmouse_enabled.title": "Virtual Mouse Enabled", "controlify.toast.vmouse_enabled.description": "Controlify virtual mouse is now enabled for this screen.", "controlify.toast.vmouse_disabled.title": "Virtual Mouse Disabled", diff --git a/src/main/resources/assets/controlify/mappings/compound-5be9f119-838b-3afd-92b3-e1069f897a51_a9dbf1cb-246b-3c70-91c9-40dcf2a13889.json b/src/main/resources/assets/controlify/mappings/compound-5be9f119-838b-3afd-92b3-e1069f897a51_a9dbf1cb-246b-3c70-91c9-40dcf2a13889.json new file mode 100644 index 0000000..d32ef68 --- /dev/null +++ b/src/main/resources/assets/controlify/mappings/compound-5be9f119-838b-3afd-92b3-e1069f897a51_a9dbf1cb-246b-3c70-91c9-40dcf2a13889.json @@ -0,0 +1,176 @@ +{ + "axes": [ + { + "ids": [0, 1], + "identifier": "ps4_left_stick", + "deadzone": true, + "rest": 0.0, + "axis_names": [ + ["right", "left"], + ["down", "up"] + ] + }, + { + "ids": [2, 5], + "identifier": "ps4_right_stick", + "deadzone": true, + "axis_names": [ + ["right", "left"], + ["down", "up"] + ], + "rest": 0.0 + }, + { + "ids": [3], + "identifier": "ps4_left_trigger", + "rest": 0.0, + "deadzone": false, + "range": [0.0, 1.0], + "axis_names": [ + ["down", "up"] + ] + }, + { + "ids": [4], + "identifier": "ps4_right_trigger", + "rest": 0.0, + "deadzone": false, + "range": [0.0, 1.0], + "axis_names": [ + ["down", "up"] + ] + }, + { + "ids": [6, 7], + "identifier": "xbox_left_stick", + "deadzone": true, + "rest": 0.0, + "axis_names": [ + ["right", "left"], + ["down", "up"] + ] + }, + { + "ids": [8, 9], + "identifier": "xbox_right_stick", + "deadzone": true, + "axis_names": [ + ["right", "left"], + ["down", "up"] + ], + "rest": 0.0 + }, + { + "ids": [10], + "identifier": "xbox_left_trigger", + "deadzone": false, + "rest": 0.0, + "range": [0.0, 1.0], + "axis_names": [ + ["down", "up"] + ] + }, + { + "ids": [11], + "identifier": "xbox_right_trigger", + "deadzone": false, + "rest": 0.0, + "range": [0.0, 1.0], + "axis_names": [ + ["down", "up"] + ] + } + ], + "buttons": [ + { + "button": 0, + "name": "a" + }, + { + "button": 1, + "name": "b" + }, + { + "button": 2, + "name": "x" + }, + { + "button": 3, + "name": "y" + }, + { + "button": 4, + "name": "left_bumper" + }, + { + "button": 5, + "name": "right_bumper" + }, + { + "button": 6, + "name": "back" + }, + { + "button": 7, + "name": "start" + }, + { + "button": 8, + "name": "left_stick" + }, + { + "button": 9, + "name": "right_stick" + }, + { + "button": 10, + "name": "a1" + }, + { + "button": 11, + "name": "b1" + }, + { + "button": 12, + "name": "x1" + }, + { + "button": 13, + "name": "y1" + }, + { + "button": 14, + "name": "left_bumper1" + }, + { + "button": 15, + "name": "right_bumper1" + }, + { + "button": 16, + "name": "back1" + }, + { + "button": 17, + "name": "start1" + }, + { + "button": 18, + "name": "left_stick1" + }, + { + "button": 19, + "name": "right_stick1" + } + ], + "hats": [ + { + "hat": 0, + "name": "dpad_xbox" + }, + { + "hat": 1, + "name": "dpad_ps4" + } + ] +} diff --git a/src/main/resources/assets/controlify/mappings/xbox_one.json b/src/main/resources/assets/controlify/mappings/xbox_one.json deleted file mode 100644 index 1d74741..0000000 --- a/src/main/resources/assets/controlify/mappings/xbox_one.json +++ /dev/null @@ -1,92 +0,0 @@ -{ - "axes": [ - { - "ids": [0, 1], - "identifier": "left_stick", - "deadzone": true, - "rest": 0.0, - "axis_names": [ - ["right", "left"], - ["down", "up"] - ] - }, - { - "ids": [2, 3], - "identifier": "right_stick", - "deadzone": true, - "axis_names": [ - ["right", "left"], - ["down", "up"] - ], - "rest": 0.0 - }, - { - "ids": [4], - "identifier": "left_trigger", - "deadzone": false, - "rest": 0.0, - "range": [0.0, 1.0], - "axis_names": [ - ["down", "up"] - ] - }, - { - "ids": [5], - "identifier": "right_trigger", - "deadzone": false, - "rest": 0.0, - "range": [0.0, 1.0], - "axis_names": [ - ["down", "up"] - ] - } - ], - "buttons": [ - { - "button": 0, - "name": "a" - }, - { - "button": 1, - "name": "b" - }, - { - "button": 2, - "name": "x" - }, - { - "button": 3, - "name": "y" - }, - { - "button": 4, - "name": "left_bumper" - }, - { - "button": 5, - "name": "right_bumper" - }, - { - "button": 6, - "name": "back" - }, - { - "button": 7, - "name": "start" - }, - { - "button": 8, - "name": "left_stick" - }, - { - "button": 9, - "name": "right_stick" - } - ], - "hats": [ - { - "hat": 0, - "name": "dpad" - } - ] -} diff --git a/src/main/resources/controlify.mixins.json b/src/main/resources/controlify.mixins.json index a63d922..0ee4827 100644 --- a/src/main/resources/controlify.mixins.json +++ b/src/main/resources/controlify.mixins.json @@ -25,14 +25,17 @@ "feature.bind.KeyMappingAccessor", "feature.chatkbheight.ChatComponentMixin", "feature.chatkbheight.ChatScreenMixin", - "feature.guide.ClientPacketListenerMixin", - "feature.guide.GuiMixin", + "feature.guide.ingame.ClientPacketListenerMixin", + "feature.guide.ingame.GuiMixin", + "feature.guide.screen.AbstractButtonMixin", + "feature.guide.screen.AbstractWidgetMixin", + "feature.guide.screen.TabNavigationBarMixin", "feature.screenop.vanilla.AbstractButtonMixin", "feature.screenop.vanilla.AbstractContainerEventHandlerMixin", "feature.screenop.vanilla.AbstractSelectionListMixin", "feature.screenop.vanilla.AbstractSliderButtonMixin", "feature.screenop.vanilla.ContainerObjectSelectionListEntryMixin", - + "feature.screenop.vanilla.CreateWorldScreenMixin", "feature.screenop.vanilla.CreativeModeInventoryScreenAccessor", "feature.screenop.vanilla.CreativeModeInventoryScreenMixin", "feature.screenop.vanilla.JoinMultiplayerScreenAccessor", diff --git a/src/testmod/java/dev/isxander/controlify/test/ClientTestHelper.java b/src/testmod/java/dev/isxander/controlify/test/ClientTestHelper.java index 1830970..ec2dc6a 100644 --- a/src/testmod/java/dev/isxander/controlify/test/ClientTestHelper.java +++ b/src/testmod/java/dev/isxander/controlify/test/ClientTestHelper.java @@ -73,8 +73,8 @@ public class ClientTestHelper { } @Override - public String guid() { - return "FAKE"; + public int joystickId() { + return -1; } @Override