forked from Clones/Controlify
controllers!
This commit is contained in:
@ -15,31 +15,11 @@ plugins {
|
||||
group = "dev.isxander"
|
||||
version = "1.0.0+1.19.3"
|
||||
|
||||
/* UNCOMMENT OR DELETE IF YOU WANT TESTMOD SOURCESET
|
||||
val testmod by sourceSets.registering {
|
||||
compileClasspath += sourceSets.main.get().compileClasspath
|
||||
runtimeClasspath += sourceSets.main.get().runtimeClasspath
|
||||
}
|
||||
|
||||
loom {
|
||||
runs {
|
||||
register("testmod") {
|
||||
client()
|
||||
ideConfigGenerated(true)
|
||||
name("Test Mod")
|
||||
source(testmod.get())
|
||||
}
|
||||
}
|
||||
|
||||
createRemapConfigurations(testmod.get())
|
||||
}
|
||||
*/
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven("https://maven.terraformersmc.com")
|
||||
maven("https://maven.isxander.dev/releases")
|
||||
|
||||
maven("https://maven.quiltmc.org/repository/release")
|
||||
maven("https://api.modrinth.com/maven") {
|
||||
name = "Modrinth"
|
||||
content {
|
||||
@ -52,13 +32,13 @@ val minecraftVersion = libs.versions.minecraft.get()
|
||||
|
||||
dependencies {
|
||||
minecraft(libs.minecraft)
|
||||
mappings("net.fabricmc:yarn:$minecraftVersion+build.${libs.versions.yarn.get()}:v2")
|
||||
mappings(loom.layered {
|
||||
mappings("org.quiltmc:quilt-mappings:$minecraftVersion+build.${libs.versions.quilt.mappings.get()}:intermediary-v2")
|
||||
officialMojangMappings()
|
||||
})
|
||||
modImplementation(libs.fabric.loader)
|
||||
|
||||
// modImplementation(libs.fabric.api)
|
||||
// modImplementation(fabricApi.module("fabric-resource-loader-v0", libs.versions.fabric.api.get()))
|
||||
|
||||
modRuntimeOnly("maven.modrinth:smoothboot-fabric:1.19-1.7.1") // improve system performance when booting dev env
|
||||
modImplementation(libs.fabric.api)
|
||||
}
|
||||
|
||||
tasks {
|
||||
@ -180,7 +160,7 @@ publishing {
|
||||
val password = "XANDER_MAVEN_PASS".let { System.getenv(it) ?: findProperty(it) }?.toString()
|
||||
if (username != null && password != null) {
|
||||
maven(url = "https://maven.isxander.dev/releases") {
|
||||
name = "Xander Releases"
|
||||
name = "XanderReleases"
|
||||
credentials {
|
||||
this.username = username
|
||||
this.password = password
|
||||
|
@ -1,9 +1,9 @@
|
||||
org.gradle.jvmargs=-Xmx4G
|
||||
|
||||
modId=fabric-mod-template
|
||||
modName=Fabric Mod Template
|
||||
modDescription=Template for Xander's mods with extra gradle goodies.
|
||||
modId=controlify
|
||||
modName=Controlify
|
||||
modDescription=Another controller mod - for fabric!
|
||||
|
||||
curseforgeId=
|
||||
modrinthId=
|
||||
githubProject=isXander/FabricModTemplate
|
||||
githubProject=isXander/Controlify
|
||||
|
@ -1,16 +1,16 @@
|
||||
[versions]
|
||||
loom = "1.0.+"
|
||||
loom = "1.1.+"
|
||||
loom_quiltflower = "1.8.+"
|
||||
minotaur = "2.5.+"
|
||||
minotaur = "2.6.+"
|
||||
cursegradle = "2.+"
|
||||
github_release = "2.+"
|
||||
machete = "1.+"
|
||||
grgit = "5.0.+"
|
||||
|
||||
minecraft = "1.19.3"
|
||||
yarn = "4"
|
||||
fabric_loader = "0.14.12"
|
||||
fabric_api = "0.69.1+1.19.3"
|
||||
minecraft = "23w04a"
|
||||
quilt_mappings = "10"
|
||||
fabric_loader = "0.14.13"
|
||||
fabric_api = "0.73.1+1.19.4"
|
||||
|
||||
[libraries]
|
||||
minecraft = { module = "com.mojang:minecraft", version.ref = "minecraft" }
|
||||
|
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@ -1,5 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-all.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
@ -13,5 +13,5 @@ dependencyResolutionManagement {
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.name = "FabricModTemplate"
|
||||
rootProject.name = "Controlify"
|
||||
|
||||
|
74
src/main/java/dev/isxander/controlify/Controlify.java
Normal file
74
src/main/java/dev/isxander/controlify/Controlify.java
Normal file
@ -0,0 +1,74 @@
|
||||
package dev.isxander.controlify;
|
||||
|
||||
import dev.isxander.controlify.compatibility.screen.ScreenProcessorProvider;
|
||||
import dev.isxander.controlify.controller.AxesState;
|
||||
import dev.isxander.controlify.controller.ButtonState;
|
||||
import dev.isxander.controlify.controller.Controller;
|
||||
import dev.isxander.controlify.controller.ControllerState;
|
||||
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
|
||||
public class Controlify {
|
||||
private static Controlify instance = null;
|
||||
|
||||
private Controller currentController;
|
||||
private InputMode currentInputMode;
|
||||
|
||||
public void onInitializeInput() {
|
||||
// find already connected controllers
|
||||
for (int i = 0; i < GLFW.GLFW_JOYSTICK_LAST; i++) {
|
||||
if (GLFW.glfwJoystickPresent(i)) {
|
||||
currentController = Controller.byId(i);
|
||||
System.out.println("Connected: " + currentController.name());
|
||||
this.setCurrentInputMode(InputMode.CONTROLLER);
|
||||
}
|
||||
}
|
||||
|
||||
// listen for new controllers
|
||||
GLFW.glfwSetJoystickCallback((jid, event) -> {
|
||||
System.out.println("Event: " + event);
|
||||
if (event == GLFW.GLFW_CONNECTED) {
|
||||
currentController = Controller.byId(jid);
|
||||
System.out.println("Connected: " + currentController.name());
|
||||
this.setCurrentInputMode(InputMode.CONTROLLER);
|
||||
} else if (event == GLFW.GLFW_DISCONNECTED) {
|
||||
Controller.CONTROLLERS.remove(jid);
|
||||
currentController = Controller.CONTROLLERS.values().stream().filter(Controller::connected).findFirst().orElse(null);
|
||||
System.out.println("Disconnected: " + jid);
|
||||
this.setCurrentInputMode(currentController == null ? InputMode.KEYBOARD_MOUSE : InputMode.CONTROLLER);
|
||||
}
|
||||
});
|
||||
|
||||
ClientTickEvents.START_CLIENT_TICK.register(client -> {
|
||||
updateControllers();
|
||||
});
|
||||
}
|
||||
|
||||
public void updateControllers() {
|
||||
for (Controller controller : Controller.CONTROLLERS.values()) {
|
||||
controller.updateState();
|
||||
}
|
||||
|
||||
ControllerState state = currentController == null ? ControllerState.EMPTY : currentController.state();
|
||||
|
||||
if (state.hasAnyInput())
|
||||
this.setCurrentInputMode(InputMode.CONTROLLER);
|
||||
|
||||
Minecraft client = Minecraft.getInstance();
|
||||
if (client.screen != null && currentController != null) ScreenProcessorProvider.provide(client.screen).onControllerUpdate(currentController);
|
||||
}
|
||||
|
||||
public InputMode getCurrentInputMode() {
|
||||
return currentInputMode;
|
||||
}
|
||||
|
||||
public void setCurrentInputMode(InputMode currentInputMode) {
|
||||
this.currentInputMode = currentInputMode;
|
||||
}
|
||||
|
||||
public static Controlify getInstance() {
|
||||
if (instance == null) instance = new Controlify();
|
||||
return instance;
|
||||
}
|
||||
}
|
6
src/main/java/dev/isxander/controlify/InputMode.java
Normal file
6
src/main/java/dev/isxander/controlify/InputMode.java
Normal file
@ -0,0 +1,6 @@
|
||||
package dev.isxander.controlify;
|
||||
|
||||
public enum InputMode {
|
||||
KEYBOARD_MOUSE,
|
||||
CONTROLLER;
|
||||
}
|
@ -0,0 +1,100 @@
|
||||
package dev.isxander.controlify.compatibility.screen;
|
||||
|
||||
import dev.isxander.controlify.Controlify;
|
||||
import dev.isxander.controlify.InputMode;
|
||||
import dev.isxander.controlify.compatibility.screen.component.ComponentProcessorProvider;
|
||||
import dev.isxander.controlify.controller.Controller;
|
||||
import dev.isxander.controlify.controller.ControllerState;
|
||||
import dev.isxander.controlify.mixins.ScreenAccessor;
|
||||
import net.minecraft.client.gui.ComponentPath;
|
||||
import net.minecraft.client.gui.navigation.FocusNavigationEvent;
|
||||
import net.minecraft.client.gui.navigation.ScreenDirection;
|
||||
import net.minecraft.client.gui.screens.Screen;
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
|
||||
public class ScreenProcessor {
|
||||
private static final int REPEAT_DELAY = 5;
|
||||
private static final int INITIAL_REPEAT_DELAY = 20;
|
||||
|
||||
public final Screen screen;
|
||||
private int lastMoved = 0;
|
||||
|
||||
public ScreenProcessor(Screen screen) {
|
||||
this.screen = screen;
|
||||
}
|
||||
|
||||
public void onControllerUpdate(Controller controller) {
|
||||
handleComponentNavigation(controller);
|
||||
handleButtons(controller);
|
||||
}
|
||||
|
||||
private void handleComponentNavigation(Controller controller) {
|
||||
if (screen.getFocused() != null) {
|
||||
var focused = screen.getFocused();
|
||||
var processor = ComponentProcessorProvider.provide(focused);
|
||||
if (processor.overrideControllerNavigation(this, controller)) return;
|
||||
}
|
||||
|
||||
var accessor = (ScreenAccessor) screen;
|
||||
|
||||
boolean repeatEventAvailable = ++lastMoved > INITIAL_REPEAT_DELAY;
|
||||
|
||||
var axes = controller.state().axes();
|
||||
var prevAxes = controller.prevState().axes();
|
||||
var buttons = controller.state().buttons();
|
||||
var prevButtons = controller.prevState().buttons();
|
||||
|
||||
FocusNavigationEvent.ArrowNavigation event = null;
|
||||
if (axes.leftStickX() > 0.5f && (repeatEventAvailable || prevAxes.leftStickX() <= 0.5f)) {
|
||||
event = accessor.invokeCreateArrowEvent(ScreenDirection.RIGHT);
|
||||
} else if (axes.leftStickX() < -0.5f && (repeatEventAvailable || prevAxes.leftStickX() >= -0.5f)) {
|
||||
event = accessor.invokeCreateArrowEvent(ScreenDirection.LEFT);
|
||||
} else if (axes.leftStickY() < -0.5f && (repeatEventAvailable || prevAxes.leftStickY() >= -0.5f)) {
|
||||
event = accessor.invokeCreateArrowEvent(ScreenDirection.UP);
|
||||
} else if (axes.leftStickY() > 0.5f && (repeatEventAvailable || prevAxes.leftStickY() <= 0.5f)) {
|
||||
event = accessor.invokeCreateArrowEvent(ScreenDirection.DOWN);
|
||||
} else if (buttons.dpadUp() && (repeatEventAvailable || !prevButtons.dpadUp())) {
|
||||
event = accessor.invokeCreateArrowEvent(ScreenDirection.UP);
|
||||
} else if (buttons.dpadDown() && (repeatEventAvailable || !prevButtons.dpadDown())) {
|
||||
event = accessor.invokeCreateArrowEvent(ScreenDirection.DOWN);
|
||||
} else if (buttons.dpadLeft() && (repeatEventAvailable || !prevButtons.dpadLeft())) {
|
||||
event = accessor.invokeCreateArrowEvent(ScreenDirection.LEFT);
|
||||
} else if (buttons.dpadRight() && (repeatEventAvailable || !prevButtons.dpadRight())) {
|
||||
event = accessor.invokeCreateArrowEvent(ScreenDirection.RIGHT);
|
||||
}
|
||||
|
||||
if (event != null) {
|
||||
ComponentPath path = screen.nextFocusPath(event);
|
||||
if (path != null) {
|
||||
accessor.invokeChangeFocus(path);
|
||||
lastMoved = repeatEventAvailable ? INITIAL_REPEAT_DELAY - REPEAT_DELAY : 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void handleButtons(Controller controller) {
|
||||
if (screen.getFocused() != null) {
|
||||
var focused = screen.getFocused();
|
||||
var processor = ComponentProcessorProvider.provide(focused);
|
||||
if (processor.overrideControllerButtons(this, controller)) return;
|
||||
}
|
||||
|
||||
var buttons = controller.state().buttons();
|
||||
var prevButtons = controller.prevState().buttons();
|
||||
|
||||
if (buttons.a() && !prevButtons.a())
|
||||
screen.keyPressed(GLFW.GLFW_KEY_ENTER, 0, 0);
|
||||
if (buttons.b() && !prevButtons.b())
|
||||
screen.onClose();
|
||||
}
|
||||
|
||||
public void onWidgetRebuild() {
|
||||
// initial focus
|
||||
if (screen.getFocused() == null && Controlify.getInstance().getCurrentInputMode() == InputMode.CONTROLLER) {
|
||||
var accessor = (ScreenAccessor) screen;
|
||||
ComponentPath path = screen.nextFocusPath(accessor.invokeCreateArrowEvent(ScreenDirection.DOWN));
|
||||
if (path != null)
|
||||
accessor.invokeChangeFocus(path);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
package dev.isxander.controlify.compatibility.screen;
|
||||
|
||||
import net.minecraft.client.gui.screens.Screen;
|
||||
|
||||
public interface ScreenProcessorProvider {
|
||||
ScreenProcessor screenProcessor();
|
||||
|
||||
static ScreenProcessor provide(Screen screen) {
|
||||
return ((ScreenProcessorProvider) screen).screenProcessor();
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
package dev.isxander.controlify.compatibility.screen.component;
|
||||
|
||||
import dev.isxander.controlify.compatibility.screen.ScreenProcessor;
|
||||
import dev.isxander.controlify.controller.AxesState;
|
||||
import dev.isxander.controlify.controller.ButtonState;
|
||||
import dev.isxander.controlify.controller.Controller;
|
||||
import dev.isxander.controlify.controller.ControllerState;
|
||||
import net.minecraft.client.gui.components.events.GuiEventListener;
|
||||
|
||||
public class ComponentProcessor<T extends GuiEventListener> {
|
||||
static final ComponentProcessor<?> EMPTY = new ComponentProcessor<>(null);
|
||||
|
||||
protected final T component;
|
||||
|
||||
public ComponentProcessor(T component) {
|
||||
this.component = component;
|
||||
}
|
||||
|
||||
public boolean overrideControllerNavigation(ScreenProcessor screen, Controller controller) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean overrideControllerButtons(ScreenProcessor screen, Controller controller) {
|
||||
return false;
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
package dev.isxander.controlify.compatibility.screen.component;
|
||||
|
||||
import net.minecraft.client.gui.components.events.GuiEventListener;
|
||||
|
||||
public interface ComponentProcessorProvider {
|
||||
ComponentProcessor<?> componentProcessor();
|
||||
|
||||
static ComponentProcessor<?> provide(GuiEventListener component) {
|
||||
if (component instanceof ComponentProcessorProvider provider)
|
||||
return provider.componentProcessor();
|
||||
return ComponentProcessor.EMPTY;
|
||||
}
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
package dev.isxander.controlify.compatibility.screen.component;
|
||||
|
||||
import dev.isxander.controlify.compatibility.screen.ScreenProcessor;
|
||||
import dev.isxander.controlify.controller.Controller;
|
||||
import dev.isxander.controlify.controller.ControllerState;
|
||||
import net.minecraft.client.gui.components.AbstractSliderButton;
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class SliderComponentProcessor extends ComponentProcessor<AbstractSliderButton> {
|
||||
private final Supplier<Boolean> canChangeValueGetter;
|
||||
private final Consumer<Boolean> canChangeValueSetter;
|
||||
|
||||
private static final int SLIDER_CHANGE_DELAY = 1;
|
||||
private int lastSliderChange = SLIDER_CHANGE_DELAY;
|
||||
|
||||
public SliderComponentProcessor(AbstractSliderButton component, Supplier<Boolean> canChangeValueGetter, Consumer<Boolean> canChangeValueSetter) {
|
||||
super(component);
|
||||
this.canChangeValueGetter = canChangeValueGetter;
|
||||
this.canChangeValueSetter = canChangeValueSetter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean overrideControllerNavigation(ScreenProcessor screen, Controller controller) {
|
||||
if (!this.canChangeValueGetter.get()) return false;
|
||||
|
||||
var canSliderChange = ++lastSliderChange > SLIDER_CHANGE_DELAY;
|
||||
|
||||
var axes = controller.state().axes();
|
||||
var buttons = controller.state().buttons();
|
||||
if (axes.leftStickX() > 0.5f || buttons.dpadRight()) {
|
||||
if (canSliderChange) {
|
||||
component.keyPressed(GLFW.GLFW_KEY_RIGHT, 0, 0);
|
||||
lastSliderChange = 0;
|
||||
}
|
||||
|
||||
return true;
|
||||
} else if (axes.leftStickX() < -0.5f || buttons.dpadLeft()) {
|
||||
if (canSliderChange) {
|
||||
component.keyPressed(GLFW.GLFW_KEY_LEFT, 0, 0);
|
||||
lastSliderChange = 0;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean overrideControllerButtons(ScreenProcessor screen, Controller controller) {
|
||||
if (!this.canChangeValueGetter.get()) return false;
|
||||
|
||||
var buttons = controller.state().buttons();
|
||||
var prevButtons = controller.prevState().buttons();
|
||||
if (buttons.b() && !prevButtons.b()) {
|
||||
this.canChangeValueSetter.accept(false);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
package dev.isxander.controlify.controller;
|
||||
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
|
||||
public record AxesState(
|
||||
float leftStickX, float leftStickY,
|
||||
float rightStickX, float rightStickY,
|
||||
float leftTrigger, float rightTrigger
|
||||
) {
|
||||
public static AxesState EMPTY = new AxesState(0, 0, 0, 0, 0, 0);
|
||||
|
||||
public AxesState leftJoystickDeadZone(float deadZoneX, float deadZoneY) {
|
||||
return new AxesState(
|
||||
Math.abs(leftStickX) < deadZoneX ? 0 : leftStickX,
|
||||
Math.abs(leftStickY) < deadZoneY ? 0 : leftStickY,
|
||||
rightStickX, rightStickY, leftTrigger, rightTrigger
|
||||
);
|
||||
}
|
||||
|
||||
public AxesState rightJoystickDeadZone(float deadZoneX, float deadZoneY) {
|
||||
return new AxesState(
|
||||
leftStickX, leftStickY,
|
||||
Math.abs(rightStickX) < deadZoneX ? 0 : rightStickX,
|
||||
Math.abs(rightStickY) < deadZoneY ? 0 : rightStickY,
|
||||
leftTrigger, rightTrigger
|
||||
);
|
||||
}
|
||||
|
||||
public AxesState leftTriggerDeadZone(float deadZone) {
|
||||
return new AxesState(
|
||||
leftStickX, leftStickY, rightStickX, rightStickY,
|
||||
Math.abs(leftTrigger) < deadZone ? 0 : leftTrigger,
|
||||
rightTrigger
|
||||
);
|
||||
}
|
||||
|
||||
public AxesState rightTriggerDeadZone(float deadZone) {
|
||||
return new AxesState(
|
||||
leftStickX, leftStickY, rightStickX, rightStickY,
|
||||
leftTrigger,
|
||||
Math.abs(rightTrigger) < deadZone ? 0 : rightTrigger
|
||||
);
|
||||
}
|
||||
|
||||
public static AxesState fromController(Controller controller) {
|
||||
if (controller == null || !controller.connected())
|
||||
return EMPTY;
|
||||
|
||||
var state = controller.getGamepadState();
|
||||
var axes = state.axes();
|
||||
|
||||
float leftX = axes.get(GLFW.GLFW_GAMEPAD_AXIS_LEFT_X);
|
||||
float leftY = axes.get(GLFW.GLFW_GAMEPAD_AXIS_LEFT_Y);
|
||||
float rightX = axes.get(GLFW.GLFW_GAMEPAD_AXIS_RIGHT_X);
|
||||
float rightY = axes.get(GLFW.GLFW_GAMEPAD_AXIS_RIGHT_Y);
|
||||
float leftTrigger = (axes.get(GLFW.GLFW_GAMEPAD_AXIS_LEFT_TRIGGER) + 1f) / 2f;
|
||||
float rightTrigger = (axes.get(GLFW.GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER) + 1f) / 2f;
|
||||
|
||||
return new AxesState(leftX, leftY, rightX, rightY, leftTrigger, rightTrigger);
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
package dev.isxander.controlify.controller;
|
||||
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
|
||||
public record ButtonState(
|
||||
boolean a, boolean b, boolean x, boolean y,
|
||||
boolean leftBumper, boolean rightBumper,
|
||||
boolean back, boolean start,
|
||||
boolean dpadUp, boolean dpadDown, boolean dpadLeft, boolean dpadRight,
|
||||
boolean leftStick, boolean rightStick
|
||||
) {
|
||||
public static ButtonState EMPTY = new ButtonState(
|
||||
false, false, false, false,
|
||||
false, false,
|
||||
false, false,
|
||||
false, false, false, false,
|
||||
false, false
|
||||
);
|
||||
|
||||
public static ButtonState fromController(Controller controller) {
|
||||
if (controller == null || !controller.connected())
|
||||
return EMPTY;
|
||||
|
||||
var state = controller.getGamepadState();
|
||||
var buttons = state.buttons();
|
||||
|
||||
boolean a = buttons.get(GLFW.GLFW_GAMEPAD_BUTTON_A) == GLFW.GLFW_PRESS;
|
||||
boolean b = buttons.get(GLFW.GLFW_GAMEPAD_BUTTON_B) == GLFW.GLFW_PRESS;
|
||||
boolean x = buttons.get(GLFW.GLFW_GAMEPAD_BUTTON_X) == GLFW.GLFW_PRESS;
|
||||
boolean y = buttons.get(GLFW.GLFW_GAMEPAD_BUTTON_Y) == GLFW.GLFW_PRESS;
|
||||
boolean leftBumper = buttons.get(GLFW.GLFW_GAMEPAD_BUTTON_LEFT_BUMPER) == GLFW.GLFW_PRESS;
|
||||
boolean rightBumper = buttons.get(GLFW.GLFW_GAMEPAD_BUTTON_RIGHT_BUMPER) == GLFW.GLFW_PRESS;
|
||||
boolean back = buttons.get(GLFW.GLFW_GAMEPAD_BUTTON_BACK) == GLFW.GLFW_PRESS;
|
||||
boolean start = buttons.get(GLFW.GLFW_GAMEPAD_BUTTON_START) == GLFW.GLFW_PRESS;
|
||||
boolean dpadUp = buttons.get(GLFW.GLFW_GAMEPAD_BUTTON_DPAD_UP) == GLFW.GLFW_PRESS;
|
||||
boolean dpadDown = buttons.get(GLFW.GLFW_GAMEPAD_BUTTON_DPAD_DOWN) == GLFW.GLFW_PRESS;
|
||||
boolean dpadLeft = buttons.get(GLFW.GLFW_GAMEPAD_BUTTON_DPAD_LEFT) == GLFW.GLFW_PRESS;
|
||||
boolean dpadRight = buttons.get(GLFW.GLFW_GAMEPAD_BUTTON_DPAD_RIGHT) == GLFW.GLFW_PRESS;
|
||||
boolean leftStick = buttons.get(GLFW.GLFW_GAMEPAD_BUTTON_LEFT_THUMB) == GLFW.GLFW_PRESS;
|
||||
boolean rightStick = buttons.get(GLFW.GLFW_GAMEPAD_BUTTON_RIGHT_THUMB) == GLFW.GLFW_PRESS;
|
||||
|
||||
return new ButtonState(a, b, x, y, leftBumper, rightBumper, back, start, dpadUp, dpadDown, dpadLeft, dpadRight, leftStick, rightStick);
|
||||
}
|
||||
}
|
119
src/main/java/dev/isxander/controlify/controller/Controller.java
Normal file
119
src/main/java/dev/isxander/controlify/controller/Controller.java
Normal file
@ -0,0 +1,119 @@
|
||||
package dev.isxander.controlify.controller;
|
||||
|
||||
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
import org.lwjgl.glfw.GLFWGamepadState;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
public final class Controller {
|
||||
public static final Map<Integer, Controller> CONTROLLERS = new HashMap<>();
|
||||
private final int id;
|
||||
private final String guid;
|
||||
private final String name;
|
||||
private final boolean gamepad;
|
||||
private ControllerState state = ControllerState.EMPTY;
|
||||
private ControllerState prevState = ControllerState.EMPTY;
|
||||
|
||||
public Controller(int id, String guid, String name, boolean gamepad) {
|
||||
this.id = id;
|
||||
this.guid = guid;
|
||||
this.name = name;
|
||||
this.gamepad = gamepad;
|
||||
}
|
||||
|
||||
public ControllerState state() {
|
||||
return state;
|
||||
}
|
||||
|
||||
public ControllerState prevState() {
|
||||
return prevState;
|
||||
}
|
||||
|
||||
public void updateState() {
|
||||
if (!connected()) {
|
||||
state = prevState = ControllerState.EMPTY;
|
||||
return;
|
||||
}
|
||||
|
||||
prevState = state;
|
||||
|
||||
AxesState axesState = AxesState.fromController(this)
|
||||
.leftJoystickDeadZone(0.2f, 0.2f)
|
||||
.rightJoystickDeadZone(0.2f, 0.2f)
|
||||
.leftTriggerDeadZone(0.1f)
|
||||
.rightTriggerDeadZone(0.1f);
|
||||
ButtonState buttonState = ButtonState.fromController(this);
|
||||
state = new ControllerState(axesState, buttonState);
|
||||
}
|
||||
|
||||
public boolean connected() {
|
||||
return GLFW.glfwJoystickPresent(id);
|
||||
}
|
||||
|
||||
GLFWGamepadState getGamepadState() {
|
||||
GLFWGamepadState state = GLFWGamepadState.create();
|
||||
if (gamepad)
|
||||
GLFW.glfwGetGamepadState(id, state);
|
||||
return state;
|
||||
}
|
||||
|
||||
public int id() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String guid() {
|
||||
return guid;
|
||||
}
|
||||
|
||||
public String name() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public boolean gamepad() {
|
||||
return gamepad;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == this) return true;
|
||||
if (obj == null || obj.getClass() != this.getClass()) return false;
|
||||
var that = (Controller) obj;
|
||||
return this.id == that.id &&
|
||||
Objects.equals(this.guid, that.guid) &&
|
||||
Objects.equals(this.name, that.name) &&
|
||||
this.gamepad == that.gamepad;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Controller[" +
|
||||
"id=" + id + ", " +
|
||||
"name=" + name + ']';
|
||||
}
|
||||
|
||||
public static Controller byId(int id) {
|
||||
if (id > GLFW.GLFW_JOYSTICK_LAST)
|
||||
throw new IllegalArgumentException("Invalid joystick id: " + id);
|
||||
if (CONTROLLERS.containsKey(id))
|
||||
return CONTROLLERS.get(id);
|
||||
|
||||
String guid = GLFW.glfwGetJoystickGUID(id);
|
||||
boolean gamepad = GLFW.glfwJoystickIsGamepad(id);
|
||||
String name = gamepad ? GLFW.glfwGetGamepadName(id) : GLFW.glfwGetJoystickName(id);
|
||||
if (name == null) name = Integer.toString(id);
|
||||
|
||||
Controller controller = new Controller(id, guid, name, gamepad);
|
||||
CONTROLLERS.put(id, controller);
|
||||
|
||||
return controller;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package dev.isxander.controlify.controller;
|
||||
|
||||
public record ControllerState(AxesState axes, ButtonState buttons) {
|
||||
public static final ControllerState EMPTY = new ControllerState(AxesState.EMPTY, ButtonState.EMPTY);
|
||||
|
||||
public boolean hasAnyInput() {
|
||||
return !this.equals(EMPTY);
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
package dev.isxander.controlify.mixins;
|
||||
|
||||
import dev.isxander.controlify.compatibility.screen.component.ComponentProcessor;
|
||||
import dev.isxander.controlify.compatibility.screen.component.ComponentProcessorProvider;
|
||||
import dev.isxander.controlify.compatibility.screen.component.SliderComponentProcessor;
|
||||
import net.minecraft.client.gui.components.AbstractSliderButton;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
|
||||
@Mixin(AbstractSliderButton.class)
|
||||
public class AbstractSliderButtonMixin implements ComponentProcessorProvider {
|
||||
@Shadow private boolean canChangeValue;
|
||||
|
||||
@Unique
|
||||
private final SliderComponentProcessor controlify$processor = new SliderComponentProcessor(
|
||||
(AbstractSliderButton) (Object) this,
|
||||
() -> this.canChangeValue,
|
||||
val -> this.canChangeValue = val
|
||||
);
|
||||
|
||||
@Override
|
||||
public ComponentProcessor<AbstractSliderButton> componentProcessor() {
|
||||
return controlify$processor;
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package dev.isxander.controlify.mixins;
|
||||
|
||||
import dev.isxander.controlify.Controlify;
|
||||
import dev.isxander.controlify.InputMode;
|
||||
import net.minecraft.client.KeyboardHandler;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
@Mixin(KeyboardHandler.class)
|
||||
public class KeyboardHandlerMixin {
|
||||
@Inject(method = "keyPress", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/Minecraft;setLastInputType(Lnet/minecraft/client/InputType;)V"))
|
||||
private void onKeyboardInput(long window, int key, int scancode, int action, int modifiers, CallbackInfo ci) {
|
||||
Controlify.getInstance().setCurrentInputMode(InputMode.KEYBOARD_MOUSE);
|
||||
}
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package dev.isxander.controlify.mixins;
|
||||
|
||||
import dev.isxander.controlify.Controlify;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
@Mixin(Minecraft.class)
|
||||
public class MinecraftMixin {
|
||||
@Inject(method = "<init>", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/KeyboardHandler;setup(J)V", shift = At.Shift.AFTER))
|
||||
private void onInputInitialized(CallbackInfo ci) {
|
||||
Controlify.getInstance().onInitializeInput();
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
package dev.isxander.controlify.mixins;
|
||||
|
||||
import dev.isxander.controlify.Controlify;
|
||||
import dev.isxander.controlify.InputMode;
|
||||
import net.minecraft.client.MouseHandler;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
@Mixin(MouseHandler.class)
|
||||
public class MouseHandlerMixin {
|
||||
@Inject(method = "onPress", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/Minecraft;setLastInputType(Lnet/minecraft/client/InputType;)V"))
|
||||
private void onMouseClickInput(long window, int button, int action, int modifiers, CallbackInfo ci) {
|
||||
Controlify.getInstance().setCurrentInputMode(InputMode.KEYBOARD_MOUSE);
|
||||
}
|
||||
|
||||
@Inject(method = "onMove", at = @At("RETURN"))
|
||||
private void onMouseMoveInput(long window, double x, double y, CallbackInfo ci) {
|
||||
Controlify.getInstance().setCurrentInputMode(InputMode.KEYBOARD_MOUSE);
|
||||
}
|
||||
|
||||
@Inject(method = "onScroll", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/Minecraft;getOverlay()Lnet/minecraft/client/gui/screens/Overlay;"))
|
||||
private void onMouseScrollInput(long window, double scrollDeltaX, double scrollDeltaY, CallbackInfo ci) {
|
||||
Controlify.getInstance().setCurrentInputMode(InputMode.KEYBOARD_MOUSE);
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package dev.isxander.controlify.mixins;
|
||||
|
||||
import net.minecraft.client.gui.ComponentPath;
|
||||
import net.minecraft.client.gui.navigation.FocusNavigationEvent;
|
||||
import net.minecraft.client.gui.navigation.ScreenDirection;
|
||||
import net.minecraft.client.gui.screens.Screen;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Invoker;
|
||||
|
||||
@Mixin(Screen.class)
|
||||
public interface ScreenAccessor {
|
||||
@Invoker
|
||||
FocusNavigationEvent.ArrowNavigation invokeCreateArrowEvent(ScreenDirection direction);
|
||||
|
||||
@Invoker
|
||||
void invokeChangeFocus(ComponentPath path);
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
package dev.isxander.controlify.mixins;
|
||||
|
||||
import dev.isxander.controlify.compatibility.screen.ScreenProcessorProvider;
|
||||
import dev.isxander.controlify.compatibility.screen.ScreenProcessor;
|
||||
import net.minecraft.client.gui.screens.Screen;
|
||||
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;
|
||||
|
||||
@Mixin(Screen.class)
|
||||
public class ScreenMixin implements ScreenProcessorProvider {
|
||||
@Unique
|
||||
private final ScreenProcessor controlify$processor = new ScreenProcessor((Screen) (Object) this);
|
||||
|
||||
@Override
|
||||
public ScreenProcessor screenProcessor() {
|
||||
return controlify$processor;
|
||||
}
|
||||
|
||||
@Inject(method = "rebuildWidgets", at = @At("RETURN"))
|
||||
private void onScreenInit(CallbackInfo ci) {
|
||||
screenProcessor().onWidgetRebuild();
|
||||
}
|
||||
}
|
16
src/main/resources/controlify.mixins.json
Normal file
16
src/main/resources/controlify.mixins.json
Normal file
@ -0,0 +1,16 @@
|
||||
{
|
||||
"package": "dev.isxander.controlify.mixins",
|
||||
"required": true,
|
||||
"minVersion": "0.8",
|
||||
"compatibilityLevel": "JAVA_17",
|
||||
"mixins": [
|
||||
],
|
||||
"client": [
|
||||
"AbstractSliderButtonMixin",
|
||||
"KeyboardHandlerMixin",
|
||||
"MinecraftMixin",
|
||||
"MouseHandlerMixin",
|
||||
"ScreenAccessor",
|
||||
"ScreenMixin"
|
||||
]
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
{
|
||||
"package": "dev.isxander.fabricmodtemplate.mixins",
|
||||
"required": true,
|
||||
"minVersion": "0.8",
|
||||
"compatibilityLevel": "JAVA_17",
|
||||
"mixins": [
|
||||
|
||||
]
|
||||
}
|
@ -18,7 +18,7 @@
|
||||
|
||||
},
|
||||
"mixins": [
|
||||
"fabric-mod-template.mixins.json"
|
||||
"controlify.mixins.json"
|
||||
],
|
||||
"depends": {
|
||||
"fabricloader": ">=0.14.0",
|
||||
|
Reference in New Issue
Block a user