Politeness Intensifies

- Train navigation now tries to pick destinations and directions avoiding other trains and stations on the way
- Trains now slow to a secondary top speed when approaching a turn
- Attempts to fix trains not always showing up when entering a clients tracking distance
- Substantial increase to carriage contraption's client tracking range
- Fixed Carriage Contraptions starting to render before fully aligned to their position/angle
- Fixed trains remaining stuck to far away signals after being controlled manually for a bit
- Fixed crash when placing tracks into a replaceable block
- Fixed a handful of dist issues for dedicated servers
- Fixed controls allowing control even when a train is not fully assembled yet
- Controls now disengage on relog/esc
This commit is contained in:
simibubi 2022-03-11 23:37:41 +01:00
parent 7ba4af1bea
commit 71e18eb505
25 changed files with 380 additions and 150 deletions

View file

@ -2143,7 +2143,7 @@ d080b1b25e5bc8baf5aee68691b08c7f12ece3b0 assets/create/models/item/windmill_bear
a80fb25a0b655e76be986b5b49fcb0f03461a1ab assets/create/models/item/zinc_nugget.json
b1689617190c05ef34bd18456b0c7ae09bb3210f assets/create/models/item/zinc_ore.json
5049f72c327a88f175f6f9425909e098fc711100 assets/create/sounds.json
0f1b4b980afba9bf2caf583b88e261bba8b10313 data/create/advancements/aesthetics.json
5d0cc4c0255dc241e61c173b31ddca70c88d08e4 data/create/advancements/aesthetics.json
613e64b44bed959da899fdd54c1cacb227fb33f2 data/create/advancements/andesite_alloy.json
81885c6bfb85792c88aaa7c9b70f58832945d31f data/create/advancements/andesite_casing.json
83c046bd200623933545c9e4326f782fb02c87fa data/create/advancements/arm_blaze_burner.json

View file

@ -28,8 +28,8 @@
"trigger": "create:bracket_apply",
"conditions": {
"accepted_entries": [
"create:cogwheel",
"create:large_cogwheel"
"create:large_cogwheel",
"create:cogwheel"
]
}
},

View file

