Skip to content

Commit 43d6e9e

Browse files
committed
barely working plumes
1 parent e525905 commit 43d6e9e

22 files changed

Lines changed: 561 additions & 24 deletions

src/main/java/dev/propulsionteam/propulsionsimulated/PropulsionConfig.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ public class PropulsionConfig {
5353
public static final ModConfigSpec.DoubleValue ATMOSPHERIC_PRESSURE_AMOUNT;
5454
public static final ModConfigSpec.DoubleValue THRUST_UNITS_PER_KN;
5555
public static final ModConfigSpec.IntValue CLIENT_PARTICLES_PER_TICK;
56+
public static final ModConfigSpec.BooleanValue USE_TEXTURE_PLUMES;
5657
public static final Map<String, ModConfigSpec.IntValue> FUEL_EFFICIENCY_ENTRIES = new LinkedHashMap<>();
5758
public static final Map<String, ModConfigSpec.IntValue> FUEL_BURN_RATE_ENTRIES = new LinkedHashMap<>();
5859
public static final Map<String, ModConfigSpec.ConfigValue<String>> THRUSTER_DYE_COLORS = new LinkedHashMap<>();
@@ -100,6 +101,9 @@ public class PropulsionConfig {
100101

101102
CLIENT_PARTICLES_PER_TICK = COMMON_BUILDER.comment("Max client particles per tick while active.")
102103
.defineInRange("clientParticlesPerTick", 4, 0, 64);
104+
USE_TEXTURE_PLUMES = COMMON_BUILDER.comment(
105+
"Render thruster exhaust with animated translucent models (Aeronautics steam-vent style) instead of particles.")
106+
.define("useTexturePlumes", true);
103107

104108
COMMON_BUILDER.pop(); // thruster
105109

src/main/java/dev/propulsionteam/propulsionsimulated/content/thruster/AbstractThrusterBlockEntity.java

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,9 @@ public abstract class AbstractThrusterBlockEntity extends SmartBlockEntity
6666
//Ticking
6767
private int currentTick = 0;
6868

69+
/** Client-only; smoothed plume fade/length for texture exhaust. */
70+
private ThrusterPlumeVisuals plumeVisuals;
71+
6972
protected double getParticleBroadcastRange() { return PARTICLE_BROADCAST_RANGE_BLOCKS; }
7073
protected float getParticleVelocity() { return PARTICLE_VELOCITY; }
7174
protected double getThrustUnitsPerKn() { return PropulsionConfig.getThrustUnitsPerKnOrDefault(); }
@@ -97,9 +100,11 @@ public AbstractThrusterBlockEntity(BlockEntityType<?> typeIn, BlockPos pos, Bloc
97100
@Override
98101
public void initialize() {
99102
super.initialize();
103+
BlockState state = getBlockState();
104+
Direction facing = state.getValue(AbstractThrusterBlock.FACING);
105+
calculateObstruction(level, worldPosition, facing);
106+
100107
if (!level.isClientSide) {
101-
BlockState state = getBlockState();
102-
calculateObstruction(level, worldPosition, state.getValue(AbstractThrusterBlock.FACING));
103108
ThrusterData data = this.getThrusterData();
104109
data.setDirection(getThrustDirectionLocal());
105110
data.setThrust(0);
@@ -146,6 +151,9 @@ public float getPower() {
146151
if (controlMode == ControlMode.PERIPHERAL) {
147152
return digitalInput;
148153
}
154+
if (level != null && level.isClientSide) {
155+
return level.getBestNeighborSignal(worldPosition) / 15.0f;
156+
}
149157
return redstoneInput / 15.0f;
150158
}
151159

@@ -185,6 +193,11 @@ public void tick() {
185193
BlockState currentBlockState = isOutsideWorldHeight ? getBlockState() : SimulatedThrustAdapter.getBlockStateSafe(level,worldPosition);
186194
if (level.isClientSide) {
187195
ThrusterSoundHooks.clientTick(this);
196+
getPlumeVisuals().clientTick(this);
197+
if (currentTick % 20 == 0) {
198+
calculateObstruction(level, worldPosition, currentBlockState.getValue(AbstractThrusterBlock.FACING));
199+
}
200+
currentTick++;
188201
return;
189202
}
190203
if (shouldEmitParticles()) {
@@ -245,9 +258,48 @@ public void dirtyThrust() {
245258
}
246259

247260
public boolean shouldEmitParticles() {
261+
if (PropulsionConfig.USE_TEXTURE_PLUMES.get() && supportsTexturePlume()) {
262+
return false;
263+
}
248264
return isPowered() && isWorking();
249265
}
250266

267+
public ThrusterPlumeVisuals getPlumeVisuals() {
268+
if (plumeVisuals == null) {
269+
plumeVisuals = new ThrusterPlumeVisuals();
270+
}
271+
return plumeVisuals;
272+
}
273+
274+
/** When false, creative/solid thrusters with particle type NONE skip texture plumes too. */
275+
protected boolean supportsTexturePlume() {
276+
return true;
277+
}
278+
279+
public boolean shouldRenderTexturePlume() {
280+
if (!PropulsionConfig.USE_TEXTURE_PLUMES.get() || !supportsTexturePlume()) {
281+
return false;
282+
}
283+
if (emptyBlocks == 0 || !isPowered()) {
284+
return false;
285+
}
286+
if (isMultiblockThruster() && !isMultiblockController()) {
287+
return false;
288+
}
289+
if (level != null && level.isClientSide) {
290+
return getThrottle() > 0.01f && (getCurrentThrust() > 0.01f || isWorking());
291+
}
292+
return getThrottle() > 0.01f && isWorking();
293+
}
294+
295+
protected boolean isMultiblockThruster() {
296+
return false;
297+
}
298+
299+
protected boolean isMultiblockController() {
300+
return true;
301+
}
302+
251303
protected boolean shouldDamageEntities() {
252304
return PropulsionConfig.DAMAGE_ENTITIES.get() && isPowered() && isWorking();
253305
}
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
package dev.propulsionteam.propulsionsimulated.content.thruster;
2+
3+
import net.createmod.catnip.math.VecHelper;
4+
import dev.propulsionteam.propulsionsimulated.content.thruster.creative_thruster.CreativeThrusterBlockEntity;
5+
import dev.propulsionteam.propulsionsimulated.content.thruster.ion_thruster.IonThrusterBlockEntity;
6+
import dev.propulsionteam.propulsionsimulated.content.thruster.solid_fuel_thruster.SolidFuelThrusterBlockEntity;
7+
import net.minecraft.core.Direction;
8+
import net.minecraft.world.phys.Vec3;
9+
10+
/**
11+
* Maps model-space nozzle openings (voxel coords) into world block space (0–1 from block min corner),
12+
* using the same centered rotations as {@link net.createmod.catnip.math.VecHelper} /
13+
* blockstate variants.
14+
*/
15+
public final class ThrusterPlumeAnchors {
16+
private ThrusterPlumeAnchors() {
17+
}
18+
19+
/** Offset from block center (0.5, 0.5, 0.5) for BER pose stack. */
20+
public static Vec3 offsetFromBlockCenter(AbstractThrusterBlockEntity blockEntity) {
21+
Vec3 model = modelNozzleOpening(blockEntity);
22+
Vec3 world = rotateModelToWorld(model, blockEntity.getFacing(), layout(blockEntity));
23+
return world.subtract(0.5, 0.5, 0.5);
24+
}
25+
26+
private static Vec3 modelNozzleOpening(AbstractThrusterBlockEntity blockEntity) {
27+
if (blockEntity instanceof SolidFuelThrusterBlockEntity) {
28+
return voxel(13, 8, 8);
29+
}
30+
if (blockEntity instanceof IonThrusterBlockEntity) {
31+
return voxel(8, 1, 8);
32+
}
33+
if (blockEntity instanceof CreativeThrusterBlockEntity) {
34+
return voxel(8, 8, 10);
35+
}
36+
// Standard / fluid thruster — nozzle mouth at low +Z in model file
37+
return voxel(8, 8, 14);
38+
}
39+
40+
private static Vec3 voxel(float x, float y, float z) {
41+
return new Vec3(x / 16.0, y / 16.0, z / 16.0);
42+
}
43+
44+
private static Layout layout(AbstractThrusterBlockEntity blockEntity) {
45+
if (blockEntity instanceof SolidFuelThrusterBlockEntity) {
46+
return Layout.SOLID_FUEL;
47+
}
48+
if (blockEntity instanceof IonThrusterBlockEntity) {
49+
return Layout.ION;
50+
}
51+
return Layout.THRUSTER;
52+
}
53+
54+
private static Vec3 rotateModelToWorld(Vec3 point, Direction facing, Layout layout) {
55+
return switch (layout) {
56+
case THRUSTER -> rotateThruster(point, facing);
57+
case SOLID_FUEL -> rotateSolidFuel(point, facing);
58+
case ION -> rotateIon(point, facing);
59+
};
60+
}
61+
62+
/** Same as {@code VectorThrusterBlockEntity.VectorThrusterLinkTransform#rotatePointForFacing}. */
63+
private static Vec3 rotateThruster(Vec3 vec, Direction facing) {
64+
return switch (facing) {
65+
case NORTH -> vec;
66+
case EAST -> VecHelper.rotateCentered(vec, -90, Direction.Axis.Y);
67+
case SOUTH -> VecHelper.rotateCentered(vec, 180, Direction.Axis.Y);
68+
case WEST -> VecHelper.rotateCentered(vec, 90, Direction.Axis.Y);
69+
case UP -> VecHelper.rotateCentered(vec, 90, Direction.Axis.X);
70+
case DOWN -> VecHelper.rotateCentered(vec, -90, Direction.Axis.X);
71+
};
72+
}
73+
74+
/** Matches {@code assets/.../blockstates/solid_fuel_thruster.json}. */
75+
private static Vec3 rotateSolidFuel(Vec3 vec, Direction facing) {
76+
return switch (facing) {
77+
case WEST -> vec;
78+
case EAST -> VecHelper.rotateCentered(vec, 180, Direction.Axis.Y);
79+
case NORTH -> VecHelper.rotateCentered(vec, 90, Direction.Axis.Y);
80+
case SOUTH -> VecHelper.rotateCentered(vec, -90, Direction.Axis.Y);
81+
case UP -> VecHelper.rotateCentered(
82+
VecHelper.rotateCentered(vec, -90, Direction.Axis.X),
83+
90, Direction.Axis.Y);
84+
case DOWN -> VecHelper.rotateCentered(
85+
VecHelper.rotateCentered(vec, 90, Direction.Axis.X),
86+
90, Direction.Axis.Y);
87+
};
88+
}
89+
90+
/** Matches {@code assets/.../blockstates/ion_thruster.json}. */
91+
private static Vec3 rotateIon(Vec3 vec, Direction facing) {
92+
return switch (facing) {
93+
case UP -> vec;
94+
case DOWN -> VecHelper.rotateCentered(vec, 180, Direction.Axis.X);
95+
case NORTH -> VecHelper.rotateCentered(vec, 90, Direction.Axis.X);
96+
case SOUTH -> VecHelper.rotateCentered(
97+
VecHelper.rotateCentered(vec, 180, Direction.Axis.Y),
98+
90, Direction.Axis.X);
99+
case EAST -> VecHelper.rotateCentered(
100+
VecHelper.rotateCentered(vec, 90, Direction.Axis.Y),
101+
90, Direction.Axis.X);
102+
case WEST -> VecHelper.rotateCentered(
103+
VecHelper.rotateCentered(vec, -90, Direction.Axis.Y),
104+
90, Direction.Axis.X);
105+
};
106+
}
107+
108+
private enum Layout {
109+
THRUSTER,
110+
SOLID_FUEL,
111+
ION
112+
}
113+
}

0 commit comments

Comments
 (0)