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.ingame.InGameInputHandler;
import dev.isxander.controlify.mixins.feature.virtualmouse.MouseHandlerAccessor;
import dev.isxander.controlify.utils.ControllerUtils;
import dev.isxander.controlify.utils.DebugLog;
import dev.isxander.controlify.utils.Log;
import dev.isxander.controlify.utils.ToastUtils;
import dev.isxander.controlify.utils.*;
import dev.isxander.controlify.virtualmouse.VirtualMouseHandler;
import dev.isxander.controlify.wireless.LowBatteryNotifier;
import io.github.libsdl4j.api.rwops.SDL_RWops;
@ -40,6 +37,7 @@ import net.minecraft.CrashReportCategory;
import net.minecraft.ReportedException;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.client.multiplayer.ServerData;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.resources.Resource;
@ -48,7 +46,6 @@ import org.jetbrains.annotations.Nullable;
import org.lwjgl.glfw.GLFW;
import org.lwjgl.system.MemoryUtil;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.util.ArrayDeque;
@ -187,6 +184,9 @@ public class Controlify implements ControlifyApi {
ClientLifecycleEvents.CLIENT_STOPPING.register(minecraft -> {
controllerHIDService().stop();
});
ConnectServerEvent.EVENT.register((minecraft, address, data) -> {
notifyNewServer(data);
});
// set up the hotplugging callback with GLFW
// TODO: investigate if there is any benefit to implementing this with SDL
@ -664,8 +664,12 @@ public class Controlify implements ControlifyApi {
}
lastInputSwitchTime = Blaze3D.getTime();
if (this.currentInputMode.isController())
if (this.currentInputMode.isController()) {
getCurrentController().ifPresent(Controller::clearState);
if (minecraft.getCurrentServer() != null) {
notifyNewServer(minecraft.getCurrentServer());
}
}
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() {
if (instance == null) instance = new Controlify();
return instance;

View File

@ -1,10 +1,16 @@
package dev.isxander.controlify.config;
import com.google.common.collect.Lists;
import com.google.gson.annotations.SerializedName;
import dev.isxander.controlify.reacharound.ReachAroundMode;
import net.minecraft.client.Minecraft;
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.Set;
public class GlobalSettings {
public static final GlobalSettings DEFAULT = new GlobalSettings();
@ -13,7 +19,9 @@ public class GlobalSettings {
AbstractContainerScreen.class
);
public boolean keyboardMovement = false;
@SerializedName("keyboardMovement")
public boolean alwaysKeyboardMovement = false;
public List<String> keyboardMovementWhitelist = new ArrayList<>();
public boolean outOfFocusInput = false;
public boolean loadVibrationNatives = false;
public boolean vibrationOnboarded = false;
@ -23,4 +31,12 @@ public class GlobalSettings {
public boolean notifyLowBattery = true;
public boolean delegateSetup = false;
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.ServerPolicy;
import dev.isxander.yacl3.api.*;
import dev.isxander.yacl3.api.controller.BooleanControllerBuilder;
import dev.isxander.yacl3.api.controller.EnumControllerBuilder;
import dev.isxander.yacl3.api.controller.FloatSliderControllerBuilder;
import dev.isxander.yacl3.api.controller.TickBoxControllerBuilder;
import dev.isxander.yacl3.api.controller.*;
import net.minecraft.ChatFormatting;
import net.minecraft.Util;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.client.multiplayer.ServerData;
import net.minecraft.network.chat.CommonComponents;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import java.util.concurrent.atomic.AtomicReference;
public class GlobalSettingsScreenFactory {
public static Screen createGlobalSettingsScreen(Screen parent) {
var globalSettings = Controlify.instance().config().globalSettings();
AtomicReference<ListOption<String>> whitelist = new AtomicReference<>();
return YetAnotherConfigLib.createBuilder()
.title(Component.translatable("controlify.gui.global_settings.title"))
.category(ConfigCategory.createBuilder()
@ -35,83 +37,115 @@ public class GlobalSettingsScreenFactory {
.controller(opt -> BooleanControllerBuilder.create(opt).yesNoFormatter())
.flag(OptionFlag.GAME_RESTART)
.build())
.option(Option.<ReachAroundMode>createBuilder()
.name(Component.translatable("controlify.gui.reach_around"))
.description(state -> OptionDescription.createBuilder()
.webpImage(screenshot("reach-around-placement.webp"))
.text(Component.translatable("controlify.gui.reach_around.tooltip"))
.text(Component.translatable("controlify.gui.reach_around.tooltip.parity").withStyle(ChatFormatting.GRAY))
.text(state == ReachAroundMode.EVERYWHERE ? Component.translatable("controlify.gui.reach_around.tooltip.warning").withStyle(ChatFormatting.RED) : Component.empty())
.text(ServerPolicies.REACH_AROUND.get() != ServerPolicy.DISALLOWED ? Component.translatable("controlify.gui.server_controlled").withStyle(ChatFormatting.GOLD) : Component.empty())
.build())
.binding(GlobalSettings.DEFAULT.reachAround, () -> globalSettings.reachAround, v -> globalSettings.reachAround = v)
.controller(opt -> EnumControllerBuilder.create(opt)
.enumClass(ReachAroundMode.class)
.valueFormatter(mode -> switch (ServerPolicies.REACH_AROUND.get()) {
case UNSET, ALLOWED -> mode.getDisplayName();
case DISALLOWED -> CommonComponents.OPTION_OFF;
}))
.available(ServerPolicies.REACH_AROUND.get().isAllowed())
.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()
.name(Component.translatable("controlify.gui.allow_server_rumble"))
.description(OptionDescription.createBuilder()
.text(Component.translatable("controlify.gui.allow_server_rumble.tooltip"))
.build())
.binding(GlobalSettings.DEFAULT.allowServerRumble, () -> globalSettings.allowServerRumble, v -> globalSettings.allowServerRumble = v)
.controller(TickBoxControllerBuilder::create)
.listener((opt, val) -> {
if (!val) ControlifyApi.get().getCurrentController().ifPresent(c -> c.rumbleManager().clearEffects());
})
.build())
.option(Option.<Boolean>createBuilder()
.name(Component.translatable("controlify.gui.notify_low_battery"))
.description(OptionDescription.createBuilder()
.text(Component.translatable("controlify.gui.notify_low_battery.tooltip"))
.build())
.binding(GlobalSettings.DEFAULT.notifyLowBattery, () -> globalSettings.notifyLowBattery, v -> globalSettings.notifyLowBattery = v)
.controller(TickBoxControllerBuilder::create)
.build())
.option(Option.<Boolean>createBuilder()
.name(Component.translatable("controlify.gui.out_of_focus_input"))
.description(OptionDescription.createBuilder()
.text(Component.translatable("controlify.gui.out_of_focus_input.tooltip"))
.build())
.binding(GlobalSettings.DEFAULT.outOfFocusInput, () -> globalSettings.outOfFocusInput, v -> globalSettings.outOfFocusInput = v)
.controller(TickBoxControllerBuilder::create)
.build())
.option(Option.<Boolean>createBuilder()
.name(Component.translatable("controlify.gui.keyboard_movement"))
.description(OptionDescription.createBuilder()
.text(Component.translatable("controlify.gui.keyboard_movement.tooltip"))
.build())
.binding(GlobalSettings.DEFAULT.keyboardMovement, () -> globalSettings.keyboardMovement, v -> globalSettings.keyboardMovement = v)
.controller(TickBoxControllerBuilder::create)
.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())
.group(OptionGroup.createBuilder()
.name(Component.translatable("controlify.gui.server_options"))
.option(Option.<ReachAroundMode>createBuilder()
.name(Component.translatable("controlify.gui.reach_around"))
.description(state -> OptionDescription.createBuilder()
.webpImage(screenshot("reach-around-placement.webp"))
.text(Component.translatable("controlify.gui.reach_around.tooltip"))
.text(Component.translatable("controlify.gui.reach_around.tooltip.parity").withStyle(ChatFormatting.GRAY))
.text(state == ReachAroundMode.EVERYWHERE ? Component.translatable("controlify.gui.reach_around.tooltip.warning").withStyle(ChatFormatting.RED) : Component.empty())
.text(ServerPolicies.REACH_AROUND.get() != ServerPolicy.DISALLOWED ? Component.translatable("controlify.gui.server_controlled").withStyle(ChatFormatting.GOLD) : Component.empty())
.build())
.binding(GlobalSettings.DEFAULT.reachAround, () -> globalSettings.reachAround, v -> globalSettings.reachAround = v)
.controller(opt -> EnumControllerBuilder.create(opt)
.enumClass(ReachAroundMode.class)
.formatValue(mode -> switch (ServerPolicies.REACH_AROUND.get()) {
case UNSET, ALLOWED -> mode.getDisplayName();
case DISALLOWED -> CommonComponents.OPTION_OFF;
}))
.available(ServerPolicies.REACH_AROUND.get().isAllowed())
.build())
.option(Option.<Boolean>createBuilder()
.name(Component.translatable("controlify.gui.allow_server_rumble"))
.description(OptionDescription.createBuilder()
.text(Component.translatable("controlify.gui.allow_server_rumble.tooltip"))
.build())
.binding(GlobalSettings.DEFAULT.allowServerRumble, () -> globalSettings.allowServerRumble, v -> globalSettings.allowServerRumble = v)
.controller(TickBoxControllerBuilder::create)
.listener((opt, val) -> {
if (!val) ControlifyApi.get().getCurrentController().ifPresent(c -> c.rumbleManager().clearEffects());
})
.build())
.option(Option.<Boolean>createBuilder()
.name(Component.translatable("controlify.gui.keyboard_movement"))
.description(OptionDescription.createBuilder()
.text(Component.translatable("controlify.gui.keyboard_movement.tooltip"))
.build())
.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)
.build())
.option(Option.<Boolean>createBuilder()
.name(Component.translatable("controlify.gui.out_of_focus_input"))
.description(OptionDescription.createBuilder()
.text(Component.translatable("controlify.gui.out_of_focus_input.tooltip"))
.build())
.binding(GlobalSettings.DEFAULT.outOfFocusInput, () -> globalSettings.outOfFocusInput, v -> globalSettings.outOfFocusInput = v)
.controller(TickBoxControllerBuilder::create)
.build())
.option(Option.<Boolean>createBuilder()
.name(Component.translatable("controlify.gui.notify_low_battery"))
.description(OptionDescription.createBuilder()
.text(Component.translatable("controlify.gui.notify_low_battery.tooltip"))
.build())
.binding(GlobalSettings.DEFAULT.notifyLowBattery, () -> globalSettings.notifyLowBattery, v -> globalSettings.notifyLowBattery = v)
.controller(TickBoxControllerBuilder::create)
.build())
.build())
.build())
.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.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;
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);
}