Skip to content

Commit 34aef8e

Browse files
committed
Added train banking
1 parent f1b4fcc commit 34aef8e

11 files changed

Lines changed: 423 additions & 6 deletions

File tree

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package net.Realism.Interfaces;
2+
3+
import java.util.UUID;
4+
5+
/**
6+
* Interface for adding banking (roll) functionality to OrientedContraptionEntity
7+
*/
8+
public interface IOrientedContraptionEntity {
9+
/**
10+
* Get the current roll angle
11+
*/
12+
float realism$getRoll();
13+
14+
/**
15+
* Set the current roll angle
16+
*/
17+
void realism$setRoll(float roll);
18+
19+
/**
20+
* Get the previous tick's roll angle (for interpolation)
21+
*/
22+
float realism$getPrevRoll();
23+
24+
/**
25+
* Set the previous tick's roll angle (for interpolation)
26+
*/
27+
void realism$setPrevRoll(float prevRoll);
28+
29+
/**
30+
* Get the interpolated roll angle for rendering
31+
* @param partialTicks The partial tick time
32+
* @return Interpolated roll angle
33+
*/
34+
float realism$getViewRoll(float partialTicks);
35+
36+
UUID getuid();
37+
}
38+

common/src/main/java/net/Realism/RNetworking.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import io.netty.buffer.Unpooled;
55
import net.Realism.network.ETCSStartStopPacket;
66
import net.Realism.network.ETCSSyncPacket;
7+
import net.Realism.network.RollSyncPacket;
78
import net.Realism.network.SteerDirectionPacket;
89
import net.Realism.util.C2SPacket;
910
import net.Realism.util.S2CPacket;
@@ -145,6 +146,10 @@ public static void register() {
145146
ETCSStartStopPacket::read
146147

147148
);
149+
registerS2C(
150+
RollSyncPacket.class,
151+
RollSyncPacket::read
152+
);
148153

149154
}
150155
}

