forked from Clones/Controlify
➕ Keyboard-like movement whitelist + toast on new servers (close #176)
This commit is contained in:
@ -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;
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
Reference in New Issue
Block a user