diff --git a/gradle.properties b/gradle.properties index 584895d..851ac81 100644 --- a/gradle.properties +++ b/gradle.properties @@ -12,7 +12,7 @@ mapping_version=2023.09.03-1.20.1 mod_id=improved_aerodynamics mod_name=VS: Improved Aerodynamics mod_license=MIT -mod_version=0.3.0 +mod_version=0.4.0 azurelib_version=3.2.7 mod_group_id=dev.lumareth.improvedaerodynamics mod_authors=Lumareth diff --git a/src/main/java/dev/lumareth/improvedAerodynamics/Config.java b/src/main/java/dev/lumareth/improvedAerodynamics/Config.java index ee2610c..e8ae359 100644 --- a/src/main/java/dev/lumareth/improvedAerodynamics/Config.java +++ b/src/main/java/dev/lumareth/improvedAerodynamics/Config.java @@ -131,6 +131,26 @@ public final class Config { .comment("Auto trim: below this airspeed (blocks per second) the trim holds its current value") .defineInRange("trimMinSpeed", 10.0D, 0.0D, 100.0D); + public static final ForgeConfigSpec.DoubleValue FLAP_TAKEOFF_DEGREES = BUILDER + .comment("Flaps: deflection in degrees of flaps at the takeoff setting. Flaps need the improved flight model") + .defineInRange("flapTakeoffDegrees", 15.0D, 0.0D, 60.0D); + + public static final ForgeConfigSpec.DoubleValue FLAP_LANDING_DEGREES = BUILDER + .comment("Flaps: deflection in degrees of flaps at the landing setting") + .defineInRange("flapLandingDegrees", 35.0D, 0.0D, 60.0D); + + public static final ForgeConfigSpec.DoubleValue FLAP_SLEW_DEGREES = BUILDER + .comment("Flaps: how fast they move, in degrees per second") + .defineInRange("flapSlewDegrees", 10.0D, 1.0D, 90.0D); + + public static final ForgeConfigSpec.DoubleValue FLAP_TAKEOFF_MAX_SPEED = BUILDER + .comment("Flaps: airspeed in blocks per second above which the air pushes takeoff flaps back in, fully retracted at 25% more") + .defineInRange("flapTakeoffMaxSpeed", 45.0D, 1.0D, 1000.0D); + + public static final ForgeConfigSpec.DoubleValue FLAP_LANDING_MAX_SPEED = BUILDER + .comment("Flaps: the same for the landing setting") + .defineInRange("flapLandingMaxSpeed", 35.0D, 1.0D, 1000.0D); + static { BUILDER.pop(); BUILDER.comment("Vectored afterburner nozzle block").push("nozzle"); diff --git a/src/main/java/dev/lumareth/improvedAerodynamics/ConfigCommands.java b/src/main/java/dev/lumareth/improvedAerodynamics/ConfigCommands.java index 5d95674..65af630 100644 --- a/src/main/java/dev/lumareth/improvedAerodynamics/ConfigCommands.java +++ b/src/main/java/dev/lumareth/improvedAerodynamics/ConfigCommands.java @@ -57,6 +57,11 @@ public final class ConfigCommands { new Option<>("exhaust_style", Config.EXHAUST_STYLE, StringArgumentType.word(), String.class, List.of("wariumvs", "improved")), new Option<>("nozzle_yaw_mix", Config.NOZZLE_YAW_MIX, DoubleArgumentType.doubleArg(), Double.class), new Option<>("nozzle_light", Config.NOZZLE_LIGHT, BoolArgumentType.bool(), Boolean.class), + new Option<>("flap_takeoff_degrees", Config.FLAP_TAKEOFF_DEGREES, DoubleArgumentType.doubleArg(), Double.class), + new Option<>("flap_landing_degrees", Config.FLAP_LANDING_DEGREES, DoubleArgumentType.doubleArg(), Double.class), + new Option<>("flap_slew_degrees", Config.FLAP_SLEW_DEGREES, DoubleArgumentType.doubleArg(), Double.class), + new Option<>("flap_takeoff_max_speed", Config.FLAP_TAKEOFF_MAX_SPEED, DoubleArgumentType.doubleArg(), Double.class), + new Option<>("flap_landing_max_speed", Config.FLAP_LANDING_MAX_SPEED, DoubleArgumentType.doubleArg(), Double.class), new Option<>("lookahead_seconds", Config.LOOKAHEAD_SECONDS, DoubleArgumentType.doubleArg(), Double.class)); private ConfigCommands() { diff --git a/src/main/java/dev/lumareth/improvedAerodynamics/ImprovedAerodynamics.java b/src/main/java/dev/lumareth/improvedAerodynamics/ImprovedAerodynamics.java index 706cb4c..642c820 100644 --- a/src/main/java/dev/lumareth/improvedAerodynamics/ImprovedAerodynamics.java +++ b/src/main/java/dev/lumareth/improvedAerodynamics/ImprovedAerodynamics.java @@ -24,6 +24,7 @@ public class ImprovedAerodynamics { MinecraftForge.EVENT_BUS.register(PhysicsBridge.class); MinecraftForge.EVENT_BUS.register(ConfigCommands.class); MinecraftForge.EVENT_BUS.register(ExhaustSync.class); + MinecraftForge.EVENT_BUS.register(dev.lumareth.improvedAerodynamics.flight.FlapInteraction.class); ModLoadingContext.get().registerConfig(ModConfig.Type.SERVER, Config.SPEC); ModLoadingContext.get().registerConfig(ModConfig.Type.CLIENT, Config.CLIENT_SPEC); NozzleContent.register(FMLJavaModLoadingContext.get().getModEventBus()); diff --git a/src/main/java/dev/lumareth/improvedAerodynamics/client/KeyBindings.java b/src/main/java/dev/lumareth/improvedAerodynamics/client/KeyBindings.java index ce39fa0..8396504 100644 --- a/src/main/java/dev/lumareth/improvedAerodynamics/client/KeyBindings.java +++ b/src/main/java/dev/lumareth/improvedAerodynamics/client/KeyBindings.java @@ -2,6 +2,7 @@ package dev.lumareth.improvedAerodynamics.client; import com.mojang.blaze3d.platform.InputConstants; import dev.lumareth.improvedAerodynamics.ImprovedAerodynamics; +import dev.lumareth.improvedAerodynamics.network.FlapPacket; import dev.lumareth.improvedAerodynamics.network.Network; import dev.lumareth.improvedAerodynamics.network.ToggleAfterburnerPacket; import dev.lumareth.improvedAerodynamics.network.ToggleAutoTrimPacket; @@ -22,6 +23,12 @@ public final class KeyBindings { public static final KeyMapping TOGGLE_AFTERBURNER = new KeyMapping("key.improved_aerodynamics.toggle_afterburner", KeyConflictContext.IN_GAME, InputConstants.Type.KEYSYM, GLFW.GLFW_KEY_K, "key.categories.improved_aerodynamics"); + public static final KeyMapping FLAPS_DOWN = new KeyMapping("key.improved_aerodynamics.flaps_down", + KeyConflictContext.IN_GAME, InputConstants.Type.KEYSYM, GLFW.GLFW_KEY_G, "key.categories.improved_aerodynamics"); + + public static final KeyMapping FLAPS_UP = new KeyMapping("key.improved_aerodynamics.flaps_up", + KeyConflictContext.IN_GAME, InputConstants.Type.KEYSYM, GLFW.GLFW_KEY_H, "key.categories.improved_aerodynamics"); + private KeyBindings() { } @@ -34,6 +41,8 @@ public final class KeyBindings { public static void onRegisterKeyMappings(RegisterKeyMappingsEvent event) { event.register(TOGGLE_AUTO_TRIM); event.register(TOGGLE_AFTERBURNER); + event.register(FLAPS_DOWN); + event.register(FLAPS_UP); } } @@ -61,6 +70,19 @@ public final class KeyBindings { Network.CHANNEL.sendToServer(new ToggleAfterburnerPacket()); } } + while (FLAPS_DOWN.consumeClick()) { + sendFlaps(minecraft, true); + } + while (FLAPS_UP.consumeClick()) { + sendFlaps(minecraft, false); + } + } + + private static void sendFlaps(Minecraft minecraft, boolean extend) { + if (minecraft.player != null && minecraft.player.getVehicle() != null && minecraft.getConnection() != null + && Network.CHANNEL.isRemotePresent(minecraft.getConnection().getConnection())) { + Network.CHANNEL.sendToServer(new FlapPacket(extend)); + } } } } diff --git a/src/main/java/dev/lumareth/improvedAerodynamics/flight/AeroState.java b/src/main/java/dev/lumareth/improvedAerodynamics/flight/AeroState.java index 632203a..8af7567 100644 --- a/src/main/java/dev/lumareth/improvedAerodynamics/flight/AeroState.java +++ b/src/main/java/dev/lumareth/improvedAerodynamics/flight/AeroState.java @@ -54,6 +54,8 @@ public final class AeroState implements PhysicsBridge.Publishable { double roll; double trimIntegral; double trimOutput; + // Deflection of this node's flaps in radians, moves towards what the flap lever and airspeed allow + double flapAngle; // Torque (ship axes) one unit of pitch input on this node's pitch surfaces makes, measured every substep final Vector3d pitchResponse = new Vector3d(); final Vector3d pitchResponseBuilding = new Vector3d(); @@ -67,6 +69,7 @@ public final class AeroState implements PhysicsBridge.Publishable { trimIntegral = data.autoTrim() ? data.storedTrim() : 0.0; trimOutput = trimIntegral; savedTrim = trimIntegral; + flapAngle = Math.toRadians(FlapSetting.of(data.flapSetting()).degrees()); } } @@ -222,9 +225,10 @@ public final class AeroState implements PhysicsBridge.Publishable { NodeData node = s.node() != AeroSurface.NO_NODE ? frame.nodes.get(s.node()) : null; NodeState state = node != null ? nodeStates.get(node.key()) : null; double input = s.type().controllable ? input(s, node, state, 0.0) : 0.0; + double flap = s.flap() && state != null ? state.flapAngle : 0.0; if (improved) { - boolean nowStalled = ImprovedAero.evaluate(s, v, w, r, input, weight, tuning, stalled[i], out); + boolean nowStalled = ImprovedAero.evaluate(s, v, w, r, input, flap, weight, tuning, stalled[i], out); if (commit) { stalled[i] = nowStalled; wingStalled |= nowStalled && s.type() == SurfaceType.WING; @@ -239,7 +243,7 @@ public final class AeroState implements PhysicsBridge.Publishable { probe.clear(); double base = 0.0; if (improved) { - ImprovedAero.evaluate(s, v, w, r, input, weight, tuning, stalled[i], probe); + ImprovedAero.evaluate(s, v, w, r, input, flap, weight, tuning, stalled[i], probe); } else { WariumAero.evaluate(s, v, w, r, input, weight, probe); } @@ -247,7 +251,7 @@ public final class AeroState implements PhysicsBridge.Publishable { probe.clear(); double nudged = input(s, node, state, delta); if (improved) { - ImprovedAero.evaluate(s, v, w, r, nudged, weight, tuning, stalled[i], probe); + ImprovedAero.evaluate(s, v, w, r, nudged, flap, weight, tuning, stalled[i], probe); } else { WariumAero.evaluate(s, v, w, r, nudged, weight, probe); } @@ -296,10 +300,25 @@ public final class AeroState implements PhysicsBridge.Publishable { state.pitch = moveTowards(state.pitch, node.pitch(), step); state.yaw = moveTowards(state.yaw, node.yaw(), step); state.roll = moveTowards(state.roll, node.roll(), step); + updateFlaps(state, node, worldVelocity.length(), dt); AutoTrim.update(state, node, worldVelocity, altitude, rotation, inertia, w, dt, improved && wingStalled); } } + /** + * Moves a node's flaps towards the angle its lever asks for. Above the setting's maximum speed the air pushes them + * back in, so a craft that is too fast for its flaps has them half retracted, and all the way at 25% more. + */ + private static void updateFlaps(NodeState state, NodeData node, double speed, double dt) { + FlapSetting setting = FlapSetting.of(node.flapSetting()); + double target = Math.toRadians(setting.degrees()); + double limit = setting.maxSpeed(); + if (speed > limit) { + target *= Math.max(0.0, 1.0 - (speed - limit) / (0.25 * limit)); + } + state.flapAngle = moveTowards(state.flapAngle, target, Math.toRadians(Config.FLAP_SLEW_DEGREES.get()) * dt); + } + static double moveTowards(double value, double target, double maxStep) { if (Math.abs(target - value) <= maxStep) { return target; diff --git a/src/main/java/dev/lumareth/improvedAerodynamics/flight/AeroSurface.java b/src/main/java/dev/lumareth/improvedAerodynamics/flight/AeroSurface.java index 712388f..e200e11 100644 --- a/src/main/java/dev/lumareth/improvedAerodynamics/flight/AeroSurface.java +++ b/src/main/java/dev/lumareth/improvedAerodynamics/flight/AeroSurface.java @@ -14,16 +14,17 @@ package dev.lumareth.improvedAerodynamics.flight; * @param brakeInput air brakes only: Warium's input value, they ignore smoothing and trim * @param aspectRatio span squared over area of the wing the block belongs to, 0 if not known yet * @param wingGroup whether the wing the block belongs to contains WING blocks, not only control surfaces or stabilizers + * @param flap whether the block was made a flap with shift-right-click, it then also follows its node's flap setting */ public record AeroSurface(long key, SurfaceType type, boolean slab, double x, double y, double z, int facingX, int facingZ, double pressure, double fluidFactor, double speedMultiplier, long node, ControlAxis axis1, double multiplier1, ControlAxis axis2, double multiplier2, - double brakeInput, double aspectRatio, boolean wingGroup) { + double brakeInput, double aspectRatio, boolean wingGroup, boolean flap) { public static final long NO_NODE = Long.MIN_VALUE; AeroSurface withWingShape(double aspectRatio, boolean wingGroup) { return new AeroSurface(key, type, slab, x, y, z, facingX, facingZ, pressure, fluidFactor, speedMultiplier, - node, axis1, multiplier1, axis2, multiplier2, brakeInput, aspectRatio, wingGroup); + node, axis1, multiplier1, axis2, multiplier2, brakeInput, aspectRatio, wingGroup, flap); } /** Warium's "Direction.getClockWise(Axis.Y)" of the facing, the span direction of a horizontal surface. */ diff --git a/src/main/java/dev/lumareth/improvedAerodynamics/flight/FlapControl.java b/src/main/java/dev/lumareth/improvedAerodynamics/flight/FlapControl.java new file mode 100644 index 0000000..4b9aa7d --- /dev/null +++ b/src/main/java/dev/lumareth/improvedAerodynamics/flight/FlapControl.java @@ -0,0 +1,37 @@ +package dev.lumareth.improvedAerodynamics.flight; + +import net.mcreator.valkyrienwarium.entity.SeatEntityEntity; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.chat.Component; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.level.block.entity.BlockEntity; + +/** Server side of the flap keys: moves the flap lever of the control node the pilot's seat is linked to. */ +public final class FlapControl { + private FlapControl() { + } + + /** @param step +1 for more flaps, -1 for fewer */ + public static void step(ServerPlayer player, int step) { + Entity vehicle = player.getVehicle(); + if (!(vehicle instanceof SeatEntityEntity)) { + player.displayClientMessage(Component.translatable("message.improved_aerodynamics.flaps.no_seat"), true); + return; + } + BlockEntity node = AutoTrimToggle.findNode(vehicle.level(), vehicle.blockPosition()); + if (node == null) { + player.displayClientMessage(Component.translatable("message.improved_aerodynamics.flaps.no_node"), true); + return; + } + CompoundTag tag = node.getPersistentData(); + int current = FlapSetting.of(tag.getInt(WariumCapture.FLAP_SETTING_TAG)).ordinal(); + int next = Math.max(0, Math.min(FlapSetting.values().length - 1, current + (step > 0 ? 1 : -1))); + if (next != current) { + tag.putInt(WariumCapture.FLAP_SETTING_TAG, next); + node.setChanged(); + } + player.displayClientMessage(Component.translatable("message.improved_aerodynamics.flaps", + Component.translatable(FlapSetting.of(next).translationKey())), true); + } +} diff --git a/src/main/java/dev/lumareth/improvedAerodynamics/flight/FlapInteraction.java b/src/main/java/dev/lumareth/improvedAerodynamics/flight/FlapInteraction.java new file mode 100644 index 0000000..00946f0 --- /dev/null +++ b/src/main/java/dev/lumareth/improvedAerodynamics/flight/FlapInteraction.java @@ -0,0 +1,66 @@ +package dev.lumareth.improvedAerodynamics.flight; + +import dev.lumareth.improvedAerodynamics.ImprovedAerodynamics; +import net.minecraft.core.BlockPos; +import net.minecraft.core.registries.Registries; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.TagKey; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraftforge.event.entity.player.PlayerInteractEvent; +import net.minecraftforge.eventbus.api.SubscribeEvent; + +/** + * Shift-right-click on a block in the {@code improved_aerodynamics:flappable} tag (Warium's horizontal control + * surfaces) with empty hands makes it a flap, or a normal control surface again. A flap follows the flap lever of the + * control node it is linked to, on top of the pilot's input. + */ +public final class FlapInteraction { + public static final TagKey FLAPPABLE = TagKey.create(Registries.BLOCK, + new ResourceLocation(ImprovedAerodynamics.MOD_ID, "flappable")); + + private FlapInteraction() { + } + + @SubscribeEvent + public static void onRightClickBlock(PlayerInteractEvent.RightClickBlock event) { + Player player = event.getEntity(); + if (event.getHand() != InteractionHand.MAIN_HAND || !player.isShiftKeyDown() || player.isPassenger() + || !player.getMainHandItem().isEmpty() || !player.getOffhandItem().isEmpty()) { + return; + } + Level level = event.getLevel(); + BlockPos pos = event.getPos(); + BlockState state = level.getBlockState(pos); + if (!state.is(FLAPPABLE)) { + return; + } + // Handled on both sides so the block's own right-click action does not run on either + event.setCanceled(true); + event.setCancellationResult(InteractionResult.sidedSuccess(level.isClientSide)); + if (level.isClientSide) { + return; + } + BlockEntity blockEntity = level.getBlockEntity(pos); + if (blockEntity == null) { + return; + } + CompoundTag tag = blockEntity.getPersistentData(); + boolean flap = !tag.getBoolean(WariumCapture.FLAP_TAG); + tag.putBoolean(WariumCapture.FLAP_TAG, flap); + blockEntity.setChanged(); + if (flap && !tag.contains("ControlX")) { + player.displayClientMessage(Component.translatable("message.improved_aerodynamics.flap.unlinked"), true); + } else { + player.displayClientMessage(Component.translatable(flap + ? "message.improved_aerodynamics.flap.on" : "message.improved_aerodynamics.flap.off"), true); + } + } +} diff --git a/src/main/java/dev/lumareth/improvedAerodynamics/flight/FlapSetting.java b/src/main/java/dev/lumareth/improvedAerodynamics/flight/FlapSetting.java new file mode 100644 index 0000000..277452b --- /dev/null +++ b/src/main/java/dev/lumareth/improvedAerodynamics/flight/FlapSetting.java @@ -0,0 +1,38 @@ +package dev.lumareth.improvedAerodynamics.flight; + +import dev.lumareth.improvedAerodynamics.Config; + +/** Positions of a control node's flap lever. */ +public enum FlapSetting { + UP, + TAKEOFF, + LANDING; + + /** The setting for a stored number, UP for anything that is not one. */ + public static FlapSetting of(int ordinal) { + FlapSetting[] values = values(); + return ordinal >= 0 && ordinal < values.length ? values[ordinal] : UP; + } + + /** Deflection in degrees the flaps move to at this setting. */ + double degrees() { + return switch (this) { + case UP -> 0.0; + case TAKEOFF -> Config.FLAP_TAKEOFF_DEGREES.get(); + case LANDING -> Config.FLAP_LANDING_DEGREES.get(); + }; + } + + /** Airspeed in blocks per second above which the air starts to push the flaps back in. Infinite when they are up. */ + double maxSpeed() { + return switch (this) { + case UP -> Double.POSITIVE_INFINITY; + case TAKEOFF -> Config.FLAP_TAKEOFF_MAX_SPEED.get(); + case LANDING -> Config.FLAP_LANDING_MAX_SPEED.get(); + }; + } + + public String translationKey() { + return "message.improved_aerodynamics.flaps." + name().toLowerCase(java.util.Locale.ROOT); + } +} diff --git a/src/main/java/dev/lumareth/improvedAerodynamics/flight/ImprovedAero.java b/src/main/java/dev/lumareth/improvedAerodynamics/flight/ImprovedAero.java index e71a30f..9785f80 100644 --- a/src/main/java/dev/lumareth/improvedAerodynamics/flight/ImprovedAero.java +++ b/src/main/java/dev/lumareth/improvedAerodynamics/flight/ImprovedAero.java @@ -27,6 +27,12 @@ final class ImprovedAero { private static final double MAX_DEFLECTION = Math.toRadians(20.0); // Share of the deflection that acts as angle of attack, gives Warium's control authority at the design speed private static final double FLAP_EFFECTIVENESS = 0.75; + // How much of the lift a flap adds raises the stall angle, and the parasitic drag coefficient per radian² of flap + // Share of the flap's deflection that acts as angle of attack: 35 degrees of landing flap adds about 1.1 to the + // lift coefficient, like a real plain or slotted flap + private static final double FLAP_ALPHA_FACTOR = 0.4; + private static final double FLAP_STALL_GAIN = 0.7; + private static final double FLAP_DRAG = 0.35; // Angle of attack of a level cambered wing, where K is calibrated private static final double REFERENCE_ANGLE = Math.toRadians(4.0); private static final double REFERENCE_SPEED = 40.0; @@ -62,10 +68,11 @@ final class ImprovedAero { * @param w ship angular velocity, ship axes * @param r block center relative to the center of mass, ship axes * @param input control input of the surface, -1 to 1 + * @param flap flap deflection in radians, 0 for surfaces that are not flaps * @param stalled whether the surface was stalled in the previous substep * @return whether the surface is stalled now */ - static boolean evaluate(AeroSurface s, double[] v, double[] w, double[] r, double input, double weight, + static boolean evaluate(AeroSurface s, double[] v, double[] w, double[] r, double input, double flap, double weight, Tuning tuning, boolean stalled, Wrench out) { // Airflow at the block: ship velocity plus w x r double vx = v[0] + w[1] * r[2] - w[2] * r[1]; @@ -114,6 +121,10 @@ final class ImprovedAero { // Same sign convention as Warium: positive input pushes along n while moving against the facing alpha += FLAP_EFFECTIVENESS * MAX_DEFLECTION * input * (u <= 0.0 ? 1.0 : -1.0); } + // A flap bends the airfoil's camber whichever way the air comes, so it always adds lift, and it moves the stall + // up with it: a flap raises the highest lift a wing can make + double flapShift = FLAP_ALPHA_FACTOR * flap; + alpha += flapShift; alpha = Math.max(-Math.PI / 2.0, Math.min(Math.PI / 2.0, alpha)); // Low aspect ratio wings keep their flow attached longer thanks to leading-edge vortices. Control surfaces @@ -124,6 +135,9 @@ final class ImprovedAero { : s.wingGroup() && s.type().controllable ? SurfaceType.WING.stallAngle : s.type().stallAngle; double critical = Math.toRadians(baseStall) * (1.0 + tuning.vortexStallBonus() * vortexFactor) * tuning.stallAngleScale(); double absAlpha = Math.abs(alpha); + if (alpha >= 0.0) { + critical += FLAP_STALL_GAIN * flapShift; + } // Flips only where both blends agree, so the hysteresis never makes the force jump if (!stalled && absAlpha > critical + STALL_BLEND) { stalled = true; @@ -156,7 +170,7 @@ final class ImprovedAero { double dy = -(vn * ny) / planeSpeed; double dz = -(u * cz + vn * nz) / planeSpeed; double liftForce = q * lift * tuning.liftScale(); - double dragForce = q * drag / m; + double dragForce = q * (drag + FLAP_DRAG * flap * flap) / m; out.addForce(r, lx * liftForce + dx * dragForce, ly * liftForce + dy * dragForce, lz * liftForce + dz * dragForce); return stalled; } diff --git a/src/main/java/dev/lumareth/improvedAerodynamics/flight/NodeData.java b/src/main/java/dev/lumareth/improvedAerodynamics/flight/NodeData.java index bc5e0a8..f3d1fef 100644 --- a/src/main/java/dev/lumareth/improvedAerodynamics/flight/NodeData.java +++ b/src/main/java/dev/lumareth/improvedAerodynamics/flight/NodeData.java @@ -5,6 +5,7 @@ package dev.lumareth.improvedAerodynamics.flight; * trim state. * * @param storedTrim trim saved in the node, used when the physics thread first sees the node + * @param flapSetting the node's flap lever, see {@link FlapSetting#ordinal()} */ -public record NodeData(long key, double pitch, double yaw, double roll, double throttle, boolean autoTrim, double storedTrim) { +public record NodeData(long key, double pitch, double yaw, double roll, double throttle, boolean autoTrim, double storedTrim, int flapSetting) { } diff --git a/src/main/java/dev/lumareth/improvedAerodynamics/flight/WariumCapture.java b/src/main/java/dev/lumareth/improvedAerodynamics/flight/WariumCapture.java index 0c196d7..4c57b67 100644 --- a/src/main/java/dev/lumareth/improvedAerodynamics/flight/WariumCapture.java +++ b/src/main/java/dev/lumareth/improvedAerodynamics/flight/WariumCapture.java @@ -31,6 +31,9 @@ import org.valkyrienskies.mod.common.VSGameUtilsKt; public final class WariumCapture { public static final String AUTO_TRIM_TAG = "improved_aerodynamics_autotrim"; public static final String TRIM_TAG = "improved_aerodynamics_trim"; + /** Block entity flag of a surface that is a flap, and the flap lever of a control node. */ + public static final String FLAP_TAG = "improved_aerodynamics_flap"; + public static final String FLAP_SETTING_TAG = "improved_aerodynamics_flap_setting"; private static final int TRIM_SAVE_INTERVAL = 20; // The procedure currently running, procedures run one at a time on the server thread @@ -77,10 +80,12 @@ public final class WariumCapture { String key = "Null"; String key2 = "Null"; double brakeInput = 0.0; + boolean flap = false; if (type.controllable || type == SurfaceType.AIR_BRAKE) { BlockEntity blockEntity = level.getBlockEntity(pos); if (blockEntity != null) { CompoundTag tag = blockEntity.getPersistentData(); + flap = type == SurfaceType.CONTROL_SURFACE && tag.getBoolean(FLAP_TAG); key = tag.getString("Key"); key2 = type == SurfaceType.AIR_BRAKE ? "Null" : tag.getString("Key2"); BlockPos nodePos = BlockPos.containing(tag.getDouble("ControlX"), tag.getDouble("ControlY"), tag.getDouble("ControlZ")); @@ -97,7 +102,7 @@ public final class WariumCapture { return new AeroSurface(pos.asLong(), type, slab, pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5, facing.getStepX(), facing.getStepZ(), pressure, fluidFactor(level), speedMultiplier, node, ControlAxis.of(key), ControlAxis.multiplier(key), ControlAxis.of(key2), ControlAxis.multiplier(key2), - brakeInput, 0.0, false); + brakeInput, 0.0, false, flap); } /** Registers a node's inputs once per tick, and saves its trim to it now and then so it survives reloading. */ @@ -119,7 +124,8 @@ public final class WariumCapture { } } aero.addNode(new NodeData(nodeKey, tag.getDouble("Pitch"), tag.getDouble("Yaw"), tag.getDouble("Roll"), - tag.getDouble("Throttle"), autoTrim, storedTrim)); + tag.getDouble("Throttle"), autoTrim, storedTrim, + FlapSetting.of(tag.getInt(FLAP_SETTING_TAG)).ordinal())); } /** Raw node value for a key like "Pitch+", as Warium's air brakes read it. */ diff --git a/src/main/java/dev/lumareth/improvedAerodynamics/network/FlapPacket.java b/src/main/java/dev/lumareth/improvedAerodynamics/network/FlapPacket.java new file mode 100644 index 0000000..49b4710 --- /dev/null +++ b/src/main/java/dev/lumareth/improvedAerodynamics/network/FlapPacket.java @@ -0,0 +1,33 @@ +package dev.lumareth.improvedAerodynamics.network; + +import dev.lumareth.improvedAerodynamics.flight.FlapControl; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.server.level.ServerPlayer; +import net.minecraftforge.fml.ModList; +import net.minecraftforge.network.NetworkEvent; + +import java.util.function.Supplier; + +/** Sent when a pilot moves the flap lever: more flaps (true) or fewer. */ +public final class FlapPacket { + private final boolean extend; + + public FlapPacket(boolean extend) { + this.extend = extend; + } + + public void encode(FriendlyByteBuf buffer) { + buffer.writeBoolean(extend); + } + + public static FlapPacket decode(FriendlyByteBuf buffer) { + return new FlapPacket(buffer.readBoolean()); + } + + public void handle(Supplier context) { + ServerPlayer player = context.get().getSender(); + if (player != null && ModList.get().isLoaded("valkyrien_warium")) { + FlapControl.step(player, extend ? 1 : -1); + } + } +} diff --git a/src/main/java/dev/lumareth/improvedAerodynamics/network/Network.java b/src/main/java/dev/lumareth/improvedAerodynamics/network/Network.java index 7ab3755..22c46ed 100644 --- a/src/main/java/dev/lumareth/improvedAerodynamics/network/Network.java +++ b/src/main/java/dev/lumareth/improvedAerodynamics/network/Network.java @@ -33,5 +33,11 @@ public final class Network { .decoder(ExhaustPacket::decode) .consumerMainThread(ExhaustPacket::handle) .add(); + // Higher id than the neighbours so it does not collide with packets added on other branches + CHANNEL.messageBuilder(FlapPacket.class, 10, NetworkDirection.PLAY_TO_SERVER) + .encoder(FlapPacket::encode) + .decoder(FlapPacket::decode) + .consumerMainThread(FlapPacket::handle) + .add(); } } diff --git a/src/main/resources/assets/improved_aerodynamics/lang/de_de.json b/src/main/resources/assets/improved_aerodynamics/lang/de_de.json index 2453ead..51ae889 100644 --- a/src/main/resources/assets/improved_aerodynamics/lang/de_de.json +++ b/src/main/resources/assets/improved_aerodynamics/lang/de_de.json @@ -22,5 +22,16 @@ "message.improved_aerodynamics.afterburner.off": "Nachbrenner aus", "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_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.flaps_down": "Klappen runter (eine Stufe)", + "key.improved_aerodynamics.flaps_up": "Klappen rauf (eine Stufe)", + "message.improved_aerodynamics.flaps": "Klappen: %s", + "message.improved_aerodynamics.flaps.up": "eingefahren", + "message.improved_aerodynamics.flaps.takeoff": "Start", + "message.improved_aerodynamics.flaps.landing": "Landung", + "message.improved_aerodynamics.flaps.no_seat": "Setz dich in einen Warium-Steuersitz, um die Klappen zu nutzen", + "message.improved_aerodynamics.flaps.no_node": "Dieser Sitz ist mit keinem Vehicle Control Node verbunden", + "message.improved_aerodynamics.flap.on": "Das Ruder ist jetzt eine Klappe", + "message.improved_aerodynamics.flap.off": "Das Ruder ist wieder ein normales Ruder", + "message.improved_aerodynamics.flap.unlinked": "Das Ruder ist jetzt eine Klappe, aber noch mit keinem Vehicle Control Node verbunden" } diff --git a/src/main/resources/assets/improved_aerodynamics/lang/en_us.json b/src/main/resources/assets/improved_aerodynamics/lang/en_us.json index 11bd43c..1741219 100644 --- a/src/main/resources/assets/improved_aerodynamics/lang/en_us.json +++ b/src/main/resources/assets/improved_aerodynamics/lang/en_us.json @@ -22,5 +22,16 @@ "message.improved_aerodynamics.afterburner.off": "Afterburner off", "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_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.flaps_down": "Flaps down (one step)", + "key.improved_aerodynamics.flaps_up": "Flaps up (one step)", + "message.improved_aerodynamics.flaps": "Flaps: %s", + "message.improved_aerodynamics.flaps.up": "up", + "message.improved_aerodynamics.flaps.takeoff": "takeoff", + "message.improved_aerodynamics.flaps.landing": "landing", + "message.improved_aerodynamics.flaps.no_seat": "Sit in a Warium control seat to use the flaps", + "message.improved_aerodynamics.flaps.no_node": "This seat is not linked to a vehicle control node", + "message.improved_aerodynamics.flap.on": "Control surface is now a flap", + "message.improved_aerodynamics.flap.off": "Control surface is a normal control surface again", + "message.improved_aerodynamics.flap.unlinked": "Control surface is now a flap, but it is not linked to a vehicle control node yet" } diff --git a/src/main/resources/data/improved_aerodynamics/tags/blocks/flappable.json b/src/main/resources/data/improved_aerodynamics/tags/blocks/flappable.json new file mode 100644 index 0000000..1f129e2 --- /dev/null +++ b/src/main/resources/data/improved_aerodynamics/tags/blocks/flappable.json @@ -0,0 +1,221 @@ +{ + "replace": false, + "values": [ + { + "id": "valkyrien_warium:control_surface", + "required": false + }, + { + "id": "valkyrien_warium:control_surface_offset_top", + "required": false + }, + { + "id": "valkyrien_warium:control_surface_offset_bottom", + "required": false + }, + { + "id": "valkyrien_warium:control_surface_dark_gray", + "required": false + }, + { + "id": "valkyrien_warium:control_surface_offset_top_dark_gray", + "required": false + }, + { + "id": "valkyrien_warium:control_surface_offset_bottom_dark_gray", + "required": false + }, + { + "id": "valkyrien_warium:wooden_control_surface", + "required": false + }, + { + "id": "valkyrien_warium:wooden_control_surface_offset_top", + "required": false + }, + { + "id": "valkyrien_warium:wooden_control_surface_offset_bottom", + "required": false + }, + { + "id": "valkyrien_warium:red_control_surface", + "required": false + }, + { + "id": "valkyrien_warium:red_control_surface_offset_top", + "required": false + }, + { + "id": "valkyrien_warium:red_control_surface_offset_bottom", + "required": false + }, + { + "id": "valkyrien_warium:blue_control_surface", + "required": false + }, + { + "id": "valkyrien_warium:blue_control_surface_offset_top", + "required": false + }, + { + "id": "valkyrien_warium:blue_control_surface_offset_bottom", + "required": false + }, + { + "id": "valkyrien_warium:brown_control_surface", + "required": false + }, + { + "id": "valkyrien_warium:brown_control_surface_offset_top", + "required": false + }, + { + "id": "valkyrien_warium:brown_control_surface_offset_bottom", + "required": false + }, + { + "id": "valkyrien_warium:green_control_surface", + "required": false + }, + { + "id": "valkyrien_warium:green_control_surface_offset_bottom", + "required": false + }, + { + "id": "valkyrien_warium:cyan_control_surface", + "required": false + }, + { + "id": "valkyrien_warium:cyan_control_surface_offset_top", + "required": false + }, + { + "id": "valkyrien_warium:cyan_control_surface_offset_bottom", + "required": false + }, + { + "id": "valkyrien_warium:orange_control_surface", + "required": false + }, + { + "id": "valkyrien_warium:orange_control_surface_offset_top", + "required": false + }, + { + "id": "valkyrien_warium:orange_control_surface_offset_bottom", + "required": false + }, + { + "id": "valkyrien_warium:yellow_control_surface", + "required": false + }, + { + "id": "valkyrien_warium:yellow_control_surface_offset_top", + "required": false + }, + { + "id": "valkyrien_warium:yellow_control_surface_offset_bottom", + "required": false + }, + { + "id": "valkyrien_warium:gray_control_surface", + "required": false + }, + { + "id": "valkyrien_warium:gray_control_surface_offset_top", + "required": false + }, + { + "id": "valkyrien_warium:gray_control_surface_offset_bottom", + "required": false + }, + { + "id": "valkyrien_warium:light_gray_control_surface", + "required": false + }, + { + "id": "valkyrien_warium:light_gray_control_surface_offset_top", + "required": false + }, + { + "id": "valkyrien_warium:light_gray_control_surface_offset_bottom", + "required": false + }, + { + "id": "valkyrien_warium:light_blue_control_surface", + "required": false + }, + { + "id": "valkyrien_warium:light_blue_control_surface_offset_top", + "required": false + }, + { + "id": "valkyrien_warium:light_blue_control_surface_offset_bottom", + "required": false + }, + { + "id": "valkyrien_warium:pink_control_surface", + "required": false + }, + { + "id": "valkyrien_warium:pink_control_surface_offset_top", + "required": false + }, + { + "id": "valkyrien_warium:pink_control_surface_offset_bottom", + "required": false + }, + { + "id": "valkyrien_warium:magenta_control_surface", + "required": false + }, + { + "id": "valkyrien_warium:magenta_control_surface_offset_top", + "required": false + }, + { + "id": "valkyrien_warium:magenta_control_surface_offset_bottom", + "required": false + }, + { + "id": "valkyrien_warium:purple_control_surface", + "required": false + }, + { + "id": "valkyrien_warium:purple_control_surface_offset_top", + "required": false + }, + { + "id": "valkyrien_warium:purple_control_surface_offset_bottom", + "required": false + }, + { + "id": "valkyrien_warium:white_control_surface", + "required": false + }, + { + "id": "valkyrien_warium:white_control_surface_offset_top", + "required": false + }, + { + "id": "valkyrien_warium:white_control_surface_offset_bottom", + "required": false + }, + { + "id": "valkyrien_warium:black_control_surface", + "required": false + }, + { + "id": "valkyrien_warium:black_control_surface_offset_top", + "required": false + }, + { + "id": "valkyrien_warium:black_control_surface_offset_bottom", + "required": false + }, + { + "id": "valkyrien_warium:green_control_surface_offset_top", + "required": false + } + ] +} \ No newline at end of file