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
+133
-20
No files matched your search
@@ -7,6 +7,7 @@ import com.mojang.brigadier.arguments.StringArgumentType;
|
|||||||
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
|
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
|
||||||
import com.mojang.brigadier.tree.LiteralCommandNode;
|
import com.mojang.brigadier.tree.LiteralCommandNode;
|
||||||
import dev.lumareth.improvedAerodynamics.exhaust.NozzleCommands;
|
import dev.lumareth.improvedAerodynamics.exhaust.NozzleCommands;
|
||||||
|
import dev.lumareth.improvedAerodynamics.flight.ControlMode;
|
||||||
import net.minecraft.commands.CommandSourceStack;
|
import net.minecraft.commands.CommandSourceStack;
|
||||||
import net.minecraft.commands.Commands;
|
import net.minecraft.commands.Commands;
|
||||||
import net.minecraft.commands.SharedSuggestionProvider;
|
import net.minecraft.commands.SharedSuggestionProvider;
|
||||||
@@ -21,7 +22,8 @@ import java.util.List;
|
|||||||
/**
|
/**
|
||||||
* /improvedaerodynamics (or /aero) to read and change the most important server config values in game.
|
* /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.
|
* {@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.
|
* All of them are read live, so changes apply immediately.
|
||||||
*/
|
*/
|
||||||
public final class ConfigCommands {
|
public final class ConfigCommands {
|
||||||
@@ -73,21 +75,47 @@ public final class ConfigCommands {
|
|||||||
|
|
||||||
@SubscribeEvent
|
@SubscribeEvent
|
||||||
public static void onRegisterCommands(RegisterCommandsEvent event) {
|
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")
|
LiteralArgumentBuilder<CommandSourceStack> root = Commands.literal("improvedaerodynamics")
|
||||||
.requires(ConfigCommands::mayUse)
|
.executes(context -> mayUse(context.getSource()) ? list(context.getSource()) : denied(context.getSource()));
|
||||||
.executes(context -> list(context.getSource()));
|
|
||||||
for (Option<?> option : OPTIONS) {
|
for (Option<?> option : OPTIONS) {
|
||||||
root.then(optionNode(option));
|
root.then(optionNode(option).requires(ConfigCommands::mayUse));
|
||||||
}
|
}
|
||||||
root.then(SeedgenCommands.node());
|
root.then(SeedgenCommands.node().requires(ConfigCommands::mayUse));
|
||||||
root.then(NozzleCommands.node());
|
root.then(NozzleCommands.node().requires(ConfigCommands::mayUse));
|
||||||
|
root.then(controlsNode());
|
||||||
LiteralCommandNode<CommandSourceStack> node = event.getDispatcher().register(root);
|
LiteralCommandNode<CommandSourceStack> node = event.getDispatcher().register(root);
|
||||||
event.getDispatcher().register(Commands.literal("aero")
|
event.getDispatcher().register(Commands.literal("aero")
|
||||||
.requires(ConfigCommands::mayUse)
|
.executes(context -> mayUse(context.getSource()) ? list(context.getSource()) : denied(context.getSource()))
|
||||||
.executes(context -> list(context.getSource()))
|
|
||||||
.redirect(node));
|
.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) {
|
private static <T> LiteralArgumentBuilder<CommandSourceStack> optionNode(Option<T> option) {
|
||||||
return Commands.literal(option.name())
|
return Commands.literal(option.name())
|
||||||
.executes(context -> {
|
.executes(context -> {
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ public class ImprovedAerodynamics {
|
|||||||
MinecraftForge.EVENT_BUS.register(PhysicsBridge.class);
|
MinecraftForge.EVENT_BUS.register(PhysicsBridge.class);
|
||||||
MinecraftForge.EVENT_BUS.register(ConfigCommands.class);
|
MinecraftForge.EVENT_BUS.register(ConfigCommands.class);
|
||||||
MinecraftForge.EVENT_BUS.register(ExhaustSync.class);
|
MinecraftForge.EVENT_BUS.register(ExhaustSync.class);
|
||||||
|
MinecraftForge.EVENT_BUS.register(dev.lumareth.improvedAerodynamics.flight.ControlMode.class);
|
||||||
ModLoadingContext.get().registerConfig(ModConfig.Type.SERVER, Config.SPEC);
|
ModLoadingContext.get().registerConfig(ModConfig.Type.SERVER, Config.SPEC);
|
||||||
ModLoadingContext.get().registerConfig(ModConfig.Type.CLIENT, Config.CLIENT_SPEC);
|
ModLoadingContext.get().registerConfig(ModConfig.Type.CLIENT, Config.CLIENT_SPEC);
|
||||||
// Own file name: the default one is taken by Config.CLIENT_SPEC
|
// Own file name: the default one is taken by Config.CLIENT_SPEC
|
||||||
|
|||||||
@@ -10,10 +10,8 @@ import net.minecraftforge.eventbus.api.SubscribeEvent;
|
|||||||
import net.minecraftforge.fml.common.Mod;
|
import net.minecraftforge.fml.common.Mod;
|
||||||
import org.lwjgl.glfw.GLFW;
|
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 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",
|
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");
|
KeyConflictContext.IN_GAME, InputConstants.Type.KEYSYM, GLFW.GLFW_KEY_LEFT_ALT, "key.categories.improved_aerodynamics");
|
||||||
|
|
||||||
@@ -27,7 +25,6 @@ public final class AimKeys {
|
|||||||
|
|
||||||
@SubscribeEvent
|
@SubscribeEvent
|
||||||
public static void onRegisterKeyMappings(RegisterKeyMappingsEvent event) {
|
public static void onRegisterKeyMappings(RegisterKeyMappingsEvent event) {
|
||||||
event.register(TOGGLE);
|
|
||||||
event.register(FREE_LOOK);
|
event.register(FREE_LOOK);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,6 +64,8 @@ public final class CursorAimClient {
|
|||||||
private static final double FREE_LOOK_RETURN_RATE = 10.0;
|
private static final double FREE_LOOK_RETURN_RATE = 10.0;
|
||||||
|
|
||||||
private static boolean active;
|
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
|
// Cursor as components along the craft's forward (gun reticle), right and up axes
|
||||||
private static double cf = 1.0;
|
private static double cf = 1.0;
|
||||||
private static double cr;
|
private static double cr;
|
||||||
@@ -93,14 +95,14 @@ public final class CursorAimClient {
|
|||||||
}
|
}
|
||||||
Minecraft minecraft = Minecraft.getInstance();
|
Minecraft minecraft = Minecraft.getInstance();
|
||||||
LocalPlayer player = minecraft.player;
|
LocalPlayer player = minecraft.player;
|
||||||
while (AimKeys.TOGGLE.consumeClick()) {
|
if (!active) {
|
||||||
if (active) {
|
if (cursorAimMode && player != null && seated(player) && serverHasMod(minecraft)) {
|
||||||
deactivate(minecraft);
|
|
||||||
} else if (player != null) {
|
|
||||||
activate(minecraft, player);
|
activate(minecraft, player);
|
||||||
}
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
if (!active) {
|
if (!cursorAimMode) {
|
||||||
|
deactivate(minecraft);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (player == null || !seated(player)) {
|
if (player == null || !seated(player)) {
|
||||||
@@ -110,6 +112,11 @@ public final class CursorAimClient {
|
|||||||
send(minecraft, true);
|
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) {
|
private static boolean seated(LocalPlayer player) {
|
||||||
return ModList.get().isLoaded("valkyrien_warium") && AimSeat.seatOf(player) != null;
|
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();
|
long nodeKey = basis.nodePos().asLong();
|
||||||
double length = Math.sqrt(cf * cf + cr * cr + cu * cu);
|
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);
|
CursorAimRegistry.clear(nodeKey);
|
||||||
return;
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -39,5 +39,10 @@ public final class Network {
|
|||||||
.decoder(AimPacket::decode)
|
.decoder(AimPacket::decode)
|
||||||
.consumerMainThread(AimPacket::handle)
|
.consumerMainThread(AimPacket::handle)
|
||||||
.add();
|
.add();
|
||||||
|
CHANNEL.messageBuilder(ControlModePacket.class, 51, NetworkDirection.PLAY_TO_CLIENT)
|
||||||
|
.encoder(ControlModePacket::encode)
|
||||||
|
.decoder(ControlModePacket::decode)
|
||||||
|
.consumerMainThread(ControlModePacket::handle)
|
||||||
|
.add();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -23,7 +23,6 @@
|
|||||||
"message.improved_aerodynamics.afterburner.throttle": "Der Nachbrenner braucht mindestens Schub %s",
|
"message.improved_aerodynamics.afterburner.throttle": "Der Nachbrenner braucht mindestens Schub %s",
|
||||||
"message.improved_aerodynamics.afterburner.no_seat": "Setz dich in einen Warium-Steuersitz, um den Nachbrenner zu nutzen",
|
"message.improved_aerodynamics.afterburner.no_seat": "Setz dich in einen Warium-Steuersitz, um den Nachbrenner zu nutzen",
|
||||||
"message.improved_aerodynamics.afterburner.no_node": "Dieser Sitz ist mit keinem Vehicle Control Node verbunden",
|
"message.improved_aerodynamics.afterburner.no_node": "Dieser Sitz ist mit keinem Vehicle Control Node verbunden",
|
||||||
"key.improved_aerodynamics.toggle_cursor_aim": "Cursor-Steuerung umschalten",
|
|
||||||
"key.improved_aerodynamics.cursor_aim_free_look": "Cursor-Steuerung: Freie Sicht (halten)",
|
"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.on": "Cursor-Steuerung an",
|
||||||
"message.improved_aerodynamics.cursor_aim.off": "Cursor-Steuerung aus",
|
"message.improved_aerodynamics.cursor_aim.off": "Cursor-Steuerung aus",
|
||||||
|
|||||||
@@ -23,7 +23,6 @@
|
|||||||
"message.improved_aerodynamics.afterburner.throttle": "The afterburner needs a throttle of at least %s",
|
"message.improved_aerodynamics.afterburner.throttle": "The afterburner needs a throttle of at least %s",
|
||||||
"message.improved_aerodynamics.afterburner.no_seat": "Sit in a Warium control seat to use the afterburner",
|
"message.improved_aerodynamics.afterburner.no_seat": "Sit in a Warium control seat to use the afterburner",
|
||||||
"message.improved_aerodynamics.afterburner.no_node": "This seat is not linked to a vehicle control node",
|
"message.improved_aerodynamics.afterburner.no_node": "This seat is not linked to a vehicle control node",
|
||||||
"key.improved_aerodynamics.toggle_cursor_aim": "Toggle cursor aim",
|
|
||||||
"key.improved_aerodynamics.cursor_aim_free_look": "Cursor aim: free look (hold)",
|
"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.on": "Cursor aim on",
|
||||||
"message.improved_aerodynamics.cursor_aim.off": "Cursor aim off",
|
"message.improved_aerodynamics.cursor_aim.off": "Cursor aim off",
|
||||||
|
|||||||
Reference in new issue
Block a user