Add vectored afterburner nozzle with afterburner key and exhaust style
- 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
This commit is contained in:
1 parent
1e659816b8
commit
70eb237d70
56 files changed
+2197
-12
No files matched your search
@@ -0,0 +1,281 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user