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