@ -41,7 +41,7 @@ public class AllEntityTypes {
GantryContraptionEntity::new, () -> ContraptionEntityRenderer::new, 10, 40, false);
public static final EntityEntry<CarriageContraptionEntity> CARRIAGE_CONTRAPTION =
contraption("carriage_contraption", CarriageContraptionEntity::new,
() -> CarriageContraptionEntityRenderer::new, 5, 3, true);
() -> CarriageContraptionEntityRenderer::new, 15, 3, true);
public static final EntityEntry<SuperGlueEntity> SUPER_GLUE =
register("super_glue", SuperGlueEntity::new, () -> SuperGlueRenderer::new, MobCategory.MISC, 10,

View file

@ -5,6 +5,9 @@ import java.util.Collection;
import java.util.HashSet;
import java.util.Vector;
import org.lwjgl.glfw.GLFW;
import com.mojang.blaze3d.platform.InputConstants;
import com.simibubi.create.content.contraptions.components.structureMovement.AbstractContraptionEntity;
import com.simibubi.create.foundation.networking.AllPackets;
import com.simibubi.create.foundation.utility.ControlsUtil;
@ -13,6 +16,7 @@ import com.simibubi.create.foundation.utility.Lang;
import net.minecraft.client.KeyMapping;
import net.minecraft.client.Minecraft;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.LevelAccessor;
public class ControlsHandler {
@ -24,6 +28,13 @@ public class ControlsHandler {
static WeakReference<AbstractContraptionEntity> entityRef = new WeakReference<>(null);
static BlockPos controlsPos;
public static void levelUnloaded(LevelAccessor level) {
packetCooldown = 0;
entityRef = new WeakReference<>(null);
controlsPos = null;
currentlyPressed.clear();
}
public static void startControlling(AbstractContraptionEntity entity, BlockPos controllerLocalPos) {
entityRef = new WeakReference<AbstractContraptionEntity>(entity);
controlsPos = controllerLocalPos;
@ -36,10 +47,10 @@ public class ControlsHandler {
ControlsUtil.getControls()
.forEach(kb -> kb.setDown(ControlsUtil.isActuallyPressed(kb)));
AbstractContraptionEntity abstractContraptionEntity = entityRef.get();
if (!currentlyPressed.isEmpty() && abstractContraptionEntity != null)
AllPackets.channel.sendToServer(
new ControlsInputPacket(currentlyPressed, false, abstractContraptionEntity.getId(), controlsPos));
AllPackets.channel.sendToServer(new ControlsInputPacket(currentlyPressed, false,
abstractContraptionEntity.getId(), controlsPos, false));
packetCooldown = 0;
entityRef = new WeakReference<>(null);
@ -57,6 +68,16 @@ public class ControlsHandler {
if (packetCooldown > 0)
packetCooldown--;
if (InputConstants.isKeyDown(Minecraft.getInstance()
.getWindow()
.getWindow(), GLFW.GLFW_KEY_ESCAPE)) {
BlockPos pos = controlsPos;
stopControlling();
AllPackets.channel
.sendToServer(new ControlsInputPacket(currentlyPressed, false, entity.getId(), pos, true));
return;
}
Vector<KeyMapping> controls = ControlsUtil.getControls();
Collection<Integer> pressedKeys = new HashSet<>();
for (int i = 0; i < controls.size(); i++) {
@ -71,13 +92,14 @@ public class ControlsHandler {
// Released Keys
if (!releasedKeys.isEmpty()) {
AllPackets.channel.sendToServer(new ControlsInputPacket(releasedKeys, false, entity.getId(), controlsPos));
AllPackets.channel
.sendToServer(new ControlsInputPacket(releasedKeys, false, entity.getId(), controlsPos, false));
// AllSoundEvents.CONTROLLER_CLICK.playAt(player.level, player.blockPosition(), 1f, .5f, true);
}
// Newly Pressed Keys
if (!newKeys.isEmpty()) {
AllPackets.channel.sendToServer(new ControlsInputPacket(newKeys, true, entity.getId(), controlsPos));
AllPackets.channel.sendToServer(new ControlsInputPacket(newKeys, true, entity.getId(), controlsPos, false));
packetCooldown = PACKET_RATE;
// AllSoundEvents.CONTROLLER_CLICK.playAt(player.level, player.blockPosition(), 1f, .75f, true);
}
@ -86,7 +108,7 @@ public class ControlsHandler {
if (packetCooldown == 0) {
if (!pressedKeys.isEmpty()) {
AllPackets.channel
.sendToServer(new ControlsInputPacket(pressedKeys, true, entity.getId(), controlsPos));
.sendToServer(new ControlsInputPacket(pressedKeys, true, entity.getId(), controlsPos, false));
packetCooldown = PACKET_RATE;
}
}

View file

@ -22,13 +22,15 @@ public class ControlsInputPacket extends SimplePacketBase {
private boolean press;
private int contraptionEntityId;
private BlockPos controlsPos;
private boolean stopControlling;
public ControlsInputPacket(Collection<Integer> activatedButtons, boolean press, int contraptionEntityId,
BlockPos controlsPos) {
BlockPos controlsPos, boolean stopControlling) {
this.contraptionEntityId = contraptionEntityId;
this.activatedButtons = activatedButtons;
this.press = press;
this.controlsPos = controlsPos;
this.stopControlling = stopControlling;
}
public ControlsInputPacket(FriendlyByteBuf buffer) {
@ -39,6 +41,7 @@ public class ControlsInputPacket extends SimplePacketBase {
for (int i = 0; i < size; i++)
activatedButtons.add(buffer.readVarInt());
controlsPos = buffer.readBlockPos();
stopControlling = buffer.readBoolean();
}
@Override
@ -48,6 +51,7 @@ public class ControlsInputPacket extends SimplePacketBase {
buffer.writeVarInt(activatedButtons.size());
activatedButtons.forEach(buffer::writeVarInt);
buffer.writeBlockPos(controlsPos);
buffer.writeBoolean(stopControlling);
}
@Override
@ -64,11 +68,14 @@ public class ControlsInputPacket extends SimplePacketBase {
Entity entity = world.getEntity(contraptionEntityId);
if (!(entity instanceof AbstractContraptionEntity ace))
return;
if (!ace.toGlobalVector(Vec3.atCenterOf(controlsPos), 0)
.closerThan(player.position(), 16))
if (stopControlling) {
ace.stopControlling(controlsPos);
return;
}
ControlsServerHandler.receivePressed(world, ace, controlsPos, uniqueID, activatedButtons, press);
if (ace.toGlobalVector(Vec3.atCenterOf(controlsPos), 0)
.closerThan(player.position(), 16))
ControlsServerHandler.receivePressed(world, ace, controlsPos, uniqueID, activatedButtons, press);
});
ctx.setPacketHandled(true);
}

View file

@ -49,7 +49,8 @@ public class NixieTubeBlock extends DoubleFaceAttachedBlock
public NixieTubeBlock(Properties properties, DyeColor color) {
super(properties);
this.color = color;
registerDefaultState(defaultBlockState().setValue(FACE, DoubleAttachFace.FLOOR));
registerDefaultState(defaultBlockState().setValue(FACE, DoubleAttachFace.FLOOR)
.setValue(WATERLOGGED, false));
}
@Override

View file

@ -2,7 +2,6 @@ package com.simibubi.create.content.logistics.trains;
import java.util.Iterator;
import com.jozufozu.flywheel.repack.joml.Math;
import com.jozufozu.flywheel.util.transform.TransformStack;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.PoseStack.Pose;

View file

@ -57,6 +57,8 @@ public class GlobalRailwayManager {
}
}
public void playerLogout(Player player) {}
public void levelLoaded(LevelAccessor level) {
MinecraftServer server = level.getServer();
if (server == null || server.overworld() != level)
@ -75,13 +77,6 @@ public class GlobalRailwayManager {
signalEdgeGroups = savedData.getSignalBlocks();
}
public void levelUnloaded(LevelAccessor level) {
// MinecraftServer server = level.getServer();
// if (server == null || server.overworld() != level)
// return;
// cleanUp();
}
public void cleanUp() {
trackNetworks = new HashMap<>();
signalEdgeGroups = new HashMap<>();

View file

@ -136,9 +136,12 @@ public class Carriage {
ISignalBoundaryListener passiveListener = point.ignoreSignals();
toMove += correction + bogeyCorrection;
double moved = point.travel(graph, toMove, toMove > 0 ? frontTrackSelector : backTrackSelector,
toMove > 0 ? atFront ? frontListener : atBack ? backListener : passiveListener
: atFront ? backListener : atBack ? frontListener : passiveListener);
double moved =
point
.travel(graph, toMove, toMove > 0 ? frontTrackSelector : backTrackSelector,
toMove > 0 ? atFront ? frontListener : atBack ? backListener : passiveListener
: atFront ? backListener : atBack ? frontListener : passiveListener,
point.ignoreTurns());
blocked |= point.blocked;
distanceMoved.setValue(moved);
@ -227,8 +230,17 @@ public class Carriage {
entity.setPos(positionAnchor);
entity.prevYaw = entity.yaw;
entity.prevPitch = entity.pitch;
entity.yaw = (float) (Mth.atan2(diffZ, diffX) * 180 / Math.PI) + 180;
entity.pitch = (float) (Math.atan2(diffY, Math.sqrt(diffX * diffX + diffZ * diffZ)) * 180 / Math.PI) * -1;
if (entity.firstPositionUpdate) {
entity.xo = entity.getX();
entity.yo = entity.getY();
entity.zo = entity.getZ();
entity.prevYaw = entity.yaw;
entity.prevPitch = entity.pitch;
}
}
public TravellingPoint getLeadingPoint() {

View file

@ -7,6 +7,7 @@ import com.simibubi.create.content.logistics.trains.IBogeyBlock;
import com.simibubi.create.content.logistics.trains.TrackGraph;
import com.simibubi.create.foundation.utility.AngleHelper;
import com.simibubi.create.foundation.utility.Couple;
import com.simibubi.create.foundation.utility.Iterate;
import com.simibubi.create.foundation.utility.VecHelper;
import com.simibubi.create.foundation.utility.animation.LerpedFloat;
@ -62,9 +63,15 @@ public class CarriageBogey {
xRot = AngleHelper.deg(Math.atan2(diffY, Math.sqrt(diffX * diffX + diffZ * diffZ)));
}
wheelAngle.setValue((wheelAngle.getValue() - angleDiff) % 360);
pitch.setValue(xRot);
yaw.setValue(-yRot);
double newWheelAngle = (wheelAngle.getValue() - angleDiff) % 360;
for (boolean twice : Iterate.trueAndFalse) {
if (twice && !entity.firstPositionUpdate)
continue;
wheelAngle.setValue(newWheelAngle);
pitch.setValue(xRot);
yaw.setValue(-yRot);
}
}
public TravellingPoint leading() {

View file

@ -32,6 +32,8 @@ import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate.StructureBlockInfo;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
public class CarriageContraption extends Contraption {
@ -177,6 +179,7 @@ public class CarriageContraption extends Contraption {
}
@Override
@OnlyIn(Dist.CLIENT)
public ContraptionLighter<?> makeLighter() {
return new NonStationaryLighter<>(this);
}

View file

@ -61,10 +61,12 @@ public class CarriageContraptionEntity extends OrientedContraptionEntity {
public boolean movingBackwards;
public boolean leftTickingChunks;
public boolean firstPositionUpdate;
public CarriageContraptionEntity(EntityType<?> type, Level world) {
super(type, world);
validForRender = false;
firstPositionUpdate = true;
}
@Override
@ -172,12 +174,15 @@ public class CarriageContraptionEntity extends OrientedContraptionEntity {
carriage.alignEntity(this);
Vec3 diff = position().subtract(xo, yo, zo);
Vec3 relativeDiff = VecHelper.rotate(diff, yaw, Axis.Y);
double signum = Math.signum(-relativeDiff.x);
double distanceTo = diff.length() * signum;
double distanceTo = 0;
if (!firstPositionUpdate) {
Vec3 diff = position().subtract(xo, yo, zo);
Vec3 relativeDiff = VecHelper.rotate(diff, yaw, Axis.Y);
double signum = Math.signum(-relativeDiff.x);
distanceTo = diff.length() * signum;
movingBackwards = signum < 0;
}
movingBackwards = signum < 0;
carriage.bogeys.getFirst()
.updateAngles(this, distanceTo);
if (carriage.isOnTwoBogeys())
@ -187,6 +192,7 @@ public class CarriageContraptionEntity extends OrientedContraptionEntity {
if (carriage.train.derailed)
spawnDerailParticles(carriage);
firstPositionUpdate = false;
validForRender = true;
}
@ -200,6 +206,18 @@ public class CarriageContraptionEntity extends OrientedContraptionEntity {
}
}
@Override
public void onClientRemoval() {
super.onClientRemoval();
entityData.set(CARRIAGE_DATA, new CarriageSyncData());
if (carriage != null) {
carriage.pointsInitialised = false;
carriage.leadingBogey().couplingAnchors = Couple.create(null, null);
carriage.trailingBogey().couplingAnchors = Couple.create(null, null);
}
firstPositionUpdate = true;
}
@Override
protected void writeAdditional(CompoundTag compound, boolean spawnPacket) {
super.writeAdditional(compound, spawnPacket);
@ -250,7 +268,7 @@ public class CarriageContraptionEntity extends OrientedContraptionEntity {
return false;
if (carriage.train.derailed)
return false;
if (!level.isClientSide && carriage.train.heldForAssembly) {
if (carriage.train.heldForAssembly) {
player.displayClientMessage(Lang.translate("schedule.train_still_assembling"), true);
return false;
}
@ -260,6 +278,7 @@ public class CarriageContraptionEntity extends OrientedContraptionEntity {
train.status.manualControls();
train.navigation.cancelNavigation();
train.runtime.paused = true;
train.navigation.waitingForSignal = null;
return true;
}
@ -347,7 +366,7 @@ public class CarriageContraptionEntity extends OrientedContraptionEntity {
if (lookAhead != null) {
if (spaceDown) {
nav.startNavigation(lookAhead, false);
nav.startNavigation(lookAhead, -1, false);
navDistanceTotal = nav.distanceToDestination;
return true;
}

View file

@ -25,7 +25,7 @@ public class CarriageContraptionEntityRenderer extends ContraptionEntityRenderer
bogey.couplingAnchors.replace(v -> null);
if (!super.shouldRender(entity, clippingHelper, cameraX, cameraY, cameraZ))
return false;
return entity.validForRender;
return entity.validForRender && !entity.firstPositionUpdate;
}
@Override

View file

@ -239,8 +239,8 @@ public class CarriageSyncData {
TravellingPoint toApproach = pointsToApproach[index];
point.travel(graph, partial * f,
point.follow(toApproach, b -> success.setValue(success.booleanValue() && b)),
point.ignoreSignals());
point.follow(toApproach, b -> success.setValue(success.booleanValue() && b)), point.ignoreSignals(),
point.ignoreTurns());
// could not pathfind to server location
if (!success.booleanValue()) {

View file

@ -6,10 +6,14 @@ import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.PriorityQueue;
import java.util.Set;
import java.util.UUID;
import javax.annotation.Nullable;
import org.apache.commons.lang3.mutable.MutableDouble;
import org.apache.commons.lang3.mutable.MutableObject;
import com.simibubi.create.Create;
@ -49,6 +53,7 @@ public class Navigation {
private TravellingPoint signalScout;
public Pair<UUID, Boolean> waitingForSignal;
public double distanceToSignal;
public int ticksWaitingForSignal;
public Navigation(Train train) {
this.train = train;
@ -84,9 +89,11 @@ public class Navigation {
destination.reserveFor(train);
double acceleration = AllConfigs.SERVER.trains.getAccelerationMPTT();
double turnTopSpeed = AllConfigs.SERVER.trains.getTurningTopSpeedMPT();
double brakingDistance = (train.speed * train.speed) / (2 * acceleration);
double speedMod = destinationBehindTrain ? -1 : 1;
double preDepartureLookAhead = train.getCurrentStation() != null ? 4.5 : 0;
double distanceToNextCurve = -1;
// Signals
if (train.graph != null) {
@ -98,10 +105,14 @@ public class Navigation {
: train.carriages.get(train.carriages.size() - 1)
.getTrailingPoint();
if (waitingForSignal == null)
if (waitingForSignal == null) {
distanceToSignal = Double.MAX_VALUE;
ticksWaitingForSignal = 0;
}
if (distanceToSignal > 1 / 16f) {
MutableDouble curveDistanceTracker = new MutableDouble(-1);
signalScout.node1 = leadingPoint.node1;
signalScout.node2 = leadingPoint.node2;
signalScout.edge = leadingPoint.edge;
@ -124,7 +135,15 @@ public class Navigation {
return;
}
signalEdgeGroup.reserved = boundary;
}, (distance, edge) -> {
float current = curveDistanceTracker.floatValue();
if (current == -1 || distance < current)
curveDistanceTracker.setValue(distance);
});
distanceToNextCurve = curveDistanceTracker.floatValue();
} else {
ticksWaitingForSignal++;
}
}
@ -165,7 +184,17 @@ public class Navigation {
}
}
train.targetSpeed = targetDistance > brakingDistance ? topSpeed * speedMod : 0;
double targetSpeed = targetDistance > brakingDistance ? topSpeed * speedMod : 0;
if (distanceToNextCurve != -1) {
double slowingDistance = brakingDistance - (turnTopSpeed * turnTopSpeed) / (2 * acceleration);
double targetTurnSpeed =
distanceToNextCurve > slowingDistance ? topSpeed * speedMod : turnTopSpeed * speedMod;
if (Math.abs(targetTurnSpeed) < Math.abs(targetSpeed))
targetSpeed = targetTurnSpeed;
}
train.targetSpeed = targetSpeed;
train.approachTargetSpeed(1);
}
@ -228,16 +257,17 @@ public class Navigation {
train.runtime.transitInterrupted();
}
public double startNavigation(GlobalStation destination, boolean simulate) {
Pair<Double, List<Couple<TrackNode>>> pathTo = findPathTo(destination);
boolean noneFound = pathTo.getFirst() == null;
double distance = noneFound ? -1 : Math.abs(pathTo.getFirst());
public double startNavigation(GlobalStation destination, double maxCost, boolean simulate) {
DiscoveredPath pathTo = findPathTo(destination, maxCost);
boolean noneFound = pathTo == null;
double distance = noneFound ? -1 : Math.abs(pathTo.distance);
double cost = noneFound ? -1 : pathTo.cost;
if (simulate)
return distance;
return cost;
distanceToDestination = distance;
currentPath = pathTo.getSecond();
currentPath = pathTo.path;
if (noneFound) {
distanceToDestination = 0;
@ -246,7 +276,7 @@ public class Navigation {
return -1;
}
destinationBehindTrain = pathTo.getFirst() < 0;
destinationBehindTrain = pathTo.distance < 0;
if (this.destination == destination)
return 0;
@ -274,24 +304,20 @@ public class Navigation {
}
this.destination = destination;
return distanceToDestination;
return cost;
}
private Pair<Double, List<Couple<TrackNode>>> findPathTo(GlobalStation destination) {
@Nullable
private DiscoveredPath findPathTo(GlobalStation destination, double maxCost) {
TrackGraph graph = train.graph;
List<Couple<TrackNode>> path = new ArrayList<>();
if (graph == null)
return Pair.of(null, path);
MutableObject<Pair<Double, List<Couple<TrackNode>>>> frontResult = new MutableObject<>(Pair.of(null, path));
MutableObject<Pair<Double, List<Couple<TrackNode>>>> backResult = new MutableObject<>(Pair.of(null, path));
return null;
Couple<DiscoveredPath> results = Couple.create(null, null);
for (boolean forward : Iterate.trueAndFalse) {
if (this.destination == destination && destinationBehindTrain == forward)
continue;
List<Couple<TrackNode>> currentPath = new ArrayList<>();
TravellingPoint initialPoint = forward ? train.carriages.get(0)
.getLeadingPoint()
: train.carriages.get(train.carriages.size() - 1)
@ -300,7 +326,7 @@ public class Navigation {
: graph.getConnectionsFrom(initialPoint.node2)
.get(initialPoint.node1);
search(Double.MAX_VALUE, forward, (distance, reachedVia, currentEntry, globalStation) -> {
search(Double.MAX_VALUE, maxCost, forward, (distance, cost, reachedVia, currentEntry, globalStation) -> {
if (globalStation != destination)
return false;
@ -310,6 +336,7 @@ public class Navigation {
TrackNode node2 = currentEntry.getFirst()
.getSecond();
List<Couple<TrackNode>> currentPath = new ArrayList<>();
Pair<Boolean, Couple<TrackNode>> backTrack = reachedVia.get(edge);
Couple<TrackNode> toReach = Couple.create(node1, node2);
TrackEdge edgeReached = edge;
@ -325,11 +352,7 @@ public class Navigation {
double position = edge.getLength(node1, node2) - destination.getLocationOn(node1, node2, edge);
double distanceToDestination = distance - position;
if (forward)
frontResult.setValue(Pair.of(distanceToDestination, currentPath));
else
backResult.setValue(Pair.of(-distanceToDestination, currentPath));
results.set(forward, new DiscoveredPath((forward ? 1 : -1) * distanceToDestination, cost, currentPath));
return true;
});
@ -337,11 +360,11 @@ public class Navigation {
break;
}
Pair<Double, List<Couple<TrackNode>>> front = frontResult.getValue();
Pair<Double, List<Couple<TrackNode>>> back = backResult.getValue();
DiscoveredPath front = results.getFirst();
DiscoveredPath back = results.getSecond();
boolean frontEmpty = front.getFirst() == null;
boolean backEmpty = back.getFirst() == null;
boolean frontEmpty = front == null;
boolean backEmpty = back == null;
if (backEmpty)
return front;
if (frontEmpty)
@ -354,10 +377,22 @@ public class Navigation {
if (!canDriveForward)
return back;
boolean frontBetter = -back.getFirst() > front.getFirst();
boolean frontBetter = maxCost == -1 ? -back.distance > front.distance : back.cost > front.cost;
return frontBetter ? front : back;
}
public class DiscoveredPath {
List<Couple<TrackNode>> path;
double distance;
double cost;
public DiscoveredPath(double distance, double cost, List<Couple<TrackNode>> path) {
this.distance = distance;
this.cost = cost;
this.path = path;
}
}
public GlobalStation findNearestApproachable(boolean forward) {
TrackGraph graph = train.graph;
if (graph == null)
@ -368,7 +403,7 @@ public class Navigation {
double minDistance = .75f * (train.speed * train.speed) / (2 * acceleration);
double maxDistance = Math.max(32, 1.5f * (train.speed * train.speed) / (2 * acceleration));
search(maxDistance, forward, (distance, reachedVia, currentEntry, globalStation) -> {
search(maxDistance, forward, (distance, cost, reachedVia, currentEntry, globalStation) -> {
if (distance < minDistance)
return false;
@ -389,10 +424,36 @@ public class Navigation {
}
public void search(double maxDistance, boolean forward, StationTest stationTest) {
search(maxDistance, -1, forward, stationTest);
}
public void search(double maxDistance, double maxCost, boolean forward, StationTest stationTest) {
TrackGraph graph = train.graph;
if (graph == null)
return;
Map<TrackEdge, Integer> penalties = new IdentityHashMap<>();
boolean costRelevant = maxCost >= 0;
if (costRelevant) {
for (Train otherTrain : Create.RAILWAYS.trains.values()) {
if (otherTrain.graph != graph)
continue;
int navigationPenalty = otherTrain.getNavigationPenalty();
otherTrain.getEndpointEdges()
.forEach(nodes -> {
if (nodes.either(Objects::isNull))
return;
for (boolean flip : Iterate.trueAndFalse) {
TrackEdge e = graph.getConnection(flip ? nodes.swap() : nodes);
if (e == null)
continue;
int existing = penalties.getOrDefault(e, 0);
penalties.put(e, existing + navigationPenalty / 2);
}
});
}
}
TravellingPoint startingPoint = forward ? train.carriages.get(0)
.getLeadingPoint()
: train.carriages.get(train.carriages.size() - 1)
@ -400,8 +461,7 @@ public class Navigation {
Set<TrackEdge> visited = new HashSet<>();
Map<TrackEdge, Pair<Boolean, Couple<TrackNode>>> reachedVia = new IdentityHashMap<>();
PriorityQueue<Pair<Double, Pair<Couple<TrackNode>, TrackEdge>>> frontier =
new PriorityQueue<>((p1, p2) -> Double.compare(p1.getFirst(), p2.getFirst()));
PriorityQueue<FrontierEntry> frontier = new PriorityQueue<>();
TrackNode initialNode1 = forward ? startingPoint.node1 : startingPoint.node2;
TrackNode initialNode2 = forward ? startingPoint.node2 : startingPoint.node1;
@ -410,20 +470,23 @@ public class Navigation {
double distanceToNode2 = forward ? initialEdge.getLength(initialNode1, initialNode2) - startingPoint.position
: startingPoint.position;
frontier.add(Pair.of(distanceToNode2, Pair.of(Couple.create(initialNode1, initialNode2), initialEdge)));
frontier.add(new FrontierEntry(distanceToNode2, 0, initialNode1, initialNode2, initialEdge));
Search: while (!frontier.isEmpty()) {
Pair<Double, Pair<Couple<TrackNode>, TrackEdge>> poll = frontier.poll();
double distance = poll.getFirst();
FrontierEntry entry = frontier.poll();
double distance = entry.distance;
int penalty = entry.penalty;
if (distance > maxDistance)
continue;
Pair<Couple<TrackNode>, TrackEdge> currentEntry = poll.getSecond();
TrackEdge edge = currentEntry.getSecond();
TrackNode node1 = currentEntry.getFirst()
.getFirst();
TrackNode node2 = currentEntry.getFirst()
.getSecond();
TrackEdge edge = entry.edge;
TrackNode node1 = entry.node1;
TrackNode node2 = entry.node2;
if (costRelevant)
penalty += penalties.getOrDefault(edge, 0);
EdgeData signalData = edge.getEdgeData();
if (signalData.hasPoints()) {
@ -431,44 +494,78 @@ public class Navigation {
if (node1 == initialNode1
&& point.getLocationOn(node1, node2, edge) < edge.getLength(node1, node2) - distanceToNode2)
continue;
if (costRelevant && distance + penalty > maxCost)
continue Search;
if (!point.canNavigateVia(node2))
continue Search;
if (point instanceof GlobalStation station && station.canApproachFrom(node2)
&& stationTest.test(distance, reachedVia, currentEntry, station))
return;
if (point instanceof GlobalStation station) {
if (station.getPresentTrain() != null)
penalty += Train.Penalties.STATION_WITH_TRAIN;
if (station.canApproachFrom(node2) && stationTest.test(distance, distance + penalty, reachedVia,
Pair.of(Couple.create(node1, node2), edge), station))
return;
penalty += Train.Penalties.STATION;
}
}
}
if (costRelevant && distance + penalty > maxCost)
continue;
List<Entry<TrackNode, TrackEdge>> validTargets = new ArrayList<>();
Map<TrackNode, TrackEdge> connectionsFrom = graph.getConnectionsFrom(node2);
for (Entry<TrackNode, TrackEdge> entry : connectionsFrom.entrySet()) {
TrackNode newNode = entry.getKey();
TrackEdge newEdge = entry.getValue();
for (Entry<TrackNode, TrackEdge> connection : connectionsFrom.entrySet()) {
TrackNode newNode = connection.getKey();
TrackEdge newEdge = connection.getValue();
Vec3 currentDirection = edge.getDirection(node1, node2, false);
Vec3 newDirection = newEdge.getDirection(node2, newNode, true);
if (currentDirection.dot(newDirection) < 3 / 4f)
continue;
if (!visited.add(entry.getValue()))
if (!visited.add(connection.getValue()))
continue;
validTargets.add(entry);
validTargets.add(connection);
}
if (validTargets.isEmpty())
continue;
for (Entry<TrackNode, TrackEdge> entry : validTargets) {
TrackNode newNode = entry.getKey();
TrackEdge newEdge = entry.getValue();
for (Entry<TrackNode, TrackEdge> target : validTargets) {
TrackNode newNode = target.getKey();
TrackEdge newEdge = target.getValue();
double newDistance = newEdge.getLength(node2, newNode) + distance;
int newPenalty = penalty;
reachedVia.put(newEdge, Pair.of(validTargets.size() > 1, Couple.create(node1, node2)));
frontier.add(Pair.of(newEdge.getLength(node2, newNode) + distance,
Pair.of(Couple.create(node2, newNode), newEdge)));
frontier.add(new FrontierEntry(newDistance, newPenalty, node2, newNode, newEdge));
}
}
}
private class FrontierEntry implements Comparable<FrontierEntry> {
double distance;
int penalty;
TrackNode node1;
TrackNode node2;
TrackEdge edge;
public FrontierEntry(double distance, int penalty, TrackNode node1, TrackNode node2, TrackEdge edge) {
this.distance = distance;
this.penalty = penalty;
this.node1 = node1;
this.node2 = node2;
this.edge = edge;
}
@Override
public int compareTo(FrontierEntry o) {
return Double.compare(distance + penalty, o.distance + o.penalty);
}
}
@FunctionalInterface
public interface StationTest {
boolean test(double distance, Map<TrackEdge, Pair<Boolean, Couple<TrackNode>>> reachedVia,
boolean test(double distance, double cost, Map<TrackEdge, Pair<Boolean, Couple<TrackNode>>> reachedVia,
Pair<Couple<TrackNode>, TrackEdge> current, GlobalStation station);
}
@ -491,6 +588,7 @@ public class Navigation {
tag.putUUID("BlockingSignal", waitingForSignal.getFirst());
tag.putBoolean("BlockingSignalSide", waitingForSignal.getSecond());
tag.putDouble("DistanceToSignal", distanceToSignal);
tag.putInt("TicksWaitingForSignal", ticksWaitingForSignal);
return tag;
}
@ -513,6 +611,7 @@ public class Navigation {
if (waitingForSignal == null)
return;
distanceToSignal = tag.getDouble("DistanceToSignal");
ticksWaitingForSignal = tag.getInt("TicksWaitingForSignal");
}
}

View file

@ -35,6 +35,7 @@ import com.simibubi.create.content.logistics.trains.management.schedule.Schedule
import com.simibubi.create.content.logistics.trains.management.schedule.ScheduleRuntime.State;
import com.simibubi.create.foundation.config.AllConfigs;
import com.simibubi.create.foundation.networking.AllPackets;
import com.simibubi.create.foundation.utility.Couple;
import com.simibubi.create.foundation.utility.Iterate;
import com.simibubi.create.foundation.utility.Lang;
import com.simibubi.create.foundation.utility.NBTHelper;
@ -246,24 +247,34 @@ public class Train {
}
private void updateNavigationTarget(double distance) {
if (navigation.destination != null) {
boolean recalculate = navigation.distanceToDestination % 100 > 20;
boolean imminentRecalculate = navigation.distanceToDestination > 5;
double toSubstract = navigation.destinationBehindTrain ? -distance : distance;
navigation.distanceToDestination -= toSubstract;
boolean signalMode = navigation.waitingForSignal != null;
if (navigation.destination == null)
return;
boolean recalculate = navigation.distanceToDestination % 100 > 20;
boolean imminentRecalculate = navigation.distanceToDestination > 5;
double toSubstract = navigation.destinationBehindTrain ? -distance : distance;
navigation.distanceToDestination -= toSubstract;
boolean signalMode = navigation.waitingForSignal != null;
boolean navigatingManually = runtime.paused;
if (signalMode) {
navigation.distanceToSignal -= toSubstract;
recalculate = navigation.distanceToSignal % 100 > 20;
}
if (recalculate && (signalMode ? navigation.distanceToSignal : navigation.distanceToDestination) % 100 <= 20
|| imminentRecalculate && navigation.distanceToDestination <= 5) {
if (signalMode) {
navigation.distanceToSignal -= toSubstract;
recalculate = navigation.distanceToSignal % 100 > 20;
navigation.waitingForSignal = null;
return;
}
if (recalculate && (signalMode ? navigation.distanceToSignal : navigation.distanceToDestination) % 100 <= 20
|| imminentRecalculate && navigation.distanceToDestination <= 5) {
if (signalMode) {
navigation.waitingForSignal = null;
return;
}
navigation.startNavigation(navigation.destination, false);
GlobalStation destination = navigation.destination;
if (!navigatingManually) {
GlobalStation preferredDestination = runtime.findNextStation();
if (preferredDestination != null)
destination = preferredDestination;
}
navigation.startNavigation(destination, navigatingManually ? -1 : Double.MAX_VALUE, false);
}
}
@ -680,11 +691,36 @@ public class Train {
return;
occupiedSignalBlocks.add(id);
prevGroup.setValue(id);
}));
}), signalScout.ignoreTurns());
});
}
public Couple<Couple<TrackNode>> getEndpointEdges() {
return Couple.create(carriages.get(0)
.getLeadingPoint(),
carriages.get(carriages.size() - 1)
.getTrailingPoint())
.map(tp -> Couple.create(tp.node1, tp.node2));
}
public static class Penalties {
static final int STATION = 200, STATION_WITH_TRAIN = 300;
static final int MANUAL_TRAIN = 200, IDLE_TRAIN = 700, ARRIVING_TRAIN = 50, WAITING_TRAIN = 50, ANY_TRAIN = 25;
}
public int getNavigationPenalty() {
if (manualTick)
return Penalties.MANUAL_TRAIN;
if (runtime.getSchedule() == null || runtime.paused)
return Penalties.IDLE_TRAIN;
if (navigation.waitingForSignal != null && navigation.ticksWaitingForSignal > 0)
return Penalties.WAITING_TRAIN + Math.min(navigation.ticksWaitingForSignal / 20, 1000);
if (navigation.destination != null && navigation.distanceToDestination < 50 || navigation.distanceToSignal < 20)
return Penalties.ARRIVING_TRAIN;
return Penalties.ANY_TRAIN;
}
public CompoundTag write() {
CompoundTag tag = new CompoundTag();
tag.putUUID("Id", id);

View file

@ -24,6 +24,7 @@ import com.simibubi.create.content.logistics.trains.TrackGraphHelper;
import com.simibubi.create.content.logistics.trains.TrackNode;
import com.simibubi.create.content.logistics.trains.entity.TravellingPoint.ISignalBoundaryListener;
import com.simibubi.create.content.logistics.trains.entity.TravellingPoint.ITrackSelector;
import com.simibubi.create.content.logistics.trains.entity.TravellingPoint.ITurnListener;
import com.simibubi.create.content.logistics.trains.entity.TravellingPoint.SteerDirection;
import com.simibubi.create.foundation.item.TooltipHelper;
import com.simibubi.create.foundation.networking.AllPackets;
@ -63,7 +64,7 @@ public class TrainRelocator {
public static boolean isRelocating() {
return relocatingTrain != null;
}
@OnlyIn(Dist.CLIENT)
public static void onClicked(ClickInputEvent event) {
if (relocatingTrain == null)
@ -105,7 +106,7 @@ public class TrainRelocator {
return null;
BlockPos blockPos = blockhit.getBlockPos();
if (simulate && toVisualise != null) {
if (simulate && toVisualise != null && lastHoveredResult != null) {
for (int i = 0; i < toVisualise.size() - 1; i++) {
Vec3 vec1 = toVisualise.get(i);
Vec3 vec2 = toVisualise.get(i + 1);
@ -158,6 +159,7 @@ public class TrainRelocator {
TravellingPoint probe = new TravellingPoint(node1, node2, edge, graphLocation.position);
ISignalBoundaryListener ignoreSignals = probe.ignoreSignals();
ITurnListener ignoreTurns = probe.ignoreTurns();
List<Pair<Couple<TrackNode>, Double>> recordedLocations = new ArrayList<>();
List<Vec3> recordedVecs = new ArrayList<>();
Consumer<TravellingPoint> recorder = tp -> {
@ -171,7 +173,7 @@ public class TrainRelocator {
train.forEachTravellingPointBackwards((tp, d) -> {
if (blocked.booleanValue())
return;
probe.travel(graph, d, steer, ignoreSignals);
probe.travel(graph, d, steer, ignoreSignals, ignoreTurns);
recorder.accept(probe);
if (probe.blocked) {
blocked.setTrue();

View file

@ -56,6 +56,9 @@ public class TravellingPoint {
public static interface ISignalBoundaryListener extends BiConsumer<Double, Pair<SignalBoundary, Couple<UUID>>> {
};
public static interface ITurnListener extends BiConsumer<Double, TrackEdge> {
};
public TravellingPoint() {}
public TravellingPoint(TrackNode node1, TrackNode node2, TrackEdge edge, double position) {
@ -70,6 +73,11 @@ public class TravellingPoint {
};
}
public ITurnListener ignoreTurns() {
return (d, c) -> {
};
}
public ITrackSelector random() {
return (graph, pair) -> pair.getSecond()
.get(Create.RANDOM.nextInt(pair.getSecond()
@ -170,7 +178,7 @@ public class TravellingPoint {
}
public double travel(TrackGraph graph, double distance, ITrackSelector trackSelector,
ISignalBoundaryListener signalListener) {
ISignalBoundaryListener signalListener, ITurnListener turnListener) {
blocked = false;
double edgeLength = edge.getLength(node1, node2);
if (distance == 0)
@ -185,7 +193,7 @@ public class TravellingPoint {
boolean forward = distance > 0;
double collectedDistance = forward ? -prevPos : -edgeLength + prevPos;
edgeTraversedFrom(graph, forward, signalListener, prevPos, collectedDistance);
edgeTraversedFrom(graph, forward, signalListener, turnListener, prevPos, collectedDistance);
if (forward) {
// Moving forward
@ -223,7 +231,9 @@ public class TravellingPoint {
position -= edgeLength;
collectedDistance += edgeLength;
edgeTraversedFrom(graph, forward, signalListener, 0, collectedDistance);
if (edge.isTurn())
turnListener.accept(collectedDistance, edge);
edgeTraversedFrom(graph, forward, signalListener, turnListener, 0, collectedDistance);
prevPos = 0;
edgeLength = edge.getLength(node1, node2);
@ -268,7 +278,7 @@ public class TravellingPoint {
edgeLength = edge.getLength(node1, node2);
position += edgeLength;
edgeTraversedFrom(graph, forward, signalListener, edgeLength, collectedDistance);
edgeTraversedFrom(graph, forward, signalListener, turnListener, edgeLength, collectedDistance);
}
}
@ -277,7 +287,10 @@ public class TravellingPoint {
}
private void edgeTraversedFrom(TrackGraph graph, boolean forward, ISignalBoundaryListener signalListener,
double prevPos, double totalDistance) {
ITurnListener turnListener, double prevPos, double totalDistance) {
if (edge.isTurn())
turnListener.accept(Math.max(0, totalDistance), edge);
EdgeData signalsOnEdge = edge.getEdgeData();
if (!signalsOnEdge.hasSignalBoundaries())
return;

View file

@ -97,7 +97,7 @@ public class ScheduleRuntime {
destinationReached();
return;
}
if (train.navigation.startNavigation(nextStation, false) != -1)
if (train.navigation.startNavigation(nextStation, Double.MAX_VALUE, false) != -1)
state = State.IN_TRANSIT;
}
@ -134,7 +134,7 @@ public class ScheduleRuntime {
if (!globalStation.name.matches(regex))
continue;
boolean matchesCurrent = train.currentStation != null && train.currentStation.equals(globalStation.id);
double cost = matchesCurrent ? 0 : train.navigation.startNavigation(globalStation, true);
double cost = matchesCurrent ? 0 : train.navigation.startNavigation(globalStation, bestCost, true);
if (cost < 0)
continue;
if (cost > bestCost)

View file

@ -58,9 +58,10 @@ public class TrackBlockItem extends BlockItem {
}
boolean placing = !(state.getBlock() instanceof ITrackBlock);
if (placing && !state.getMaterial()
.isReplaceable()) {
pos = pos.relative(pContext.getClickedFace());
if (placing) {
if (!state.getMaterial()
.isReplaceable())
pos = pos.relative(pContext.getClickedFace());
state = getPlacementState(pContext);
if (state == null)
return InteractionResult.FAIL;

View file

@ -6,12 +6,11 @@ import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.apache.commons.lang3.tuple.Pair;
import com.simibubi.create.AllBlocks;
import com.simibubi.create.content.curiosities.girder.GirderBlock;
import com.simibubi.create.content.logistics.trains.BezierConnection;
import com.simibubi.create.foundation.utility.Iterate;
import com.simibubi.create.foundation.utility.Pair;
import com.simibubi.create.foundation.utility.VecHelper;
import net.minecraft.core.BlockPos;
@ -33,7 +32,7 @@ public class TrackPaver {
if (defaultBlockState.hasProperty(SlabBlock.TYPE))
defaultBlockState = defaultBlockState.setValue(SlabBlock.TYPE, SlabType.DOUBLE);
boolean wallLike = isWallLike(defaultBlockState);
if (defaultBlockState.getBlock() instanceof GirderBlock)
for (Direction d : Iterate.horizontalDirections)
if (Vec3.atLowerCornerOf(d.getNormal())
@ -144,9 +143,9 @@ public class TrackPaver {
int floor = Mth.floor(yValue);
boolean placeSlab = slab && yValue - floor >= .5;
BlockPos targetPos = new BlockPos(entry.getKey()
.getKey(), floor,
.getFirst(), floor,
entry.getKey()
.getValue());
.getSecond());
targetPos = targetPos.offset(tePosition)
.above(placeSlab ? 1 : 0);
BlockState stateToPlace =

View file

@ -159,14 +159,13 @@ public class ClientEvents {
}
@SubscribeEvent
public static void onRenderSelection(DrawSelectionEvent event) {
}
public static void onRenderSelection(DrawSelectionEvent event) {}
@SubscribeEvent
public static void onJoin(ClientPlayerNetworkEvent.LoggedInEvent event) {
CreateClient.checkGraphicsFanciness();
}
@SubscribeEvent
public static void onLeave(ClientPlayerNetworkEvent.LoggedOutEvent event) {
CreateClient.RAILWAYS.cleanUp();
@ -183,12 +182,13 @@ public class ClientEvents {
@SubscribeEvent
public static void onUnloadWorld(WorldEvent.Unload event) {
if (event.getWorld()
.isClientSide()) {
CreateClient.invalidateRenderers();
CreateClient.SOUL_PULSE_EFFECT_HANDLER.refresh();
AnimationTickHolder.reset();
}
if (!event.getWorld()
.isClientSide())
return;
CreateClient.invalidateRenderers();
CreateClient.SOUL_PULSE_EFFECT_HANDLER.refresh();
AnimationTickHolder.reset();
ControlsHandler.levelUnloaded(event.getWorld());
}
@SubscribeEvent
@ -281,13 +281,15 @@ public class ClientEvents {
Fluid fluid = fluidstate.getType();
if (AllFluids.CHOCOLATE.get().isSame(fluid)) {
if (AllFluids.CHOCOLATE.get()
.isSame(fluid)) {
event.setDensity(5f);
event.setCanceled(true);
return;
}
if (AllFluids.HONEY.get().isSame(fluid)) {
if (AllFluids.HONEY.get()
.isSame(fluid)) {
event.setDensity(1.5f);
event.setCanceled(true);
return;
@ -307,18 +309,20 @@ public class ClientEvents {
Level level = Minecraft.getInstance().level;
BlockPos blockPos = info.getBlockPosition();
FluidState fluidstate = level.getFluidState(blockPos);
if (info.getPosition().y > blockPos.getY() + fluidstate.getHeight(level, blockPos))
return;
if (info.getPosition().y > blockPos.getY() + fluidstate.getHeight(level, blockPos))
return;
Fluid fluid = fluidstate.getType();
if (AllFluids.CHOCOLATE.get().isSame(fluid)) {
if (AllFluids.CHOCOLATE.get()
.isSame(fluid)) {
event.setRed(98 / 256f);
event.setGreen(32 / 256f);
event.setBlue(32 / 256f);
}
if (AllFluids.HONEY.get().isSame(fluid)) {
if (AllFluids.HONEY.get()
.isSame(fluid)) {
event.setRed(234 / 256f);
event.setGreen(174 / 256f);
event.setBlue(47 / 256f);

View file

@ -49,6 +49,7 @@ import net.minecraftforge.event.entity.living.LivingEvent.LivingUpdateEvent;
import net.minecraftforge.event.entity.player.AttackEntityEvent;
import net.minecraftforge.event.entity.player.PlayerEvent;
import net.minecraftforge.event.entity.player.PlayerEvent.PlayerLoggedInEvent;
import net.minecraftforge.event.entity.player.PlayerEvent.PlayerLoggedOutEvent;
import net.minecraftforge.event.server.ServerStoppingEvent;
import net.minecraftforge.event.world.BiomeLoadingEvent;
import net.minecraftforge.event.world.BlockEvent.FluidPlaceBlockEvent;
@ -82,6 +83,12 @@ public class CommonEvents {
ToolboxHandler.playerLogin(player);
Create.RAILWAYS.playerLogin(player);
}
@SubscribeEvent
public static void playerLoggedOut(PlayerLoggedOutEvent event) {
Player player = event.getPlayer();
Create.RAILWAYS.playerLogout(player);
}
@SubscribeEvent
public static void whenFluidsMeet(FluidPlaceBlockEvent event) {
@ -188,7 +195,6 @@ public class CommonEvents {
Create.REDSTONE_LINK_NETWORK_HANDLER.onUnloadWorld(world);
Create.TORQUE_PROPAGATOR.onUnloadWorld(world);
WorldAttached.invalidateWorld(world);
Create.RAILWAYS.levelUnloaded(world);
}
@SubscribeEvent

View file

@ -2,19 +2,24 @@ package com.simibubi.create.foundation.config;
public class CTrains extends ConfigBase {
public final ConfigFloat trainTopSpeed = f(40, 0, "trainTopSpeed", Comments.mps, Comments.trainTopSpeed);
public final ConfigFloat trainAcceleration =
f(6, 0, "trainAcceleration", Comments.acc, Comments.trainAcceleration);
public final ConfigFloat trainTopSpeed = f(36, 0, "trainTopSpeed", Comments.mps, Comments.trainTopSpeed);
public final ConfigFloat trainTurningTopSpeed =
f(18, 0, "trainTurningTopSpeed", Comments.mps, Comments.trainTurningTopSpeed);
public final ConfigFloat trainAcceleration = f(4, 0, "trainAcceleration", Comments.acc, Comments.trainAcceleration);
@Override
public String getName() {
return "trains";
}
public double getTopSpeedMPT() {
return trainTopSpeed.getF() / 20;
}
public double getTurningTopSpeedMPT() {
return trainTurningTopSpeed.getF() / 20;
}
public double getAccelerationMPTT() {
return trainAcceleration.getF() / 400;
}
@ -23,6 +28,7 @@ public class CTrains extends ConfigBase {
static String mps = "[in Blocks/Second]";
static String acc = "[in Blocks/Second²]";
static String trainTopSpeed = "The top speed of any assembled Train.";
static String trainTurningTopSpeed = "The top speed of Trains during a turn.";
static String trainAcceleration = "The acceleration of any assembled Train.";
}

View file

@ -4,7 +4,6 @@ import java.util.Random;
import javax.annotation.Nullable;
import com.jozufozu.flywheel.repack.joml.Math;
import com.mojang.math.Quaternion;
import com.mojang.math.Vector3f;