- Add vectored afterburner nozzle block with AzureLib model, deflection, throttle and afterburner state - Drive nozzles from the vehicle control node and map pitch, roll and yaw to differential deflection - Add afterburner toggle key and packet, linked to the control node - Replace Warium exhaust effect entities with improved flames, particles and sounds when exhaustStyle is improved - Add /aero seedgen to measure the air ceiling for the air placeholder - Upgrade Warium VS to 1.1.7 and make the lift use horizontal speed
282 lines
10 KiB
Java
282 lines
10 KiB
Java
package dev.lumareth.improvedAerodynamics.exhaust;
|
|
|
|
import dev.lumareth.improvedAerodynamics.Config;
|
|
import dev.lumareth.improvedAerodynamics.exhaust.client.NozzleClientEffects;
|
|
import net.minecraft.core.BlockPos;
|
|
import net.minecraft.core.Direction;
|
|
import net.minecraft.nbt.CompoundTag;
|
|
import net.minecraft.server.level.ServerLevel;
|
|
import net.minecraft.network.Connection;
|
|
import net.minecraft.network.protocol.Packet;
|
|
import net.minecraft.network.protocol.game.ClientGamePacketListener;
|
|
import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket;
|
|
import net.minecraft.world.level.Level;
|
|
import net.minecraft.world.level.block.DirectionalBlock;
|
|
import net.minecraft.world.level.block.entity.BlockEntity;
|
|
import net.minecraft.world.level.block.state.BlockState;
|
|
import net.minecraft.world.phys.AABB;
|
|
import org.jetbrains.annotations.Nullable;
|
|
|
|
/**
|
|
* State of one vectored afterburner nozzle. The server owns it: whatever drives the nozzle (a debug command now, flight
|
|
* logic later) calls {@link #setDeflectionTarget}, {@link #setThrottle} and {@link #setAfterburner}, the rest follows
|
|
* from them. Everything the client needs to draw the nozzle is in the update tag, so players that join or reload the
|
|
* chunk see the right state.
|
|
*/
|
|
public class NozzleBlockEntity extends BlockEntity {
|
|
// Warium's throttle goes from 0 to 10
|
|
private static final float MAX_NODE_THROTTLE = 10.0F;
|
|
private static final boolean WARIUM_LOADED = net.minecraftforge.fml.ModList.get().isLoaded("valkyrien_warium");
|
|
// What changes are worth an update packet: a quarter degree, two percent throttle
|
|
private static final float DEFLECTION_STEP = (float) Math.toRadians(0.25);
|
|
private static final float THROTTLE_STEP = 0.02F;
|
|
|
|
private float deflectionTarget;
|
|
private float deflectionPrevious;
|
|
private float deflectionCurrent;
|
|
private float throttle;
|
|
private boolean afterburnerRequested;
|
|
private boolean afterburnerAuto;
|
|
private boolean afterburner;
|
|
private boolean rightEngine;
|
|
|
|
// Last values sent to the clients
|
|
private int sentDeflection;
|
|
private int sentThrottle;
|
|
private boolean sentAfterburner;
|
|
private boolean sentRightEngine;
|
|
|
|
/** The client's sound and particle state, owned by {@link NozzleClientEffects}. */
|
|
@Nullable
|
|
private Object clientEffects;
|
|
|
|
public NozzleBlockEntity(BlockPos pos, BlockState state) {
|
|
super(NozzleContent.NOZZLE_ENTITY.get(), pos, state);
|
|
}
|
|
|
|
// ---- API ----
|
|
|
|
/** Sets where the nozzle should swing to, in radians, positive turns the nozzle's rotation bone positively. */
|
|
public void setDeflectionTarget(float radians) {
|
|
float limit = (float) Math.toRadians(Config.NOZZLE_MAX_DEFLECTION.get());
|
|
deflectionTarget = Math.max(-limit, Math.min(limit, radians));
|
|
changed();
|
|
}
|
|
|
|
/** Sets the engine throttle, 0 to 1. Only matters for a nozzle that is not linked to a control node. */
|
|
public void setThrottle(float throttle) {
|
|
this.throttle = Math.max(0.0F, Math.min(1.0F, throttle));
|
|
changed();
|
|
}
|
|
|
|
/** Forces the afterburner on, independent of the control node. Switching it off leaves it to the afterburner key again. */
|
|
public void setAfterburner(boolean on) {
|
|
afterburnerRequested = on;
|
|
changed();
|
|
}
|
|
|
|
public void setRightEngine(boolean right) {
|
|
rightEngine = right;
|
|
changed();
|
|
}
|
|
|
|
public float getDeflectionTarget() {
|
|
return deflectionTarget;
|
|
}
|
|
|
|
/** The deflection being drawn, eased towards the target, interpolated between ticks. */
|
|
public float getDeflection(float partialTick) {
|
|
return deflectionPrevious + (deflectionCurrent - deflectionPrevious) * partialTick;
|
|
}
|
|
|
|
public float getThrottle() {
|
|
return throttle;
|
|
}
|
|
|
|
public boolean isAfterburner() {
|
|
return afterburner;
|
|
}
|
|
|
|
public boolean isRightEngine() {
|
|
return rightEngine;
|
|
}
|
|
|
|
public Direction getFacing() {
|
|
return getBlockState().getValue(DirectionalBlock.FACING);
|
|
}
|
|
|
|
// ---- Ticking ----
|
|
|
|
public static void tick(Level level, BlockPos pos, BlockState state, NozzleBlockEntity nozzle) {
|
|
nozzle.tick(level);
|
|
}
|
|
|
|
private void tick(Level level) {
|
|
if (!level.isClientSide) {
|
|
drive(level);
|
|
}
|
|
float maxStep = (float) Math.toRadians(Config.NOZZLE_SLEW_RATE.get() / 20.0);
|
|
deflectionPrevious = deflectionCurrent;
|
|
deflectionCurrent += Math.max(-maxStep, Math.min(maxStep, deflectionTarget - deflectionCurrent));
|
|
|
|
if (level.isClientSide) {
|
|
NozzleClientEffects.tick(this);
|
|
return;
|
|
}
|
|
boolean lit = afterburnerRequested || afterburnerAuto;
|
|
if (lit != afterburner) {
|
|
afterburner = lit;
|
|
setChanged();
|
|
}
|
|
sync();
|
|
boolean light = lit && Config.NOZZLE_LIGHT.get();
|
|
BlockState state = getBlockState();
|
|
if (state.getValue(NozzleBlock.LIT) != light) {
|
|
level.setBlock(worldPosition, state.setValue(NozzleBlock.LIT, light), 3);
|
|
}
|
|
if (WARIUM_LOADED && level instanceof ServerLevel serverLevel) {
|
|
ExhaustThrust.apply(serverLevel, worldPosition, getFacing(),
|
|
NozzleGeometry.exhaustDirection(getFacing(), deflectionCurrent, rightEngine));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Takes throttle, deflection and afterburner from the linked control node. A nozzle without a link keeps what the
|
|
* API or the debug command gave it.
|
|
*/
|
|
private void drive(Level level) {
|
|
NozzleControl.Inputs inputs = NozzleControl.read(level, this);
|
|
if (inputs == null) {
|
|
afterburnerAuto = false;
|
|
return;
|
|
}
|
|
// No flame without a running engine behind the nozzle: the jet exhaust there hands over its power
|
|
boolean running = !WARIUM_LOADED || ExhaustThrust.power(level, worldPosition, getFacing()) > 0.0;
|
|
throttle = running ? Math.min(1.0F, (float) inputs.throttle() / MAX_NODE_THROTTLE) : 0.0F;
|
|
float pitch = (float) inputs.pitchUp();
|
|
float roll = (float) inputs.rollRight();
|
|
deflectionTarget = NozzleMixing.deflection(pitch, roll, (float) inputs.yawRight(), rightEngine);
|
|
double minimum = Config.AFTERBURNER_MIN_THROTTLE.get();
|
|
if (inputs.afterburnerSwitch() && inputs.throttle() < minimum) {
|
|
// Too little throttle: out, and it stays out until the key is pressed again
|
|
inputs.node().getPersistentData().putBoolean(AfterburnerToggle.TAG, false);
|
|
inputs.node().setChanged();
|
|
}
|
|
afterburnerAuto = running && inputs.afterburnerSwitch() && inputs.throttle() >= minimum;
|
|
}
|
|
|
|
/** Saves the state, and tells the clients if something they can see changed. */
|
|
private void changed() {
|
|
setChanged();
|
|
sync();
|
|
}
|
|
|
|
private void sync() {
|
|
if (level == null || level.isClientSide) {
|
|
return;
|
|
}
|
|
int deflection = Math.round(deflectionTarget / DEFLECTION_STEP);
|
|
int throttleStep = Math.round(throttle / THROTTLE_STEP);
|
|
if (deflection != sentDeflection || throttleStep != sentThrottle || afterburner != sentAfterburner || rightEngine != sentRightEngine) {
|
|
sentDeflection = deflection;
|
|
sentThrottle = throttleStep;
|
|
sentAfterburner = afterburner;
|
|
sentRightEngine = rightEngine;
|
|
level.sendBlockUpdated(worldPosition, getBlockState(), getBlockState(), UPDATE_CLIENTS);
|
|
}
|
|
}
|
|
|
|
private static final int UPDATE_CLIENTS = 2;
|
|
|
|
// ---- Saving and syncing ----
|
|
|
|
@Override
|
|
protected void saveAdditional(CompoundTag tag) {
|
|
super.saveAdditional(tag);
|
|
tag.putFloat("Deflection", deflectionTarget);
|
|
tag.putFloat("Throttle", throttle);
|
|
tag.putBoolean("Afterburner", afterburner);
|
|
tag.putBoolean("AfterburnerRequested", afterburnerRequested);
|
|
tag.putBoolean("RightEngine", rightEngine);
|
|
}
|
|
|
|
@Override
|
|
public void load(CompoundTag tag) {
|
|
super.load(tag);
|
|
deflectionTarget = tag.getFloat("Deflection");
|
|
throttle = tag.getFloat("Throttle");
|
|
afterburner = tag.getBoolean("Afterburner");
|
|
afterburnerRequested = tag.getBoolean("AfterburnerRequested");
|
|
afterburnerAuto = afterburner && !afterburnerRequested;
|
|
rightEngine = tag.getBoolean("RightEngine");
|
|
if (level == null || !level.isClientSide || deflectionCurrent == 0.0F && deflectionPrevious == 0.0F) {
|
|
// Loaded fresh: start at the target instead of swinging in from zero
|
|
deflectionCurrent = deflectionTarget;
|
|
deflectionPrevious = deflectionTarget;
|
|
}
|
|
sentDeflection = Math.round(deflectionTarget / DEFLECTION_STEP);
|
|
sentThrottle = Math.round(throttle / THROTTLE_STEP);
|
|
sentAfterburner = afterburner;
|
|
sentRightEngine = rightEngine;
|
|
}
|
|
|
|
@Override
|
|
public CompoundTag getUpdateTag() {
|
|
return saveWithoutMetadata();
|
|
}
|
|
|
|
@Override
|
|
public Packet<ClientGamePacketListener> getUpdatePacket() {
|
|
return ClientboundBlockEntityDataPacket.create(this);
|
|
}
|
|
|
|
@Override
|
|
public void onDataPacket(Connection connection, ClientboundBlockEntityDataPacket packet) {
|
|
CompoundTag tag = packet.getTag();
|
|
if (tag != null) {
|
|
// Warium's key configuration screen reads the keys from the block's persistent data on the client, so that
|
|
// has to arrive too. Not load() as a whole: the deflection on screen must keep easing from where it is
|
|
super.load(tag);
|
|
deflectionTarget = tag.getFloat("Deflection");
|
|
throttle = tag.getFloat("Throttle");
|
|
afterburner = tag.getBoolean("Afterburner");
|
|
rightEngine = tag.getBoolean("RightEngine");
|
|
}
|
|
}
|
|
|
|
// ---- Client ----
|
|
|
|
@Nullable
|
|
public Object getClientEffects() {
|
|
return clientEffects;
|
|
}
|
|
|
|
public void setClientEffects(@Nullable Object clientEffects) {
|
|
this.clientEffects = clientEffects;
|
|
}
|
|
|
|
@Override
|
|
public AABB getRenderBoundingBox() {
|
|
// The nozzle swings out of the block and the flame trails behind it
|
|
return new AABB(worldPosition).inflate(2.0);
|
|
}
|
|
|
|
@Override
|
|
public void setRemoved() {
|
|
super.setRemoved();
|
|
stopEffects();
|
|
}
|
|
|
|
@Override
|
|
public void onChunkUnloaded() {
|
|
super.onChunkUnloaded();
|
|
stopEffects();
|
|
}
|
|
|
|
private void stopEffects() {
|
|
if (level != null && level.isClientSide) {
|
|
NozzleClientEffects.stop(this);
|
|
}
|
|
}
|
|
}
|