package dev.isxander.controlify.controller.gamepad; import dev.isxander.controlify.Controlify; import dev.isxander.controlify.bindings.ControllerBindings; import dev.isxander.controlify.controller.AbstractController; import dev.isxander.controlify.controller.BatteryLevel; import dev.isxander.controlify.controller.hid.ControllerHIDService; import dev.isxander.controlify.debug.DebugProperties; import dev.isxander.controlify.driver.*; import dev.isxander.controlify.rumble.RumbleManager; import dev.isxander.controlify.rumble.RumbleSource; import dev.isxander.controlify.utils.ControllerUtils; import dev.isxander.controlify.utils.Log; import net.minecraft.resources.ResourceLocation; import org.lwjgl.glfw.GLFW; import java.util.Set; public class GamepadController extends AbstractController { private GamepadState state = GamepadState.EMPTY; private GamepadState prevState = GamepadState.EMPTY; private final RumbleManager rumbleManager; private GamepadState.GyroState absoluteGyro = new GamepadState.GyroState(); public final GamepadDrivers drivers; private final Set uniqueDrivers; private int antiSnapbackTicksL, antiSnapbackTicksR; public GamepadController(int joystickId, ControllerHIDService.ControllerHIDInfo hidInfo) { super(joystickId, hidInfo); if (!GLFW.glfwJoystickIsGamepad(joystickId)) throw new IllegalArgumentException("Joystick " + joystickId + " is not a gamepad!"); this.drivers = GamepadDrivers.forController(joystickId, hidInfo.hidDevice()); this.uniqueDrivers = drivers.getUniqueDrivers(); this.drivers.printDrivers(); if (!this.name.startsWith(type().friendlyName())) setName(this.drivers.nameProviderDriver().getName()); this.rumbleManager = new RumbleManager(this); this.defaultConfig = new GamepadConfig(); this.config = new GamepadConfig(); if (hidInfo.hidDevice().map(hid -> SteamDeckDriver.isSteamDeck(hid.getVendorId(), hid.getProductId())).orElse(false)) { this.defaultConfig.mixedInput = true; this.config.mixedInput = true; } this.bindings = new ControllerBindings<>(this); } @Override public GamepadState state() { return state; } @Override public GamepadState prevState() { return prevState; } @Override public void updateState() { prevState = state; uniqueDrivers.forEach(Driver::update); BasicGamepadInputDriver.BasicGamepadState basicState = drivers.basicGamepadInputDriver().getBasicGamepadState(); GamepadState.AxesState deadzoneAxesState = basicState.axes() .leftJoystickDeadZone(config().getLeftStickDeadzone()) .rightJoystickDeadZone(config().getRightStickDeadzone()); if (DebugProperties.USE_SNAPBACK) { if (antiSnapbackTicksL > 0) { deadzoneAxesState = deadzoneAxesState.neutraliseLeft(); antiSnapbackTicksL--; } else if (ControllerUtils.shouldApplyAntiSnapBack(deadzoneAxesState.leftStickX(), deadzoneAxesState.leftStickY(), prevState.gamepadAxes().leftStickX(), prevState.gamepadAxes().leftStickY(), 0.08f)) { antiSnapbackTicksL = 2; deadzoneAxesState = deadzoneAxesState.neutraliseLeft(); } if (antiSnapbackTicksR > 0) { deadzoneAxesState = deadzoneAxesState.neutraliseRight(); antiSnapbackTicksR--; } else if (ControllerUtils.shouldApplyAntiSnapBack(deadzoneAxesState.rightStickX(), deadzoneAxesState.rightStickY(), prevState.gamepadAxes().rightStickX(), prevState.gamepadAxes().rightStickY(), 0.08f)) { antiSnapbackTicksR = 2; deadzoneAxesState = deadzoneAxesState.neutraliseRight(); } } // TODO: Add some sort of gyro filtering GamepadState.GyroState gyroState = new GamepadState.GyroState(drivers.gyroDriver().getGyroState()).sub(config().gyroCalibration); this.absoluteGyro.add(gyroState); state = new GamepadState(deadzoneAxesState, basicState.axes(), basicState.buttons(), gyroState, absoluteGyro); if (DebugProperties.PRINT_TRIGGER_STATE) { Log.LOGGER.info("Left Trigger: " + state.gamepadAxes().leftTrigger()); Log.LOGGER.info("Right Trigger: " + state.gamepadAxes().rightTrigger()); } } public GamepadState.GyroState absoluteGyroState() { return absoluteGyro; } public boolean hasGyro() { return drivers.gyroDriver().isGyroSupported(); } @Override public void clearState() { state = GamepadState.EMPTY; } @Override public boolean setRumble(float strongMagnitude, float weakMagnitude, RumbleSource source) { if (!supportsRumble() || !config().allowVibrations) return false; var strengthMod = config().getRumbleStrength(source); if (source != RumbleSource.MASTER) strengthMod *= config().getRumbleStrength(RumbleSource.MASTER); strongMagnitude *= strengthMod; weakMagnitude *= strengthMod; return drivers.rumbleDriver().rumble(Math.min(strongMagnitude, 1), Math.min(weakMagnitude, 1)); } @Override public boolean supportsRumble() { return drivers.rumbleDriver().isRumbleSupported(); } @Override public RumbleManager rumbleManager() { return this.rumbleManager; } @Override public BatteryLevel batteryLevel() { return drivers.batteryDriver().getBatteryLevel(); } @Override public void close() { uniqueDrivers.forEach(Driver::close); } @Override public ResourceLocation icon() { String theme = config().theme == BuiltinGamepadTheme.DEFAULT ? type().themeId() : config().theme.id(); return Controlify.id("textures/gui/gamepad/" + theme + "/icon.png"); } }