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; } }