1
0
forked from Clones/Controlify

gyro & look input modifier & deadzone bug

This commit is contained in:
isXander
2023-04-11 11:04:47 +01:00
parent d3fc0a946b
commit beece493c3
13 changed files with 416 additions and 104 deletions

View File

@ -24,8 +24,6 @@ public abstract class AbstractController<S extends ControllerState, C extends Co
private final String uid;
private final String guid;
private final ControllerType type;
private final long ptrJoystick;
private final RumbleManager rumbleManager;
private final ControllerBindings<S> bindings;
protected C config, defaultConfig;
@ -39,9 +37,6 @@ public abstract class AbstractController<S extends ControllerState, C extends Co
this.joystickId = joystickId;
this.guid = GLFW.glfwGetJoystickGUID(joystickId);
this.ptrJoystick = SDL2NativesManager.isLoaded() ? SDL.SDL_JoystickOpen(joystickId) : 0;
this.rumbleManager = new RumbleManager(this);
if (hidInfo.path().isPresent()) {
this.uid = hidInfo.createControllerUID().orElseThrow();
this.type = hidInfo.type();
@ -115,6 +110,7 @@ public abstract class AbstractController<S extends ControllerState, C extends Co
newConfig = gson.fromJson(json, new TypeToken<C>(getClass()){}.getType());
} catch (Exception e) {
Controlify.LOGGER.error("Could not set config for controller " + name() + " (" + uid() + ")! Using default config instead. Printing json: " + json.toString(), e);
Controlify.instance().config().setDirty();
return;
}
@ -123,46 +119,10 @@ public abstract class AbstractController<S extends ControllerState, C extends Co
} else {
Controlify.LOGGER.error("Could not set config for controller " + name() + " (" + uid() + ")! Using default config instead.");
this.config = defaultConfig();
Controlify.instance().config().setDirty();
}
}
@Override
public boolean setRumble(float strongMagnitude, float weakMagnitude, RumbleSource source) {
if (!canRumble()) return false;
var strengthMod = config().getRumbleStrength(source);
if (source != RumbleSource.MASTER)
strengthMod *= config().getRumbleStrength(RumbleSource.MASTER);
strongMagnitude *= strengthMod;
weakMagnitude *= strengthMod;
// the duration doesn't matter because we are not updating the joystick state,
// so there is never any SDL check to stop the rumble after the desired time.
if (!SDL.SDL_JoystickRumble(ptrJoystick, (int)(strongMagnitude * 65535.0F), (int)(weakMagnitude * 65535.0F), 1)) {
Controlify.LOGGER.error("Could not rumble controller " + name() + ": " + SDL.SDL_GetError());
return false;
}
return true;
}
@Override
public boolean canRumble() {
return SDL2NativesManager.isLoaded()
&& config().allowVibrations
&& ControlifyApi.get().currentInputMode() == InputMode.CONTROLLER;
}
@Override
public RumbleManager rumbleManager() {
return this.rumbleManager;
}
@Override
public void close() {
SDL.SDL_JoystickClose(ptrJoystick);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;

View File

@ -2,6 +2,7 @@ package dev.isxander.controlify.controller;
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.gamepad.GamepadController;
import dev.isxander.controlify.controller.hid.ControllerHIDService;
@ -10,6 +11,9 @@ import dev.isxander.controlify.debug.DebugProperties;
import dev.isxander.controlify.rumble.RumbleCapable;
import dev.isxander.controlify.rumble.RumbleManager;
import dev.isxander.controlify.rumble.RumbleSource;
import net.minecraft.CrashReport;
import net.minecraft.CrashReportCategory;
import net.minecraft.ReportedException;
import org.lwjgl.glfw.GLFW;
import java.util.HashMap;
@ -37,38 +41,49 @@ public interface Controller<S extends ControllerState, C extends ControllerConfi
void updateState();
void clearState();
default void open() {}
default void close() {}
RumbleManager rumbleManager();
default boolean canBeUsed() {
return true;
}
default void close() {
}
Map<String, Controller<?, ?>> CONTROLLERS = new HashMap<>();
static Controller<?, ?> createOrGet(int joystickId, ControllerHIDService.ControllerHIDInfo hidInfo) {
Optional<String> uid = hidInfo.createControllerUID();
if (uid.isPresent() && CONTROLLERS.containsKey(uid.get())) {
return CONTROLLERS.get(uid.get());
}
static Optional<Controller<?, ?>> createOrGet(int joystickId, ControllerHIDService.ControllerHIDInfo hidInfo) {
try {
Optional<String> uid = hidInfo.createControllerUID();
if (uid.isPresent() && CONTROLLERS.containsKey(uid.get())) {
return Optional.of(CONTROLLERS.get(uid.get()));
}
if (GLFW.glfwJoystickIsGamepad(joystickId) && !DebugProperties.FORCE_JOYSTICK) {
GamepadController controller = new GamepadController(joystickId, hidInfo);
if (hidInfo.type().dontLoad()) {
Controlify.LOGGER.warn("Preventing load of controller #" + joystickId + " because its type prevents loading.");
return Optional.empty();
}
if (GLFW.glfwJoystickIsGamepad(joystickId) && !DebugProperties.FORCE_JOYSTICK && !hidInfo.type().forceJoystick()) {
GamepadController controller = new GamepadController(joystickId, hidInfo);
CONTROLLERS.put(controller.uid(), controller);
return Optional.of(controller);
}
SingleJoystickController controller = new SingleJoystickController(joystickId, hidInfo);
CONTROLLERS.put(controller.uid(), controller);
return controller;
return Optional.of(controller);
} catch (Throwable e) {
CrashReport crashReport = CrashReport.forThrowable(e, "Creating controller #" + joystickId);
CrashReportCategory category = crashReport.addCategory("Controller Info");
category.setDetail("Joystick ID", joystickId);
category.setDetail("Controller identification", hidInfo.type());
category.setDetail("HID path", hidInfo.path().orElse("N/A"));
throw new ReportedException(crashReport);
}
SingleJoystickController controller = new SingleJoystickController(joystickId, hidInfo);
CONTROLLERS.put(controller.uid(), controller);
return controller;
}
static void remove(Controller<?, ?> controller) {
CONTROLLERS.remove(controller.uid(), controller);
controller.close();
}
Controller<?, ?> DUMMY = new Controller<>() {

View File

@ -5,10 +5,13 @@ import dev.isxander.controlify.controller.ControllerConfig;
public class GamepadConfig extends ControllerConfig {
public float leftStickDeadzoneX = 0.2f;
public float leftStickDeadzoneY = 0.2f;
public float rightStickDeadzoneX = 0.2f;
public float rightStickDeadzoneY = 0.2f;
public float gyroLookSensitivity = 0f;
public boolean gyroRequiresButton = true;
public boolean flickStick = false;
public BuiltinGamepadTheme theme = BuiltinGamepadTheme.DEFAULT;
@Override

View File

@ -1,7 +1,15 @@
package dev.isxander.controlify.controller.gamepad;
import dev.isxander.controlify.Controlify;
import dev.isxander.controlify.InputMode;
import dev.isxander.controlify.api.ControlifyApi;
import dev.isxander.controlify.controller.AbstractController;
import dev.isxander.controlify.controller.hid.ControllerHIDService;
import dev.isxander.controlify.controller.sdl2.SDL2NativesManager;
import dev.isxander.controlify.debug.DebugProperties;
import dev.isxander.controlify.rumble.RumbleManager;
import dev.isxander.controlify.rumble.RumbleSource;
import org.libsdl.SDL;
import org.lwjgl.glfw.GLFW;
import org.lwjgl.glfw.GLFWGamepadState;
@ -9,6 +17,12 @@ public class GamepadController extends AbstractController<GamepadState, GamepadC
private GamepadState state = GamepadState.EMPTY;
private GamepadState prevState = GamepadState.EMPTY;
private long gamepadPtr;
private boolean rumbleSupported, triggerRumbleSupported;
private final RumbleManager rumbleManager;
private boolean hasGyro;
private GamepadState.GyroState absoluteGyro = GamepadState.GyroState.ORIGIN;
public GamepadController(int joystickId, ControllerHIDService.ControllerHIDInfo hidInfo) {
super(joystickId, hidInfo);
if (!GLFW.glfwJoystickIsGamepad(joystickId))
@ -17,6 +31,8 @@ public class GamepadController extends AbstractController<GamepadState, GamepadC
if (!this.name.startsWith(type().friendlyName()))
setName(GLFW.glfwGetGamepadName(joystickId));
this.rumbleManager = new RumbleManager(this);
this.defaultConfig = new GamepadConfig();
this.config = new GamepadConfig();
}
@ -40,7 +56,26 @@ public class GamepadController extends AbstractController<GamepadState, GamepadC
.leftJoystickDeadZone(config().leftStickDeadzoneX, config().leftStickDeadzoneY)
.rightJoystickDeadZone(config().rightStickDeadzoneX, config().rightStickDeadzoneY);
GamepadState.ButtonState buttonState = GamepadState.ButtonState.fromController(this);
state = new GamepadState(axesState, rawAxesState, buttonState);
GamepadState.GyroState gyroDelta = null;
if (this.hasGyro) {
float[] gyro = new float[3];
SDL.SDL_GameControllerGetSensorData(gamepadPtr, SDL.SDL_SENSOR_GYRO, gyro, 3);
gyroDelta = new GamepadState.GyroState(gyro[0], gyro[1], gyro[2]);
if (DebugProperties.PRINT_GYRO) Controlify.LOGGER.info("Gyro delta: " + gyroDelta);
absoluteGyro = absoluteGyro.add(gyroDelta);
}
SDL.SDL_GameControllerUpdate();
state = new GamepadState(axesState, rawAxesState, buttonState, gyroDelta, absoluteGyro);
}
public GamepadState.GyroState absoluteGyroState() {
return absoluteGyro;
}
public boolean hasGyro() {
return hasGyro;
}
@Override
@ -49,7 +84,7 @@ public class GamepadController extends AbstractController<GamepadState, GamepadC
}
public void consumeButtonState() {
this.state = new GamepadState(state().gamepadAxes(), state().rawGamepadAxes(), GamepadState.ButtonState.EMPTY);
this.state = new GamepadState(state().gamepadAxes(), state().rawGamepadAxes(), GamepadState.ButtonState.EMPTY, state().gyroDelta(), state().absoluteGyroPos());
}
GLFWGamepadState getGamepadState() {
@ -58,4 +93,60 @@ public class GamepadController extends AbstractController<GamepadState, GamepadC
return state;
}
@Override
public boolean setRumble(float strongMagnitude, float weakMagnitude, RumbleSource source) {
if (!canRumble()) return false;
var strengthMod = config().getRumbleStrength(source);
if (source != RumbleSource.MASTER)
strengthMod *= config().getRumbleStrength(RumbleSource.MASTER);
strongMagnitude *= strengthMod;
weakMagnitude *= strengthMod;
// the duration doesn't matter because we are not updating the gamecontroller state,
// so there is never any SDL check to stop the rumble after the desired time.
if (!SDL.SDL_GameControllerRumble(gamepadPtr, (int)(strongMagnitude * 65535.0F), (int)(weakMagnitude * 65535.0F), 0)) {
Controlify.LOGGER.error("Could not rumble controller " + name() + ": " + SDL.SDL_GetError());
return false;
}
return true;
}
@Override
public boolean canRumble() {
return rumbleSupported
&& config().allowVibrations
&& ControlifyApi.get().currentInputMode() == InputMode.CONTROLLER;
}
@Override
public RumbleManager rumbleManager() {
return this.rumbleManager;
}
@Override
public void open() {
if (SDL2NativesManager.isLoaded()) {
this.gamepadPtr = SDL.SDL_GameControllerOpen(joystickId);
Controlify.LOGGER.info(SDL.SDL_GetError());
this.rumbleSupported = SDL.SDL_GameControllerHasRumble(gamepadPtr);
this.triggerRumbleSupported = SDL.SDL_GameControllerHasRumble(gamepadPtr);
if (this.hasGyro = SDL.SDL_GameControllerHasSensor(gamepadPtr, SDL.SDL_SENSOR_GYRO)) {
SDL.SDL_GameControllerSetSensorEnabled(gamepadPtr, SDL.SDL_SENSOR_GYRO, true);
}
} else {
this.gamepadPtr = 0;
this.rumbleSupported = false;
this.hasGyro = false;
}
}
@Override
public void close() {
SDL.SDL_GameControllerClose(gamepadPtr);
this.gamepadPtr = 0;
this.rumbleSupported = false;
this.hasGyro = false;
}
}

View File

@ -2,25 +2,37 @@ package dev.isxander.controlify.controller.gamepad;
import dev.isxander.controlify.controller.ControllerState;
import dev.isxander.controlify.utils.ControllerUtils;
import org.jetbrains.annotations.Nullable;
import org.lwjgl.glfw.GLFW;
import java.util.List;
import java.util.Objects;
public final class GamepadState implements ControllerState {
public static final GamepadState EMPTY = new GamepadState(AxesState.EMPTY, AxesState.EMPTY, ButtonState.EMPTY);
public static final GamepadState EMPTY = new GamepadState(AxesState.EMPTY, AxesState.EMPTY, ButtonState.EMPTY, GyroState.ORIGIN, GyroState.ORIGIN);
private final AxesState gamepadAxes;
private final AxesState rawGamepadAxes;
private final ButtonState gamepadButtons;
private final GyroState absoluteGyroPos;
private final @Nullable GyroState gyroDelta;
private final List<Float> unnamedAxes;
private final List<Float> unnamedRawAxes;
private final List<Boolean> unnamedButtons;
public GamepadState(AxesState gamepadAxes, AxesState rawGamepadAxes, ButtonState gamepadButtons) {
public GamepadState(
AxesState gamepadAxes,
AxesState rawGamepadAxes,
ButtonState gamepadButtons,
@Nullable GamepadState.GyroState gyroDelta,
GyroState absoluteGyroPos
) {
this.gamepadAxes = gamepadAxes;
this.rawGamepadAxes = rawGamepadAxes;
this.gamepadButtons = gamepadButtons;
this.gyroDelta = gyroDelta;
this.absoluteGyroPos = absoluteGyroPos;
this.unnamedAxes = List.of(
gamepadAxes.leftStickX(),
@ -90,6 +102,19 @@ public final class GamepadState implements ControllerState {
return gamepadButtons;
}
public GyroState gyroDelta() {
if (gyroDelta == null) return GyroState.ORIGIN;
return gyroDelta;
}
public GyroState absoluteGyroPos() {
return absoluteGyroPos;
}
public boolean supportsGyro() {
return gyroDelta != null;
}
@Override
public boolean equals(Object obj) {
if (obj == this) return true;
@ -213,4 +238,12 @@ public final class GamepadState implements ControllerState {
return new ButtonState(a, b, x, y, leftBumper, rightBumper, back, start, guide, dpadUp, dpadDown, dpadLeft, dpadRight, leftStick, rightStick);
}
}
public record GyroState(float pitch, float yaw, float roll) {
public static GyroState ORIGIN = new GyroState(0, 0, 0);
public GyroState add(GyroState other) {
return new GyroState(pitch + other.pitch, yaw + other.yaw, roll + other.roll);
}
}
}

View File

@ -2,12 +2,17 @@ package dev.isxander.controlify.controller.joystick;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import dev.isxander.controlify.Controlify;
import dev.isxander.controlify.InputMode;
import dev.isxander.controlify.api.ControlifyApi;
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 dev.isxander.controlify.controller.sdl2.SDL2NativesManager;
import dev.isxander.controlify.rumble.RumbleManager;
import dev.isxander.controlify.rumble.RumbleSource;
import org.libsdl.SDL;
import java.util.Objects;
@ -15,6 +20,10 @@ public class SingleJoystickController extends AbstractController<JoystickState,
private JoystickState state = JoystickState.EMPTY, prevState = JoystickState.EMPTY;
private final JoystickMapping mapping;
private long ptrJoystick;
private RumbleManager rumbleManager;
private boolean rumbleSupported;
public SingleJoystickController(int joystickId, ControllerHIDService.ControllerHIDInfo hidInfo) {
super(joystickId, hidInfo);
@ -75,4 +84,50 @@ public class SingleJoystickController extends AbstractController<JoystickState,
super.setConfig(gson, json);
this.config.setup(this);
}
@Override
public boolean setRumble(float strongMagnitude, float weakMagnitude, RumbleSource source) {
if (!canRumble()) return false;
var strengthMod = config().getRumbleStrength(source);
if (source != RumbleSource.MASTER)
strengthMod *= config().getRumbleStrength(RumbleSource.MASTER);
strongMagnitude *= strengthMod;
weakMagnitude *= strengthMod;
// the duration doesn't matter because we are not updating the joystick state,
// so there is never any SDL check to stop the rumble after the desired time.
if (!SDL.SDL_JoystickRumbleTriggers(ptrJoystick, (int)(strongMagnitude * 65535.0F), (int)(weakMagnitude * 65535.0F), 1)) {
Controlify.LOGGER.error("Could not rumble controller " + name() + ": " + SDL.SDL_GetError());
return false;
}
return true;
}
@Override
public boolean canRumble() {
return rumbleSupported
&& config().allowVibrations
&& ControlifyApi.get().currentInputMode() == InputMode.CONTROLLER;
}
@Override
public RumbleManager rumbleManager() {
return this.rumbleManager;
}
@Override
public void open() {
this.ptrJoystick = SDL2NativesManager.isLoaded() ? SDL.SDL_JoystickOpen(joystickId) : 0;
this.rumbleSupported = SDL2NativesManager.isLoaded() && SDL.SDL_JoystickHasRumble(this.ptrJoystick);
this.rumbleManager = new RumbleManager(this);
}
@Override
public void close() {
SDL.SDL_JoystickClose(ptrJoystick);
this.rumbleSupported = false;
this.rumbleManager = null;
}
}