forked from Clones/Controlify
manual controller switching & keyboard movement setting
This commit is contained in:
@ -3,8 +3,6 @@ package dev.isxander.controlify;
|
||||
import com.mojang.blaze3d.Blaze3D;
|
||||
import com.mojang.logging.LogUtils;
|
||||
import dev.isxander.controlify.api.ControlifyApi;
|
||||
import dev.isxander.controlify.api.bind.ControlifyBindingsApi;
|
||||
import dev.isxander.controlify.bindings.ControllerBindings;
|
||||
import dev.isxander.controlify.controller.Controller;
|
||||
import dev.isxander.controlify.controller.ControllerState;
|
||||
import dev.isxander.controlify.gui.screen.ControllerDeadzoneCalibrationScreen;
|
||||
@ -15,6 +13,7 @@ import dev.isxander.controlify.api.event.ControlifyEvents;
|
||||
import dev.isxander.controlify.ingame.guide.InGameButtonGuide;
|
||||
import dev.isxander.controlify.ingame.InGameInputHandler;
|
||||
import dev.isxander.controlify.mixins.feature.virtualmouse.MouseHandlerAccessor;
|
||||
import dev.isxander.controlify.utils.ToastUtils;
|
||||
import dev.isxander.controlify.virtualmouse.VirtualMouseHandler;
|
||||
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
|
||||
import net.minecraft.client.Minecraft;
|
||||
@ -32,6 +31,8 @@ public class Controlify implements ControlifyApi {
|
||||
public static final Logger LOGGER = LogUtils.getLogger();
|
||||
private static Controlify instance = null;
|
||||
|
||||
private final Minecraft minecraft = Minecraft.getInstance();
|
||||
|
||||
private Controller<?, ?> currentController = Controller.DUMMY;
|
||||
private InGameInputHandler inGameInputHandler;
|
||||
public InGameButtonGuide inGameButtonGuide;
|
||||
@ -46,11 +47,13 @@ public class Controlify implements ControlifyApi {
|
||||
private int consecutiveInputSwitches = 0;
|
||||
private double lastInputSwitchTime = 0;
|
||||
|
||||
private Controller<?, ?> switchableController = null;
|
||||
private double askSwitchTime = 0;
|
||||
private ToastUtils.ControlifyToast askSwitchToast = null;
|
||||
|
||||
public void initializeControllers() {
|
||||
LOGGER.info("Discovering and initializing controllers...");
|
||||
|
||||
Minecraft minecraft = Minecraft.getInstance();
|
||||
|
||||
config().load();
|
||||
|
||||
controllerHIDService = new ControllerHIDService();
|
||||
@ -65,9 +68,7 @@ public class Controlify implements ControlifyApi {
|
||||
if (config().currentControllerUid().equals(controller.uid()))
|
||||
setCurrentController(controller);
|
||||
|
||||
if (!config().loadOrCreateControllerData(controller)) {
|
||||
calibrationQueue.add(controller);
|
||||
}
|
||||
config().loadOrCreateControllerData(controller);
|
||||
}
|
||||
}
|
||||
|
||||
@ -78,39 +79,9 @@ public class Controlify implements ControlifyApi {
|
||||
// listen for new controllers
|
||||
GLFW.glfwSetJoystickCallback((jid, event) -> {
|
||||
if (event == GLFW.GLFW_CONNECTED) {
|
||||
var firstController = Controller.CONTROLLERS.values().isEmpty();
|
||||
var controller = Controller.createOrGet(jid, controllerHIDService.fetchType());
|
||||
LOGGER.info("Controller connected: " + controller.name());
|
||||
|
||||
if (firstController) {
|
||||
this.setCurrentController(controller);
|
||||
this.setInputMode(InputMode.CONTROLLER);
|
||||
}
|
||||
|
||||
if (!config().loadOrCreateControllerData(currentController)) {
|
||||
calibrationQueue.add(currentController);
|
||||
}
|
||||
|
||||
minecraft.getToasts().addToast(SystemToast.multiline(
|
||||
minecraft,
|
||||
SystemToast.SystemToastIds.PERIODIC_NOTIFICATION,
|
||||
Component.translatable("controlify.toast.controller_connected.title"),
|
||||
Component.translatable("controlify.toast.controller_connected.description", currentController.name())
|
||||
));
|
||||
this.onControllerHotplugged(jid);
|
||||
} else if (event == GLFW.GLFW_DISCONNECTED) {
|
||||
var controller = Controller.CONTROLLERS.remove(jid);
|
||||
if (controller != null) {
|
||||
setCurrentController(Controller.CONTROLLERS.values().stream().findFirst().orElse(null));
|
||||
LOGGER.info("Controller disconnected: " + controller.name());
|
||||
this.setInputMode(currentController == null ? InputMode.KEYBOARD_MOUSE : InputMode.CONTROLLER);
|
||||
|
||||
minecraft.getToasts().addToast(SystemToast.multiline(
|
||||
minecraft,
|
||||
SystemToast.SystemToastIds.PERIODIC_NOTIFICATION,
|
||||
Component.translatable("controlify.toast.controller_disconnected.title"),
|
||||
Component.translatable("controlify.toast.controller_disconnected.description", controller.name())
|
||||
));
|
||||
}
|
||||
this.onControllerDisconnect(jid);
|
||||
}
|
||||
});
|
||||
|
||||
@ -131,13 +102,6 @@ public class Controlify implements ControlifyApi {
|
||||
screen = new ControllerDeadzoneCalibrationScreen(calibrationQueue.poll(), screen);
|
||||
}
|
||||
minecraft.setScreen(screen);
|
||||
|
||||
minecraft.getToasts().addToast(SystemToast.multiline(
|
||||
minecraft,
|
||||
SystemToast.SystemToastIds.PERIODIC_NOTIFICATION,
|
||||
Component.translatable("controlify.toast.controller_calibration.title"),
|
||||
Component.translatable("controlify.toast.controller_calibration.description")
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@ -146,20 +110,32 @@ public class Controlify implements ControlifyApi {
|
||||
}
|
||||
|
||||
ControllerState state = currentController == null ? ControllerState.EMPTY : currentController.state();
|
||||
|
||||
if (switchableController != null && Blaze3D.getTime() - askSwitchTime <= 10000) {
|
||||
if (switchableController.state().hasAnyInput()) {
|
||||
this.setCurrentController(switchableController);
|
||||
if (askSwitchToast != null) {
|
||||
askSwitchToast.remove();
|
||||
askSwitchToast = null;
|
||||
}
|
||||
switchableController = null;
|
||||
state = ControllerState.EMPTY;
|
||||
}
|
||||
}
|
||||
|
||||
if (!config().globalSettings().outOfFocusInput && !client.isWindowActive())
|
||||
state = ControllerState.EMPTY;
|
||||
|
||||
if (state.hasAnyInput())
|
||||
this.setInputMode(InputMode.CONTROLLER);
|
||||
|
||||
if (consecutiveInputSwitches > 20) {
|
||||
if (consecutiveInputSwitches > 500) {
|
||||
LOGGER.warn("Controlify detected current controller to be constantly giving input and has been disabled.");
|
||||
minecraft.getToasts().addToast(SystemToast.multiline(
|
||||
minecraft,
|
||||
SystemToast.SystemToastIds.PERIODIC_NOTIFICATION,
|
||||
ToastUtils.sendToast(
|
||||
Component.translatable("controlify.toast.faulty_input.title"),
|
||||
Component.translatable("controlify.toast.faulty_input.description")
|
||||
));
|
||||
Component.translatable("controlify.toast.faulty_input.description"),
|
||||
true
|
||||
);
|
||||
this.setCurrentController(null);
|
||||
consecutiveInputSwitches = 0;
|
||||
}
|
||||
@ -184,6 +160,41 @@ public class Controlify implements ControlifyApi {
|
||||
return config;
|
||||
}
|
||||
|
||||
private void onControllerHotplugged(int jid) {
|
||||
var controller = Controller.createOrGet(jid, controllerHIDService.fetchType());
|
||||
LOGGER.info("Controller connected: " + controller.name());
|
||||
|
||||
config().loadOrCreateControllerData(currentController);
|
||||
|
||||
this.askToSwitchController(controller);
|
||||
}
|
||||
|
||||
private void onControllerDisconnect(int jid) {
|
||||
var controller = Controller.CONTROLLERS.remove(jid);
|
||||
if (controller != null) {
|
||||
setCurrentController(Controller.CONTROLLERS.values().stream().findFirst().orElse(null));
|
||||
LOGGER.info("Controller disconnected: " + controller.name());
|
||||
this.setInputMode(currentController == null ? InputMode.KEYBOARD_MOUSE : InputMode.CONTROLLER);
|
||||
|
||||
ToastUtils.sendToast(
|
||||
Component.translatable("controlify.toast.controller_disconnected.title"),
|
||||
Component.translatable("controlify.toast.controller_disconnected.description", controller.name()),
|
||||
false
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private void askToSwitchController(Controller<?, ?> controller) {
|
||||
this.switchableController = controller;
|
||||
this.askSwitchTime = Blaze3D.getTime();
|
||||
|
||||
askSwitchToast = ToastUtils.sendToast(
|
||||
Component.translatable("controlify.toast.ask_to_switch.title"),
|
||||
Component.translatable("controlify.toast.ask_to_switch.description", controller.name()),
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Controller<?, ?> currentController() {
|
||||
if (currentController == null)
|
||||
@ -199,6 +210,10 @@ public class Controlify implements ControlifyApi {
|
||||
if (this.currentController == controller) return;
|
||||
this.currentController = controller;
|
||||
|
||||
if (switchableController == controller) {
|
||||
switchableController = null;
|
||||
}
|
||||
|
||||
LOGGER.info("Updated current controller to " + controller.name() + "(" + controller.uid() + ")");
|
||||
|
||||
if (!config().currentControllerUid().equals(controller.uid())) {
|
||||
@ -209,6 +224,9 @@ public class Controlify implements ControlifyApi {
|
||||
if (Minecraft.getInstance().player != null) {
|
||||
this.inGameButtonGuide = new InGameButtonGuide(controller, Minecraft.getInstance().player);
|
||||
}
|
||||
|
||||
if (!controller.config().calibrated && controller != Controller.DUMMY)
|
||||
calibrationQueue.add(controller);
|
||||
}
|
||||
|
||||
public InGameInputHandler inGameInputHandler() {
|
||||
|
@ -12,5 +12,6 @@ public class GlobalSettings {
|
||||
AbstractContainerScreen.class
|
||||
);
|
||||
|
||||
public boolean keyboardMovement = false;
|
||||
public boolean outOfFocusInput = false;
|
||||
}
|
||||
|
@ -56,6 +56,12 @@ public class YACLHelper {
|
||||
.binding(GlobalSettings.DEFAULT.outOfFocusInput, () -> globalSettings.outOfFocusInput, v -> globalSettings.outOfFocusInput = v)
|
||||
.controller(TickBoxController::new)
|
||||
.build())
|
||||
.option(Option.createBuilder(boolean.class)
|
||||
.name(Component.translatable("controlify.gui.keyboard_movement"))
|
||||
.tooltip(Component.translatable("controlify.gui.keyboard_movement.tooltip"))
|
||||
.binding(GlobalSettings.DEFAULT.keyboardMovement, () -> globalSettings.keyboardMovement, v -> globalSettings.keyboardMovement = v)
|
||||
.controller(TickBoxController::new)
|
||||
.build())
|
||||
.option(ButtonOption.createBuilder()
|
||||
.name(Component.translatable("controlify.gui.open_issue_tracker"))
|
||||
.action((screen, button) -> Util.getPlatform().openUri("https://github.com/isxander/controlify/issues"))
|
||||
|
@ -18,6 +18,8 @@ public abstract class ControllerConfig {
|
||||
|
||||
public boolean showGuide = true;
|
||||
|
||||
public boolean calibrated = false;
|
||||
|
||||
public abstract void setDeadzone(int axis, float deadzone);
|
||||
public abstract float getDeadzone(int axis);
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ import java.util.*;
|
||||
|
||||
public class ControllerHIDService implements HidServicesListener {
|
||||
private final HidServicesSpecification specification;
|
||||
private HidServices services;
|
||||
|
||||
private final Map<String, HIDIdentifier> unconsumedHIDs;
|
||||
private boolean disabled = false;
|
||||
@ -21,7 +22,7 @@ public class ControllerHIDService implements HidServicesListener {
|
||||
|
||||
public void start() {
|
||||
try {
|
||||
var services = HidManager.getHidServices(specification);
|
||||
services = HidManager.getHidServices(specification);
|
||||
services.addHidServicesListener(this);
|
||||
|
||||
services.start();
|
||||
@ -32,6 +33,14 @@ public class ControllerHIDService implements HidServicesListener {
|
||||
}
|
||||
|
||||
public ControllerHIDInfo fetchType() {
|
||||
services.scan();
|
||||
try {
|
||||
// wait for scan to complete on separate thread
|
||||
Thread.sleep(200);
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
var typeMap = ControllerType.getTypeMap();
|
||||
for (var entry : unconsumedHIDs.entrySet()) {
|
||||
var path = entry.getKey();
|
||||
|
@ -2,6 +2,7 @@ package dev.isxander.controlify.gui.screen;
|
||||
|
||||
import com.mojang.blaze3d.systems.RenderSystem;
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import dev.isxander.controlify.Controlify;
|
||||
import dev.isxander.controlify.controller.Controller;
|
||||
import net.minecraft.ChatFormatting;
|
||||
import net.minecraft.client.gui.components.Button;
|
||||
@ -104,6 +105,9 @@ public class ControllerDeadzoneCalibrationScreen extends Screen {
|
||||
calibrated = true;
|
||||
readyButton.active = true;
|
||||
readyButton.setMessage(Component.translatable("controlify.calibration.done"));
|
||||
|
||||
controller.config().calibrated = true;
|
||||
Controlify.instance().config().save();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
package dev.isxander.controlify.ingame;
|
||||
|
||||
import dev.isxander.controlify.Controlify;
|
||||
import dev.isxander.controlify.controller.Controller;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.player.Input;
|
||||
@ -39,6 +40,11 @@ public class ControllerPlayerMovement extends Input {
|
||||
this.left = bindings.WALK_LEFT.state() > 0.1;
|
||||
this.right = bindings.WALK_RIGHT.state() > 0.1;
|
||||
|
||||
if (Controlify.instance().config().globalSettings().keyboardMovement) {
|
||||
this.forwardImpulse = Math.signum(this.forwardImpulse);
|
||||
this.leftImpulse = Math.signum(this.leftImpulse);
|
||||
}
|
||||
|
||||
if (slowDown) {
|
||||
this.leftImpulse *= f;
|
||||
this.forwardImpulse *= f;
|
||||
|
@ -0,0 +1,13 @@
|
||||
package dev.isxander.controlify.mixins.feature.autoswitch;
|
||||
|
||||
import net.minecraft.client.gui.components.toasts.ToastComponent;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Mixin(ToastComponent.class)
|
||||
public interface ToastComponentAccessor {
|
||||
@Accessor
|
||||
List<ToastComponent.ToastInstance<?>> getVisible();
|
||||
}
|
47
src/main/java/dev/isxander/controlify/utils/ToastUtils.java
Normal file
47
src/main/java/dev/isxander/controlify/utils/ToastUtils.java
Normal file
@ -0,0 +1,47 @@
|
||||
package dev.isxander.controlify.utils;
|
||||
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.Font;
|
||||
import net.minecraft.client.gui.components.toasts.SystemToast;
|
||||
import net.minecraft.client.gui.components.toasts.ToastComponent;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.util.FormattedCharSequence;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class ToastUtils {
|
||||
public static ControlifyToast sendToast(Component title, Component message, boolean longer) {
|
||||
ControlifyToast toast = ControlifyToast.create(title, message, longer);
|
||||
Minecraft.getInstance().getToasts().addToast(toast);
|
||||
return toast;
|
||||
}
|
||||
|
||||
public static class ControlifyToast extends SystemToast {
|
||||
private boolean removed;
|
||||
|
||||
private ControlifyToast(Component title, List<FormattedCharSequence> description, int maxWidth, boolean longer) {
|
||||
super(longer ? SystemToastIds.UNSECURE_SERVER_WARNING : SystemToastIds.PERIODIC_NOTIFICATION, title, description, maxWidth);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Visibility render(@NotNull PoseStack matrices, @NotNull ToastComponent manager, long startTime) {
|
||||
if (removed)
|
||||
return Visibility.HIDE;
|
||||
|
||||
return super.render(matrices, manager, startTime);
|
||||
}
|
||||
|
||||
public void remove() {
|
||||
this.removed = true;
|
||||
}
|
||||
|
||||
public static ControlifyToast create(Component title, Component message, boolean longer) {
|
||||
Font font = Minecraft.getInstance().font;
|
||||
List<FormattedCharSequence> list = font.split(message, 200);
|
||||
int i = Math.max(200, list.stream().mapToInt(font::width).max().orElse(200));
|
||||
return new ControlifyToast(title, list, i + 30, longer);
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user