forked from Clones/Controlify
controllers!
This commit is contained in:
@ -15,31 +15,11 @@ plugins {
|
|||||||
group = "dev.isxander"
|
group = "dev.isxander"
|
||||||
version = "1.0.0+1.19.3"
|
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 {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
maven("https://maven.terraformersmc.com")
|
maven("https://maven.terraformersmc.com")
|
||||||
maven("https://maven.isxander.dev/releases")
|
maven("https://maven.isxander.dev/releases")
|
||||||
|
maven("https://maven.quiltmc.org/repository/release")
|
||||||
maven("https://api.modrinth.com/maven") {
|
maven("https://api.modrinth.com/maven") {
|
||||||
name = "Modrinth"
|
name = "Modrinth"
|
||||||
content {
|
content {
|
||||||
@ -52,13 +32,13 @@ val minecraftVersion = libs.versions.minecraft.get()
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
minecraft(libs.minecraft)
|
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.loader)
|
||||||
|
|
||||||
// modImplementation(libs.fabric.api)
|
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks {
|
tasks {
|
||||||
@ -180,7 +160,7 @@ publishing {
|
|||||||
val password = "XANDER_MAVEN_PASS".let { System.getenv(it) ?: findProperty(it) }?.toString()
|
val password = "XANDER_MAVEN_PASS".let { System.getenv(it) ?: findProperty(it) }?.toString()
|
||||||
if (username != null && password != null) {
|
if (username != null && password != null) {
|
||||||
maven(url = "https://maven.isxander.dev/releases") {
|
maven(url = "https://maven.isxander.dev/releases") {
|
||||||
name = "Xander Releases"
|
name = "XanderReleases"
|
||||||
credentials {
|
credentials {
|
||||||
this.username = username
|
this.username = username
|
||||||
this.password = password
|
this.password = password
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
org.gradle.jvmargs=-Xmx4G
|
org.gradle.jvmargs=-Xmx4G
|
||||||
|
|
||||||
modId=fabric-mod-template
|
modId=controlify
|
||||||
modName=Fabric Mod Template
|
modName=Controlify
|
||||||
modDescription=Template for Xander's mods with extra gradle goodies.
|
modDescription=Another controller mod - for fabric!
|
||||||
|
|
||||||
curseforgeId=
|
curseforgeId=
|
||||||
modrinthId=
|
modrinthId=
|
||||||
githubProject=isXander/FabricModTemplate
|
githubProject=isXander/Controlify
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
[versions]
|
[versions]
|
||||||
loom = "1.0.+"
|
loom = "1.1.+"
|
||||||
loom_quiltflower = "1.8.+"
|
loom_quiltflower = "1.8.+"
|
||||||
minotaur = "2.5.+"
|
minotaur = "2.6.+"
|
||||||
cursegradle = "2.+"
|
cursegradle = "2.+"
|
||||||
github_release = "2.+"
|
github_release = "2.+"
|
||||||
machete = "1.+"
|
machete = "1.+"
|
||||||
grgit = "5.0.+"
|
grgit = "5.0.+"
|
||||||
|
|
||||||
minecraft = "1.19.3"
|
minecraft = "23w04a"
|
||||||
yarn = "4"
|
quilt_mappings = "10"
|
||||||
fabric_loader = "0.14.12"
|
fabric_loader = "0.14.13"
|
||||||
fabric_api = "0.69.1+1.19.3"
|
fabric_api = "0.73.1+1.19.4"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
minecraft = { module = "com.mojang:minecraft", version.ref = "minecraft" }
|
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
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
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
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
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": [
|
"mixins": [
|
||||||
"fabric-mod-template.mixins.json"
|
"controlify.mixins.json"
|
||||||
],
|
],
|
||||||
"depends": {
|
"depends": {
|
||||||
"fabricloader": ">=0.14.0",
|
"fabricloader": ">=0.14.0",
|
||||||
|
Reference in New Issue
Block a user