1
0
forked from Clones/Controlify

Keyboard-like movement whitelist + toast on new servers (close #176)

This commit is contained in:
Xander
2023-10-25 17:13:33 +01:00
parent 29eec1e411
commit 46cb4b963c
8 changed files with 206 additions and 86 deletions

View File

@ -23,10 +23,7 @@ import dev.isxander.controlify.api.event.ControlifyEvents;
import dev.isxander.controlify.gui.guide.InGameButtonGuide; import dev.isxander.controlify.gui.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.ControllerUtils; import dev.isxander.controlify.utils.*;
import dev.isxander.controlify.utils.DebugLog;
import dev.isxander.controlify.utils.Log;
import dev.isxander.controlify.utils.ToastUtils;
import dev.isxander.controlify.virtualmouse.VirtualMouseHandler; import dev.isxander.controlify.virtualmouse.VirtualMouseHandler;
import dev.isxander.controlify.wireless.LowBatteryNotifier; import dev.isxander.controlify.wireless.LowBatteryNotifier;
import io.github.libsdl4j.api.rwops.SDL_RWops; import io.github.libsdl4j.api.rwops.SDL_RWops;
@ -40,6 +37,7 @@ import net.minecraft.CrashReportCategory;
import net.minecraft.ReportedException; 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.client.multiplayer.ServerData;
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.server.packs.resources.Resource; import net.minecraft.server.packs.resources.Resource;
@ -48,7 +46,6 @@ import org.jetbrains.annotations.Nullable;
import org.lwjgl.glfw.GLFW; import org.lwjgl.glfw.GLFW;
import org.lwjgl.system.MemoryUtil; import org.lwjgl.system.MemoryUtil;
import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.ArrayDeque; import java.util.ArrayDeque;
@ -187,6 +184,9 @@ public class Controlify implements ControlifyApi {
ClientLifecycleEvents.CLIENT_STOPPING.register(minecraft -> { ClientLifecycleEvents.CLIENT_STOPPING.register(minecraft -> {
controllerHIDService().stop(); controllerHIDService().stop();
}); });
ConnectServerEvent.EVENT.register((minecraft, address, data) -> {
notifyNewServer(data);
});
// set up the hotplugging callback with GLFW // set up the hotplugging callback with GLFW
// TODO: investigate if there is any benefit to implementing this with SDL // TODO: investigate if there is any benefit to implementing this with SDL
@ -664,8 +664,12 @@ public class Controlify implements ControlifyApi {
} }
lastInputSwitchTime = Blaze3D.getTime(); lastInputSwitchTime = Blaze3D.getTime();
if (this.currentInputMode.isController()) if (this.currentInputMode.isController()) {
getCurrentController().ifPresent(Controller::clearState); getCurrentController().ifPresent(Controller::clearState);
if (minecraft.getCurrentServer() != null) {
notifyNewServer(minecraft.getCurrentServer());
}
}
ControllerPlayerMovement.updatePlayerInput(minecraft.player); ControllerPlayerMovement.updatePlayerInput(minecraft.player);
@ -728,6 +732,20 @@ public class Controlify implements ControlifyApi {
} }
} }
private void notifyNewServer(ServerData data) {
if (!currentInputMode().isController())
return;
if (config().globalSettings().seenServers.add(data.ip)) {
ToastUtils.sendToast(
Component.translatable("controlify.toast.new_server.title"),
Component.translatable("controlify.toast.new_server.description", data.name),
true
);
config().save();
}
}
public static Controlify instance() { public static Controlify instance() {
if (instance == null) instance = new Controlify(); if (instance == null) instance = new Controlify();
return instance; return instance;

View File

@ -1,10 +1,16 @@
package dev.isxander.controlify.config; package dev.isxander.controlify.config;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.gson.annotations.SerializedName;
import dev.isxander.controlify.reacharound.ReachAroundMode; import dev.isxander.controlify.reacharound.ReachAroundMode;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
import net.minecraft.client.multiplayer.ServerData;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set;
public class GlobalSettings { public class GlobalSettings {
public static final GlobalSettings DEFAULT = new GlobalSettings(); public static final GlobalSettings DEFAULT = new GlobalSettings();
@ -13,7 +19,9 @@ public class GlobalSettings {
AbstractContainerScreen.class AbstractContainerScreen.class
); );
public boolean keyboardMovement = false; @SerializedName("keyboardMovement")
public boolean alwaysKeyboardMovement = false;
public List<String> keyboardMovementWhitelist = new ArrayList<>();
public boolean outOfFocusInput = false; public boolean outOfFocusInput = false;
public boolean loadVibrationNatives = false; public boolean loadVibrationNatives = false;
public boolean vibrationOnboarded = false; public boolean vibrationOnboarded = false;
@ -23,4 +31,12 @@ public class GlobalSettings {
public boolean notifyLowBattery = true; public boolean notifyLowBattery = true;
public boolean delegateSetup = false; public boolean delegateSetup = false;
public float ingameButtonGuideScale = 1f; public float ingameButtonGuideScale = 1f;
public Set<String> seenServers = new HashSet<>();
public boolean shouldUseKeyboardMovement() {
ServerData server = Minecraft.getInstance().getCurrentServer();
return alwaysKeyboardMovement
|| (server != null && keyboardMovementWhitelist.stream().anyMatch(server.ip::equalsIgnoreCase));
}
} }

View File

@ -7,20 +7,22 @@ import dev.isxander.controlify.reacharound.ReachAroundMode;
import dev.isxander.controlify.server.ServerPolicies; import dev.isxander.controlify.server.ServerPolicies;
import dev.isxander.controlify.server.ServerPolicy; import dev.isxander.controlify.server.ServerPolicy;
import dev.isxander.yacl3.api.*; import dev.isxander.yacl3.api.*;
import dev.isxander.yacl3.api.controller.BooleanControllerBuilder; import dev.isxander.yacl3.api.controller.*;
import dev.isxander.yacl3.api.controller.EnumControllerBuilder;
import dev.isxander.yacl3.api.controller.FloatSliderControllerBuilder;
import dev.isxander.yacl3.api.controller.TickBoxControllerBuilder;
import net.minecraft.ChatFormatting; import net.minecraft.ChatFormatting;
import net.minecraft.Util; import net.minecraft.Util;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.screens.Screen; import net.minecraft.client.gui.screens.Screen;
import net.minecraft.client.multiplayer.ServerData;
import net.minecraft.network.chat.CommonComponents; import net.minecraft.network.chat.CommonComponents;
import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
import java.util.concurrent.atomic.AtomicReference;
public class GlobalSettingsScreenFactory { public class GlobalSettingsScreenFactory {
public static Screen createGlobalSettingsScreen(Screen parent) { public static Screen createGlobalSettingsScreen(Screen parent) {
var globalSettings = Controlify.instance().config().globalSettings(); var globalSettings = Controlify.instance().config().globalSettings();
AtomicReference<ListOption<String>> whitelist = new AtomicReference<>();
return YetAnotherConfigLib.createBuilder() return YetAnotherConfigLib.createBuilder()
.title(Component.translatable("controlify.gui.global_settings.title")) .title(Component.translatable("controlify.gui.global_settings.title"))
.category(ConfigCategory.createBuilder() .category(ConfigCategory.createBuilder()
@ -35,6 +37,12 @@ public class GlobalSettingsScreenFactory {
.controller(opt -> BooleanControllerBuilder.create(opt).yesNoFormatter()) .controller(opt -> BooleanControllerBuilder.create(opt).yesNoFormatter())
.flag(OptionFlag.GAME_RESTART) .flag(OptionFlag.GAME_RESTART)
.build()) .build())
.option(ButtonOption.createBuilder()
.name(Component.translatable("controlify.gui.open_issue_tracker"))
.action((screen, button) -> Util.getPlatform().openUri("https://github.com/isxander/controlify/issues"))
.build())
.group(OptionGroup.createBuilder()
.name(Component.translatable("controlify.gui.server_options"))
.option(Option.<ReachAroundMode>createBuilder() .option(Option.<ReachAroundMode>createBuilder()
.name(Component.translatable("controlify.gui.reach_around")) .name(Component.translatable("controlify.gui.reach_around"))
.description(state -> OptionDescription.createBuilder() .description(state -> OptionDescription.createBuilder()
@ -47,20 +55,12 @@ public class GlobalSettingsScreenFactory {
.binding(GlobalSettings.DEFAULT.reachAround, () -> globalSettings.reachAround, v -> globalSettings.reachAround = v) .binding(GlobalSettings.DEFAULT.reachAround, () -> globalSettings.reachAround, v -> globalSettings.reachAround = v)
.controller(opt -> EnumControllerBuilder.create(opt) .controller(opt -> EnumControllerBuilder.create(opt)
.enumClass(ReachAroundMode.class) .enumClass(ReachAroundMode.class)
.valueFormatter(mode -> switch (ServerPolicies.REACH_AROUND.get()) { .formatValue(mode -> switch (ServerPolicies.REACH_AROUND.get()) {
case UNSET, ALLOWED -> mode.getDisplayName(); case UNSET, ALLOWED -> mode.getDisplayName();
case DISALLOWED -> CommonComponents.OPTION_OFF; case DISALLOWED -> CommonComponents.OPTION_OFF;
})) }))
.available(ServerPolicies.REACH_AROUND.get().isAllowed()) .available(ServerPolicies.REACH_AROUND.get().isAllowed())
.build()) .build())
.option(Option.<Boolean>createBuilder()
.name(Component.translatable("controlify.gui.ui_sounds"))
.description(OptionDescription.createBuilder()
.text(Component.translatable("controlify.gui.ui_sounds.tooltip"))
.build())
.binding(GlobalSettings.DEFAULT.uiSounds, () -> globalSettings.uiSounds, v -> globalSettings.uiSounds = v)
.controller(TickBoxControllerBuilder::create)
.build())
.option(Option.<Boolean>createBuilder() .option(Option.<Boolean>createBuilder()
.name(Component.translatable("controlify.gui.allow_server_rumble")) .name(Component.translatable("controlify.gui.allow_server_rumble"))
.description(OptionDescription.createBuilder() .description(OptionDescription.createBuilder()
@ -73,11 +73,60 @@ public class GlobalSettingsScreenFactory {
}) })
.build()) .build())
.option(Option.<Boolean>createBuilder() .option(Option.<Boolean>createBuilder()
.name(Component.translatable("controlify.gui.notify_low_battery")) .name(Component.translatable("controlify.gui.keyboard_movement"))
.description(OptionDescription.createBuilder() .description(OptionDescription.createBuilder()
.text(Component.translatable("controlify.gui.notify_low_battery.tooltip")) .text(Component.translatable("controlify.gui.keyboard_movement.tooltip"))
.build()) .build())
.binding(GlobalSettings.DEFAULT.notifyLowBattery, () -> globalSettings.notifyLowBattery, v -> globalSettings.notifyLowBattery = v) .binding(GlobalSettings.DEFAULT.alwaysKeyboardMovement, () -> globalSettings.alwaysKeyboardMovement, v -> globalSettings.alwaysKeyboardMovement = v)
.controller(TickBoxControllerBuilder::create)
.build())
.option(ButtonOption.createBuilder()
.name(Component.translatable("controlify.gui.add_server_to_keyboard_move_whitelist"))
.description(OptionDescription.createBuilder()
.text(Component.translatable("controlify.gui.add_server_to_keyboard_move_whitelist.tooltip"))
.build())
.action((screen, button) -> {
ServerData server = Minecraft.getInstance().getCurrentServer();
if (server != null) {
whitelist.get().insertNewEntry().requestSet(server.ip);
}
})
.available(Minecraft.getInstance().getCurrentServer() != null)
.build())
.build())
.group(Util.make(() -> {
var list = ListOption.<String>createBuilder()
.name(Component.translatable("controlify.gui.keyboard_movement_whitelist"))
.description(OptionDescription.createBuilder()
.text(Component.translatable("controlify.gui.keyboard_movement_whitelist.tooltip"))
.build())
.binding(GlobalSettings.DEFAULT.keyboardMovementWhitelist, () -> globalSettings.keyboardMovementWhitelist, v -> globalSettings.keyboardMovementWhitelist = v)
.controller(StringControllerBuilder::create)
.initial("Server IP here")
.build();
whitelist.set(list);
return list;
}))
.group(OptionGroup.createBuilder()
.name(Component.translatable("controlify.gui.miscellaneous"))
.option(Option.<Float>createBuilder()
.name(Component.translatable("controlify.gui.ingame_button_guide_scale"))
.description(val -> OptionDescription.createBuilder()
.text(Component.translatable("controlify.gui.ingame_button_guide_scale.tooltip"))
.text(val != 1f ? Component.translatable("controlify.gui.ingame_button_guide_scale.tooltip.warning").withStyle(ChatFormatting.RED) : Component.empty())
.build())
.binding(GlobalSettings.DEFAULT.ingameButtonGuideScale, () -> globalSettings.ingameButtonGuideScale, v -> globalSettings.ingameButtonGuideScale = v)
.controller(opt -> FloatSliderControllerBuilder.create(opt)
.range(0.5f, 1.5f)
.step(0.05f)
.formatValue(v -> Component.literal(String.format("%.0f%%", v*100))))
.build())
.option(Option.<Boolean>createBuilder()
.name(Component.translatable("controlify.gui.ui_sounds"))
.description(OptionDescription.createBuilder()
.text(Component.translatable("controlify.gui.ui_sounds.tooltip"))
.build())
.binding(GlobalSettings.DEFAULT.uiSounds, () -> globalSettings.uiSounds, v -> globalSettings.uiSounds = v)
.controller(TickBoxControllerBuilder::create) .controller(TickBoxControllerBuilder::create)
.build()) .build())
.option(Option.<Boolean>createBuilder() .option(Option.<Boolean>createBuilder()
@ -89,28 +138,13 @@ public class GlobalSettingsScreenFactory {
.controller(TickBoxControllerBuilder::create) .controller(TickBoxControllerBuilder::create)
.build()) .build())
.option(Option.<Boolean>createBuilder() .option(Option.<Boolean>createBuilder()
.name(Component.translatable("controlify.gui.keyboard_movement")) .name(Component.translatable("controlify.gui.notify_low_battery"))
.description(OptionDescription.createBuilder() .description(OptionDescription.createBuilder()
.text(Component.translatable("controlify.gui.keyboard_movement.tooltip")) .text(Component.translatable("controlify.gui.notify_low_battery.tooltip"))
.build()) .build())
.binding(GlobalSettings.DEFAULT.keyboardMovement, () -> globalSettings.keyboardMovement, v -> globalSettings.keyboardMovement = v) .binding(GlobalSettings.DEFAULT.notifyLowBattery, () -> globalSettings.notifyLowBattery, v -> globalSettings.notifyLowBattery = v)
.controller(TickBoxControllerBuilder::create) .controller(TickBoxControllerBuilder::create)
.build()) .build())
.option(Option.<Float>createBuilder()
.name(Component.translatable("controlify.gui.ingame_button_guide_scale"))
.description(val -> OptionDescription.createBuilder()
.text(Component.translatable("controlify.gui.ingame_button_guide_scale.tooltip"))
.text(val != 1f ? Component.translatable("controlify.gui.ingame_button_guide_scale.tooltip.warning").withStyle(ChatFormatting.RED) : Component.empty())
.build())
.binding(GlobalSettings.DEFAULT.ingameButtonGuideScale, () -> globalSettings.ingameButtonGuideScale, v -> globalSettings.ingameButtonGuideScale = v)
.controller(opt -> FloatSliderControllerBuilder.create(opt)
.range(0.5f, 1.5f)
.step(0.05f)
.valueFormatter(v -> Component.literal(String.format("%.0f%%", v*100))))
.build())
.option(ButtonOption.createBuilder()
.name(Component.translatable("controlify.gui.open_issue_tracker"))
.action((screen, button) -> Util.getPlatform().openUri("https://github.com/isxander/controlify/issues"))
.build()) .build())
.build()) .build())
.build().generateScreen(parent); .build().generateScreen(parent);

View File

@ -37,7 +37,7 @@ public class ControllerPlayerMovement extends Input {
this.forwardImpulse = bindings.WALK_FORWARD.state() - bindings.WALK_BACKWARD.state(); this.forwardImpulse = bindings.WALK_FORWARD.state() - bindings.WALK_BACKWARD.state();
this.leftImpulse = bindings.WALK_LEFT.state() - bindings.WALK_RIGHT.state(); this.leftImpulse = bindings.WALK_LEFT.state() - bindings.WALK_RIGHT.state();
if (Controlify.instance().config().globalSettings().keyboardMovement) { if (Controlify.instance().config().globalSettings().shouldUseKeyboardMovement()) {
float threshold = controller.config().buttonActivationThreshold; float threshold = controller.config().buttonActivationThreshold;
this.forwardImpulse = Math.abs(this.forwardImpulse) >= threshold ? Math.copySign(1, this.forwardImpulse) : 0; this.forwardImpulse = Math.abs(this.forwardImpulse) >= threshold ? Math.copySign(1, this.forwardImpulse) : 0;

View File

@ -0,0 +1,20 @@
package dev.isxander.controlify.mixins.feature.util;
import dev.isxander.controlify.utils.ConnectServerEvent;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.screens.ConnectScreen;
import net.minecraft.client.multiplayer.ServerData;
import net.minecraft.client.multiplayer.resolver.ServerAddress;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(ConnectScreen.class)
public class ConnectScreenMixin {
@Inject(method = "connect", at = @At("HEAD"))
private void onConnect(Minecraft client, ServerAddress address, @Nullable ServerData serverInfo, CallbackInfo ci) {
ConnectServerEvent.EVENT.invoker().onConnect(client, address, serverInfo);
}
}

View File

@ -0,0 +1,19 @@
package dev.isxander.controlify.utils;
import net.fabricmc.fabric.api.event.Event;
import net.fabricmc.fabric.api.event.EventFactory;
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.ServerData;
import net.minecraft.client.multiplayer.resolver.ServerAddress;
import org.jetbrains.annotations.Nullable;
@FunctionalInterface
public interface ConnectServerEvent {
Event<ConnectServerEvent> EVENT = EventFactory.createArrayBacked(ConnectServerEvent.class, listeners -> (minecraft, address, info) -> {
for (ConnectServerEvent listener : listeners) {
listener.onConnect(minecraft, address, info);
}
});
void onConnect(Minecraft minecraft, ServerAddress address, @Nullable ServerData info);
}

View File

@ -7,6 +7,8 @@
"controlify.gui.carousel.art_credit": "Controller art by %s.", "controlify.gui.carousel.art_credit": "Controller art by %s.",
"controlify.gui.global_settings.title": "Global Settings", "controlify.gui.global_settings.title": "Global Settings",
"controlify.gui.server_options": "Server Options",
"controlify.gui.miscellaneous": "Miscellaneous",
"controlify.gui.load_vibration_natives": "Load Natives", "controlify.gui.load_vibration_natives": "Load Natives",
"controlify.gui.load_vibration_natives.tooltip": "If enabled, Controlify will download and load native libraries on launch to enable support for enhanced features such as vibration and gyro. The download process only happens once and only downloads for your specific OS. Disabling this will not delete the natives, it just won't load them.", "controlify.gui.load_vibration_natives.tooltip": "If enabled, Controlify will download and load native libraries on launch to enable support for enhanced features such as vibration and gyro. The download process only happens once and only downloads for your specific OS. Disabling this will not delete the natives, it just won't load them.",
"controlify.gui.load_vibration_natives.tooltip.warning": "You must enable vibration support per-controller as well as this setting.", "controlify.gui.load_vibration_natives.tooltip.warning": "You must enable vibration support per-controller as well as this setting.",
@ -28,7 +30,11 @@
"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": "Keyboard-like Movement",
"controlify.gui.keyboard_movement.tooltip": "Emulates keyboard WASD when using analog inputs like a thumbstick. This also applies to boat handling. This may be required on servers with harsh anti-cheats.", "controlify.gui.keyboard_movement.tooltip": "Emulates keyboard WASD when using analog inputs like a thumbstick. This also applies to boat handling. This may be required on servers with harsh anti-cheats.\nEnabling this option bypasses the whitelist below and enables it for all servers.",
"controlify.gui.add_server_to_keyboard_move_whitelist": "Add current server to whitelist",
"controlify.gui.add_server_to_keyboard_move_whitelist.tooltip": "Adds the current server you're on to the keyboard-like movement list.",
"controlify.gui.keyboard_movement_whitelist": "Keyboard-like Movement Whitelist",
"controlify.gui.keyboard_movement_whitelist.tooltip": "A list of servers that will have keyboard-like movement enabled. This is a list of server IPs. Servers listed here will have keyboard-like movement even if the option above is disabled.",
"controlify.gui.ingame_button_guide_scale": "Ingame Button Guide Scale", "controlify.gui.ingame_button_guide_scale": "Ingame Button Guide Scale",
"controlify.gui.ingame_button_guide_scale.tooltip": "How big the ingame button guide is. This is a percentage of the currently selected GUI scale. 100% will match the usual scale.", "controlify.gui.ingame_button_guide_scale.tooltip": "How big the ingame button guide is. This is a percentage of the currently selected GUI scale. 100% will match the usual scale.",
"controlify.gui.ingame_button_guide_scale.tooltip.warning": "This may cause scaling issues that will make it to look bad at anything but 100%.", "controlify.gui.ingame_button_guide_scale.tooltip.warning": "This may cause scaling issues that will make it to look bad at anything but 100%.",
@ -177,6 +183,8 @@
"controlify.toast.reach_around_disallowed.description": "This server has prohibited the use of reach-around block placement that you have enabled.", "controlify.toast.reach_around_disallowed.description": "This server has prohibited the use of reach-around block placement that you have enabled.",
"controlify.toast.unmapped_joystick.title": "Joystick needs configuring!", "controlify.toast.unmapped_joystick.title": "Joystick needs configuring!",
"controlify.toast.unmapped_joystick.description": "%s is a joystick, meaning it has an abstract number of inputs, and controls cannot be automatically set. You must manually configure your controls in the settings. If this is a mistake, you are most likely on Linux!", "controlify.toast.unmapped_joystick.description": "%s is a joystick, meaning it has an abstract number of inputs, and controls cannot be automatically set. You must manually configure your controls in the settings. If this is a mistake, you are most likely on Linux!",
"controlify.toast.new_server.title": "New server detected",
"controlify.toast.new_server.description": "You're currently joining a new server. Make sure the server rules allows the analog movement of your controller. If not, you can add the server to the keyboard-like movement list in Controlify's global settings.",
"controlify.sdl2_onboarding.title": "Controlify Native Library", "controlify.sdl2_onboarding.title": "Controlify Native Library",
"controlify.sdl2_onboarding.message": "Many features in Controlify require an extra library that needs to be downloaded for your system. If you do not download this library, you will lose access to many features such as: controller vibration, gyroscope control, better controller identification. This is a seamless process and will only take a few seconds. If you choose no, you may change your mind later in Controlify settings, but you won't have access to these features in the meantime.", "controlify.sdl2_onboarding.message": "Many features in Controlify require an extra library that needs to be downloaded for your system. If you do not download this library, you will lose access to many features such as: controller vibration, gyroscope control, better controller identification. This is a seamless process and will only take a few seconds. If you choose no, you may change your mind later in Controlify settings, but you won't have access to these features in the meantime.",
@ -242,6 +250,10 @@
"controlify.binding.controlify.vmouse_lclick": "VMouse LClick", "controlify.binding.controlify.vmouse_lclick": "VMouse LClick",
"controlify.binding.controlify.vmouse_rclick": "VMouse RClick", "controlify.binding.controlify.vmouse_rclick": "VMouse RClick",
"controlify.binding.controlify.vmouse_shift_click": "VMouse Shift Click", "controlify.binding.controlify.vmouse_shift_click": "VMouse Shift Click",
"controlify.binding.controlify.vmouse_snap_up": "Snap Mouse Up",
"controlify.binding.controlify.vmouse_snap_down": "Snap Mouse Down",
"controlify.binding.controlify.vmouse_snap_left": "Snap Mouse Left",
"controlify.binding.controlify.vmouse_snap_right": "Snap Mouse Right",
"controlify.binding.controlify.vmouse_scroll_up": "VMouse Scroll Up", "controlify.binding.controlify.vmouse_scroll_up": "VMouse Scroll Up",
"controlify.binding.controlify.vmouse_scroll_down": "VMouse Scroll Down", "controlify.binding.controlify.vmouse_scroll_down": "VMouse Scroll Down",
"controlify.binding.controlify.vmouse_escape": "VMouse Key Escape", "controlify.binding.controlify.vmouse_escape": "VMouse Key Escape",

View File

@ -85,6 +85,7 @@
"feature.screenop.vanilla.TitleScreenMixin", "feature.screenop.vanilla.TitleScreenMixin",
"feature.screenop.vanilla.WorldSelectionListEntryMixin", "feature.screenop.vanilla.WorldSelectionListEntryMixin",
"feature.settingsbutton.ControlsScreenMixin", "feature.settingsbutton.ControlsScreenMixin",
"feature.util.ConnectScreenMixin",
"feature.virtualmouse.GameRendererMixin", "feature.virtualmouse.GameRendererMixin",
"feature.virtualmouse.InputConstantsMixin", "feature.virtualmouse.InputConstantsMixin",
"feature.virtualmouse.KeyboardHandlerAccessor", "feature.virtualmouse.KeyboardHandlerAccessor",