1
0
forked from Clones/Controlify

manual controller switching & keyboard movement setting

This commit is contained in:
isXander
2023-03-07 20:03:29 +00:00
parent c9294e281d
commit 300817e561
12 changed files with 166 additions and 57 deletions

View File

@ -3,8 +3,6 @@ package dev.isxander.controlify;
import com.mojang.blaze3d.Blaze3D; import com.mojang.blaze3d.Blaze3D;
import com.mojang.logging.LogUtils; import com.mojang.logging.LogUtils;
import dev.isxander.controlify.api.ControlifyApi; 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.Controller;
import dev.isxander.controlify.controller.ControllerState; import dev.isxander.controlify.controller.ControllerState;
import dev.isxander.controlify.gui.screen.ControllerDeadzoneCalibrationScreen; 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.guide.InGameButtonGuide;
import dev.isxander.controlify.ingame.InGameInputHandler; import dev.isxander.controlify.ingame.InGameInputHandler;
import dev.isxander.controlify.mixins.feature.virtualmouse.MouseHandlerAccessor; import dev.isxander.controlify.mixins.feature.virtualmouse.MouseHandlerAccessor;
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.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
@ -32,6 +31,8 @@ public class Controlify implements ControlifyApi {
public static final Logger LOGGER = LogUtils.getLogger(); public static final Logger LOGGER = LogUtils.getLogger();
private static Controlify instance = null; private static Controlify instance = null;
private final Minecraft minecraft = Minecraft.getInstance();
private Controller<?, ?> currentController = Controller.DUMMY; private Controller<?, ?> currentController = Controller.DUMMY;
private InGameInputHandler inGameInputHandler; private InGameInputHandler inGameInputHandler;
public InGameButtonGuide inGameButtonGuide; public InGameButtonGuide inGameButtonGuide;
@ -46,11 +47,13 @@ public class Controlify implements ControlifyApi {
private int consecutiveInputSwitches = 0; private int consecutiveInputSwitches = 0;
private double lastInputSwitchTime = 0; private double lastInputSwitchTime = 0;
private Controller<?, ?> switchableController = null;
private double askSwitchTime = 0;
private ToastUtils.ControlifyToast askSwitchToast = null;
public void initializeControllers() { public void initializeControllers() {
LOGGER.info("Discovering and initializing controllers..."); LOGGER.info("Discovering and initializing controllers...");
Minecraft minecraft = Minecraft.getInstance();
config().load(); config().load();
controllerHIDService = new ControllerHIDService(); controllerHIDService = new ControllerHIDService();
@ -65,9 +68,7 @@ public class Controlify implements ControlifyApi {
if (config().currentControllerUid().equals(controller.uid())) if (config().currentControllerUid().equals(controller.uid()))
setCurrentController(controller); setCurrentController(controller);
if (!config().loadOrCreateControllerData(controller)) { config().loadOrCreateControllerData(controller);
calibrationQueue.add(controller);
}
} }
} }
@ -78,39 +79,9 @@ public class Controlify implements ControlifyApi {
// listen for new controllers // listen for new controllers
GLFW.glfwSetJoystickCallback((jid, event) -> { GLFW.glfwSetJoystickCallback((jid, event) -> {
if (event == GLFW.GLFW_CONNECTED) { if (event == GLFW.GLFW_CONNECTED) {
var firstController = Controller.CONTROLLERS.values().isEmpty(); this.onControllerHotplugged(jid);
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())
));
} else if (event == GLFW.GLFW_DISCONNECTED) { } else if (event == GLFW.GLFW_DISCONNECTED) {
var controller = Controller.CONTROLLERS.remove(jid); this.onControllerDisconnect(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())
));
}
} }
}); });
@ -131,13 +102,6 @@ public class Controlify implements ControlifyApi {
screen = new ControllerDeadzoneCalibrationScreen(calibrationQueue.poll(), screen); screen = new ControllerDeadzoneCalibrationScreen(calibrationQueue.poll(), screen);
} }
minecraft.setScreen(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(); 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()) if (!config().globalSettings().outOfFocusInput && !client.isWindowActive())
state = ControllerState.EMPTY; state = ControllerState.EMPTY;
if (state.hasAnyInput()) if (state.hasAnyInput())
this.setInputMode(InputMode.CONTROLLER); 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."); LOGGER.warn("Controlify detected current controller to be constantly giving input and has been disabled.");
minecraft.getToasts().addToast(SystemToast.multiline( ToastUtils.sendToast(
minecraft,
SystemToast.SystemToastIds.PERIODIC_NOTIFICATION,
Component.translatable("controlify.toast.faulty_input.title"), 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); this.setCurrentController(null);
consecutiveInputSwitches = 0; consecutiveInputSwitches = 0;
} }
@ -184,6 +160,41 @@ public class Controlify implements ControlifyApi {
return config; 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 @Override
public @NotNull Controller<?, ?> currentController() { public @NotNull Controller<?, ?> currentController() {
if (currentController == null) if (currentController == null)
@ -199,6 +210,10 @@ public class Controlify implements ControlifyApi {
if (this.currentController == controller) return; if (this.currentController == controller) return;
this.currentController = controller; this.currentController = controller;
if (switchableController == controller) {
switchableController = null;
}
LOGGER.info("Updated current controller to " + controller.name() + "(" + controller.uid() + ")"); LOGGER.info("Updated current controller to " + controller.name() + "(" + controller.uid() + ")");
if (!config().currentControllerUid().equals(controller.uid())) { if (!config().currentControllerUid().equals(controller.uid())) {
@ -209,6 +224,9 @@ public class Controlify implements ControlifyApi {
if (Minecraft.getInstance().player != null) { if (Minecraft.getInstance().player != null) {
this.inGameButtonGuide = new InGameButtonGuide(controller, Minecraft.getInstance().player); this.inGameButtonGuide = new InGameButtonGuide(controller, Minecraft.getInstance().player);
} }
if (!controller.config().calibrated && controller != Controller.DUMMY)
calibrationQueue.add(controller);
} }
public InGameInputHandler inGameInputHandler() { public InGameInputHandler inGameInputHandler() {

View File

@ -12,5 +12,6 @@ public class GlobalSettings {
AbstractContainerScreen.class AbstractContainerScreen.class
); );
public boolean keyboardMovement = false;
public boolean outOfFocusInput = false; public boolean outOfFocusInput = false;
} }

View File

@ -56,6 +56,12 @@ public class YACLHelper {
.binding(GlobalSettings.DEFAULT.outOfFocusInput, () -> globalSettings.outOfFocusInput, v -> globalSettings.outOfFocusInput = v) .binding(GlobalSettings.DEFAULT.outOfFocusInput, () -> globalSettings.outOfFocusInput, v -> globalSettings.outOfFocusInput = v)
.controller(TickBoxController::new) .controller(TickBoxController::new)
.build()) .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() .option(ButtonOption.createBuilder()
.name(Component.translatable("controlify.gui.open_issue_tracker")) .name(Component.translatable("controlify.gui.open_issue_tracker"))
.action((screen, button) -> Util.getPlatform().openUri("https://github.com/isxander/controlify/issues")) .action((screen, button) -> Util.getPlatform().openUri("https://github.com/isxander/controlify/issues"))

View File

@ -18,6 +18,8 @@ public abstract class ControllerConfig {
public boolean showGuide = true; public boolean showGuide = true;
public boolean calibrated = false;
public abstract void setDeadzone(int axis, float deadzone); public abstract void setDeadzone(int axis, float deadzone);
public abstract float getDeadzone(int axis); public abstract float getDeadzone(int axis);
} }

View File

@ -9,6 +9,7 @@ import java.util.*;
public class ControllerHIDService implements HidServicesListener { public class ControllerHIDService implements HidServicesListener {
private final HidServicesSpecification specification; private final HidServicesSpecification specification;
private HidServices services;
private final Map<String, HIDIdentifier> unconsumedHIDs; private final Map<String, HIDIdentifier> unconsumedHIDs;
private boolean disabled = false; private boolean disabled = false;
@ -21,7 +22,7 @@ public class ControllerHIDService implements HidServicesListener {
public void start() { public void start() {
try { try {
var services = HidManager.getHidServices(specification); services = HidManager.getHidServices(specification);
services.addHidServicesListener(this); services.addHidServicesListener(this);
services.start(); services.start();
@ -32,6 +33,14 @@ public class ControllerHIDService implements HidServicesListener {
} }
public ControllerHIDInfo fetchType() { 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(); var typeMap = ControllerType.getTypeMap();
for (var entry : unconsumedHIDs.entrySet()) { for (var entry : unconsumedHIDs.entrySet()) {
var path = entry.getKey(); var path = entry.getKey();

View File

@ -2,6 +2,7 @@ package dev.isxander.controlify.gui.screen;
import com.mojang.blaze3d.systems.RenderSystem; import com.mojang.blaze3d.systems.RenderSystem;
import com.mojang.blaze3d.vertex.PoseStack; import com.mojang.blaze3d.vertex.PoseStack;
import dev.isxander.controlify.Controlify;
import dev.isxander.controlify.controller.Controller; import dev.isxander.controlify.controller.Controller;
import net.minecraft.ChatFormatting; import net.minecraft.ChatFormatting;
import net.minecraft.client.gui.components.Button; import net.minecraft.client.gui.components.Button;
@ -104,6 +105,9 @@ public class ControllerDeadzoneCalibrationScreen extends Screen {
calibrated = true; calibrated = true;
readyButton.active = true; readyButton.active = true;
readyButton.setMessage(Component.translatable("controlify.calibration.done")); readyButton.setMessage(Component.translatable("controlify.calibration.done"));
controller.config().calibrated = true;
Controlify.instance().config().save();
} }
} }

View File

@ -1,5 +1,6 @@
package dev.isxander.controlify.ingame; package dev.isxander.controlify.ingame;
import dev.isxander.controlify.Controlify;
import dev.isxander.controlify.controller.Controller; import dev.isxander.controlify.controller.Controller;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.client.player.Input; import net.minecraft.client.player.Input;
@ -39,6 +40,11 @@ public class ControllerPlayerMovement extends Input {
this.left = bindings.WALK_LEFT.state() > 0.1; this.left = bindings.WALK_LEFT.state() > 0.1;
this.right = bindings.WALK_RIGHT.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) { if (slowDown) {
this.leftImpulse *= f; this.leftImpulse *= f;
this.forwardImpulse *= f; this.forwardImpulse *= f;

View File

@ -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();
}

View 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);
}
}
}

View File

@ -4,6 +4,8 @@
"controlify.gui.current_controller.tooltip": "In Controlify's infancy, only one controller can be used at a time, this selects which one you want to use.", "controlify.gui.current_controller.tooltip": "In Controlify's infancy, only one controller can be used at a time, this selects which one you want to use.",
"controlify.gui.out_of_focus_input": "Out of Focus Input", "controlify.gui.out_of_focus_input": "Out of Focus Input",
"controlify.gui.out_of_focus_input.tooltip": "If enabled, Controlify will still receive input even if the game window is not focused.", "controlify.gui.out_of_focus_input.tooltip": "If enabled, Controlify will still receive input even if the game window is not focused.",
"controlify.gui.keyboard_movement": "Keyboard-like Movement",
"controlify.gui.keyboard_movement.tooltip": "Makes movement either on or off rather than being smooth with a thumbstick, this may help in cases where server anti-cheats are harsh.",
"controlify.gui.open_issue_tracker": "Open Issue Tracker", "controlify.gui.open_issue_tracker": "Open Issue Tracker",
"controlify.gui.group.basic": "Basic", "controlify.gui.group.basic": "Basic",
@ -55,12 +57,10 @@
"controlify.toast.vmouse_enabled.description": "Controlify virtual mouse is now enabled for this screen.", "controlify.toast.vmouse_enabled.description": "Controlify virtual mouse is now enabled for this screen.",
"controlify.toast.vmouse_disabled.title": "Virtual Mouse Disabled", "controlify.toast.vmouse_disabled.title": "Virtual Mouse Disabled",
"controlify.toast.vmouse_disabled.description": "Controlify virtual mouse is now disabled for this screen.", "controlify.toast.vmouse_disabled.description": "Controlify virtual mouse is now disabled for this screen.",
"controlify.toast.controller_connected.title": "Controller Connected", "controlify.toast.ask_to_switch.title": "Switch Controller?",
"controlify.toast.controller_connected.description": "A controller named '%s' has just been connected. You can switch to your other controller in Controlify settings.", "controlify.toast.ask_to_switch.description": "A new controller named '%s' has just been connected. Press any button to switch to it.",
"controlify.toast.controller_disconnected.title": "Controller Disconnected", "controlify.toast.controller_disconnected.title": "Controller Disconnected",
"controlify.toast.controller_disconnected.description": "'%s' was disconnected.", "controlify.toast.controller_disconnected.description": "'%s' was disconnected.",
"controlify.toast.controller_calibration.title": "New controller detected",
"controlify.toast.controller_calibration.description": "A new controller(s) has been detected, you must calibrate before you use it!",
"controlify.toast.faulty_input.title": "Controller disabled", "controlify.toast.faulty_input.title": "Controller disabled",
"controlify.toast.faulty_input.description": "Your controller has been disabled because Controlify detected it was causing you problems using keyboard and mouse input. This is likely due to setting your deadzone values too low or your joystick is not mapped properly, making the controller think it is always giving input.", "controlify.toast.faulty_input.description": "Your controller has been disabled because Controlify detected it was causing you problems using keyboard and mouse input. This is likely due to setting your deadzone values too low or your joystick is not mapped properly, making the controller think it is always giving input.",

View File

@ -1,3 +1,5 @@
accessWidener v2 named accessWidener v2 named
accessible class net/minecraft/client/gui/screens/LanguageSelectScreen$LanguageSelectionList accessible class net/minecraft/client/gui/screens/LanguageSelectScreen$LanguageSelectionList
accessible class net/minecraft/client/gui/components/toasts/ToastComponent$ToastInstance
accessible method net/minecraft/client/gui/components/toasts/SystemToast <init> (Lnet/minecraft/client/gui/components/toasts/SystemToast$SystemToastIds;Lnet/minecraft/network/chat/Component;Ljava/util/List;I)V

View File

@ -21,6 +21,7 @@
"core.MinecraftMixin", "core.MinecraftMixin",
"core.MouseHandlerMixin", "core.MouseHandlerMixin",
"feature.accessibility.LocalPlayerMixin", "feature.accessibility.LocalPlayerMixin",
"feature.autoswitch.ToastComponentAccessor",
"feature.bind.KeyMappingAccessor", "feature.bind.KeyMappingAccessor",
"feature.guide.ClientPacketListenerMixin", "feature.guide.ClientPacketListenerMixin",
"feature.guide.GuiMixin", "feature.guide.GuiMixin",