Shift-right-click a block in improved_aerodynamics:flappable to make it a flap. G/H step the node's flap lever UP/TAKEOFF/LANDING; flaps slew to the configured angle, add lift and drag and raise the stall angle, and retract above a max speed. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YVREx3uo7nz1FEhzveWTUh
174 lines
8.6 KiB
Java
174 lines
8.6 KiB
Java
package dev.lumareth.improvedAerodynamics.flight;
|
|
|
|
import dev.lumareth.improvedAerodynamics.Config;
|
|
import net.mcreator.valkyrienwarium.init.ValkyrienWariumModBlocks;
|
|
import net.mcreator.valkyrienwarium.init.ValkyrienWariumModGameRules;
|
|
import net.mcreator.valkyrienwarium.procedures.PressureGetProcedure;
|
|
import net.mcreator.valkyrienwarium.ships.ForceInducedShips;
|
|
import net.minecraft.core.BlockPos;
|
|
import net.minecraft.core.Direction;
|
|
import net.minecraft.nbt.CompoundTag;
|
|
import net.minecraft.resources.ResourceLocation;
|
|
import net.minecraft.server.level.ServerLevel;
|
|
import net.minecraft.world.level.LevelAccessor;
|
|
import net.minecraft.world.level.block.LiquidBlock;
|
|
import net.minecraft.world.level.block.entity.BlockEntity;
|
|
import net.minecraft.world.level.block.state.BlockState;
|
|
import net.minecraft.world.level.block.state.properties.DirectionProperty;
|
|
import net.minecraft.world.level.block.state.properties.EnumProperty;
|
|
import net.minecraft.world.level.block.state.properties.Property;
|
|
import net.minecraftforge.registries.ForgeRegistries;
|
|
import org.joml.Vector3d;
|
|
import org.valkyrienskies.core.api.ships.Ship;
|
|
import org.valkyrienskies.mod.common.VSGameUtilsKt;
|
|
|
|
/**
|
|
* Server thread side of the physics rate aerodynamics: the Warium procedure mixins call {@link #begin} when a
|
|
* procedure starts and {@link #capture} instead of each force it adds. A procedure only adds forces after passing
|
|
* all its conditions (air around the block, minimum speed, linked node), so a captured block is one whose forces
|
|
* apply this tick; its forces are then computed on the physics thread from the ship's current motion.
|
|
*/
|
|
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
|
|
private static SurfaceType type;
|
|
private static LevelAccessor world;
|
|
private static BlockPos pos;
|
|
private static BlockState state;
|
|
|
|
private WariumCapture() {
|
|
}
|
|
|
|
public static void begin(SurfaceType surfaceType, LevelAccessor level, double x, double y, double z, BlockState blockState) {
|
|
type = surfaceType;
|
|
world = level;
|
|
pos = BlockPos.containing(x, y, z);
|
|
state = blockState;
|
|
}
|
|
|
|
/**
|
|
* Called instead of a force the running procedure adds.
|
|
*
|
|
* @return true if the block was taken over, so the original force must not be added
|
|
*/
|
|
public static boolean capture(ForceInducedShips inducer) {
|
|
if (!Config.PHYSICS_RATE_AERODYNAMICS.get() || !(world instanceof ServerLevel level)) {
|
|
return false;
|
|
}
|
|
// ForceInducedShips is final, so the compiler needs the detour through Object to accept the mixed-in interface
|
|
AeroState aero = ((AeroHolder) (Object) inducer).improvedAerodynamics$aero();
|
|
if (!aero.has(pos.asLong())) {
|
|
aero.add(createSurface(level, aero));
|
|
}
|
|
return true;
|
|
}
|
|
|
|
private static AeroSurface createSurface(ServerLevel level, AeroState aero) {
|
|
Direction facing = facing(state);
|
|
ResourceLocation id = ForgeRegistries.BLOCKS.getKey(state.getBlock());
|
|
boolean slab = id != null && id.getPath().contains("slab");
|
|
double speedMultiplier = Math.max(1, level.getGameRules().getInt(ValkyrienWariumModGameRules.AERODYNAMIC_SPEED_MULTIPLIER) / 100);
|
|
double pressure = PressureGetProcedure.execute(level, pos.getX(), pos.getY(), pos.getZ());
|
|
|
|
long node = AeroSurface.NO_NODE;
|
|
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"));
|
|
if (level.getBlockState(nodePos).is(ValkyrienWariumModBlocks.VEHICLE_CONTROL_NODE.get())) {
|
|
node = nodePos.asLong();
|
|
readNode(level, nodePos, aero);
|
|
if (type == SurfaceType.AIR_BRAKE) {
|
|
brakeInput = nodeValue(level, nodePos, key) * ControlAxis.multiplier(key);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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, flap);
|
|
}
|
|
|
|
/** Registers a node's inputs once per tick, and saves its trim to it now and then so it survives reloading. */
|
|
private static void readNode(ServerLevel level, BlockPos nodePos, AeroState aero) {
|
|
long nodeKey = nodePos.asLong();
|
|
BlockEntity blockEntity = level.getBlockEntity(nodePos);
|
|
if (aero.hasNode(nodeKey) || blockEntity == null) {
|
|
return;
|
|
}
|
|
CompoundTag tag = blockEntity.getPersistentData();
|
|
boolean autoTrim = tag.getBoolean(AUTO_TRIM_TAG);
|
|
double storedTrim = tag.getDouble(TRIM_TAG);
|
|
if (autoTrim && level.getGameTime() % TRIM_SAVE_INTERVAL == 0) {
|
|
double trim = aero.trim(nodeKey);
|
|
if (!Double.isNaN(trim) && Math.abs(trim - storedTrim) > 1.0E-3) {
|
|
tag.putDouble(TRIM_TAG, trim);
|
|
blockEntity.setChanged();
|
|
storedTrim = trim;
|
|
}
|
|
}
|
|
aero.addNode(new NodeData(nodeKey, tag.getDouble("Pitch"), tag.getDouble("Yaw"), tag.getDouble("Roll"),
|
|
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. */
|
|
private static double nodeValue(ServerLevel level, BlockPos nodePos, String key) {
|
|
BlockEntity blockEntity = level.getBlockEntity(nodePos);
|
|
if (blockEntity == null || key.isEmpty()) {
|
|
return 0.0;
|
|
}
|
|
String name = key.endsWith("+") || key.endsWith("-") ? key.substring(0, key.length() - 1) : key;
|
|
return blockEntity.getPersistentData().getDouble(name);
|
|
}
|
|
|
|
/** Warium's multiplier for surfaces under water, checked at the block's world position. */
|
|
private static double fluidFactor(ServerLevel level) {
|
|
double underWater = switch (type) {
|
|
case VERTICAL_WING -> 15.0;
|
|
case CONTROL_SURFACE, VERTICAL_CONTROL_SURFACE -> 80.0;
|
|
case AIR_BRAKE -> 40.0;
|
|
default -> 1.0;
|
|
};
|
|
if (underWater == 1.0) {
|
|
return 1.0;
|
|
}
|
|
Vector3d center = new Vector3d(pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5);
|
|
Ship ship = VSGameUtilsKt.getShipManagingPos(level, pos);
|
|
if (ship != null) {
|
|
ship.getShipToWorld().transformPosition(center);
|
|
}
|
|
boolean inLiquid = level.getBlockState(BlockPos.containing(center.x, center.y, center.z)).getBlock() instanceof LiquidBlock;
|
|
return inLiquid ? underWater : 1.0;
|
|
}
|
|
|
|
/** Warium's way of reading a block's direction: its "facing", else the positive end of its "axis", else north. */
|
|
private static Direction facing(BlockState blockState) {
|
|
Property<?> facing = blockState.getBlock().getStateDefinition().getProperty("facing");
|
|
if (facing instanceof DirectionProperty directionProperty) {
|
|
return blockState.getValue(directionProperty);
|
|
}
|
|
Property<?> axis = blockState.getBlock().getStateDefinition().getProperty("axis");
|
|
if (axis instanceof EnumProperty<?> enumProperty && blockState.getValue(enumProperty) instanceof Direction.Axis value) {
|
|
return Direction.fromAxisAndDirection(value, Direction.AxisDirection.POSITIVE);
|
|
}
|
|
return Direction.NORTH;
|
|
}
|
|
}
|