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.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() {

View File

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

View File

@ -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"))

View File

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

View File

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

View File

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

View File

@ -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;

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.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.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.group.basic": "Basic",
@ -55,12 +57,10 @@
"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.description": "Controlify virtual mouse is now disabled for this screen.",
"controlify.toast.controller_connected.title": "Controller Connected",
"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.title": "Switch Controller?",
"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.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.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
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.MouseHandlerMixin",
"feature.accessibility.LocalPlayerMixin",
"feature.autoswitch.ToastComponentAccessor",
"feature.bind.KeyMappingAccessor",
"feature.guide.ClientPacketListenerMixin",
"feature.guide.GuiMixin",