Choose cursor aim with /aero controls instead of a keybind
/aero controls keyboard|cursor_aim is open to every player, stored per player and synced to the client, which switches on automatically when seated. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YVREx3uo7nz1FEhzveWTUh
This commit is contained in:
10 files changed
+131
-18
No files matched your search
@@ -5,6 +5,7 @@ import com.mojang.brigadier.arguments.BoolArgumentType;
|
||||
import com.mojang.brigadier.arguments.DoubleArgumentType;
|
||||
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
|
||||
import com.mojang.brigadier.tree.LiteralCommandNode;
|
||||
import dev.lumareth.improvedAerodynamics.flight.ControlMode;
|
||||
import net.minecraft.commands.CommandSourceStack;
|
||||
import net.minecraft.commands.Commands;
|
||||
import net.minecraft.network.chat.Component;
|
||||
@@ -18,7 +19,8 @@ import java.util.List;
|
||||
/**
|
||||
* /improvedaerodynamics (or /aero) to read and change the most important server config values in game.
|
||||
* {@code /aero} lists them, {@code /aero <option>} shows one, {@code /aero <option> <value>} sets and saves it.
|
||||
* Needs operator rights, except for the owner of a singleplayer world, who may use them without cheats.
|
||||
* The config options need operator rights, except for the owner of a singleplayer world, who may use them without cheats.
|
||||
* {@code /aero controls keyboard|cursor_aim} is open to every player and picks how they fly.
|
||||
* All of them are read live, so changes apply immediately.
|
||||
*/
|
||||
public final class ConfigCommands {
|
||||
@@ -60,19 +62,45 @@ public final class ConfigCommands {
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onRegisterCommands(RegisterCommandsEvent event) {
|
||||
// The root is open to everybody for /aero controls, the config options check for rights themselves
|
||||
LiteralArgumentBuilder<CommandSourceStack> root = Commands.literal("improvedaerodynamics")
|
||||
.requires(ConfigCommands::mayUse)
|
||||
.executes(context -> list(context.getSource()));
|
||||
.executes(context -> mayUse(context.getSource()) ? list(context.getSource()) : denied(context.getSource()));
|
||||
for (Option<?> option : OPTIONS) {
|
||||
root.then(optionNode(option));
|
||||
root.then(optionNode(option).requires(ConfigCommands::mayUse));
|
||||
}
|
||||
root.then(controlsNode());
|
||||
LiteralCommandNode<CommandSourceStack> node = event.getDispatcher().register(root);
|
||||
event.getDispatcher().register(Commands.literal("aero")
|
||||
.requires(ConfigCommands::mayUse)
|
||||
.executes(context -> list(context.getSource()))
|
||||
.executes(context -> mayUse(context.getSource()) ? list(context.getSource()) : denied(context.getSource()))
|
||||
.redirect(node));
|
||||
}
|
||||
|
||||
private static int denied(CommandSourceStack source) {
|
||||
source.sendFailure(Component.literal("Use /aero controls keyboard or /aero controls cursor_aim"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** /aero controls [keyboard|cursor_aim], for every player (permission level 0). */
|
||||
private static LiteralArgumentBuilder<CommandSourceStack> controlsNode() {
|
||||
return Commands.literal("controls")
|
||||
.requires(source -> source.getEntity() instanceof ServerPlayer)
|
||||
.executes(context -> {
|
||||
ServerPlayer player = context.getSource().getPlayerOrException();
|
||||
boolean cursorAim = ControlMode.isCursorAim(player);
|
||||
context.getSource().sendSuccess(() -> Component.literal("Controls: " + (cursorAim ? "cursor_aim" : "keyboard")), false);
|
||||
return 1;
|
||||
})
|
||||
.then(Commands.literal("keyboard").executes(context -> setControls(context.getSource(), false)))
|
||||
.then(Commands.literal("cursor_aim").executes(context -> setControls(context.getSource(), true)));
|
||||
}
|
||||
|
||||
private static int setControls(CommandSourceStack source, boolean cursorAim) throws com.mojang.brigadier.exceptions.CommandSyntaxException {
|
||||
ServerPlayer player = source.getPlayerOrException();
|
||||
ControlMode.set(player, cursorAim);
|
||||
source.sendSuccess(() -> Component.literal("Controls set to " + (cursorAim ? "cursor_aim" : "keyboard")), false);
|
||||
return 1;
|
||||
}
|
||||
|
||||
private static <T> LiteralArgumentBuilder<CommandSourceStack> optionNode(Option<T> option) {
|
||||
return Commands.literal(option.name())
|
||||
.executes(context -> {
|
||||
|
||||
@@ -23,6 +23,7 @@ public class ImprovedAerodynamics {
|
||||
MinecraftForge.EVENT_BUS.register(LookaheadLoader.class);
|
||||
MinecraftForge.EVENT_BUS.register(PhysicsBridge.class);
|
||||
MinecraftForge.EVENT_BUS.register(ConfigCommands.class);
|
||||
MinecraftForge.EVENT_BUS.register(dev.lumareth.improvedAerodynamics.flight.ControlMode.class);
|
||||
ModLoadingContext.get().registerConfig(ModConfig.Type.SERVER, Config.SPEC);
|
||||
ModLoadingContext.get().registerConfig(ModConfig.Type.CLIENT, PipperConfig.SPEC);
|
||||
ModLoadingContext.get().registerConfig(ModConfig.Type.SERVER, CursorAimConfig.SPEC, "improved_aerodynamics-cursor-aim.toml");
|
||||
|
||||
@@ -10,10 +10,8 @@ import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
|
||||
/** Keys of cursor aim. The free look key is not shift: in a seat, shift gets the player out of it. */
|
||||
/** Keys of cursor aim, which is switched on with /aero controls cursor_aim. The free look key is not shift: in a seat, shift gets the player out of it. */
|
||||
public final class AimKeys {
|
||||
public static final KeyMapping TOGGLE = new KeyMapping("key.improved_aerodynamics.toggle_cursor_aim",
|
||||
KeyConflictContext.IN_GAME, InputConstants.Type.KEYSYM, GLFW.GLFW_KEY_K, "key.categories.improved_aerodynamics");
|
||||
public static final KeyMapping FREE_LOOK = new KeyMapping("key.improved_aerodynamics.cursor_aim_free_look",
|
||||
KeyConflictContext.IN_GAME, InputConstants.Type.KEYSYM, GLFW.GLFW_KEY_LEFT_ALT, "key.categories.improved_aerodynamics");
|
||||
|
||||
@@ -27,7 +25,6 @@ public final class AimKeys {
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onRegisterKeyMappings(RegisterKeyMappingsEvent event) {
|
||||
event.register(TOGGLE);
|
||||
event.register(FREE_LOOK);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,6 +64,8 @@ public final class CursorAimClient {
|
||||
private static final double FREE_LOOK_RETURN_RATE = 10.0;
|
||||
|
||||
private static boolean active;
|
||||
// Chosen with /aero controls, told by the server
|
||||
private static boolean cursorAimMode;
|
||||
// Cursor as components along the craft's forward (gun reticle), right and up axes
|
||||
private static double cf = 1.0;
|
||||
private static double cr;
|
||||
@@ -93,14 +95,14 @@ public final class CursorAimClient {
|
||||
}
|
||||
Minecraft minecraft = Minecraft.getInstance();
|
||||
LocalPlayer player = minecraft.player;
|
||||
while (AimKeys.TOGGLE.consumeClick()) {
|
||||
if (active) {
|
||||
deactivate(minecraft);
|
||||
} else if (player != null) {
|
||||
if (!active) {
|
||||
if (cursorAimMode && player != null && seated(player) && serverHasMod(minecraft)) {
|
||||
activate(minecraft, player);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!active) {
|
||||
if (!cursorAimMode) {
|
||||
deactivate(minecraft);
|
||||
return;
|
||||
}
|
||||
if (player == null || !seated(player)) {
|
||||
@@ -110,6 +112,11 @@ public final class CursorAimClient {
|
||||
send(minecraft, true);
|
||||
}
|
||||
|
||||
/** Called when the server tells the client how its player flies. */
|
||||
public static void setMode(boolean cursorAim) {
|
||||
cursorAimMode = cursorAim;
|
||||
}
|
||||
|
||||
private static boolean seated(LocalPlayer player) {
|
||||
return ModList.get().isLoaded("valkyrien_warium") && AimSeat.seatOf(player) != null;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
package dev.lumareth.improvedAerodynamics.flight;
|
||||
|
||||
import dev.lumareth.improvedAerodynamics.network.ControlModePacket;
|
||||
import dev.lumareth.improvedAerodynamics.network.Network;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraftforge.event.entity.player.PlayerEvent;
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||
import net.minecraftforge.network.PacketDistributor;
|
||||
|
||||
/**
|
||||
* How a player flies: with the keyboard (Warium's own seat controls) or with cursor aim. Chosen per player with
|
||||
* {@code /aero controls}, kept in the player's persistent data and told to their client, which does the rest.
|
||||
*/
|
||||
public final class ControlMode {
|
||||
private static final String KEY = "improved_aerodynamics_cursor_aim";
|
||||
|
||||
private ControlMode() {
|
||||
}
|
||||
|
||||
public static boolean isCursorAim(ServerPlayer player) {
|
||||
CompoundTag persisted = player.getPersistentData().getCompound(Player.PERSISTED_NBT_TAG);
|
||||
return persisted.getBoolean(KEY);
|
||||
}
|
||||
|
||||
public static void set(ServerPlayer player, boolean cursorAim) {
|
||||
CompoundTag data = player.getPersistentData();
|
||||
CompoundTag persisted = data.getCompound(Player.PERSISTED_NBT_TAG);
|
||||
persisted.putBoolean(KEY, cursorAim);
|
||||
data.put(Player.PERSISTED_NBT_TAG, persisted);
|
||||
sync(player);
|
||||
}
|
||||
|
||||
public static void sync(ServerPlayer player) {
|
||||
Network.CHANNEL.send(PacketDistributor.PLAYER.with(() -> player), new ControlModePacket(isCursorAim(player)));
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onLogin(PlayerEvent.PlayerLoggedInEvent event) {
|
||||
if (event.getEntity() instanceof ServerPlayer player) {
|
||||
sync(player);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,7 @@ public final class CursorAimServer {
|
||||
}
|
||||
long nodeKey = basis.nodePos().asLong();
|
||||
double length = Math.sqrt(cf * cf + cr * cr + cu * cu);
|
||||
if (!active || !CursorAimConfig.ENABLED.get() || !(length > 1.0E-6) || !Double.isFinite(length)) {
|
||||
if (!active || !ControlMode.isCursorAim(player) || !CursorAimConfig.ENABLED.get() || !(length > 1.0E-6) || !Double.isFinite(length)) {
|
||||
CursorAimRegistry.clear(nodeKey);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
package dev.lumareth.improvedAerodynamics.network;
|
||||
|
||||
import dev.lumareth.improvedAerodynamics.client.aim.CursorAimClient;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.fml.DistExecutor;
|
||||
import net.minecraftforge.network.NetworkEvent;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/** Tells a client whether its player flies with cursor aim, see {@code /aero controls}. */
|
||||
public final class ControlModePacket {
|
||||
private final boolean cursorAim;
|
||||
|
||||
public ControlModePacket(boolean cursorAim) {
|
||||
this.cursorAim = cursorAim;
|
||||
}
|
||||
|
||||
public void encode(FriendlyByteBuf buffer) {
|
||||
buffer.writeBoolean(cursorAim);
|
||||
}
|
||||
|
||||
public static ControlModePacket decode(FriendlyByteBuf buffer) {
|
||||
return new ControlModePacket(buffer.readBoolean());
|
||||
}
|
||||
|
||||
public void handle(Supplier<NetworkEvent.Context> context) {
|
||||
boolean value = cursorAim;
|
||||
DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> () -> CursorAimClient.setMode(value));
|
||||
context.get().setPacketHandled(true);
|
||||
}
|
||||
}
|
||||
@@ -29,5 +29,10 @@ public final class Network {
|
||||
.decoder(AimPacket::decode)
|
||||
.consumerMainThread(AimPacket::handle)
|
||||
.add();
|
||||
CHANNEL.messageBuilder(ControlModePacket.class, 51, NetworkDirection.PLAY_TO_CLIENT)
|
||||
.encoder(ControlModePacket::encode)
|
||||
.decoder(ControlModePacket::decode)
|
||||
.consumerMainThread(ControlModePacket::handle)
|
||||
.add();
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,6 @@
|
||||
"config.improved_aerodynamics.vortexLift": "Wirbelauftrieb: Flügel mit kleiner Streckung (Deltaflügel) überziehen später und liefern bei großen Anstellwinkeln mehr Auftrieb (verbessertes Flugmodell)",
|
||||
"config.improved_aerodynamics.vortexStallBonus": "Zusätzlicher Überziehwinkel der Flügel mit der kleinsten Streckung, als Anteil des normalen Überziehwinkels",
|
||||
"config.improved_aerodynamics.vortexLiftStrength": "Multiplikator für den zusätzlichen Auftrieb der Vorderkantenwirbel",
|
||||
"key.improved_aerodynamics.toggle_cursor_aim": "Cursor-Steuerung umschalten",
|
||||
"key.improved_aerodynamics.cursor_aim_free_look": "Cursor-Steuerung: Freie Sicht (halten)",
|
||||
"message.improved_aerodynamics.cursor_aim.on": "Cursor-Steuerung an",
|
||||
"message.improved_aerodynamics.cursor_aim.off": "Cursor-Steuerung aus",
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
"config.improved_aerodynamics.vortexLift": "Vortex lift: low aspect ratio (delta) wings stall later and lift more at high angles of attack (improved flight model)",
|
||||
"config.improved_aerodynamics.vortexStallBonus": "Extra stall angle of the lowest aspect ratio wings, as a fraction of the normal stall angle",
|
||||
"config.improved_aerodynamics.vortexLiftStrength": "Multiplier for the extra lift the leading-edge vortices make",
|
||||
"key.improved_aerodynamics.toggle_cursor_aim": "Toggle cursor aim",
|
||||
"key.improved_aerodynamics.cursor_aim_free_look": "Cursor aim: free look (hold)",
|
||||
"message.improved_aerodynamics.cursor_aim.on": "Cursor aim on",
|
||||
"message.improved_aerodynamics.cursor_aim.off": "Cursor aim off",
|
||||
|
||||
Reference in new issue
Block a user