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