common/src/main/java/net/Realism/config/RealismConfig.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,12 @@ public static class Client {
3232
public final ForgeConfigSpec.DoubleValue ETCSSize;
3333
public final ForgeConfigSpec.BooleanValue ETCSSounds;
3434

35+
// Banking Configuration
36+
public final ForgeConfigSpec.BooleanValue enableBanking;
37+
public final ForgeConfigSpec.DoubleValue maxBankingAngle;
38+
public final ForgeConfigSpec.DoubleValue bankingSmoothness;
39+
public final ForgeConfigSpec.DoubleValue bankingMinSpeed;
40+
public final ForgeConfigSpec.DoubleValue bankingIntensity;
3541

3642
Client(ForgeConfigSpec.Builder builder) {
3743
builder.push("general");
@@ -43,6 +49,20 @@ public static class Client {
4349
ETCSSounds = builder.comment("Enable ETCS sounds")
4450
.define("ETCS Sounds", true);
4551
builder.pop();
52+
53+
builder.push("Banking");
54+
enableBanking = builder.comment("Enable banking (roll rotation) on curved tracks")
55+
.define("Enable Banking", true);
56+
maxBankingAngle = builder.comment("Maximum banking angle in degrees")
57+
.defineInRange("Max Banking Angle", 30.0, 0.0, 90.0);
58+
bankingSmoothness = builder.comment("Banking transition smoothness (0.0 = instant, 1.0 = very smooth)")
59+
.defineInRange("Banking Smoothness", 0.15, 0.0, 1.0);
60+
bankingMinSpeed = builder.comment("Minimum speed (blocks/tick) for full banking effect")
61+
.defineInRange("Banking Min Speed", 0.5, 0.0, 10.0);
62+
bankingIntensity = builder.comment("Banking intensity multiplier")
63+
.defineInRange("Banking Intensity", 1.0, 0.0, 10.0);
64+
builder.pop();
65+
4666
debugMode = builder.comment("Enable debug mode to see the modified acceleration")
4767
.define("debugMode", false);
4868
builder.pop();

common/src/main/java/net/Realism/mixin/CarriageContraptionEntityMixin.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@
1515
@Mixin(value = CarriageContraptionEntity.class)
1616
public class CarriageContraptionEntityMixin {
1717

18-
@Shadow private Carriage carriage;
18+
@Shadow
19+
private Carriage carriage;
1920

2021
@Inject(method = "startControlling", at = @At("RETURN"), cancellable = true)
2122
private void afterStartControlling(BlockPos controlsLocalPos, Player player, CallbackInfoReturnable<Boolean> cir) {
@@ -30,6 +31,4 @@ private void afterStartControlling(BlockPos controlsLocalPos, Player player, Cal
3031
}
3132
}
3233

33-
34-
35-
}
34+
}
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
package net.Realism.mixin;
2+
3+
import com.simibubi.create.content.trains.entity.Carriage;
4+
import com.simibubi.create.content.trains.entity.CarriageBogey;
5+
import com.simibubi.create.content.trains.entity.CarriageContraptionEntity;
6+
import com.simibubi.create.content.trains.entity.TravellingPoint;
7+
import com.simibubi.create.content.trains.graph.TrackEdge;
8+
import com.simibubi.create.content.trains.track.BezierConnection;
9+
import net.Realism.Interfaces.IOrientedContraptionEntity;
10+
import net.Realism.RNetworking;
11+
import net.Realism.config.RealismConfig;
12+
import net.Realism.network.RollSyncPacket;
13+
import net.createmod.catnip.data.Couple;
14+
import net.createmod.catnip.math.VecHelper;
15+
import net.minecraft.util.Mth;
16+
import net.minecraft.world.phys.Vec3;
17+
import org.spongepowered.asm.mixin.Final;
18+
import org.spongepowered.asm.mixin.Mixin;
19+
import org.spongepowered.asm.mixin.Shadow;
20+
import org.spongepowered.asm.mixin.Unique;
21+
import org.spongepowered.asm.mixin.injection.At;
22+
import org.spongepowered.asm.mixin.injection.Inject;
23+
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
24+
25+
import java.util.Objects;
26+
27+
/**
28+
* Mixin for Carriage.DimensionalCarriageEntity to calculate banking angles
29+
*/
30+
@Mixin(targets = "com.simibubi.create.content.trains.entity.Carriage$DimensionalCarriageEntity", remap = false)
31+
public class CarriageDimensionalEntityMixin {
32+
33+
@Shadow
34+
@Final
35+
Carriage this$0; // The outer Carriage instance
36+
37+
@Shadow
38+
public Couple<Vec3> rotationAnchors;
39+
40+
/**
41+
* Inject banking calculation into alignEntity method
42+
*/
43+
@Inject(method = "alignEntity", at = @At("TAIL"))
44+
private void calculateAndApplyBanking(CarriageContraptionEntity entity, CallbackInfo ci) {
45+
// Check if banking is enabled
46+
if (!RealismConfig.CLIENT.enableBanking.get()) {
47+
return;
48+
}
49+
50+
// Ensure we have valid rotation anchors
51+
if (rotationAnchors.either(Objects::isNull)) {
52+
return;
53+
}
54+
55+
// Update previous roll
56+
IOrientedContraptionEntity orientedEntity = (IOrientedContraptionEntity) entity;
57+
orientedEntity.realism$setPrevRoll(orientedEntity.realism$getRoll());
58+
59+
// Calculate banking
60+
float banking = realism$calculateBanking(entity);
61+
orientedEntity.realism$setRoll(banking);
62+
RNetworking.sendToAll(new RollSyncPacket(orientedEntity.realism$getRoll(),orientedEntity.realism$getPrevRoll(),orientedEntity.getuid()));
63+
64+
// For first position update, ensure prevRoll matches roll
65+
if (entity.firstPositionUpdate) {
66+
orientedEntity.realism$setPrevRoll(banking);
67+
RNetworking.sendToAll(new RollSyncPacket(orientedEntity.realism$getRoll(),orientedEntity.realism$getPrevRoll(),orientedEntity.getuid()));
68+
}
69+
}
70+
71+
/**
72+
* Calculate banking angle based on track curvature and train speed
73+
*/
74+
@Unique
75+
private float realism$calculateBanking(CarriageContraptionEntity entity) {
76+
// Get the leading bogey
77+
CarriageBogey leadingBogey = this$0.leadingBogey();
78+
if (leadingBogey == null) {
79+
return 0f;
80+
}
81+
82+
// Get the leading travelling point
83+
TravellingPoint leadingPoint = leadingBogey.leading();
84+
if (leadingPoint == null || leadingPoint.edge == null) {
85+
return 0f;
86+
}
87+
88+
TrackEdge edge = leadingPoint.edge;
89+
90+
// Only apply banking on curves (BezierConnections)
91+
if (!edge.isTurn()) {
92+
return(0f);
93+
}
94+
95+
BezierConnection turn = edge.getTurn();
96+
if (turn == null || turn.normals == null) {
97+
return 0f;
98+
}
99+
100+
// Calculate normalized position along the curve (0.0 to 1.0)
101+
double edgeLength = edge.getLength();
102+
double t = edgeLength == 0 ? 0.5 : leadingPoint.position / edgeLength;
103+
t = Mth.clamp(t, 0.0, 1.0);
104+
105+
// Get interpolated track normal at current position
106+
Vec3 trackNormal = realism$getTrackNormalAt(turn, t);
107+
if (trackNormal == null) {
108+
return 0f;
109+
}
110+
111+
// Get train's forward direction
112+
Vec3 positionVec = rotationAnchors.getFirst();
113+
Vec3 coupledVec = rotationAnchors.getSecond();
114+
115+
double diffX = positionVec.x - coupledVec.x;
116+
double diffY = positionVec.y - coupledVec.y;
117+
double diffZ = positionVec.z - coupledVec.z;
118+
119+
Vec3 forward = new Vec3(diffX, diffY, diffZ).normalize();
120+
Vec3 worldUp = new Vec3(0, 1, 0);
121+
Vec3 right = forward.cross(worldUp).normalize();
122+
123+
// If right vector is too small, we're going straight up/down - no banking
124+
if (right.lengthSqr() < 0.001) {
125+
return 0f;
126+
}
127+
128+
// Project track normal onto right vector to get banking
129+
double bankingDot = trackNormal.dot(right);
130+
bankingDot = Mth.clamp(bankingDot, -1.0, 1.0);
131+
float bankingAngle = (float) Math.toDegrees(Math.asin(bankingDot));
132+
133+
// Apply intensity multiplier
134+
float intensity = RealismConfig.CLIENT.bankingIntensity.get().floatValue();
135+
bankingAngle *= intensity;
136+
137+
// Apply speed-based scaling
138+
double speed = Math.abs(this$0.train.speed);
139+
float minSpeed = RealismConfig.CLIENT.bankingMinSpeed.get().floatValue();
140+
float speedFactor = minSpeed > 0 ? (float) Mth.clamp(speed / minSpeed, 0, 1) : 1f;
141+
142+
float targetBanking = bankingAngle * speedFactor;
143+
144+
// Apply smoothing to prevent jarring transitions
145+
float currentRoll = ((IOrientedContraptionEntity) entity).realism$getRoll();
146+
float smoothness = RealismConfig.CLIENT.bankingSmoothness.get().floatValue();
147+
148+
// Clamp to maximum banking angle
149+
float maxAngle = RealismConfig.CLIENT.maxBankingAngle.get().floatValue();
150+
return Mth.clamp(targetBanking, -maxAngle, maxAngle);
151+
}
152+
153+
/**
154+
* Interpolate track normal at position t along the curve
155+
*/
156+
@Unique
157+
private Vec3 realism$getTrackNormalAt(BezierConnection turn, double t) {
158+
if (turn.normals == null) {
159+
return null;
160+
}
161+
162+
Vec3 normal1 = turn.axes.getFirst();
163+
Vec3 normal2 = turn.axes.getSecond();
164+
165+
// Linear interpolation between start and end normals
166+
Vec3 interpolated = VecHelper.lerp((float) t, normal1, normal2);
167+
168+
// Normalize to ensure it's a unit vector
169+
double length = interpolated.length();
170+
if (length < 0.001) {
171+
return new Vec3(0, 1, 0); // Default to up
172+
}
173+
174+
return interpolated.normalize();
175+
}
176+
}
177+
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
package net.Realism.mixin;
2+
3+
import com.simibubi.create.content.contraptions.OrientedContraptionEntity;
4+
import net.Realism.Interfaces.IOrientedContraptionEntity;
5+
import net.minecraft.nbt.CompoundTag;
6+
import net.minecraft.util.Mth;
7+
import org.spongepowered.asm.mixin.Mixin;
8+
import org.spongepowered.asm.mixin.Unique;
9+
import org.spongepowered.asm.mixin.injection.At;
10+
import org.spongepowered.asm.mixin.injection.Inject;
11+
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
12+
13+
import java.util.UUID;
14+
15+
/**
16+
* Mixin to add roll (banking) functionality to OrientedContraptionEntity
17+
*/
18+
@Mixin(value = OrientedContraptionEntity.class, remap = false)
19+
public abstract class OrientedContraptionEntityMixin implements IOrientedContraptionEntity {
20+
21+
@Unique
22+
private float realism$roll = 0;
23+
24+
@Unique
25+
private float realism$prevRoll = 0;
26+
27+
@Unique
28+
public UUID getuid(){
29+
return ((net.minecraft.world.entity.Entity)(Object)this).getUUID();
30+
}
31+
32+
@Override
33+
@Unique
34+
public float realism$getRoll() {
35+
return realism$roll;
36+
}
37+
38+
@Override
39+
@Unique
40+
public void realism$setRoll(float roll) {
41+
this.realism$roll = roll;
42+
}
43+
44+
@Override
45+
@Unique
46+
public float realism$getPrevRoll() {
47+
return realism$prevRoll;
48+
}
49+
50+
@Override
51+
@Unique
52+
public void realism$setPrevRoll(float prevRoll) {
53+
this.realism$prevRoll = prevRoll;
54+
}
55+
56+
@Override
57+
@Unique
58+
public float realism$getViewRoll(float partialTicks) {
59+
return Mth.lerp(partialTicks, realism$prevRoll, realism$roll);
60+
}
61+
62+
/**
63+
* Save roll to NBT
64+
*/
65+
@Inject(method = "writeAdditional", at = @At("TAIL"))
66+
protected void writeRollToNBT(CompoundTag compound, boolean spawnPacket, CallbackInfo ci) {
67+
compound.putFloat("Roll", realism$roll);
68+
}
69+
70+
/**
71+
* Load roll from NBT
72+
*/
73+
@Inject(method = "readAdditional", at = @At("TAIL"))
74+
protected void readRollFromNBT(CompoundTag compound, boolean spawnPacket, CallbackInfo ci) {
75+
realism$roll = compound.getFloat("Roll");
76+
realism$prevRoll = realism$roll;
77+
}
78+
79+
/**
80+
* Inject roll into ContraptionRotationState
81+
*/
82+
// @Inject(method = "getRotationState", at = @At("RETURN"), cancellable = true)
83+
// public void injectRollIntoRotationState(@NotNull CallbackInfoReturnable<AbstractContraptionEntity.ContraptionRotationState> cir) {
84+
// AbstractContraptionEntity.ContraptionRotationState crs = cir.getReturnValue();
85+
//
86+
// // Set xRotation to our roll value
87+
// ((ContraptionRotationStateAccessor) crs).setXRotation(realism$roll);
88+
//
89+
// cir.setReturnValue(crs);
90+
// }
91+
}
92+

0 commit comments

Comments
 (0)