Graph hopping, Part II

- added "/create dumpRailways" command
- trains now react to changes of the graph beneath them
- trains now notify owner of unexpected issues
- filtered navigation wildcard works now
- navigation now smarter I guess
- navigation cancelled when track path gets disconnected
- stations and trains no longer disassociate when graph is edited
- schedule no longer skips entry when navigation gets interrupted
- trains can now be in a "de-railed" state
This commit is contained in:
simibubi 2022-02-03 02:51:14 +01:00
parent c6278dbd24
commit c3e1e9df3f
24 changed files with 733 additions and 150 deletions

View file

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

View file

@ -19,10 +19,10 @@ import javax.annotation.Nullable;
import com.simibubi.create.Create; import com.simibubi.create.Create;
import com.simibubi.create.CreateClient; import com.simibubi.create.CreateClient;
import com.simibubi.create.content.logistics.trains.TrackNodeLocation.DiscoveredLocation; import com.simibubi.create.content.logistics.trains.TrackNodeLocation.DiscoveredLocation;
import com.simibubi.create.content.logistics.trains.entity.Train;
import com.simibubi.create.content.logistics.trains.management.GlobalStation; import com.simibubi.create.content.logistics.trains.management.GlobalStation;
import com.simibubi.create.foundation.utility.Color; import com.simibubi.create.foundation.utility.Color;
import com.simibubi.create.foundation.utility.Couple; import com.simibubi.create.foundation.utility.Couple;
import com.simibubi.create.foundation.utility.Debug;
import com.simibubi.create.foundation.utility.NBTHelper; import com.simibubi.create.foundation.utility.NBTHelper;
import com.simibubi.create.foundation.utility.VecHelper; import com.simibubi.create.foundation.utility.VecHelper;
@ -40,8 +40,8 @@ public class TrackGraph {
public static final AtomicInteger netIdGenerator = new AtomicInteger(); public static final AtomicInteger netIdGenerator = new AtomicInteger();
UUID id; public UUID id;
Color color; public Color color;
Map<TrackNodeLocation, TrackNode> nodes; Map<TrackNodeLocation, TrackNode> nodes;
Map<Integer, TrackNode> nodesById; Map<Integer, TrackNode> nodesById;
@ -58,7 +58,7 @@ public class TrackGraph {
nodes = new HashMap<>(); nodes = new HashMap<>();
nodesById = new HashMap<>(); nodesById = new HashMap<>();
connectionsByNode = new IdentityHashMap<>(); connectionsByNode = new IdentityHashMap<>();
color = Color.rainbowColor(new Random().nextInt()); color = Color.rainbowColor(new Random(graphID.getLeastSignificantBits()).nextInt());
stations = new HashMap<>(); stations = new HashMap<>();
} }
@ -85,6 +85,10 @@ public class TrackGraph {
// //
public Set<TrackNodeLocation> getNodes() {
return nodes.keySet();
}
public TrackNode locateNode(Vec3 position) { public TrackNode locateNode(Vec3 position) {
return locateNode(new TrackNodeLocation(position)); return locateNode(new TrackNodeLocation(position));
} }
@ -122,17 +126,32 @@ public class TrackGraph {
return false; return false;
if (level != null) { if (level != null) {
for (Iterator<UUID> iterator = stations.keySet().iterator(); iterator.hasNext();) { for (Iterator<UUID> iterator = stations.keySet()
.iterator(); iterator.hasNext();) {
UUID uuid = iterator.next(); UUID uuid = iterator.next();
GlobalStation globalStation = stations.get(uuid); GlobalStation globalStation = stations.get(uuid);
Couple<TrackNodeLocation> loc = globalStation.edgeLocation; Couple<TrackNodeLocation> loc = globalStation.edgeLocation;
if (loc.getFirst().equals(location) || loc.getSecond().equals(location)) { if (loc.getFirst()
.equals(location)
|| loc.getSecond()
.equals(location)) {
globalStation.migrate(level); globalStation.migrate(level);
iterator.remove(); iterator.remove();
} }
} }
} }
Map<UUID, Train> trains = Create.RAILWAYS.trains;
for (Iterator<UUID> iterator = trains.keySet()
.iterator(); iterator.hasNext();) {
UUID uuid = iterator.next();
Train train = trains.get(uuid);
if (train.graph != this)
continue;
if (train.isTravellingOn(removed))
train.detachFromTracks();
}
nodesById.remove(removed.netId); nodesById.remove(removed.netId);
if (!connectionsByNode.containsKey(removed)) if (!connectionsByNode.containsKey(removed))
return true; return true;
@ -165,6 +184,16 @@ public class TrackGraph {
nodes.clear(); nodes.clear();
nodesById.clear(); nodesById.clear();
connectionsByNode.clear(); connectionsByNode.clear();
Map<UUID, Train> trains = Create.RAILWAYS.trains;
for (Iterator<UUID> iterator = trains.keySet()
.iterator(); iterator.hasNext();) {
UUID uuid = iterator.next();
Train train = trains.get(uuid);
if (train.graph != this)
continue;
train.graph = toOther;
}
} }
public Set<TrackGraph> findDisconnectedGraphs(@Nullable Map<Integer, UUID> preAssignedIds) { public Set<TrackGraph> findDisconnectedGraphs(@Nullable Map<Integer, UUID> preAssignedIds) {
@ -214,12 +243,15 @@ public class TrackGraph {
if (!connections.isEmpty()) { if (!connections.isEmpty()) {
target.connectionsByNode.put(node, connections); target.connectionsByNode.put(node, connections);
for (TrackNode entry : connections.keySet()) { for (TrackNode entry : connections.keySet()) {
for (Iterator<UUID> iterator = stations.keySet().iterator(); iterator.hasNext();) { for (Iterator<UUID> iterator = stations.keySet()
.iterator(); iterator.hasNext();) {
UUID uuid = iterator.next(); UUID uuid = iterator.next();
GlobalStation globalStation = stations.get(uuid); GlobalStation globalStation = stations.get(uuid);
Couple<TrackNodeLocation> loc = globalStation.edgeLocation; Couple<TrackNodeLocation> loc = globalStation.edgeLocation;
if (loc.getFirst().equals(location1) && loc.getSecond().equals(entry.getLocation())) { if (loc.getFirst()
Debug.debugChat("Station " + globalStation.name + " migrated directly due to graph split"); .equals(location1)
&& loc.getSecond()
.equals(entry.getLocation())) {
target.addStation(globalStation); target.addStation(globalStation);
iterator.remove(); iterator.remove();
} }
@ -227,6 +259,18 @@ public class TrackGraph {
} }
} }
Map<UUID, Train> trains = Create.RAILWAYS.trains;
for (Iterator<UUID> iterator = trains.keySet()
.iterator(); iterator.hasNext();) {
UUID uuid = iterator.next();
Train train = trains.get(uuid);
if (train.graph != this)
continue;
if (!train.isTravellingOn(node))
continue;
train.graph = target;
}
nodes.remove(location1); nodes.remove(location1);
nodesById.remove(node.getNetId()); nodesById.remove(node.getNetId());
connectionsByNode.remove(node); connectionsByNode.remove(node);

View file

@ -9,8 +9,10 @@ import javax.annotation.Nullable;
import org.apache.commons.lang3.mutable.MutableDouble; import org.apache.commons.lang3.mutable.MutableDouble;
import org.apache.commons.lang3.mutable.MutableObject; import org.apache.commons.lang3.mutable.MutableObject;
import com.simibubi.create.Create;
import com.simibubi.create.content.logistics.trains.IBogeyBlock; import com.simibubi.create.content.logistics.trains.IBogeyBlock;
import com.simibubi.create.content.logistics.trains.entity.MovingPoint.ITrackSelector; import com.simibubi.create.content.logistics.trains.TrackGraph;
import com.simibubi.create.content.logistics.trains.entity.TravellingPoint.ITrackSelector;
import com.simibubi.create.foundation.utility.AngleHelper; import com.simibubi.create.foundation.utility.AngleHelper;
import com.simibubi.create.foundation.utility.Couple; import com.simibubi.create.foundation.utility.Couple;
import com.simibubi.create.foundation.utility.VecHelper; import com.simibubi.create.foundation.utility.VecHelper;
@ -31,6 +33,7 @@ public class Carriage {
public CarriageContraption contraption; public CarriageContraption contraption;
public int bogeySpacing; public int bogeySpacing;
public int id; public int id;
public boolean blocked;
WeakReference<CarriageContraptionEntity> entity; WeakReference<CarriageContraptionEntity> entity;
Couple<CarriageBogey> bogeys; Couple<CarriageBogey> bogeys;
@ -40,6 +43,10 @@ public class Carriage {
this.bogeys = Couple.create(bogey1, bogey2); this.bogeys = Couple.create(bogey1, bogey2);
this.entity = new WeakReference<>(null); this.entity = new WeakReference<>(null);
this.id = netIdGenerator.incrementAndGet(); this.id = netIdGenerator.incrementAndGet();
bogey1.carriage = this;
if (bogey2 != null)
bogey2.carriage = this;
} }
public void setTrain(Train train) { public void setTrain(Train train) {
@ -51,11 +58,13 @@ public class Carriage {
contraption.setCarriage(this); contraption.setCarriage(this);
} }
public double travel(Level level, double distance, @Nullable Function<MovingPoint, ITrackSelector> control) { public double travel(Level level, TrackGraph graph, double distance,
@Nullable Function<TravellingPoint, ITrackSelector> control) {
Vec3 leadingAnchor = leadingBogey().anchorPosition; Vec3 leadingAnchor = leadingBogey().anchorPosition;
Vec3 trailingAnchor = trailingBogey().anchorPosition; Vec3 trailingAnchor = trailingBogey().anchorPosition;
boolean onTwoBogeys = isOnTwoBogeys(); boolean onTwoBogeys = isOnTwoBogeys();
double stress = onTwoBogeys ? bogeySpacing - leadingAnchor.distanceTo(trailingAnchor) : 0; double stress = onTwoBogeys ? bogeySpacing - leadingAnchor.distanceTo(trailingAnchor) : 0;
blocked = false;
// positive stress: points should move apart // positive stress: points should move apart
// negative stress: points should move closer // negative stress: points should move closer
@ -65,7 +74,7 @@ public class Carriage {
double leadingPointModifier = 0.5d; double leadingPointModifier = 0.5d;
double trailingPointModifier = -0.5d; double trailingPointModifier = -0.5d;
MutableObject<MovingPoint> previous = new MutableObject<>(); MutableObject<TravellingPoint> previous = new MutableObject<>();
MutableDouble distanceMoved = new MutableDouble(distance); MutableDouble distanceMoved = new MutableDouble(distance);
bogeys.forEachWithContext((bogey, firstBogey) -> { bogeys.forEachWithContext((bogey, firstBogey) -> {
@ -76,15 +85,16 @@ public class Carriage {
double bogeyStress = bogey.getStress(); double bogeyStress = bogey.getStress();
bogey.points.forEachWithContext((point, first) -> { bogey.points.forEachWithContext((point, first) -> {
MovingPoint prevPoint = previous.getValue(); TravellingPoint prevPoint = previous.getValue();
ITrackSelector trackSelector = ITrackSelector trackSelector =
prevPoint == null ? control == null ? point.random() : control.apply(point) prevPoint == null ? control == null ? point.random() : control.apply(point)
: point.follow(prevPoint); : point.follow(prevPoint);
double correction = bogeyStress * (first ? leadingPointModifier : trailingPointModifier); double correction = bogeyStress * (first ? leadingPointModifier : trailingPointModifier);
double toMove = distanceMoved.getValue(); double toMove = distanceMoved.getValue();
double moved = point.travel(toMove, trackSelector); double moved = point.travel(graph, toMove, trackSelector);
point.travel(correction + bogeyCorrection, trackSelector); point.travel(graph, correction + bogeyCorrection, trackSelector);
blocked |= point.blocked;
distanceMoved.setValue(moved); distanceMoved.setValue(moved);
previous.setValue(point); previous.setValue(point);
@ -158,11 +168,11 @@ public class Carriage {
entity.discard(); entity.discard();
} }
public MovingPoint getLeadingPoint() { public TravellingPoint getLeadingPoint() {
return leadingBogey().leading(); return leadingBogey().leading();
} }
public MovingPoint getTrailingPoint() { public TravellingPoint getTrailingPoint() {
return trailingBogey().trailing(); return trailingBogey().trailing();
} }
@ -180,8 +190,9 @@ public class Carriage {
public static class CarriageBogey { public static class CarriageBogey {
Carriage carriage;
IBogeyBlock type; IBogeyBlock type;
Couple<MovingPoint> points; Couple<TravellingPoint> points;
Vec3 anchorPosition; Vec3 anchorPosition;
LerpedFloat wheelAngle; LerpedFloat wheelAngle;
@ -191,13 +202,16 @@ public class Carriage {
public Vec3 leadingCouplingAnchor; public Vec3 leadingCouplingAnchor;
public Vec3 trailingCouplingAnchor; public Vec3 trailingCouplingAnchor;
public CarriageBogey(IBogeyBlock type, MovingPoint point, MovingPoint point2) { int derailAngle;
public CarriageBogey(IBogeyBlock type, TravellingPoint point, TravellingPoint point2) {
this.type = type; this.type = type;
points = Couple.create(point, point2); points = Couple.create(point, point2);
wheelAngle = LerpedFloat.angular(); wheelAngle = LerpedFloat.angular();
yaw = LerpedFloat.angular(); yaw = LerpedFloat.angular();
pitch = LerpedFloat.angular(); pitch = LerpedFloat.angular();
updateAnchorPosition(); updateAnchorPosition();
derailAngle = Create.RANDOM.nextInt(90) - 45;
} }
public void updateAngles(double distanceMoved) { public void updateAngles(double distanceMoved) {
@ -209,16 +223,20 @@ public class Carriage {
double diffZ = positionVec.z - coupledVec.z; double diffZ = positionVec.z - coupledVec.z;
float yRot = AngleHelper.deg(Mth.atan2(diffZ, diffX)) + 90; float yRot = AngleHelper.deg(Mth.atan2(diffZ, diffX)) + 90;
float xRot = AngleHelper.deg(Math.atan2(diffY, Math.sqrt(diffX * diffX + diffZ * diffZ))); float xRot = AngleHelper.deg(Math.atan2(diffY, Math.sqrt(diffX * diffX + diffZ * diffZ)));
if (carriage.train.derailed)
yRot += derailAngle;
wheelAngle.setValue((wheelAngle.getValue() - angleDiff) % 360); wheelAngle.setValue((wheelAngle.getValue() - angleDiff) % 360);
pitch.setValue(xRot); pitch.setValue(xRot);
yaw.setValue(-yRot); yaw.setValue(-yRot);
} }
public MovingPoint leading() { public TravellingPoint leading() {
return points.getFirst(); return points.getFirst();
} }
public MovingPoint trailing() { public TravellingPoint trailing() {
return points.getSecond(); return points.getSecond();
} }
@ -240,16 +258,6 @@ public class Carriage {
Vec3 thisOffset = type.getConnectorAnchorOffset(); Vec3 thisOffset = type.getConnectorAnchorOffset();
thisOffset = thisOffset.multiply(1, 1, leading ? -1 : 1); thisOffset = thisOffset.multiply(1, 1, leading ? -1 : 1);
// msr.rotateY(viewYRot + 90)
// .rotateX(-viewXRot)
// .rotateY(180)
// .translate(0, 0, first ? 0 : -bogeySpacing)
// .rotateY(-180)
// .rotateX(viewXRot)
// .rotateY(-viewYRot - 90)
// .rotateY(bogey.yaw.getValue(partialTicks))
// .rotateX(bogey.pitch.getValue(partialTicks))
thisOffset = VecHelper.rotate(thisOffset, pitch.getValue(partialTicks), Axis.X); thisOffset = VecHelper.rotate(thisOffset, pitch.getValue(partialTicks), Axis.X);
thisOffset = VecHelper.rotate(thisOffset, yaw.getValue(partialTicks), Axis.Y); thisOffset = VecHelper.rotate(thisOffset, yaw.getValue(partialTicks), Axis.Y);
thisOffset = VecHelper.rotate(thisOffset, -entityYRot - 90, Axis.Y); thisOffset = VecHelper.rotate(thisOffset, -entityYRot - 90, Axis.Y);

View file

@ -3,7 +3,9 @@ package com.simibubi.create.content.logistics.trains.entity;
import com.simibubi.create.AllEntityTypes; import com.simibubi.create.AllEntityTypes;
import com.simibubi.create.Create; import com.simibubi.create.Create;
import com.simibubi.create.content.contraptions.components.structureMovement.OrientedContraptionEntity; import com.simibubi.create.content.contraptions.components.structureMovement.OrientedContraptionEntity;
import com.simibubi.create.foundation.utility.VecHelper;
import net.minecraft.core.particles.ParticleTypes;
import net.minecraft.world.entity.EntityType; import net.minecraft.world.entity.EntityType;
import net.minecraft.world.level.Level; import net.minecraft.world.level.Level;
import net.minecraft.world.phys.Vec3; import net.minecraft.world.phys.Vec3;
@ -44,13 +46,29 @@ public class CarriageContraptionEntity extends OrientedContraptionEntity {
xo = getX(); xo = getX();
yo = getY(); yo = getY();
zo = getZ(); zo = getZ();
carriage.moveEntity(this); carriage.moveEntity(this);
double distanceTo = position().distanceTo(new Vec3(xo, yo, zo)); double distanceTo = position().distanceTo(new Vec3(xo, yo, zo));
carriage.bogeys.getFirst() carriage.bogeys.getFirst()
.updateAngles(distanceTo); .updateAngles(distanceTo);
if (carriage.isOnTwoBogeys()) if (carriage.isOnTwoBogeys())
carriage.bogeys.getSecond() carriage.bogeys.getSecond()
.updateAngles(distanceTo); .updateAngles(distanceTo);
if (carriage.train.derailed)
spawnDerailParticles(carriage);
}
Vec3 derailParticleOffset = VecHelper.offsetRandomly(Vec3.ZERO, Create.RANDOM, 1.5f)
.multiply(1, .25f, 1);
private void spawnDerailParticles(Carriage carriage) {
if (random.nextFloat() < 1 / 20f) {
Vec3 v = position().add(derailParticleOffset);
level.addParticle(ParticleTypes.CAMPFIRE_COSY_SMOKE, v.x, v.y, v.z, 0, .04, 0);
}
} }
@Override @Override

View file

@ -13,7 +13,7 @@ import com.simibubi.create.content.logistics.trains.TrackEdge;
import com.simibubi.create.content.logistics.trains.TrackGraph; import com.simibubi.create.content.logistics.trains.TrackGraph;
import com.simibubi.create.content.logistics.trains.TrackNode; import com.simibubi.create.content.logistics.trains.TrackNode;
import com.simibubi.create.content.logistics.trains.TrackNodeLocation; import com.simibubi.create.content.logistics.trains.TrackNodeLocation;
import com.simibubi.create.content.logistics.trains.entity.MovingPoint.ITrackSelector; import com.simibubi.create.content.logistics.trains.entity.TravellingPoint.ITrackSelector;
import com.simibubi.create.content.logistics.trains.management.GlobalStation; import com.simibubi.create.content.logistics.trains.management.GlobalStation;
import com.simibubi.create.foundation.utility.Couple; import com.simibubi.create.foundation.utility.Couple;
import com.simibubi.create.foundation.utility.Pair; import com.simibubi.create.foundation.utility.Pair;
@ -24,18 +24,14 @@ import net.minecraft.world.phys.Vec3;
public class Navigation { public class Navigation {
TrackGraph graph;
Train train; Train train;
public GlobalStation destination; public GlobalStation destination;
public double distanceToDestination; public double distanceToDestination;
List<TrackEdge> currentPath;
List<TrackEdge> path;
public Navigation(Train train, TrackGraph graph) { public Navigation(Train train, TrackGraph graph) {
this.train = train; this.train = train;
this.graph = graph; currentPath = new ArrayList<>();
path = new ArrayList<>();
} }
public void tick(Level level) { public void tick(Level level) {
@ -47,7 +43,7 @@ public class Navigation {
if (distanceToDestination < 1 / 32f) { if (distanceToDestination < 1 / 32f) {
distanceToDestination = 0; distanceToDestination = 0;
train.speed = 0; train.speed = 0;
path.clear(); currentPath.clear();
train.arriveAt(destination); train.arriveAt(destination);
destination = null; destination = null;
return; return;
@ -83,13 +79,13 @@ public class Navigation {
return destination != null; return destination != null;
} }
public ITrackSelector control(MovingPoint mp) { public ITrackSelector control(TravellingPoint mp) {
return list -> { return (graph, list) -> {
if (!path.isEmpty()) { if (!currentPath.isEmpty()) {
TrackEdge target = path.get(0); TrackEdge target = currentPath.get(0);
for (Entry<TrackNode, TrackEdge> entry : list) { for (Entry<TrackNode, TrackEdge> entry : list) {
if (entry.getValue() == target) { if (entry.getValue() == target) {
path.remove(0); currentPath.remove(0);
return entry; return entry;
} }
} }
@ -100,30 +96,50 @@ public class Navigation {
public void cancelNavigation() { public void cancelNavigation() {
distanceToDestination = 0; distanceToDestination = 0;
path.clear(); currentPath.clear();
if (destination == null) if (destination == null)
return; return;
destination.cancelReservation(train); destination.cancelReservation(train);
destination = null;
train.runtime.transitInterrupted();
} }
public void setDestination(GlobalStation destination) { public double startNavigation(GlobalStation destination, boolean simulate) {
findPathTo(destination); Pair<Double, List<TrackEdge>> pathTo = findPathTo(destination);
if (distanceToDestination == 0)
return; if (simulate)
return pathTo.getFirst();
distanceToDestination = pathTo.getFirst();
currentPath = pathTo.getSecond();
if (distanceToDestination == -1) {
distanceToDestination = 0;
if (this.destination != null)
cancelNavigation();
return -1;
}
if (this.destination == destination) if (this.destination == destination)
return; return 0;
train.leave(); train.leave();
this.destination = destination; this.destination = destination;
return distanceToDestination;
} }
private void findPathTo(GlobalStation destination) { private Pair<Double, List<TrackEdge>> findPathTo(GlobalStation destination) {
path.clear(); TrackGraph graph = train.graph;
this.distanceToDestination = 0; List<TrackEdge> path = new ArrayList<>();
double distanceToDestination = 0;
if (graph == null)
return Pair.of(-1d, path);
Couple<TrackNodeLocation> target = destination.edgeLocation; Couple<TrackNodeLocation> target = destination.edgeLocation;
PriorityQueue<Pair<Double, Pair<Couple<TrackNode>, TrackEdge>>> frontier = PriorityQueue<Pair<Double, Pair<Couple<TrackNode>, TrackEdge>>> frontier =
new PriorityQueue<>((p1, p2) -> Double.compare(p1.getFirst(), p2.getFirst())); new PriorityQueue<>((p1, p2) -> Double.compare(p1.getFirst(), p2.getFirst()));
MovingPoint leadingPoint = train.carriages.get(0) TravellingPoint leadingPoint = train.carriages.get(0)
.getLeadingPoint(); .getLeadingPoint();
Set<TrackEdge> visited = new HashSet<>(); Set<TrackEdge> visited = new HashSet<>();
Map<TrackEdge, Pair<Boolean, TrackEdge>> reachedVia = new IdentityHashMap<>(); Map<TrackEdge, Pair<Boolean, TrackEdge>> reachedVia = new IdentityHashMap<>();
@ -167,7 +183,7 @@ public class Navigation {
else else
distanceToDestination += train.getTotalLength() + 2; distanceToDestination += train.getTotalLength() + 2;
distanceToDestination -= position; distanceToDestination -= position;
return; return Pair.of(distanceToDestination, path);
} }
for (Entry<TrackNode, TrackEdge> entry : graph.getConnectionsFrom(node2) for (Entry<TrackNode, TrackEdge> entry : graph.getConnectionsFrom(node2)
@ -194,6 +210,8 @@ public class Navigation {
Pair.of(Couple.create(node2, newNode), newEdge))); Pair.of(Couple.create(node2, newNode), newEdge)));
} }
} }
return Pair.of(-1d, path);
} }
} }

View file

@ -1,21 +1,37 @@
package com.simibubi.create.content.logistics.trains.entity; package com.simibubi.create.content.logistics.trains.entity;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.UUID; import java.util.UUID;
import java.util.function.Consumer;
import java.util.function.Function; import java.util.function.Function;
import javax.annotation.Nullable;
import org.apache.commons.lang3.mutable.MutableBoolean;
import com.simibubi.create.Create; import com.simibubi.create.Create;
import com.simibubi.create.CreateClient; import com.simibubi.create.CreateClient;
import com.simibubi.create.content.logistics.trains.TrackGraph; import com.simibubi.create.content.logistics.trains.TrackGraph;
import com.simibubi.create.content.logistics.trains.entity.MovingPoint.ITrackSelector; import com.simibubi.create.content.logistics.trains.TrackNode;
import com.simibubi.create.content.logistics.trains.entity.TravellingPoint.ITrackSelector;
import com.simibubi.create.content.logistics.trains.management.GlobalStation; import com.simibubi.create.content.logistics.trains.management.GlobalStation;
import com.simibubi.create.content.logistics.trains.management.GraphLocation;
import com.simibubi.create.content.logistics.trains.management.ScheduleRuntime; import com.simibubi.create.content.logistics.trains.management.ScheduleRuntime;
import com.simibubi.create.content.logistics.trains.management.ScheduleRuntime.State;
import com.simibubi.create.foundation.utility.Lang; import com.simibubi.create.foundation.utility.Lang;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction; import net.minecraft.core.Direction;
import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Component;
import net.minecraft.util.Mth; import net.minecraft.util.Mth;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.level.Level; import net.minecraft.world.level.Level;
import net.minecraft.world.phys.Vec3; import net.minecraft.world.phys.Vec3;
@ -28,28 +44,37 @@ public class Train {
public double targetSpeed = 0; public double targetSpeed = 0;
public UUID id; public UUID id;
public UUID owner;
public TrackGraph graph; public TrackGraph graph;
public Navigation navigation; public Navigation navigation;
public GlobalStation currentStation;
public ScheduleRuntime runtime; public ScheduleRuntime runtime;
public TrainIconType icon; public TrainIconType icon;
public Component name; public Component name;
public TrainStatus status;
public UUID currentStation;
public boolean heldForAssembly; public boolean heldForAssembly;
public boolean doubleEnded; public boolean doubleEnded;
public List<Carriage> carriages; public List<Carriage> carriages;
public List<Integer> carriageSpacing; public List<Integer> carriageSpacing;
List<TrainMigration> migratingPoints;
public int migrationCooldown;
public boolean derailed;
double[] stress; double[] stress;
public Train(UUID id, TrackGraph graph, List<Carriage> carriages, List<Integer> carriageSpacing) { public Train(UUID id, UUID owner, TrackGraph graph, List<Carriage> carriages, List<Integer> carriageSpacing) {
this.id = id; this.id = id;
this.owner = owner;
this.graph = graph; this.graph = graph;
this.carriages = carriages; this.carriages = carriages;
this.carriageSpacing = carriageSpacing; this.carriageSpacing = carriageSpacing;
this.icon = TrainIconType.getDefault(); this.icon = TrainIconType.getDefault();
this.stress = new double[carriageSpacing.size()]; this.stress = new double[carriageSpacing.size()];
this.name = Lang.translate("train.unnamed"); this.name = Lang.translate("train.unnamed");
this.status = new TrainStatus(this);
carriages.forEach(c -> { carriages.forEach(c -> {
c.setTrain(this); c.setTrain(this);
@ -60,12 +85,28 @@ public class Train {
navigation = new Navigation(this, graph); navigation = new Navigation(this, graph);
runtime = new ScheduleRuntime(this); runtime = new ScheduleRuntime(this);
heldForAssembly = true; heldForAssembly = true;
migratingPoints = new ArrayList<>();
currentStation = null;
} }
public void tick(Level level) { public void tick(Level level) {
status.tick(level);
if (graph == null) {
if (!migratingPoints.isEmpty())
reattachToTracks(level);
return;
}
runtime.tick(level); runtime.tick(level);
navigation.tick(level); navigation.tick(level);
if (navigation.destination == null && speed > 0) {
speed -= acceleration;
if (speed <= 0)
speed = 0;
}
double distance = speed; double distance = speed;
Carriage previousCarriage = null; Carriage previousCarriage = null;
@ -88,27 +129,38 @@ public class Train {
double leadingModifier = approachingStation ? -0.75d : -0.5d; double leadingModifier = approachingStation ? -0.75d : -0.5d;
double trailingModifier = approachingStation ? 0d : 0.125d; double trailingModifier = approachingStation ? 0d : 0.125d;
MovingPoint previous = null; TravellingPoint previous = null;
boolean blocked = false;
for (int i = 0; i < carriages.size(); i++) { for (int i = 0; i < carriages.size(); i++) {
double leadingStress = i == 0 ? 0 : stress[i - 1] * leadingModifier; double leadingStress = i == 0 ? 0 : stress[i - 1] * leadingModifier;
double trailingStress = i == stress.length ? 0 : stress[i] * trailingModifier; double trailingStress = i == stress.length ? 0 : stress[i] * trailingModifier;
Carriage carriage = carriages.get(i); Carriage carriage = carriages.get(i);
MovingPoint toFollow = previous; TravellingPoint toFollow = previous;
Function<MovingPoint, ITrackSelector> control = Function<TravellingPoint, ITrackSelector> control =
previous == null ? navigation::control : mp -> mp.follow(toFollow); previous == null ? navigation::control : mp -> mp.follow(toFollow);
double actualDistance = carriage.travel(level, distance + leadingStress + trailingStress, control); double actualDistance = carriage.travel(level, graph, distance + leadingStress + trailingStress, control);
blocked |= carriage.blocked;
if (i == 0) if (i == 0)
distance = actualDistance; distance = actualDistance;
previous = carriage.getTrailingPoint(); previous = carriage.getTrailingPoint();
} }
if (blocked) {
speed = 0;
navigation.cancelNavigation();
runtime.tick(level);
status.endOfTrack();
} else if (speed > 0)
status.trackOK();
if (navigation.destination != null) { if (navigation.destination != null) {
boolean recalculate = navigation.distanceToDestination > 20; boolean recalculate = navigation.distanceToDestination % 100 > 20;
navigation.distanceToDestination -= distance; navigation.distanceToDestination -= distance;
if (recalculate && navigation.distanceToDestination <= 20) if (recalculate && navigation.distanceToDestination % 100 <= 20)
navigation.setDestination(navigation.destination); navigation.startNavigation(navigation.destination, false);
} }
} }
@ -141,14 +193,87 @@ public class Train {
offset += carriageSpacing.get(i); offset += carriageSpacing.get(i);
} }
GlobalStation currentStation = getCurrentStation();
if (currentStation != null) if (currentStation != null)
currentStation.cancelReservation(this); currentStation.cancelReservation(this);
Create.RAILWAYS.trains.remove(id); Create.RAILWAYS.trains.remove(id);
CreateClient.RAILWAYS.trains.remove(id); CreateClient.RAILWAYS.trains.remove(id); // TODO Thread breach
return true; return true;
} }
public boolean isTravellingOn(TrackNode node) {
MutableBoolean affected = new MutableBoolean(false);
forEachTravellingPoint(tp -> {
if (tp.node1 == node || tp.node2 == node)
affected.setTrue();
});
return affected.booleanValue();
}
public void detachFromTracks() {
migratingPoints.clear();
navigation.cancelNavigation();
forEachTravellingPoint(tp -> migratingPoints.add(new TrainMigration(tp)));
graph = null;
}
private void forEachTravellingPoint(Consumer<TravellingPoint> callback) {
for (Carriage c : carriages) {
c.leadingBogey().points.forEach(callback::accept);
if (c.isOnTwoBogeys())
c.trailingBogey().points.forEach(callback::accept);
}
}
public void reattachToTracks(Level level) {
if (migrationCooldown > 0) {
migrationCooldown--;
return;
}
Set<Entry<UUID, TrackGraph>> entrySet = new HashSet<>(Create.RAILWAYS.trackNetworks.entrySet());
Map<UUID, List<GraphLocation>> successfulMigrations = new HashMap<>();
for (TrainMigration md : migratingPoints) {
for (Iterator<Entry<UUID, TrackGraph>> iterator = entrySet.iterator(); iterator.hasNext();) {
Entry<UUID, TrackGraph> entry = iterator.next();
GraphLocation gl = md.tryMigratingTo(entry.getValue());
if (gl == null) {
iterator.remove();
continue;
}
successfulMigrations.computeIfAbsent(entry.getKey(), uuid -> new ArrayList<>())
.add(gl);
}
}
if (entrySet.isEmpty()) {
migrationCooldown = 40;
status.failedMigration();
derailed = true;
return;
}
for (Entry<UUID, TrackGraph> entry : entrySet) {
graph = entry.getValue();
List<GraphLocation> locations = successfulMigrations.get(entry.getKey());
forEachTravellingPoint(tp -> tp.migrateTo(locations));
migratingPoints.clear();
if (derailed)
status.successfulMigration();
derailed = false;
if (runtime.getSchedule() != null) {
if (runtime.state == State.IN_TRANSIT)
runtime.state = State.PRE_TRANSIT;
}
GlobalStation currentStation = getCurrentStation();
if (currentStation != null)
currentStation.reserveFor(this);
return;
}
}
public int getTotalLength() { public int getTotalLength() {
int length = 0; int length = 0;
for (int i = 0; i < carriages.size(); i++) { for (int i = 0; i < carriages.size(); i++) {
@ -160,13 +285,38 @@ public class Train {
} }
public void leave() { public void leave() {
GlobalStation currentStation = getCurrentStation();
if (currentStation == null)
return;
currentStation.trainDeparted(this); currentStation.trainDeparted(this);
currentStation = null; this.currentStation = null;
} }
public void arriveAt(GlobalStation station) { public void arriveAt(GlobalStation station) {
currentStation = station; setCurrentStation(station);
runtime.destinationReached(); runtime.destinationReached();
} }
public void setCurrentStation(GlobalStation station) {
currentStation = station.id;
}
public GlobalStation getCurrentStation() {
if (currentStation == null)
return null;
if (graph == null)
return null;
return graph.getStation(currentStation);
}
@Nullable
public LivingEntity getOwner(Level level) {
try {
UUID uuid = owner;
return uuid == null ? null : level.getPlayerByUUID(uuid);
} catch (IllegalArgumentException illegalargumentexception) {
return null;
}
}
} }

View file

@ -0,0 +1,96 @@
package com.simibubi.create.content.logistics.trains.entity;
import java.util.Map.Entry;
import com.simibubi.create.content.logistics.trains.TrackEdge;
import com.simibubi.create.content.logistics.trains.TrackGraph;
import com.simibubi.create.content.logistics.trains.TrackNode;
import com.simibubi.create.content.logistics.trains.TrackNodeLocation;
import com.simibubi.create.content.logistics.trains.management.GraphLocation;
import com.simibubi.create.foundation.utility.Couple;
import com.simibubi.create.foundation.utility.VecHelper;
import net.minecraft.util.Mth;
import net.minecraft.world.phys.Vec3;
class TrainMigration {
Couple<TrackNodeLocation> locations;
double positionOnOldEdge;
boolean curve;
Vec3 fallback;
public TrainMigration(TravellingPoint point) {
double t = point.position / point.edge.getLength(point.node1, point.node2);
fallback = point.edge.getPosition(point.node1, point.node2, t);
curve = point.edge.isTurn();
positionOnOldEdge = point.position;
locations = Couple.create(point.node1.getLocation(), point.node2.getLocation());
}
public GraphLocation tryMigratingTo(TrackGraph graph) {
TrackNode node1 = graph.locateNode(locations.getFirst());
TrackNode node2 = graph.locateNode(locations.getSecond());
if (node1 != null && node2 != null) {
TrackEdge edge = graph.getConnectionsFrom(node1)
.get(node2);
if (edge != null) {
GraphLocation graphLocation = new GraphLocation();
graphLocation.graph = graph;
graphLocation.edge = locations;
graphLocation.position = positionOnOldEdge;
return graphLocation;
}
}
if (curve)
return null;
Vec3 prevDirection = locations.getSecond()
.getLocation()
.subtract(locations.getFirst()
.getLocation())
.normalize();
for (TrackNodeLocation loc : graph.getNodes()) {
Vec3 nodeVec = loc.getLocation();
if (nodeVec.distanceToSqr(fallback) > 32 * 32)
continue;
TrackNode newNode1 = graph.locateNode(loc);
for (Entry<TrackNode, TrackEdge> entry : graph.getConnectionsFrom(newNode1)
.entrySet()) {
TrackEdge edge = entry.getValue();
if (edge.isTurn())
continue;
TrackNode newNode2 = entry.getKey();
float radius = 1 / 64f;
Vec3 direction = edge.getDirection(newNode1, newNode2, true);
if (!Mth.equal(direction.dot(prevDirection), 1))
continue;
Vec3 intersectSphere = VecHelper.intersectSphere(nodeVec, direction, fallback, radius);
if (intersectSphere == null)
continue;
if (!Mth.equal(direction.dot(intersectSphere.subtract(nodeVec)
.normalize()), 1))
continue;
double edgeLength = edge.getLength(newNode1, newNode2);
double position = intersectSphere.distanceTo(nodeVec) - radius;
if (Double.isNaN(position))
continue;
if (position < 0)
continue;
if (position > edgeLength)
continue;
GraphLocation graphLocation = new GraphLocation();
graphLocation.graph = graph;
graphLocation.edge = Couple.create(loc, newNode2.getLocation());
graphLocation.position = position;
return graphLocation;
}
}
return null;
}
}

View file

@ -0,0 +1,85 @@
package com.simibubi.create.content.logistics.trains.entity;
import java.util.ArrayList;
import java.util.List;
import net.minecraft.ChatFormatting;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.TextComponent;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.Level;
public class TrainStatus {
Train train;
boolean navigation;
boolean track;
List<Component> queued = new ArrayList<>();
public TrainStatus(Train train) {
this.train = train;
}
public void failedNavigation() {
if (navigation)
return;
displayInformation("Unable to find Path to next Scheduled destination", false);
navigation = true;
}
public void successfulNavigation() {
if (!navigation)
return;
displayInformation("Navigation succeeded", true);
navigation = false;
}
public void failedMigration() {
if (track)
return;
displayInformation("Tracks are missing beneath the Train", false);
track = true;
}
public void endOfTrack() {
if (track)
return;
displayInformation("A Carriage has reached the end of its Track.", false);
track = true;
}
public void successfulMigration() {
if (!track)
return;
displayInformation("Train is back on Track", true);
track = false;
}
public void trackOK() {
track = false;
}
public void tick(Level level) {
if (queued.isEmpty())
return;
LivingEntity owner = train.getOwner(level);
if (owner == null)
return;
if (owner instanceof Player player) {
// TODO change to Lang.translate
player.displayClientMessage(new TextComponent("<i> Information about Train: ").append(train.name)
.withStyle(ChatFormatting.GOLD), false);
queued.forEach(c -> player.displayClientMessage(c, false));
}
queued.clear();
}
public void displayInformation(String key, boolean itsAGoodThing, Object... args) {
queued.add(new TextComponent(" - ").withStyle(ChatFormatting.GRAY)
.append(new TextComponent(key).withStyle(st -> st.withColor(itsAGoodThing ? 0xD5ECC2 : 0xFFD3B4))));
}
}

View file

@ -6,21 +6,22 @@ import java.util.List;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.Set; import java.util.Set;
import java.util.Vector; import java.util.Vector;
import java.util.function.Function; import java.util.function.BiFunction;
import com.simibubi.create.Create; import com.simibubi.create.Create;
import com.simibubi.create.content.logistics.trains.TrackEdge; import com.simibubi.create.content.logistics.trains.TrackEdge;
import com.simibubi.create.content.logistics.trains.TrackGraph; import com.simibubi.create.content.logistics.trains.TrackGraph;
import com.simibubi.create.content.logistics.trains.TrackNode; import com.simibubi.create.content.logistics.trains.TrackNode;
import com.simibubi.create.content.logistics.trains.management.GraphLocation;
import net.minecraft.world.phys.Vec3; import net.minecraft.world.phys.Vec3;
public class MovingPoint { public class TravellingPoint {
TrackGraph graph;
TrackNode node1, node2; TrackNode node1, node2;
TrackEdge edge; TrackEdge edge;
double position; double position;
boolean blocked;
public static enum SteerDirection { public static enum SteerDirection {
NONE(0), LEFT(-1), RIGHT(1); NONE(0), LEFT(-1), RIGHT(1);
@ -33,11 +34,10 @@ public class MovingPoint {
} }
public static interface ITrackSelector public static interface ITrackSelector
extends Function<List<Entry<TrackNode, TrackEdge>>, Entry<TrackNode, TrackEdge>> { extends BiFunction<TrackGraph, List<Entry<TrackNode, TrackEdge>>, Entry<TrackNode, TrackEdge>> {
}; };
public MovingPoint(TrackGraph graph, TrackNode node1, TrackNode node2, TrackEdge edge, double position) { public TravellingPoint(TrackNode node1, TrackNode node2, TrackEdge edge, double position) {
this.graph = graph;
this.node1 = node1; this.node1 = node1;
this.node2 = node2; this.node2 = node2;
this.edge = edge; this.edge = edge;
@ -45,11 +45,11 @@ public class MovingPoint {
} }
public ITrackSelector random() { public ITrackSelector random() {
return validTargets -> validTargets.get(Create.RANDOM.nextInt(validTargets.size())); return (graph, validTargets) -> validTargets.get(Create.RANDOM.nextInt(validTargets.size()));
} }
public ITrackSelector follow(MovingPoint other) { public ITrackSelector follow(TravellingPoint other) {
return validTargets -> { return (graph, validTargets) -> {
TrackNode target = other.node1; TrackNode target = other.node1;
for (Entry<TrackNode, TrackEdge> entry : validTargets) for (Entry<TrackNode, TrackEdge> entry : validTargets)
@ -99,7 +99,7 @@ public class MovingPoint {
} }
public ITrackSelector steer(SteerDirection direction, Vec3 upNormal) { public ITrackSelector steer(SteerDirection direction, Vec3 upNormal) {
return validTargets -> { return (graph, validTargets) -> {
double closest = Double.MAX_VALUE; double closest = Double.MAX_VALUE;
Entry<TrackNode, TrackEdge> best = null; Entry<TrackNode, TrackEdge> best = null;
@ -126,13 +126,13 @@ public class MovingPoint {
}; };
} }
public double travel(double distance, ITrackSelector trackSelector) { public double travel(TrackGraph graph, double distance, ITrackSelector trackSelector) {
blocked = false;
double edgeLength = edge.getLength(node1, node2); double edgeLength = edge.getLength(node1, node2);
if (distance == 0) if (distance == 0)
return 0; return 0;
double traveled = distance; double traveled = distance;
double currentT = position / edgeLength; double currentT = position / edgeLength;
double incrementT = edge.incrementT(node1, node2, currentT, distance); double incrementT = edge.incrementT(node1, node2, currentT, distance);
position = incrementT * edgeLength; position = incrementT * edgeLength;
@ -159,11 +159,12 @@ public class MovingPoint {
if (validTargets.isEmpty()) { if (validTargets.isEmpty()) {
traveled -= position - edgeLength; traveled -= position - edgeLength;
position = edgeLength; position = edgeLength;
blocked = true;
break; break;
} }
Entry<TrackNode, TrackEdge> entry = Entry<TrackNode, TrackEdge> entry =
validTargets.size() == 1 ? validTargets.get(0) : trackSelector.apply(validTargets); validTargets.size() == 1 ? validTargets.get(0) : trackSelector.apply(graph, validTargets);
node1 = node2; node1 = node2;
node2 = entry.getKey(); node2 = entry.getKey();
@ -175,7 +176,7 @@ public class MovingPoint {
return traveled; return traveled;
} }
public void reverse() { public void reverse(TrackGraph graph) {
TrackNode n = node1; TrackNode n = node1;
node1 = node2; node1 = node2;
node2 = n; node2 = n;
@ -191,4 +192,14 @@ public class MovingPoint {
.scale(1)); .scale(1));
} }
public void migrateTo(List<GraphLocation> locations) {
GraphLocation location = locations.remove(0);
TrackGraph graph = location.graph;
node1 = graph.locateNode(location.edge.getFirst());
node2 = graph.locateNode(location.edge.getSecond());
position = location.position;
edge = graph.getConnectionsFrom(node1)
.get(node2);
}
} }

View file

@ -5,12 +5,9 @@ import java.util.UUID;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import com.simibubi.create.Create;
import com.simibubi.create.content.logistics.trains.TrackNode;
import com.simibubi.create.content.logistics.trains.TrackNodeLocation; import com.simibubi.create.content.logistics.trains.TrackNodeLocation;
import com.simibubi.create.content.logistics.trains.entity.Train; import com.simibubi.create.content.logistics.trains.entity.Train;
import com.simibubi.create.foundation.utility.Couple; import com.simibubi.create.foundation.utility.Couple;
import com.simibubi.create.foundation.utility.Debug;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.CompoundTag;
@ -49,21 +46,18 @@ public class GlobalStation {
public void migrate(LevelAccessor level) { public void migrate(LevelAccessor level) {
BlockEntity blockEntity = level.getBlockEntity(stationPos); BlockEntity blockEntity = level.getBlockEntity(stationPos);
if (blockEntity instanceof StationTileEntity station) { if (blockEntity instanceof StationTileEntity station) {
Debug.debugChat("Migrating Station " + name);
station.migrate(this); station.migrate(this);
return; return;
} }
Create.LOGGER
.warn("Couldn't migrate Station: " + name + " to changed Graph because associated Tile wasn't loaded.");
} }
public void setLocation(Couple<TrackNode> nodes, double position) { public void setLocation(Couple<TrackNodeLocation> nodes, double position) {
this.edgeLocation = nodes.map(TrackNode::getLocation); this.edgeLocation = nodes;
this.position = position; this.position = position;
} }
public void reserveFor(Train train) { public void reserveFor(Train train) {
Train nearestTrain = this.nearestTrain.get(); Train nearestTrain = getNearestTrain();
if (nearestTrain == null if (nearestTrain == null
|| nearestTrain.navigation.distanceToDestination > train.navigation.distanceToDestination) || nearestTrain.navigation.distanceToDestination > train.navigation.distanceToDestination)
this.nearestTrain = new WeakReference<>(train); this.nearestTrain = new WeakReference<>(train);
@ -80,18 +74,18 @@ public class GlobalStation {
@Nullable @Nullable
public Train getPresentTrain() { public Train getPresentTrain() {
Train nearestTrain = this.nearestTrain.get(); Train nearestTrain = getNearestTrain();
if (nearestTrain == null || nearestTrain.currentStation != this) if (nearestTrain == null || nearestTrain.getCurrentStation() != this)
return null; return null;
return nearestTrain; return nearestTrain;
} }
@Nullable @Nullable
public Train getImminentTrain() { public Train getImminentTrain() {
Train nearestTrain = this.nearestTrain.get(); Train nearestTrain = getNearestTrain();
if (nearestTrain == null) if (nearestTrain == null)
return nearestTrain; return nearestTrain;
if (nearestTrain.currentStation == this) if (nearestTrain.getCurrentStation() == this)
return nearestTrain; return nearestTrain;
if (!nearestTrain.navigation.isActive()) if (!nearestTrain.navigation.isActive())
return null; return null;
@ -100,6 +94,11 @@ public class GlobalStation {
return nearestTrain; return nearestTrain;
} }
@Nullable
public Train getNearestTrain() {
return this.nearestTrain.get();
}
public CompoundTag write() { public CompoundTag write() {
CompoundTag nbt = new CompoundTag(); CompoundTag nbt = new CompoundTag();
nbt.putUUID("Id", id); nbt.putUUID("Id", id);

View file

@ -0,0 +1,13 @@
package com.simibubi.create.content.logistics.trains.management;
import com.simibubi.create.content.logistics.trains.TrackGraph;
import com.simibubi.create.content.logistics.trains.TrackNodeLocation;
import com.simibubi.create.foundation.utility.Couple;
public class GraphLocation {
public TrackGraph graph;
public Couple<TrackNodeLocation> edge;
public double position;
}

View file

@ -15,17 +15,17 @@ import net.minecraft.world.level.Level;
public class ScheduleRuntime { public class ScheduleRuntime {
enum State { public enum State {
PRE_TRANSIT, IN_TRANSIT, POST_TRANSIT PRE_TRANSIT, IN_TRANSIT, POST_TRANSIT
} }
Train train; Train train;
Schedule schedule; Schedule schedule;
boolean paused;
boolean isAutoSchedule; boolean isAutoSchedule;
int currentEntry; public boolean paused;
State state; public int currentEntry;
public State state;
static final int INTERVAL = 40; static final int INTERVAL = 40;
int cooldown; int cooldown;
@ -51,6 +51,13 @@ public class ScheduleRuntime {
} }
} }
public void transitInterrupted() {
if (schedule == null || state != State.IN_TRANSIT)
return;
state = State.PRE_TRANSIT;
cooldown = 0;
}
public void tick(Level level) { public void tick(Level level) {
if (schedule == null) if (schedule == null)
return; return;
@ -76,17 +83,18 @@ public class ScheduleRuntime {
GlobalStation nextStation = findNextStation(); GlobalStation nextStation = findNextStation();
if (nextStation == null) { if (nextStation == null) {
train.status.failedNavigation();
cooldown = INTERVAL; cooldown = INTERVAL;
return; return;
} }
if (nextStation == train.currentStation) { train.status.successfulNavigation();
if (nextStation == train.getCurrentStation()) {
state = State.IN_TRANSIT; state = State.IN_TRANSIT;
destinationReached(); destinationReached();
return; return;
} }
if (train.navigation.startNavigation(nextStation, false) != -1)
train.navigation.setDestination(nextStation); state = State.IN_TRANSIT;
state = State.IN_TRANSIT;
} }
public void tickConditions(Level level) { public void tickConditions(Level level) {
@ -113,12 +121,25 @@ public class ScheduleRuntime {
public GlobalStation findNextStation() { public GlobalStation findNextStation() {
ScheduleEntry entry = schedule.entries.get(currentEntry); ScheduleEntry entry = schedule.entries.get(currentEntry);
ScheduleDestination destination = entry.destination; ScheduleDestination destination = entry.destination;
if (destination instanceof FilteredDestination filtered) { if (destination instanceof FilteredDestination filtered) {
String regex = filtered.nameFilter.replace("*", ".*");
GlobalStation best = null;
double bestCost = Double.MAX_VALUE;
for (GlobalStation globalStation : train.graph.getStations()) { for (GlobalStation globalStation : train.graph.getStations()) {
if (globalStation.name.equals(filtered.nameFilter)) if (!globalStation.name.matches(regex))
return globalStation; continue;
double cost = train.navigation.startNavigation(globalStation, true);
if (cost < 0)
continue;
if (cost > bestCost)
continue;
best = globalStation;
bestCost = cost;
} }
return best;
} }
return null; return null;
} }

View file

@ -6,6 +6,7 @@ import com.simibubi.create.foundation.networking.TileEntityConfigurationPacket;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.level.Level; import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.BlockState;
@ -66,7 +67,7 @@ public class StationEditPacket extends TileEntityConfigurationPacket<StationTile
} }
@Override @Override
protected void applySettings(StationTileEntity te) { protected void applySettings(ServerPlayer player, StationTileEntity te) {
Level level = te.getLevel(); Level level = te.getLevel();
BlockPos blockPos = te.getBlockPos(); BlockPos blockPos = te.getBlockPos();
BlockState blockState = level.getBlockState(blockPos); BlockState blockState = level.getBlockState(blockPos);
@ -85,7 +86,7 @@ public class StationEditPacket extends TileEntityConfigurationPacket<StationTile
if (!isAssemblyMode) if (!isAssemblyMode)
return; return;
if (tryAssemble) if (tryAssemble)
te.assemble(); te.assemble(player.getUUID());
else { else {
if (disassembleAndEnterMode(te)) if (disassembleAndEnterMode(te))
te.refreshAssemblyInfo(); te.refreshAssemblyInfo();
@ -123,4 +124,7 @@ public class StationEditPacket extends TileEntityConfigurationPacket<StationTile
return te.tryEnterAssemblyMode(); return te.tryEnterAssemblyMode();
} }
@Override
protected void applySettings(StationTileEntity te) {}
} }

View file

@ -99,7 +99,7 @@ public class StationRenderer extends SafeTileEntityRenderer<StationTileEntity> {
@Override @Override
public int getViewDistance() { public int getViewDistance() {
return 96; return 96 * 2;
} }
} }

View file

@ -167,12 +167,12 @@ public class StationScreen extends AbstractStationScreen {
return; return;
} }
if (train.navigation.destination != station && train.currentStation != station) { if (train.navigation.destination != station && train.getCurrentStation() != station) {
leavingAnimation = 80; leavingAnimation = 80;
return; return;
} }
disassembleTrainButton.active = train.currentStation == station; // TODO te.canAssemble disassembleTrainButton.active = train.getCurrentStation() == station; // TODO te.canAssemble
openScheduleButton.active = train.runtime.schedule != null; openScheduleButton.active = train.runtime.schedule != null;
float f = (float) (train.navigation.distanceToDestination / 30f); float f = (float) (train.navigation.distanceToDestination / 30f);

View file

@ -20,12 +20,10 @@ import com.simibubi.create.content.logistics.trains.TrackPropagator;
import com.simibubi.create.content.logistics.trains.entity.Carriage; import com.simibubi.create.content.logistics.trains.entity.Carriage;
import com.simibubi.create.content.logistics.trains.entity.Carriage.CarriageBogey; import com.simibubi.create.content.logistics.trains.entity.Carriage.CarriageBogey;
import com.simibubi.create.content.logistics.trains.entity.CarriageContraption; import com.simibubi.create.content.logistics.trains.entity.CarriageContraption;
import com.simibubi.create.content.logistics.trains.entity.MovingPoint;
import com.simibubi.create.content.logistics.trains.entity.Train; import com.simibubi.create.content.logistics.trains.entity.Train;
import com.simibubi.create.content.logistics.trains.management.TrackTargetingBehaviour.GraphLocation; import com.simibubi.create.content.logistics.trains.entity.TravellingPoint;
import com.simibubi.create.foundation.tileEntity.SmartTileEntity; import com.simibubi.create.foundation.tileEntity.SmartTileEntity;
import com.simibubi.create.foundation.tileEntity.TileEntityBehaviour; import com.simibubi.create.foundation.tileEntity.TileEntityBehaviour;
import com.simibubi.create.foundation.utility.Debug;
import com.simibubi.create.foundation.utility.Iterate; import com.simibubi.create.foundation.utility.Iterate;
import com.simibubi.create.foundation.utility.Lang; import com.simibubi.create.foundation.utility.Lang;
import com.simibubi.create.foundation.utility.Pair; import com.simibubi.create.foundation.utility.Pair;
@ -107,9 +105,6 @@ public class StationTileEntity extends SmartTileEntity {
toMigrate != null ? new GlobalStation(toMigrate) : new GlobalStation(id, worldPosition); toMigrate != null ? new GlobalStation(toMigrate) : new GlobalStation(id, worldPosition);
globalStation.setLocation(loc.edge, loc.position); globalStation.setLocation(loc.edge, loc.position);
loc.graph.addStation(globalStation); loc.graph.addStation(globalStation);
if (toMigrate != null)
Debug.debugChat("Migrated Station " + globalStation.name);
toMigrate = null; toMigrate = null;
setChanged(); setChanged();
@ -124,6 +119,7 @@ public class StationTileEntity extends SmartTileEntity {
super.read(tag, clientPacket); super.read(tag, clientPacket);
if (tag.contains("ToMigrate")) if (tag.contains("ToMigrate"))
toMigrate = tag.getCompound("ToMigrate"); toMigrate = tag.getCompound("ToMigrate");
renderBounds = null;
} }
@Override @Override
@ -312,7 +308,7 @@ public class StationTileEntity extends SmartTileEntity {
super.setRemovedNotDueToChunkUnload(); super.setRemovedNotDueToChunkUnload();
} }
public void assemble() { public void assemble(UUID playerUUID) {
refreshAssemblyInfo(); refreshAssemblyInfo();
if (bogeyLocations[0] != 0) { if (bogeyLocations[0] != 0) {
@ -349,7 +345,7 @@ public class StationTileEntity extends SmartTileEntity {
pointOffsets.add(Double.valueOf(loc + .5 + bogeySize / 2)); pointOffsets.add(Double.valueOf(loc + .5 + bogeySize / 2));
} }
List<MovingPoint> points = new ArrayList<>(); List<TravellingPoint> points = new ArrayList<>();
Vec3 directionVec = Vec3.atLowerCornerOf(assemblyDirection.getNormal()); Vec3 directionVec = Vec3.atLowerCornerOf(assemblyDirection.getNormal());
TrackGraph graph = null; TrackGraph graph = null;
TrackNode secondNode = null; TrackNode secondNode = null;
@ -402,7 +398,7 @@ public class StationTileEntity extends SmartTileEntity {
return; return;
} }
points.add(new MovingPoint(graph, node, secondNode, edge, positionOnEdge)); points.add(new TravellingPoint(node, secondNode, edge, positionOnEdge));
} }
secondNode = node; secondNode = node;
@ -483,9 +479,9 @@ public class StationTileEntity extends SmartTileEntity {
.forEach(Carriage::discardEntity); .forEach(Carriage::discardEntity);
Create.RAILWAYS.carriageById.clear(); Create.RAILWAYS.carriageById.clear();
Train train = new Train(UUID.randomUUID(), graph, carriages, spacing); Train train = new Train(UUID.randomUUID(), playerUUID, graph, carriages, spacing);
GlobalStation station = getOrCreateGlobalStation(); GlobalStation station = getOrCreateGlobalStation();
train.currentStation = station; train.setCurrentStation(station);
station.reserveFor(train); station.reserveFor(train);
Create.RAILWAYS.trains.put(train.id, train); Create.RAILWAYS.trains.put(train.id, train);
@ -518,7 +514,7 @@ public class StationTileEntity extends SmartTileEntity {
if (isAssembling()) if (isAssembling())
return INFINITE_EXTENT_AABB; return INFINITE_EXTENT_AABB;
if (renderBounds == null) if (renderBounds == null)
renderBounds = new AABB(worldPosition, getTarget().getGlobalPosition()); renderBounds = new AABB(worldPosition, getTarget().getGlobalPosition()).inflate(2);
return renderBounds; return renderBounds;
} }

View file

@ -84,12 +84,6 @@ public class TrackTargetingBehaviour extends TileEntityBehaviour {
return targetDirection; return targetDirection;
} }
static class GraphLocation {
public TrackGraph graph;
public Couple<TrackNode> edge;
public double position;
}
public GraphLocation determineGraphLocation() { public GraphLocation determineGraphLocation() {
Level level = getWorld(); Level level = getWorld();
BlockPos pos = getGlobalPosition(); BlockPos pos = getGlobalPosition();
@ -152,7 +146,7 @@ public class TrackTargetingBehaviour extends TileEntityBehaviour {
return null; return null;
GraphLocation graphLocation = new GraphLocation(); GraphLocation graphLocation = new GraphLocation();
graphLocation.edge = Couple.create(backNode, frontNode); graphLocation.edge = Couple.create(backNode.getLocation(), frontNode.getLocation());
graphLocation.position = position; graphLocation.position = position;
graphLocation.graph = graph; graphLocation.graph = graph;
return graphLocation; return graphLocation;

View file

@ -3,6 +3,8 @@ package com.simibubi.create.content.logistics.trains.management.schedule;
import java.util.List; import java.util.List;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import org.apache.commons.lang3.StringUtils;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.simibubi.create.AllBlocks; import com.simibubi.create.AllBlocks;
import com.simibubi.create.Create; import com.simibubi.create.Create;
@ -76,6 +78,7 @@ public class FilteredDestination extends ScheduleDestination {
editBox.setBordered(false); editBox.setBordered(false);
editBox.setTextColor(0xFFFFFF); editBox.setTextColor(0xFFFFFF);
editBox.setValue(nameFilter); editBox.setValue(nameFilter);
editBox.setFilter(s -> StringUtils.countMatches(s, '*') <= 3);
editBox.changeFocus(false); editBox.changeFocus(false);
editBox.mouseClicked(0, 0, 0); editBox.mouseClicked(0, 0, 0);
editorSubWidgets editorSubWidgets

View file

@ -21,7 +21,7 @@ public class StationPoweredCondition extends ScheduleWaitCondition {
@Override @Override
public boolean tickCompletion(Level level, Train train, CompoundTag context) { public boolean tickCompletion(Level level, Train train, CompoundTag context) {
GlobalStation currentStation = train.currentStation; GlobalStation currentStation = train.getCurrentStation();
if (currentStation == null) if (currentStation == null)
return false; return false;
BlockPos stationPos = currentStation.stationPos; BlockPos stationPos = currentStation.stationPos;

View file

@ -21,7 +21,7 @@ public class StationUnloadedCondition extends ScheduleWaitCondition {
@Override @Override
public boolean tickCompletion(Level level, Train train, CompoundTag context) { public boolean tickCompletion(Level level, Train train, CompoundTag context) {
GlobalStation currentStation = train.currentStation; GlobalStation currentStation = train.getCurrentStation();
if (currentStation == null) if (currentStation == null)
return false; return false;
if (level instanceof ServerLevel serverLevel) if (level instanceof ServerLevel serverLevel)

View file

@ -168,7 +168,7 @@ public class TrackRenderer extends SafeTileEntityRenderer<TrackTileEntity> {
@Override @Override
public int getViewDistance() { public int getViewDistance() {
return 96; return 96 * 2;
} }
} }

View file

@ -26,6 +26,7 @@ public class AllCommands {
.then(new ToggleDebugCommand().register()) .then(new ToggleDebugCommand().register())
.then(FabulousWarningCommand.register()) .then(FabulousWarningCommand.register())
.then(OverlayConfigCommand.register()) .then(OverlayConfigCommand.register())
.then(DumpRailwaysCommand.register())
.then(FixLightingCommand.register()) .then(FixLightingCommand.register())
.then(HighlightCommand.register()) .then(HighlightCommand.register())
.then(CouplingCommand.register()) .then(CouplingCommand.register())

View file

@ -0,0 +1,118 @@
package com.simibubi.create.foundation.command;
import java.util.Map.Entry;
import java.util.UUID;
import java.util.function.BiConsumer;
import com.mojang.brigadier.builder.ArgumentBuilder;
import com.simibubi.create.Create;
import com.simibubi.create.content.logistics.trains.GlobalRailwayManager;
import com.simibubi.create.content.logistics.trains.TrackGraph;
import com.simibubi.create.content.logistics.trains.entity.Train;
import com.simibubi.create.content.logistics.trains.management.GlobalStation;
import com.simibubi.create.content.logistics.trains.management.ScheduleRuntime;
import com.simibubi.create.content.logistics.trains.management.ScheduleRuntime.State;
import net.minecraft.ChatFormatting;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands;
import net.minecraft.core.BlockPos;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.TextComponent;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.LivingEntity;
public class DumpRailwaysCommand {
static ArgumentBuilder<CommandSourceStack, ?> register() {
return Commands.literal("dumpRailways")
.requires(cs -> cs.hasPermission(1))
.executes(ctx -> {
CommandSourceStack source = ctx.getSource();
fillReport(source.getLevel(),
(s, f) -> source.sendSuccess(new TextComponent(s).withStyle(st -> st.withColor(f)), false));
return 1;
});
}
static void fillReport(ServerLevel level, BiConsumer<String, Integer> chat) {
GlobalRailwayManager railways = Create.RAILWAYS;
int white = ChatFormatting.WHITE.getColor();
int blue = 0xD3DEDC;
int darkBlue = 0x92A9BD;
int bright = 0xFFEFEF;
int orange = 0xFFAD60;
for (int i = 0; i < 10; i++)
chat.accept("", white);
chat.accept("-+------<< Railways Summary: >>------+-", white);
chat.accept("Track Networks: " + railways.trackNetworks.size(), blue);
chat.accept("Trains: " + railways.trains.size(), blue);
chat.accept("", white);
for (Entry<UUID, TrackGraph> entry : railways.trackNetworks.entrySet()) {
TrackGraph graph = entry.getValue();
UUID id = entry.getKey();
chat.accept(id.toString()
.substring(0, 5) + ": Track Graph, "
+ graph.getNodes()
.size()
+ " Nodes", graph.color.getRGB());
for (GlobalStation globalStation : graph.getStations()) {
BlockPos pos = globalStation.stationPos;
chat.accept(" -> " + globalStation.name + " (" + globalStation.id.toString()
.substring(0, 5) + ") [" + pos.getX() + "," + pos.getY() + "," + pos.getZ() + "]", darkBlue);
if (globalStation.getPresentTrain() != null) {
chat.accept(" > Currently Occupied by " + globalStation.getPresentTrain().name.getString(), blue);
} else {
Train imminentTrain = globalStation.getImminentTrain();
if (imminentTrain != null) {
chat.accept(" > Reserved by " + imminentTrain.name.getString() + " ("
+ Mth.floor(imminentTrain.navigation.distanceToDestination) + "m away)", blue);
}
}
}
chat.accept("", white);
}
for (Train train : railways.trains.values()) {
chat.accept(train.id.toString()
.substring(0, 5) + ": " + train.name.getString() + ", " + train.carriages.size() + " Wagons", bright);
if (train.graph != null)
chat.accept(" -> On Track: " + train.graph.id.toString()
.substring(0, 5), train.graph.color.getRGB());
if (train.derailed)
chat.accept(" -> Derailed", orange);
LivingEntity owner = train.getOwner(level);
if (owner != null)
chat.accept(" > Owned by " + owner.getName()
.getString(), blue);
GlobalStation currentStation = train.getCurrentStation();
if (currentStation != null) {
chat.accept(" > Waiting at: " + currentStation.name, blue);
} else if (train.navigation.destination != null) {
chat.accept(" > Travelling to " + train.navigation.destination.name + " ("
+ Mth.floor(train.navigation.distanceToDestination) + "m away)", darkBlue);
}
ScheduleRuntime runtime = train.runtime;
if (runtime.getSchedule() != null) {
chat.accept(" > Schedule, Entry " + runtime.currentEntry + ", "
+ (runtime.paused ? "Paused"
: runtime.state.name()
.replaceAll("_", " ")),
runtime.paused ? darkBlue : blue);
if (!runtime.paused && runtime.state != State.POST_TRANSIT) {
for (Component component : runtime.getSchedule().entries.get(runtime.currentEntry).destination
.getTitleAs("destination")) {
chat.accept(" - " + component.getString(), blue);
}
}
} else
chat.accept(" > Idle, No Schedule", darkBlue);
chat.accept("", white);
}
chat.accept("-+--------------------------------+-", white);
}
}

View file

@ -46,7 +46,7 @@ public abstract class TileEntityConfigurationPacket<TE extends SyncedTileEntity>
return; return;
BlockEntity tileEntity = world.getBlockEntity(pos); BlockEntity tileEntity = world.getBlockEntity(pos);
if (tileEntity instanceof SyncedTileEntity) { if (tileEntity instanceof SyncedTileEntity) {
applySettings((TE) tileEntity); applySettings(player, (TE) tileEntity);
((SyncedTileEntity) tileEntity).sendData(); ((SyncedTileEntity) tileEntity).sendData();
tileEntity.setChanged(); tileEntity.setChanged();
} }
@ -60,6 +60,10 @@ public abstract class TileEntityConfigurationPacket<TE extends SyncedTileEntity>
protected abstract void readSettings(FriendlyByteBuf buffer); protected abstract void readSettings(FriendlyByteBuf buffer);
protected void applySettings(ServerPlayer player, TE te) {
applySettings(te);
}
protected abstract void applySettings(TE te); protected abstract void applySettings(TE te);
} }