forked from Clones/Controlify
rewrite most of joystick mapping
This commit is contained in:
@ -21,6 +21,9 @@ import dev.isxander.controlify.utils.ToastUtils;
|
|||||||
import dev.isxander.controlify.virtualmouse.VirtualMouseHandler;
|
import dev.isxander.controlify.virtualmouse.VirtualMouseHandler;
|
||||||
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
|
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
|
||||||
import net.fabricmc.loader.api.FabricLoader;
|
import net.fabricmc.loader.api.FabricLoader;
|
||||||
|
import net.minecraft.CrashReport;
|
||||||
|
import net.minecraft.CrashReportCategory;
|
||||||
|
import net.minecraft.ReportedException;
|
||||||
import net.minecraft.client.Minecraft;
|
import net.minecraft.client.Minecraft;
|
||||||
import net.minecraft.client.gui.screens.Screen;
|
import net.minecraft.client.gui.screens.Screen;
|
||||||
import net.minecraft.network.chat.Component;
|
import net.minecraft.network.chat.Component;
|
||||||
@ -78,12 +81,14 @@ public class Controlify implements ControlifyApi {
|
|||||||
if (config().globalSettings().loadVibrationNatives)
|
if (config().globalSettings().loadVibrationNatives)
|
||||||
SDL2NativesManager.initialise();
|
SDL2NativesManager.initialise();
|
||||||
|
|
||||||
boolean dirtyControllerConfig = false;
|
|
||||||
// find already connected controllers
|
// find already connected controllers
|
||||||
for (int jid = 0; jid <= GLFW.GLFW_JOYSTICK_LAST; jid++) {
|
for (int jid = 0; jid <= GLFW.GLFW_JOYSTICK_LAST; jid++) {
|
||||||
if (GLFW.glfwJoystickPresent(jid)) {
|
if (GLFW.glfwJoystickPresent(jid)) {
|
||||||
try {
|
try {
|
||||||
var controller = Controller.createOrGet(jid, controllerHIDService.fetchType());
|
var controllerOpt = Controller.createOrGet(jid, controllerHIDService.fetchType());
|
||||||
|
if (controllerOpt.isEmpty()) continue;
|
||||||
|
var controller = controllerOpt.get();
|
||||||
|
|
||||||
LOGGER.info("Controller found: " + controller.name());
|
LOGGER.info("Controller found: " + controller.name());
|
||||||
|
|
||||||
config().loadOrCreateControllerData(controller);
|
config().loadOrCreateControllerData(controller);
|
||||||
@ -93,7 +98,7 @@ public class Controlify implements ControlifyApi {
|
|||||||
|
|
||||||
if (controller.config().allowVibrations && !config().globalSettings().loadVibrationNatives) {
|
if (controller.config().allowVibrations && !config().globalSettings().loadVibrationNatives) {
|
||||||
controller.config().allowVibrations = false;
|
controller.config().allowVibrations = false;
|
||||||
dirtyControllerConfig = true;
|
config().setDirty();
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LOGGER.error("Failed to initialize controller with jid " + jid, e);
|
LOGGER.error("Failed to initialize controller with jid " + jid, e);
|
||||||
@ -101,10 +106,6 @@ public class Controlify implements ControlifyApi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dirtyControllerConfig) {
|
|
||||||
config().save();
|
|
||||||
}
|
|
||||||
|
|
||||||
checkCompoundJoysticks();
|
checkCompoundJoysticks();
|
||||||
|
|
||||||
if (Controller.CONTROLLERS.isEmpty()) {
|
if (Controller.CONTROLLERS.isEmpty()) {
|
||||||
@ -113,6 +114,9 @@ public class Controlify implements ControlifyApi {
|
|||||||
|
|
||||||
if (currentController() == Controller.DUMMY && config().isFirstLaunch()) {
|
if (currentController() == Controller.DUMMY && config().isFirstLaunch()) {
|
||||||
this.setCurrentController(Controller.CONTROLLERS.values().stream().findFirst().orElse(null));
|
this.setCurrentController(Controller.CONTROLLERS.values().stream().findFirst().orElse(null));
|
||||||
|
} else {
|
||||||
|
// setCurrentController saves config
|
||||||
|
config().saveIfDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
// listen for new controllers
|
// listen for new controllers
|
||||||
@ -172,15 +176,10 @@ public class Controlify implements ControlifyApi {
|
|||||||
|
|
||||||
for (var controller : Controller.CONTROLLERS.values()) {
|
for (var controller : Controller.CONTROLLERS.values()) {
|
||||||
if (!outOfFocus)
|
if (!outOfFocus)
|
||||||
controller.updateState();
|
wrapControllerError(controller::updateState, "Updating controller state", controller);
|
||||||
else {
|
else
|
||||||
controller.clearState();
|
wrapControllerError(controller::clearState, "Clearing controller state", controller);
|
||||||
controller.rumbleManager().clearEffects();
|
|
||||||
}
|
}
|
||||||
controller.rumbleManager().tick();
|
|
||||||
}
|
|
||||||
|
|
||||||
ControllerState state = currentController == null ? ControllerState.EMPTY : currentController.state();
|
|
||||||
|
|
||||||
if (switchableController != null && Blaze3D.getTime() - askSwitchTime <= 10000) {
|
if (switchableController != null && Blaze3D.getTime() - askSwitchTime <= 10000) {
|
||||||
if (switchableController.state().hasAnyInput()) {
|
if (switchableController.state().hasAnyInput()) {
|
||||||
@ -189,13 +188,23 @@ public class Controlify implements ControlifyApi {
|
|||||||
askSwitchToast.remove();
|
askSwitchToast.remove();
|
||||||
askSwitchToast = null;
|
askSwitchToast = null;
|
||||||
}
|
}
|
||||||
|
switchableController.clearState();
|
||||||
switchableController = null;
|
switchableController = null;
|
||||||
state = ControllerState.EMPTY;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (outOfFocus)
|
wrapControllerError(() -> tickController(currentController, outOfFocus), "Ticking current controller", currentController);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void tickController(Controller<?, ?> controller, boolean outOfFocus) {
|
||||||
|
ControllerState state = controller.state();
|
||||||
|
|
||||||
|
if (outOfFocus) {
|
||||||
state = ControllerState.EMPTY;
|
state = ControllerState.EMPTY;
|
||||||
|
controller.rumbleManager().clearEffects();
|
||||||
|
} else {
|
||||||
|
controller.rumbleManager().tick();
|
||||||
|
}
|
||||||
|
|
||||||
if (state.hasAnyInput())
|
if (state.hasAnyInput())
|
||||||
this.setInputMode(InputMode.CONTROLLER);
|
this.setInputMode(InputMode.CONTROLLER);
|
||||||
@ -209,22 +218,31 @@ public class Controlify implements ControlifyApi {
|
|||||||
);
|
);
|
||||||
this.setCurrentController(null);
|
this.setCurrentController(null);
|
||||||
consecutiveInputSwitches = 0;
|
consecutiveInputSwitches = 0;
|
||||||
}
|
|
||||||
|
|
||||||
if (currentController == null) {
|
|
||||||
this.setInputMode(InputMode.KEYBOARD_MOUSE);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (client.screen != null) {
|
if (minecraft.screen != null) {
|
||||||
ScreenProcessorProvider.provide(client.screen).onControllerUpdate(currentController);
|
ScreenProcessorProvider.provide(minecraft.screen).onControllerUpdate(controller);
|
||||||
}
|
}
|
||||||
if (client.level != null) {
|
if (minecraft.level != null) {
|
||||||
this.inGameInputHandler().inputTick();
|
this.inGameInputHandler().inputTick();
|
||||||
}
|
}
|
||||||
this.virtualMouseHandler().handleControllerInput(currentController);
|
this.virtualMouseHandler().handleControllerInput(controller);
|
||||||
|
|
||||||
ControlifyEvents.CONTROLLER_STATE_UPDATED.invoker().onControllerStateUpdate(currentController);
|
ControlifyEvents.CONTROLLER_STATE_UPDATED.invoker().onControllerStateUpdate(controller);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void wrapControllerError(Runnable runnable, String errorTitle, Controller<?, ?> controller) {
|
||||||
|
try {
|
||||||
|
runnable.run();
|
||||||
|
} catch (Throwable e) {
|
||||||
|
CrashReport crashReport = CrashReport.forThrowable(e, errorTitle);
|
||||||
|
CrashReportCategory category = crashReport.addCategory("Affected controller");
|
||||||
|
category.setDetail("Controller name", controller::name);
|
||||||
|
category.setDetail("Controller identification", () -> controller.type().toString());
|
||||||
|
category.setDetail("Controller type", () -> controller.getClass().getCanonicalName());
|
||||||
|
throw new ReportedException(crashReport);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public ControlifyConfig config() {
|
public ControlifyConfig config() {
|
||||||
@ -232,14 +250,24 @@ public class Controlify implements ControlifyApi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void onControllerHotplugged(int jid) {
|
private void onControllerHotplugged(int jid) {
|
||||||
var controller = Controller.createOrGet(jid, controllerHIDService.fetchType());
|
var controllerOpt = Controller.createOrGet(jid, controllerHIDService.fetchType());
|
||||||
|
if (controllerOpt.isEmpty()) return;
|
||||||
|
var controller = controllerOpt.get();
|
||||||
|
|
||||||
LOGGER.info("Controller connected: " + controller.name());
|
LOGGER.info("Controller connected: " + controller.name());
|
||||||
|
|
||||||
config().loadOrCreateControllerData(currentController);
|
config().loadOrCreateControllerData(currentController);
|
||||||
|
|
||||||
|
if (controller.config().allowVibrations && !config().globalSettings().loadVibrationNatives) {
|
||||||
|
controller.config().allowVibrations = false;
|
||||||
|
config().setDirty();
|
||||||
|
}
|
||||||
|
|
||||||
this.askToSwitchController(controller);
|
this.askToSwitchController(controller);
|
||||||
|
|
||||||
checkCompoundJoysticks();
|
checkCompoundJoysticks();
|
||||||
|
|
||||||
|
config().saveIfDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onControllerDisconnect(int jid) {
|
private void onControllerDisconnect(int jid) {
|
||||||
@ -304,7 +332,12 @@ public class Controlify implements ControlifyApi {
|
|||||||
controller = Controller.DUMMY;
|
controller = Controller.DUMMY;
|
||||||
|
|
||||||
if (this.currentController == controller) return;
|
if (this.currentController == controller) return;
|
||||||
|
|
||||||
|
if (this.currentController != null)
|
||||||
|
this.currentController.close();
|
||||||
|
|
||||||
this.currentController = controller;
|
this.currentController = controller;
|
||||||
|
this.currentController.open();
|
||||||
|
|
||||||
if (switchableController == controller) {
|
if (switchableController == controller) {
|
||||||
switchableController = null;
|
switchableController = null;
|
||||||
|
@ -8,6 +8,7 @@ import dev.isxander.controlify.api.bind.ControllerBindingBuilder;
|
|||||||
import dev.isxander.controlify.controller.Controller;
|
import dev.isxander.controlify.controller.Controller;
|
||||||
import dev.isxander.controlify.controller.ControllerState;
|
import dev.isxander.controlify.controller.ControllerState;
|
||||||
import dev.isxander.controlify.api.event.ControlifyEvents;
|
import dev.isxander.controlify.api.event.ControlifyEvents;
|
||||||
|
import dev.isxander.controlify.controller.gamepad.GamepadController;
|
||||||
import dev.isxander.controlify.mixins.compat.fapi.KeyBindingRegistryImplAccessor;
|
import dev.isxander.controlify.mixins.compat.fapi.KeyBindingRegistryImplAccessor;
|
||||||
import dev.isxander.controlify.mixins.feature.bind.KeyMappingAccessor;
|
import dev.isxander.controlify.mixins.feature.bind.KeyMappingAccessor;
|
||||||
import dev.isxander.controlify.mixins.feature.bind.ToggleKeyMappingAccessor;
|
import dev.isxander.controlify.mixins.feature.bind.ToggleKeyMappingAccessor;
|
||||||
@ -38,6 +39,7 @@ public class ControllerBindings<T extends ControllerState> {
|
|||||||
public final ControllerBinding<T>
|
public final ControllerBinding<T>
|
||||||
WALK_FORWARD, WALK_BACKWARD, WALK_LEFT, WALK_RIGHT,
|
WALK_FORWARD, WALK_BACKWARD, WALK_LEFT, WALK_RIGHT,
|
||||||
LOOK_UP, LOOK_DOWN, LOOK_LEFT, LOOK_RIGHT,
|
LOOK_UP, LOOK_DOWN, LOOK_LEFT, LOOK_RIGHT,
|
||||||
|
GAMEPAD_GYRO_BUTTON,
|
||||||
JUMP, SNEAK,
|
JUMP, SNEAK,
|
||||||
ATTACK, USE,
|
ATTACK, USE,
|
||||||
SPRINT,
|
SPRINT,
|
||||||
@ -110,6 +112,15 @@ public class ControllerBindings<T extends ControllerState> {
|
|||||||
.defaultBind(GamepadBinds.RIGHT_STICK_RIGHT)
|
.defaultBind(GamepadBinds.RIGHT_STICK_RIGHT)
|
||||||
.category(MOVEMENT_CATEGORY)
|
.category(MOVEMENT_CATEGORY)
|
||||||
.build());
|
.build());
|
||||||
|
if (controller instanceof GamepadController gamepad && gamepad.hasGyro()) {
|
||||||
|
register(GAMEPAD_GYRO_BUTTON = ControllerBindingBuilder.create(controller)
|
||||||
|
.identifier("controlify", "gamepad_gyro_button")
|
||||||
|
.defaultBind(new EmptyBind<>())
|
||||||
|
.category(MOVEMENT_CATEGORY)
|
||||||
|
.build());
|
||||||
|
} else {
|
||||||
|
GAMEPAD_GYRO_BUTTON = null;
|
||||||
|
}
|
||||||
register(JUMP = ControllerBindingBuilder.create(controller)
|
register(JUMP = ControllerBindingBuilder.create(controller)
|
||||||
.identifier("controlify", "jump")
|
.identifier("controlify", "jump")
|
||||||
.defaultBind(GamepadBinds.A_BUTTON)
|
.defaultBind(GamepadBinds.A_BUTTON)
|
||||||
|
@ -42,8 +42,8 @@ public class JoystickAxisBind implements IBind<JoystickState> {
|
|||||||
JoystickMapping mapping = joystick.mapping();
|
JoystickMapping mapping = joystick.mapping();
|
||||||
|
|
||||||
String type = joystick.type().identifier();
|
String type = joystick.type().identifier();
|
||||||
String axis = mapping.axis(axisIndex).identifier();
|
String axis = mapping.axes()[axisIndex].identifier();
|
||||||
String direction = mapping.axis(axisIndex).getDirectionIdentifier(axisIndex, this.direction);
|
String direction = mapping.axes()[axisIndex].getDirectionIdentifier(axisIndex, this.direction);
|
||||||
var texture = new ResourceLocation("controlify", "textures/gui/joystick/" + type + "/axis_" + axis + "_" + direction + ".png");
|
var texture = new ResourceLocation("controlify", "textures/gui/joystick/" + type + "/axis_" + axis + "_" + direction + ".png");
|
||||||
|
|
||||||
RenderSystem.setShaderTexture(0, texture);
|
RenderSystem.setShaderTexture(0, texture);
|
||||||
|
@ -31,7 +31,7 @@ public class JoystickButtonBind implements IBind<JoystickState> {
|
|||||||
@Override
|
@Override
|
||||||
public void draw(PoseStack matrices, int x, int centerY) {
|
public void draw(PoseStack matrices, int x, int centerY) {
|
||||||
String type = joystick.type().identifier();
|
String type = joystick.type().identifier();
|
||||||
String button = joystick.mapping().button(buttonIndex).identifier();
|
String button = joystick.mapping().buttons()[buttonIndex].identifier();
|
||||||
var texture = new ResourceLocation("controlify", "textures/gui/joystick/" + type + "/button_" + button + ".png");
|
var texture = new ResourceLocation("controlify", "textures/gui/joystick/" + type + "/button_" + button + ".png");
|
||||||
|
|
||||||
RenderSystem.setShaderTexture(0, texture);
|
RenderSystem.setShaderTexture(0, texture);
|
||||||
|
@ -33,18 +33,18 @@ public class JoystickHatBind implements IBind<JoystickState> {
|
|||||||
@Override
|
@Override
|
||||||
public void draw(PoseStack matrices, int x, int centerY) {
|
public void draw(PoseStack matrices, int x, int centerY) {
|
||||||
String type = joystick.type().identifier();
|
String type = joystick.type().identifier();
|
||||||
String button = joystick.mapping().button(hatIndex).identifier();
|
String hat = joystick.mapping().hats()[hatIndex].identifier();
|
||||||
String direction = "centered";
|
String direction = "centered";
|
||||||
if (hatState.isUp())
|
if (hatState.isUp())
|
||||||
direction = "up";
|
direction = "up";
|
||||||
else if (hatState.isDown())
|
else if (hatState.isDown())
|
||||||
direction = "down";
|
direction = "down";
|
||||||
else if (hatState.isLeft())
|
else if (hatState.isLeft())
|
||||||
direction = "strong";
|
direction = "left";
|
||||||
else if (hatState.isRight())
|
else if (hatState.isRight())
|
||||||
direction = "weak";
|
direction = "right";
|
||||||
|
|
||||||
var texture = new ResourceLocation("controlify", "textures/gui/joystick/" + type + "/hat" + button + "_" + direction + ".png");
|
var texture = new ResourceLocation("controlify", "textures/gui/joystick/" + type + "/hat" + hat + "_" + direction + ".png");
|
||||||
|
|
||||||
RenderSystem.setShaderTexture(0, texture);
|
RenderSystem.setShaderTexture(0, texture);
|
||||||
RenderSystem.setShaderColor(1, 1, 1, 1);
|
RenderSystem.setShaderColor(1, 1, 1, 1);
|
||||||
|
@ -11,8 +11,8 @@ import org.quiltmc.json5.JsonReader;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
public record ControllerType(String friendlyName, String identifier) {
|
public record ControllerType(String friendlyName, String identifier, boolean forceJoystick, boolean dontLoad) {
|
||||||
public static final ControllerType UNKNOWN = new ControllerType("Unknown", "unknown");
|
public static final ControllerType UNKNOWN = new ControllerType("Unknown", "unknown", false, false);
|
||||||
|
|
||||||
private static Map<HIDIdentifier, ControllerType> typeMap = null;
|
private static Map<HIDIdentifier, ControllerType> typeMap = null;
|
||||||
private static final ResourceLocation hidDbLocation = new ResourceLocation("controlify", "controllers/controller_identification.json5");
|
private static final ResourceLocation hidDbLocation = new ResourceLocation("controlify", "controllers/controller_identification.json5");
|
||||||
@ -47,6 +47,8 @@ public record ControllerType(String friendlyName, String identifier) {
|
|||||||
while (reader.hasNext()) {
|
while (reader.hasNext()) {
|
||||||
String friendlyName = null;
|
String friendlyName = null;
|
||||||
String identifier = null;
|
String identifier = null;
|
||||||
|
boolean forceJoystick = false;
|
||||||
|
boolean dontLoad = false;
|
||||||
Set<HIDIdentifier> hids = new HashSet<>();
|
Set<HIDIdentifier> hids = new HashSet<>();
|
||||||
|
|
||||||
reader.beginObject();
|
reader.beginObject();
|
||||||
@ -77,6 +79,8 @@ public record ControllerType(String friendlyName, String identifier) {
|
|||||||
}
|
}
|
||||||
reader.endArray();
|
reader.endArray();
|
||||||
}
|
}
|
||||||
|
case "force_joystick" -> forceJoystick = reader.nextBoolean();
|
||||||
|
case "dont_load" -> dontLoad = reader.nextBoolean();
|
||||||
default -> {
|
default -> {
|
||||||
Controlify.LOGGER.warn("Unknown key in HID DB: " + name + ". Skipping...");
|
Controlify.LOGGER.warn("Unknown key in HID DB: " + name + ". Skipping...");
|
||||||
reader.skipValue();
|
reader.skipValue();
|
||||||
@ -90,7 +94,7 @@ public record ControllerType(String friendlyName, String identifier) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var type = new ControllerType(friendlyName, identifier);
|
var type = new ControllerType(friendlyName, identifier, forceJoystick, dontLoad);
|
||||||
for (var hid : hids) {
|
for (var hid : hids) {
|
||||||
typeMap.put(hid, type);
|
typeMap.put(hid, type);
|
||||||
}
|
}
|
||||||
|
@ -39,7 +39,7 @@ public class CompoundJoystickController implements JoystickController<JoystickCo
|
|||||||
this.buttonCount = joystickIds.stream().mapToInt(this::getButtonCountForJoystick).sum();
|
this.buttonCount = joystickIds.stream().mapToInt(this::getButtonCountForJoystick).sum();
|
||||||
this.hatCount = joystickIds.stream().mapToInt(this::getHatCountForJoystick).sum();
|
this.hatCount = joystickIds.stream().mapToInt(this::getHatCountForJoystick).sum();
|
||||||
|
|
||||||
this.mapping = RPJoystickMapping.fromType(type());
|
this.mapping = RPJoystickMapping.fromType(this);
|
||||||
|
|
||||||
this.config = new JoystickConfig(this);
|
this.config = new JoystickConfig(this);
|
||||||
this.defaultConfig = new JoystickConfig(this);
|
this.defaultConfig = new JoystickConfig(this);
|
||||||
|
@ -9,7 +9,7 @@ import java.util.Optional;
|
|||||||
|
|
||||||
public record CompoundJoystickInfo(Collection<String> joystickUids, String friendlyName) {
|
public record CompoundJoystickInfo(Collection<String> joystickUids, String friendlyName) {
|
||||||
public ControllerType type() {
|
public ControllerType type() {
|
||||||
return new ControllerType(friendlyName, createUID(joystickUids));
|
return new ControllerType(friendlyName, createUID(joystickUids), true, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean canBeUsed() {
|
public boolean canBeUsed() {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package dev.isxander.controlify.controller.joystick;
|
package dev.isxander.controlify.controller.joystick;
|
||||||
|
|
||||||
import dev.isxander.controlify.controller.ControllerConfig;
|
import dev.isxander.controlify.controller.ControllerConfig;
|
||||||
|
import dev.isxander.controlify.controller.joystick.mapping.JoystickMapping;
|
||||||
import org.apache.commons.lang3.Validate;
|
import org.apache.commons.lang3.Validate;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
@ -21,7 +22,7 @@ public class JoystickConfig extends ControllerConfig {
|
|||||||
if (axis < 0)
|
if (axis < 0)
|
||||||
throw new IllegalArgumentException("Axis cannot be negative!");
|
throw new IllegalArgumentException("Axis cannot be negative!");
|
||||||
|
|
||||||
deadzones.put(controller.mapping().axis(axis).identifier(), deadzone);
|
deadzones.put(controller.mapping().axes()[axis].identifier(), deadzone);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -29,16 +30,18 @@ public class JoystickConfig extends ControllerConfig {
|
|||||||
if (axis < 0)
|
if (axis < 0)
|
||||||
throw new IllegalArgumentException("Axis cannot be negative!");
|
throw new IllegalArgumentException("Axis cannot be negative!");
|
||||||
|
|
||||||
return deadzones.getOrDefault(controller.mapping().axis(axis).identifier(), 0.2f);
|
return deadzones.getOrDefault(controller.mapping().axes()[axis].identifier(), 0.2f);
|
||||||
}
|
}
|
||||||
|
|
||||||
void setup(JoystickController<?> controller) {
|
void setup(JoystickController<?> controller) {
|
||||||
this.controller = controller;
|
this.controller = controller;
|
||||||
if (this.deadzones == null) {
|
if (this.deadzones == null) {
|
||||||
deadzones = new HashMap<>();
|
deadzones = new HashMap<>();
|
||||||
for (int i = 0; i < controller.axisCount(); i++) {
|
for (int i = 0; i < controller.mapping().axes().length; i++) {
|
||||||
if (controller.mapping().axis(i).requiresDeadzone())
|
JoystickMapping.Axis axis = controller.mapping().axes()[i];
|
||||||
deadzones.put(controller.mapping().axis(i).identifier(), 0.2f);
|
|
||||||
|
if (axis.requiresDeadzone())
|
||||||
|
deadzones.put(axis.identifier(), 0.2f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,8 +9,11 @@ import dev.isxander.controlify.controller.joystick.mapping.UnmappedJoystickMappi
|
|||||||
public interface JoystickController<T extends JoystickConfig> extends Controller<JoystickState, T> {
|
public interface JoystickController<T extends JoystickConfig> extends Controller<JoystickState, T> {
|
||||||
JoystickMapping mapping();
|
JoystickMapping mapping();
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
int axisCount();
|
int axisCount();
|
||||||
|
@Deprecated
|
||||||
int buttonCount();
|
int buttonCount();
|
||||||
|
@Deprecated
|
||||||
int hatCount();
|
int hatCount();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
package dev.isxander.controlify.controller.joystick;
|
package dev.isxander.controlify.controller.joystick;
|
||||||
|
|
||||||
|
import dev.isxander.controlify.Controlify;
|
||||||
import dev.isxander.controlify.controller.ControllerState;
|
import dev.isxander.controlify.controller.ControllerState;
|
||||||
import dev.isxander.controlify.controller.joystick.mapping.JoystickMapping;
|
import dev.isxander.controlify.controller.joystick.mapping.JoystickMapping;
|
||||||
import dev.isxander.controlify.controller.joystick.mapping.UnmappedJoystickMapping;
|
import dev.isxander.controlify.controller.joystick.mapping.UnmappedJoystickMapping;
|
||||||
|
import dev.isxander.controlify.debug.DebugProperties;
|
||||||
import dev.isxander.controlify.utils.ControllerUtils;
|
import dev.isxander.controlify.utils.ControllerUtils;
|
||||||
import dev.isxander.yacl.api.NameableEnum;
|
import dev.isxander.yacl.api.NameableEnum;
|
||||||
import net.minecraft.network.chat.Component;
|
import net.minecraft.network.chat.Component;
|
||||||
@ -11,12 +13,13 @@ import org.lwjgl.glfw.GLFW;
|
|||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.FloatBuffer;
|
import java.nio.FloatBuffer;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.IntStream;
|
import java.util.stream.IntStream;
|
||||||
|
|
||||||
public class JoystickState implements ControllerState {
|
public class JoystickState implements ControllerState {
|
||||||
public static final JoystickState EMPTY = new JoystickState(UnmappedJoystickMapping.INSTANCE, List.of(), List.of(), List.of(), List.of());
|
public static final JoystickState EMPTY = new JoystickState(UnmappedJoystickMapping.EMPTY, List.of(), List.of(), List.of(), List.of());
|
||||||
|
|
||||||
private final JoystickMapping mapping;
|
private final JoystickMapping mapping;
|
||||||
|
|
||||||
@ -54,7 +57,7 @@ public class JoystickState implements ControllerState {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean hasAnyInput() {
|
public boolean hasAnyInput() {
|
||||||
return IntStream.range(0, axes().size()).anyMatch(i -> !mapping.axis(i).isAxisResting(axes().get(i)))
|
return IntStream.range(0, axes().size()).anyMatch(i -> !mapping.axes()[i].isAxisResting(axes().get(i)))
|
||||||
|| buttons().stream().anyMatch(Boolean::booleanValue)
|
|| buttons().stream().anyMatch(Boolean::booleanValue)
|
||||||
|| hats().stream().anyMatch(hat -> hat != HatState.CENTERED);
|
|| hats().stream().anyMatch(hat -> hat != HatState.CENTERED);
|
||||||
}
|
}
|
||||||
@ -70,68 +73,85 @@ public class JoystickState implements ControllerState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static JoystickState fromJoystick(JoystickController<?> joystick, int joystickId) {
|
public static JoystickState fromJoystick(JoystickController<?> joystick, int joystickId) {
|
||||||
|
if (DebugProperties.PRINT_JOY_INPUT_COUNT)
|
||||||
|
Controlify.LOGGER.info("Printing joy input for " + joystick.name());
|
||||||
|
|
||||||
FloatBuffer axesBuffer = GLFW.glfwGetJoystickAxes(joystickId);
|
FloatBuffer axesBuffer = GLFW.glfwGetJoystickAxes(joystickId);
|
||||||
List<Float> axes = new ArrayList<>();
|
float[] inAxes = new float[axesBuffer.limit()];
|
||||||
List<Float> rawAxes = new ArrayList<>();
|
|
||||||
if (axesBuffer != null) {
|
if (DebugProperties.PRINT_JOY_INPUT_COUNT)
|
||||||
|
Controlify.LOGGER.info("Axes count = " + inAxes.length);
|
||||||
|
|
||||||
|
{
|
||||||
int i = 0;
|
int i = 0;
|
||||||
while (axesBuffer.hasRemaining()) {
|
while (axesBuffer.hasRemaining()) {
|
||||||
var axisMapping = joystick.mapping().axis(i);
|
inAxes[i] = axesBuffer.get();
|
||||||
var axis = axisMapping.modifyAxis(axesBuffer.get());
|
|
||||||
var deadzone = axisMapping.requiresDeadzone();
|
|
||||||
|
|
||||||
rawAxes.add(axis);
|
|
||||||
axes.add(deadzone ? ControllerUtils.deadzone(axis, joystick.config().getDeadzone(i)) : axis);
|
|
||||||
|
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ByteBuffer buttonBuffer = GLFW.glfwGetJoystickButtons(joystickId);
|
ByteBuffer buttonBuffer = GLFW.glfwGetJoystickButtons(joystickId);
|
||||||
List<Boolean> buttons = new ArrayList<>();
|
boolean[] inButtons = new boolean[buttonBuffer.limit()];
|
||||||
if (buttonBuffer != null) {
|
|
||||||
|
if (DebugProperties.PRINT_JOY_INPUT_COUNT)
|
||||||
|
Controlify.LOGGER.info("Button count = " + inButtons.length);
|
||||||
|
|
||||||
|
{
|
||||||
|
int i = 0;
|
||||||
while (buttonBuffer.hasRemaining()) {
|
while (buttonBuffer.hasRemaining()) {
|
||||||
buttons.add(buttonBuffer.get() == GLFW.GLFW_PRESS);
|
inButtons[i] = buttonBuffer.get() == GLFW.GLFW_PRESS;
|
||||||
|
i++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ByteBuffer hatBuffer = GLFW.glfwGetJoystickHats(joystickId);
|
ByteBuffer hatBuffer = GLFW.glfwGetJoystickHats(joystickId);
|
||||||
List<JoystickState.HatState> hats = new ArrayList<>();
|
HatState[] inHats = new HatState[hatBuffer.limit()];
|
||||||
if (hatBuffer != null) {
|
|
||||||
|
if (DebugProperties.PRINT_JOY_INPUT_COUNT)
|
||||||
|
Controlify.LOGGER.info("Hat count = " + inHats.length);
|
||||||
|
|
||||||
|
{
|
||||||
|
int i = 0;
|
||||||
while (hatBuffer.hasRemaining()) {
|
while (hatBuffer.hasRemaining()) {
|
||||||
var state = switch (hatBuffer.get()) {
|
var state = switch (hatBuffer.get()) {
|
||||||
case GLFW.GLFW_HAT_CENTERED -> JoystickState.HatState.CENTERED;
|
case GLFW.GLFW_HAT_CENTERED -> HatState.CENTERED;
|
||||||
case GLFW.GLFW_HAT_UP -> JoystickState.HatState.UP;
|
case GLFW.GLFW_HAT_UP -> HatState.UP;
|
||||||
case GLFW.GLFW_HAT_RIGHT -> JoystickState.HatState.RIGHT;
|
case GLFW.GLFW_HAT_RIGHT -> HatState.RIGHT;
|
||||||
case GLFW.GLFW_HAT_DOWN -> JoystickState.HatState.DOWN;
|
case GLFW.GLFW_HAT_DOWN -> HatState.DOWN;
|
||||||
case GLFW.GLFW_HAT_LEFT -> JoystickState.HatState.LEFT;
|
case GLFW.GLFW_HAT_LEFT -> HatState.LEFT;
|
||||||
case GLFW.GLFW_HAT_RIGHT_UP -> JoystickState.HatState.RIGHT_UP;
|
case GLFW.GLFW_HAT_RIGHT_UP -> HatState.RIGHT_UP;
|
||||||
case GLFW.GLFW_HAT_RIGHT_DOWN -> JoystickState.HatState.RIGHT_DOWN;
|
case GLFW.GLFW_HAT_RIGHT_DOWN -> HatState.RIGHT_DOWN;
|
||||||
case GLFW.GLFW_HAT_LEFT_UP -> JoystickState.HatState.LEFT_UP;
|
case GLFW.GLFW_HAT_LEFT_UP -> HatState.LEFT_UP;
|
||||||
case GLFW.GLFW_HAT_LEFT_DOWN -> JoystickState.HatState.LEFT_DOWN;
|
case GLFW.GLFW_HAT_LEFT_DOWN -> HatState.LEFT_DOWN;
|
||||||
default -> throw new IllegalStateException("Unexpected value: " + hatBuffer.get());
|
default -> throw new IllegalStateException("Unexpected value: " + hatBuffer.get());
|
||||||
};
|
};
|
||||||
hats.add(state);
|
inHats[i] = state;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new JoystickState(joystick.mapping(), axes, rawAxes, buttons, hats);
|
JoystickMapping.JoystickData data = new JoystickMapping.JoystickData(inAxes, inButtons, inHats);
|
||||||
|
JoystickMapping mapping = joystick.mapping();
|
||||||
|
|
||||||
|
JoystickMapping.Axis[] axes = mapping.axes();
|
||||||
|
List<Float> rawAxes = new ArrayList<>(axes.length);
|
||||||
|
List<Float> deadzoneAxes = new ArrayList<>(axes.length);
|
||||||
|
for (int i = 0; i < axes.length; i++) {
|
||||||
|
var axis = axes[i];
|
||||||
|
float state = axis.getAxis(data);
|
||||||
|
rawAxes.add(state);
|
||||||
|
deadzoneAxes.add(axis.requiresDeadzone() ? ControllerUtils.deadzone(state, i) : state);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Boolean> buttons = Arrays.stream(mapping.buttons()).map(button -> button.isPressed(data)).toList();
|
||||||
|
List<HatState> hats = Arrays.stream(mapping.hats()).map(hat -> hat.getHatState(data)).toList();
|
||||||
|
|
||||||
|
return new JoystickState(joystick.mapping(), deadzoneAxes, rawAxes, buttons, hats);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static JoystickState empty(JoystickController<?> joystick) {
|
public static JoystickState empty(JoystickController<?> joystick) {
|
||||||
var axes = new ArrayList<Float>();
|
var axes = Arrays.stream(joystick.mapping().axes()).map(JoystickMapping.Axis::restingValue).toList();
|
||||||
var buttons = new ArrayList<Boolean>();
|
var buttons = IntStream.range(0, joystick.mapping().buttons().length).mapToObj(i -> false).toList();
|
||||||
var hats = new ArrayList<HatState>();
|
var hats = IntStream.range(0, joystick.mapping().hats().length).mapToObj(i -> HatState.CENTERED).toList();
|
||||||
|
|
||||||
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);
|
return new JoystickState(joystick.mapping(), axes, axes, buttons, hats);
|
||||||
}
|
}
|
||||||
|
@ -13,17 +13,12 @@ import java.util.Objects;
|
|||||||
|
|
||||||
public class SingleJoystickController extends AbstractController<JoystickState, JoystickConfig> implements JoystickController<JoystickConfig> {
|
public class SingleJoystickController extends AbstractController<JoystickState, JoystickConfig> implements JoystickController<JoystickConfig> {
|
||||||
private JoystickState state = JoystickState.EMPTY, prevState = JoystickState.EMPTY;
|
private JoystickState state = JoystickState.EMPTY, prevState = JoystickState.EMPTY;
|
||||||
private final int axisCount, buttonCount, hatCount;
|
|
||||||
private final JoystickMapping mapping;
|
private final JoystickMapping mapping;
|
||||||
|
|
||||||
public SingleJoystickController(int joystickId, ControllerHIDService.ControllerHIDInfo hidInfo) {
|
public SingleJoystickController(int joystickId, ControllerHIDService.ControllerHIDInfo hidInfo) {
|
||||||
super(joystickId, hidInfo);
|
super(joystickId, hidInfo);
|
||||||
|
|
||||||
this.axisCount = GLFW.glfwGetJoystickAxes(joystickId).capacity();
|
this.mapping = Objects.requireNonNull(RPJoystickMapping.fromType(this));
|
||||||
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.config = new JoystickConfig(this);
|
||||||
this.defaultConfig = new JoystickConfig(this);
|
this.defaultConfig = new JoystickConfig(this);
|
||||||
@ -57,17 +52,17 @@ public class SingleJoystickController extends AbstractController<JoystickState,
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int axisCount() {
|
public int axisCount() {
|
||||||
return axisCount;
|
return mapping().axes().length;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int buttonCount() {
|
public int buttonCount() {
|
||||||
return buttonCount;
|
return mapping.buttons().length;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hatCount() {
|
public int hatCount() {
|
||||||
return hatCount;
|
return mapping.hats().length;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -1,14 +1,13 @@
|
|||||||
package dev.isxander.controlify.controller.joystick.mapping;
|
package dev.isxander.controlify.controller.joystick.mapping;
|
||||||
|
|
||||||
import dev.isxander.controlify.bindings.JoystickAxisBind;
|
import dev.isxander.controlify.bindings.JoystickAxisBind;
|
||||||
|
import dev.isxander.controlify.controller.joystick.JoystickState;
|
||||||
import net.minecraft.network.chat.Component;
|
import net.minecraft.network.chat.Component;
|
||||||
|
|
||||||
public interface JoystickMapping {
|
public interface JoystickMapping {
|
||||||
Axis axis(int axis);
|
Axis[] axes();
|
||||||
|
Button[] buttons();
|
||||||
Button button(int button);
|
Hat[] hats();
|
||||||
|
|
||||||
Hat hat(int hat);
|
|
||||||
|
|
||||||
interface Axis {
|
interface Axis {
|
||||||
String identifier();
|
String identifier();
|
||||||
@ -17,7 +16,7 @@ public interface JoystickMapping {
|
|||||||
|
|
||||||
boolean requiresDeadzone();
|
boolean requiresDeadzone();
|
||||||
|
|
||||||
float modifyAxis(float value);
|
float getAxis(JoystickData data);
|
||||||
|
|
||||||
boolean isAxisResting(float value);
|
boolean isAxisResting(float value);
|
||||||
|
|
||||||
@ -30,11 +29,18 @@ public interface JoystickMapping {
|
|||||||
String identifier();
|
String identifier();
|
||||||
|
|
||||||
Component name();
|
Component name();
|
||||||
|
|
||||||
|
boolean isPressed(JoystickData data);
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Hat {
|
interface Hat {
|
||||||
|
JoystickState.HatState getHatState(JoystickData data);
|
||||||
|
|
||||||
String identifier();
|
String identifier();
|
||||||
|
|
||||||
Component name();
|
Component name();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
record JoystickData(float[] axes, boolean[] buttons, JoystickState.HatState[] hats) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,123 +1,282 @@
|
|||||||
package dev.isxander.controlify.controller.joystick.mapping;
|
package dev.isxander.controlify.controller.joystick.mapping;
|
||||||
|
|
||||||
import com.google.gson.Gson;
|
|
||||||
import com.google.gson.JsonArray;
|
|
||||||
import com.google.gson.JsonElement;
|
|
||||||
import com.google.gson.JsonObject;
|
|
||||||
import dev.isxander.controlify.Controlify;
|
import dev.isxander.controlify.Controlify;
|
||||||
import dev.isxander.controlify.bindings.JoystickAxisBind;
|
import dev.isxander.controlify.bindings.JoystickAxisBind;
|
||||||
import dev.isxander.controlify.controller.ControllerType;
|
import dev.isxander.controlify.controller.ControllerType;
|
||||||
|
import dev.isxander.controlify.controller.joystick.JoystickController;
|
||||||
|
import dev.isxander.controlify.controller.joystick.JoystickState;
|
||||||
import net.minecraft.client.Minecraft;
|
import net.minecraft.client.Minecraft;
|
||||||
import net.minecraft.network.chat.Component;
|
import net.minecraft.network.chat.Component;
|
||||||
import net.minecraft.resources.ResourceLocation;
|
import net.minecraft.resources.ResourceLocation;
|
||||||
import net.minecraft.world.phys.Vec2;
|
import net.minecraft.world.phys.Vec2;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
import org.quiltmc.json5.JsonReader;
|
||||||
|
import org.quiltmc.json5.JsonToken;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.io.IOException;
|
||||||
import java.util.List;
|
import java.util.*;
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
public class RPJoystickMapping implements JoystickMapping {
|
public class RPJoystickMapping implements JoystickMapping {
|
||||||
private static final Gson gson = new Gson();
|
|
||||||
|
|
||||||
private final Map<Integer, AxisMapping> axisMappings;
|
private final AxisMapping[] axes;
|
||||||
private final Map<Integer, ButtonMapping> buttonMappings;
|
private final ButtonMapping[] buttons;
|
||||||
private final Map<Integer, HatMapping> hatMappings;
|
private final HatMapping[] hats;
|
||||||
|
|
||||||
public RPJoystickMapping(JsonObject object, ControllerType type) {
|
public RPJoystickMapping(JsonReader reader, ControllerType type) throws IOException {
|
||||||
axisMappings = new HashMap<>();
|
AxisMapping[] axes = null;
|
||||||
object.getAsJsonArray("axes").forEach(element -> {
|
ButtonMapping[] buttons = null;
|
||||||
var axis = element.getAsJsonObject();
|
HatMapping[] hats = null;
|
||||||
List<Integer> ids = axis.getAsJsonArray("ids").asList().stream().map(JsonElement::getAsInt).toList();
|
|
||||||
|
|
||||||
|
reader.beginObject();
|
||||||
|
while (reader.hasNext()) {
|
||||||
|
String name = reader.nextName();
|
||||||
|
switch (name) {
|
||||||
|
case "axes" -> {
|
||||||
|
if (axes != null)
|
||||||
|
throw new IllegalStateException("Axes defined twice.");
|
||||||
|
axes = readAxes(reader, type);
|
||||||
|
}
|
||||||
|
case "buttons" -> {
|
||||||
|
if (buttons != null)
|
||||||
|
throw new IllegalStateException("Buttons defined twice.");
|
||||||
|
buttons = readButtons(reader, type);
|
||||||
|
}
|
||||||
|
case "hats" -> {
|
||||||
|
if (hats != null)
|
||||||
|
throw new IllegalStateException("Hats defined twice.");
|
||||||
|
hats = readHats(reader, type);
|
||||||
|
}
|
||||||
|
default -> {
|
||||||
|
Controlify.LOGGER.warn("Unknown field in joystick mapping: " + name + ". Expected values: ['axes', 'buttons', 'hats']");
|
||||||
|
reader.skipValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
reader.endObject();
|
||||||
|
|
||||||
|
this.axes = axes;
|
||||||
|
this.buttons = buttons;
|
||||||
|
this.hats = hats;
|
||||||
|
}
|
||||||
|
|
||||||
|
private AxisMapping[] readAxes(JsonReader reader, ControllerType type) throws IOException {
|
||||||
|
List<AxisMapping> axes = new ArrayList<>();
|
||||||
|
|
||||||
|
reader.beginArray();
|
||||||
|
while (reader.hasNext()) {
|
||||||
|
List<Integer> ids = new ArrayList<>();
|
||||||
Vec2 inpRange = null;
|
Vec2 inpRange = null;
|
||||||
Vec2 outRange = null;
|
Vec2 outRange = null;
|
||||||
if (axis.has("range")) {
|
boolean deadzone = false;
|
||||||
var rangeElement = axis.get("range");
|
float restState = 0f;
|
||||||
if (rangeElement.isJsonArray()) {
|
String identifier = null;
|
||||||
var rangeArray = rangeElement.getAsJsonArray();
|
List<String[]> axisNames = new ArrayList<>();
|
||||||
outRange = new Vec2(rangeArray.get(0).getAsFloat(), rangeArray.get(1).getAsFloat());
|
|
||||||
|
reader.beginObject();
|
||||||
|
while (reader.hasNext()) {
|
||||||
|
String name = reader.nextName();
|
||||||
|
switch (name) {
|
||||||
|
case "ids" -> {
|
||||||
|
reader.beginArray();
|
||||||
|
while (reader.hasNext()) {
|
||||||
|
ids.add(reader.nextInt());
|
||||||
|
}
|
||||||
|
reader.endArray();
|
||||||
|
}
|
||||||
|
case "identifier" -> {
|
||||||
|
identifier = reader.nextString();
|
||||||
|
}
|
||||||
|
case "range" -> {
|
||||||
|
if (reader.peek() == JsonToken.BEGIN_ARRAY) {
|
||||||
|
reader.beginArray();
|
||||||
|
outRange = new Vec2((float) reader.nextDouble(), (float) reader.nextDouble());
|
||||||
inpRange = new Vec2(-1, 1);
|
inpRange = new Vec2(-1, 1);
|
||||||
} else if (rangeElement.isJsonObject()) {
|
reader.endArray();
|
||||||
var rangeObject = rangeElement.getAsJsonObject();
|
} else {
|
||||||
|
reader.beginObject();
|
||||||
|
while (reader.hasNext()) {
|
||||||
|
String rangeName = reader.nextName();
|
||||||
|
|
||||||
var inpRangeArray = rangeObject.getAsJsonArray("in");
|
switch (rangeName) {
|
||||||
inpRange = new Vec2(inpRangeArray.get(0).getAsFloat(), inpRangeArray.get(1).getAsFloat());
|
case "in" -> {
|
||||||
|
reader.beginArray();
|
||||||
var outRangeArray = rangeObject.getAsJsonArray("out");
|
inpRange = new Vec2((float) reader.nextDouble(), (float) reader.nextDouble());
|
||||||
outRange = new Vec2(outRangeArray.get(0).getAsFloat(), outRangeArray.get(1).getAsFloat());
|
reader.endArray();
|
||||||
|
}
|
||||||
|
case "out" -> {
|
||||||
|
reader.beginArray();
|
||||||
|
outRange = new Vec2((float) reader.nextDouble(), (float) reader.nextDouble());
|
||||||
|
reader.endArray();
|
||||||
|
}
|
||||||
|
default -> {
|
||||||
|
reader.skipValue();
|
||||||
|
Controlify.LOGGER.info("Unknown axis range property: " + rangeName + ". Expected are ['in', 'out']");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var restState = axis.get("rest").getAsFloat();
|
}
|
||||||
var deadzone = axis.get("deadzone").getAsBoolean();
|
reader.endObject();
|
||||||
var identifier = axis.get("identifier").getAsString();
|
}
|
||||||
|
}
|
||||||
var axisNames = axis.getAsJsonArray("axis_names").asList().stream()
|
case "rest" -> {
|
||||||
.map(JsonElement::getAsJsonArray)
|
restState = (float) reader.nextDouble();
|
||||||
.map(JsonArray::asList)
|
}
|
||||||
.map(list -> list.stream().map(JsonElement::getAsString).toList())
|
case "deadzone" -> {
|
||||||
.toList();
|
deadzone = reader.nextBoolean();
|
||||||
|
}
|
||||||
|
case "axis_names" -> {
|
||||||
|
reader.beginArray();
|
||||||
|
while (reader.hasNext()) {
|
||||||
|
reader.beginArray();
|
||||||
|
axisNames.add(new String[] { reader.nextString(), reader.nextString() });
|
||||||
|
reader.endArray();
|
||||||
|
}
|
||||||
|
reader.endArray();
|
||||||
|
}
|
||||||
|
default -> {
|
||||||
|
reader.skipValue();
|
||||||
|
Controlify.LOGGER.info("Unknown axis property: " + name + ". Expected are ['identifier', 'axis_names', 'ids', 'range', 'rest', 'deadzone']");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
reader.endObject();
|
||||||
|
|
||||||
for (var id : ids) {
|
for (var id : ids) {
|
||||||
axisMappings.put(id, new AxisMapping(ids, identifier, inpRange, outRange, restState, deadzone, type.identifier(), axisNames));
|
axes.add(new AxisMapping(id, identifier, inpRange, outRange, restState, deadzone, type.identifier(), axisNames.get(ids.indexOf(id))));
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
reader.endArray();
|
||||||
|
|
||||||
buttonMappings = new HashMap<>();
|
return axes.toArray(new AxisMapping[0]);
|
||||||
object.getAsJsonArray("buttons").forEach(element -> {
|
}
|
||||||
var button = element.getAsJsonObject();
|
|
||||||
buttonMappings.put(button.get("button").getAsInt(), new ButtonMapping(button.get("name").getAsString(), type.identifier()));
|
|
||||||
});
|
|
||||||
|
|
||||||
hatMappings = new HashMap<>();
|
private ButtonMapping[] readButtons(JsonReader reader, ControllerType type) throws IOException {
|
||||||
object.getAsJsonArray("hats").forEach(element -> {
|
List<ButtonMapping> buttons = new ArrayList<>();
|
||||||
var hat = element.getAsJsonObject();
|
|
||||||
hatMappings.put(hat.get("hat").getAsInt(), new HatMapping(hat.get("name").getAsString(), type.identifier()));
|
reader.beginArray();
|
||||||
});
|
while (reader.hasNext()) {
|
||||||
|
int id = -1;
|
||||||
|
String btnName = null;
|
||||||
|
|
||||||
|
reader.beginObject();
|
||||||
|
while (reader.hasNext()) {
|
||||||
|
String name = reader.nextName();
|
||||||
|
switch (name) {
|
||||||
|
case "button" -> id = reader.nextInt();
|
||||||
|
case "name" -> btnName = reader.nextString();
|
||||||
|
default -> {
|
||||||
|
reader.skipValue();
|
||||||
|
Controlify.LOGGER.info("Unknown button property: " + name + ". Expected are ['button', 'name']");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
reader.endObject();
|
||||||
|
|
||||||
|
buttons.add(new ButtonMapping(id, btnName, type.identifier()));
|
||||||
|
}
|
||||||
|
reader.endArray();
|
||||||
|
|
||||||
|
return buttons.toArray(new ButtonMapping[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private HatMapping[] readHats(JsonReader reader, ControllerType type) throws IOException {
|
||||||
|
List<HatMapping> hats = new ArrayList<>();
|
||||||
|
|
||||||
|
reader.beginArray();
|
||||||
|
while (reader.hasNext()) {
|
||||||
|
int id = -1;
|
||||||
|
String hatName = null;
|
||||||
|
HatMapping.EmulatedAxis axis = null;
|
||||||
|
|
||||||
|
reader.beginObject();
|
||||||
|
while (reader.hasNext()) {
|
||||||
|
String name = reader.nextName();
|
||||||
|
switch (name) {
|
||||||
|
case "hat" -> id = reader.nextInt();
|
||||||
|
case "name" -> hatName = reader.nextString();
|
||||||
|
case "emulated_axis" -> {
|
||||||
|
int axisId = -1;
|
||||||
|
Map<Float, JoystickState.HatState> states = new HashMap<>();
|
||||||
|
|
||||||
|
reader.beginObject();
|
||||||
|
while (reader.hasNext()) {
|
||||||
|
String emulatedName = reader.nextName();
|
||||||
|
for (var hatState : JoystickState.HatState.values()) {
|
||||||
|
if (hatState.name().equalsIgnoreCase(emulatedName)) {
|
||||||
|
states.put((float) reader.nextDouble(), hatState);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (emulatedName.equalsIgnoreCase("axis")) {
|
||||||
|
axisId = reader.nextInt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
reader.endObject();
|
||||||
|
|
||||||
|
if (axisId == -1) {
|
||||||
|
Controlify.LOGGER.error("No axis id defined for emulated hat " + hatName + "! Skipping.");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (states.size() != JoystickState.HatState.values().length) {
|
||||||
|
Controlify.LOGGER.error("Not all hat states are defined for emulated hat " + hatName + "! Skipping.");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
axis = new HatMapping.EmulatedAxis(axisId, states);
|
||||||
|
}
|
||||||
|
default -> {
|
||||||
|
reader.skipValue();
|
||||||
|
Controlify.LOGGER.info("Unknown hat property: " + name + ". Expected are ['hat', 'name']");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
reader.endObject();
|
||||||
|
|
||||||
|
hats.add(new HatMapping(id, hatName, type.identifier(), axis));
|
||||||
|
}
|
||||||
|
reader.endArray();
|
||||||
|
|
||||||
|
return hats.toArray(new HatMapping[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Axis axis(int axis) {
|
public Axis[] axes() {
|
||||||
if (!axisMappings.containsKey(axis))
|
return axes;
|
||||||
return UnmappedJoystickMapping.INSTANCE.axis(axis);
|
|
||||||
return axisMappings.get(axis);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Button button(int button) {
|
public Button[] buttons() {
|
||||||
if (!buttonMappings.containsKey(button))
|
return buttons;
|
||||||
return UnmappedJoystickMapping.INSTANCE.button(button);
|
|
||||||
return buttonMappings.get(button);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Hat hat(int hat) {
|
public Hat[] hats() {
|
||||||
if (!hatMappings.containsKey(hat))
|
return hats;
|
||||||
return UnmappedJoystickMapping.INSTANCE.hat(hat);
|
|
||||||
return hatMappings.get(hat);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static JoystickMapping fromType(ControllerType type) {
|
public static JoystickMapping fromType(JoystickController<?> joystick) {
|
||||||
var resource = Minecraft.getInstance().getResourceManager().getResource(new ResourceLocation("controlify", "mappings/" + type.identifier() + ".json"));
|
var resource = Minecraft.getInstance().getResourceManager().getResource(new ResourceLocation("controlify", "mappings/" + joystick.type().identifier() + ".json"));
|
||||||
if (resource.isEmpty()) {
|
if (resource.isEmpty()) {
|
||||||
Controlify.LOGGER.warn("No joystick mapping found for controller: '" + type.identifier() + "'");
|
Controlify.LOGGER.warn("No joystick mapping found for controller: '" + joystick.type().identifier() + "'");
|
||||||
return UnmappedJoystickMapping.INSTANCE;
|
return new UnmappedJoystickMapping(joystick.joystickId());
|
||||||
}
|
}
|
||||||
|
|
||||||
try (var reader = resource.get().openAsReader()) {
|
try (var reader = JsonReader.json5(resource.get().openAsReader())) {
|
||||||
return new RPJoystickMapping(gson.fromJson(reader, JsonObject.class), type);
|
return new RPJoystickMapping(reader, joystick.type());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Controlify.LOGGER.error("Failed to load joystick mapping for controller: '" + type.identifier() + "'", e);
|
Controlify.LOGGER.error("Failed to load joystick mapping for controller: '" + joystick.type().identifier() + "'", e);
|
||||||
return UnmappedJoystickMapping.INSTANCE;
|
return new UnmappedJoystickMapping(joystick.joystickId());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private record AxisMapping(List<Integer> ids, String identifier, Vec2 inpRange, Vec2 outRange, float restingValue, boolean requiresDeadzone, String typeId, List<List<String>> axisNames) implements Axis {
|
private record AxisMapping(int id, String identifier, Vec2 inpRange, Vec2 outRange, float restingValue, boolean requiresDeadzone, String typeId, String[] axisNames) implements Axis {
|
||||||
@Override
|
@Override
|
||||||
public float modifyAxis(float value) {
|
public float getAxis(JoystickData data) {
|
||||||
if (inpRange() == null || outRange() == null)
|
float rawAxis = data.axes()[id];
|
||||||
return value;
|
|
||||||
|
|
||||||
return (value + (outRange().x - inpRange().x)) / (inpRange().y - inpRange().x) * (outRange().y - outRange().x);
|
if (inpRange() == null || outRange() == null)
|
||||||
|
return rawAxis;
|
||||||
|
|
||||||
|
return (rawAxis + (outRange().x - inpRange().x)) / (inpRange().y - inpRange().x) * (outRange().y - outRange().x);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -132,23 +291,40 @@ public class RPJoystickMapping implements JoystickMapping {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getDirectionIdentifier(int axis, JoystickAxisBind.AxisDirection direction) {
|
public String getDirectionIdentifier(int axis, JoystickAxisBind.AxisDirection direction) {
|
||||||
return this.axisNames().get(ids.indexOf(axis)).get(direction.ordinal());
|
return this.axisNames()[direction.ordinal()];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private record ButtonMapping(String identifier, String typeId) implements Button {
|
private record ButtonMapping(int id, String identifier, String typeId) implements Button {
|
||||||
|
@Override
|
||||||
|
public boolean isPressed(JoystickData data) {
|
||||||
|
return data.buttons()[id];
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Component name() {
|
public Component name() {
|
||||||
return Component.translatable("controlify.joystick_mapping." + typeId() + ".button." + identifier());
|
return Component.translatable("controlify.joystick_mapping." + typeId() + ".button." + identifier());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private record HatMapping(String identifier, String typeId) implements Hat {
|
private record HatMapping(int hatId, String identifier, String typeId, @Nullable EmulatedAxis emulatedAxis) implements Hat {
|
||||||
|
@Override
|
||||||
|
public JoystickState.HatState getHatState(JoystickData data) {
|
||||||
|
if (emulatedAxis() != null) {
|
||||||
|
var axis = emulatedAxis();
|
||||||
|
var axisValue = data.axes()[axis.axisId()];
|
||||||
|
return emulatedAxis().states().get(axisValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
return data.hats()[hatId()];
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Component name() {
|
public Component name() {
|
||||||
return Component.translatable("controlify.joystick_mapping." + typeId() + ".hat." + identifier());
|
return Component.translatable("controlify.joystick_mapping." + typeId() + ".hat." + identifier());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private record EmulatedAxis(int axisId, Map<Float, JoystickState.HatState> states) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,27 +1,65 @@
|
|||||||
package dev.isxander.controlify.controller.joystick.mapping;
|
package dev.isxander.controlify.controller.joystick.mapping;
|
||||||
|
|
||||||
import dev.isxander.controlify.bindings.JoystickAxisBind;
|
import dev.isxander.controlify.bindings.JoystickAxisBind;
|
||||||
|
import dev.isxander.controlify.controller.joystick.JoystickController;
|
||||||
|
import dev.isxander.controlify.controller.joystick.JoystickState;
|
||||||
import net.minecraft.network.chat.Component;
|
import net.minecraft.network.chat.Component;
|
||||||
|
import org.lwjgl.glfw.GLFW;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
public class UnmappedJoystickMapping implements JoystickMapping {
|
public class UnmappedJoystickMapping implements JoystickMapping {
|
||||||
public static final UnmappedJoystickMapping INSTANCE = new UnmappedJoystickMapping();
|
public static final UnmappedJoystickMapping EMPTY = new UnmappedJoystickMapping(0, 0, 0);
|
||||||
|
|
||||||
@Override
|
private final UnmappedAxis[] axes;
|
||||||
public Axis axis(int axis) {
|
private final UnmappedButton[] buttons;
|
||||||
return new UnmappedAxis(axis);
|
private final UnmappedHat[] hats;
|
||||||
|
|
||||||
|
private UnmappedJoystickMapping(int axisCount, int buttonCount, int hatCount) {
|
||||||
|
this.axes = new UnmappedAxis[axisCount];
|
||||||
|
for (int i = 0; i < axisCount; i++) {
|
||||||
|
this.axes[i] = new UnmappedAxis(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.buttons = new UnmappedButton[axisCount];
|
||||||
|
for (int i = 0; i < buttonCount; i++) {
|
||||||
|
this.buttons[i] = new UnmappedButton(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.hats = new UnmappedHat[hatCount];
|
||||||
|
for (int i = 0; i < hatCount; i++) {
|
||||||
|
this.hats[i] = new UnmappedHat(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public UnmappedJoystickMapping(int joystickId) {
|
||||||
|
this(
|
||||||
|
GLFW.glfwGetJoystickAxes(joystickId).limit(),
|
||||||
|
GLFW.glfwGetJoystickButtons(joystickId).limit(),
|
||||||
|
GLFW.glfwGetJoystickHats(joystickId).limit()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Button button(int button) {
|
public Axis[] axes() {
|
||||||
return new UnmappedButton(button);
|
return axes;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Hat hat(int hat) {
|
public Button[] buttons() {
|
||||||
return new UnmappedHat(hat);
|
return buttons;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Hat[] hats() {
|
||||||
|
return hats;
|
||||||
}
|
}
|
||||||
|
|
||||||
private record UnmappedAxis(int axis) implements Axis {
|
private record UnmappedAxis(int axis) implements Axis {
|
||||||
|
@Override
|
||||||
|
public float getAxis(JoystickData data) {
|
||||||
|
return data.axes()[axis];
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String identifier() {
|
public String identifier() {
|
||||||
@ -38,11 +76,6 @@ public class UnmappedJoystickMapping implements JoystickMapping {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public float modifyAxis(float value) {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isAxisResting(float value) {
|
public boolean isAxisResting(float value) {
|
||||||
return value == restingValue();
|
return value == restingValue();
|
||||||
@ -60,6 +93,11 @@ public class UnmappedJoystickMapping implements JoystickMapping {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private record UnmappedButton(int button) implements Button {
|
private record UnmappedButton(int button) implements Button {
|
||||||
|
@Override
|
||||||
|
public boolean isPressed(JoystickData data) {
|
||||||
|
return data.buttons()[button];
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String identifier() {
|
public String identifier() {
|
||||||
return "button-" + button;
|
return "button-" + button;
|
||||||
@ -72,6 +110,11 @@ public class UnmappedJoystickMapping implements JoystickMapping {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private record UnmappedHat(int hat) implements Hat {
|
private record UnmappedHat(int hat) implements Hat {
|
||||||
|
@Override
|
||||||
|
public JoystickState.HatState getHatState(JoystickData data) {
|
||||||
|
return data.hats()[hat];
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String identifier() {
|
public String identifier() {
|
||||||
return "hat-" + hat;
|
return "hat-" + hat;
|
||||||
|
@ -1,12 +1,19 @@
|
|||||||
package dev.isxander.controlify.debug;
|
package dev.isxander.controlify.debug;
|
||||||
|
|
||||||
public class DebugProperties {
|
import net.fabricmc.loader.api.FabricLoader;
|
||||||
// Renders debug overlay for vmouse snapping
|
|
||||||
public static final boolean DEBUG_SNAPPING = boolProp("controlify.debug.snapping", false);
|
|
||||||
// Forces all gamepads to be treated as a regular joystick
|
|
||||||
public static final boolean FORCE_JOYSTICK = boolProp("controlify.debug.force_joystick", false);
|
|
||||||
|
|
||||||
private static boolean boolProp(String name, boolean def) {
|
public class DebugProperties {
|
||||||
|
/* Renders debug overlay for vmouse snapping */
|
||||||
|
public static final boolean DEBUG_SNAPPING = boolProp("controlify.debug.snapping", false, false);
|
||||||
|
/* Forces all gamepads to be treated as a regular joystick */
|
||||||
|
public static final boolean FORCE_JOYSTICK = boolProp("controlify.debug.force_joystick", false, false);
|
||||||
|
/* Prints joystick input counts for making joystick mappings */
|
||||||
|
public static final boolean PRINT_JOY_INPUT_COUNT = boolProp("controlify.debug.print_joy_input_count", false, true);
|
||||||
|
/* Print gyro data if supported */
|
||||||
|
public static final boolean PRINT_GYRO = boolProp("controlify.debug.print_gyro", false, false);
|
||||||
|
|
||||||
|
private static boolean boolProp(String name, boolean defProd, boolean defDev) {
|
||||||
|
boolean def = FabricLoader.getInstance().isDevelopmentEnvironment() ? defDev : defProd;
|
||||||
return Boolean.parseBoolean(System.getProperty(name, Boolean.toString(def)));
|
return Boolean.parseBoolean(System.getProperty(name, Boolean.toString(def)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -53,6 +53,8 @@ public class MultiPlayerGameModeMixin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void startRumble(BlockState state) {
|
private void startRumble(BlockState state) {
|
||||||
|
stopRumble();
|
||||||
|
|
||||||
var effect = ContinuousRumbleEffect.builder()
|
var effect = ContinuousRumbleEffect.builder()
|
||||||
.byTick(tick -> new RumbleState(
|
.byTick(tick -> new RumbleState(
|
||||||
0.02f + Easings.easeInQuad(Math.min(1, state.getBlock().defaultDestroyTime() / 20f)) * 0.25f,
|
0.02f + Easings.easeInQuad(Math.min(1, state.getBlock().defaultDestroyTime() / 20f)) * 0.25f,
|
||||||
|
@ -1,9 +1,12 @@
|
|||||||
package dev.isxander.controlify.rumble;
|
package dev.isxander.controlify.rumble;
|
||||||
|
|
||||||
|
import net.minecraft.client.Minecraft;
|
||||||
|
import net.minecraft.client.gui.screens.Screen;
|
||||||
import org.apache.commons.lang3.Validate;
|
import org.apache.commons.lang3.Validate;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.function.BooleanSupplier;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
public final class BasicRumbleEffect implements RumbleEffect {
|
public final class BasicRumbleEffect implements RumbleEffect {
|
||||||
@ -11,6 +14,7 @@ public final class BasicRumbleEffect implements RumbleEffect {
|
|||||||
private int tick = 0;
|
private int tick = 0;
|
||||||
private boolean finished;
|
private boolean finished;
|
||||||
private int priority = 0;
|
private int priority = 0;
|
||||||
|
private BooleanSupplier earlyFinishCondition = () -> false;
|
||||||
|
|
||||||
public BasicRumbleEffect(RumbleState[] keyframes) {
|
public BasicRumbleEffect(RumbleState[] keyframes) {
|
||||||
this.keyframes = keyframes;
|
this.keyframes = keyframes;
|
||||||
@ -19,7 +23,7 @@ public final class BasicRumbleEffect implements RumbleEffect {
|
|||||||
@Override
|
@Override
|
||||||
public void tick() {
|
public void tick() {
|
||||||
tick++;
|
tick++;
|
||||||
if (tick >= keyframes.length)
|
if (tick >= keyframes.length || earlyFinishCondition.getAsBoolean())
|
||||||
finished = true;
|
finished = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,6 +59,12 @@ public final class BasicRumbleEffect implements RumbleEffect {
|
|||||||
return keyframes;
|
return keyframes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public BasicRumbleEffect earlyFinish(BooleanSupplier condition) {
|
||||||
|
var current = earlyFinishCondition;
|
||||||
|
this.earlyFinishCondition = () -> current.getAsBoolean() || condition.getAsBoolean();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object obj) {
|
public boolean equals(Object obj) {
|
||||||
if (obj == this) return true;
|
if (obj == this) return true;
|
||||||
@ -76,6 +86,22 @@ public final class BasicRumbleEffect implements RumbleEffect {
|
|||||||
"priority=" + this.priority() + ']';
|
"priority=" + this.priority() + ']';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public BasicRumbleEffect join(BasicRumbleEffect other) {
|
||||||
|
return BasicRumbleEffect.join(this, other);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BasicRumbleEffect repeat(int count) {
|
||||||
|
Validate.isTrue(count > 0, "count must be greater than 0");
|
||||||
|
|
||||||
|
if (count == 1) return this;
|
||||||
|
|
||||||
|
BasicRumbleEffect effect = this;
|
||||||
|
for (int i = 0; i < count - 1; i++) {
|
||||||
|
effect = BasicRumbleEffect.join(effect, this);
|
||||||
|
}
|
||||||
|
return effect;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a rumble effect where the state is determined by the tick.
|
* Creates a rumble effect where the state is determined by the tick.
|
||||||
*
|
*
|
||||||
@ -133,19 +159,8 @@ public final class BasicRumbleEffect implements RumbleEffect {
|
|||||||
return new BasicRumbleEffect(states);
|
return new BasicRumbleEffect(states);
|
||||||
}
|
}
|
||||||
|
|
||||||
public BasicRumbleEffect join(BasicRumbleEffect other) {
|
public static BooleanSupplier finishOnScreenChange() {
|
||||||
return BasicRumbleEffect.join(this, other);
|
Screen screen = Minecraft.getInstance().screen;
|
||||||
}
|
return () -> screen != Minecraft.getInstance().screen;
|
||||||
|
|
||||||
public BasicRumbleEffect repeat(int count) {
|
|
||||||
Validate.isTrue(count > 0, "count must be greater than 0");
|
|
||||||
|
|
||||||
if (count == 1) return this;
|
|
||||||
|
|
||||||
BasicRumbleEffect effect = this;
|
|
||||||
for (int i = 0; i < count - 1; i++) {
|
|
||||||
effect = BasicRumbleEffect.join(effect, this);
|
|
||||||
}
|
|
||||||
return effect;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,7 +31,7 @@
|
|||||||
"fabricloader": ">=0.14.0",
|
"fabricloader": ">=0.14.0",
|
||||||
"minecraft": "~1.19.4",
|
"minecraft": "~1.19.4",
|
||||||
"java": ">=17",
|
"java": ">=17",
|
||||||
"yet-another-config-lib": ">=2.4.0"
|
"yet-another-config-lib": "^2.4.0"
|
||||||
},
|
},
|
||||||
"breaks": {
|
"breaks": {
|
||||||
"midnightcontrols": "*"
|
"midnightcontrols": "*"
|
||||||
|
Reference in New Issue
Block a user