mirror of
https://github.com/Creators-of-Create/Create.git
synced 2024-12-14 22:23:42 +01:00
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:
parent
c6278dbd24
commit
c3e1e9df3f
24 changed files with 733 additions and 150 deletions
|
@ -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, 100, true);
|
||||
() -> CarriageContraptionEntityRenderer::new, 5, 3, true);
|
||||
|
||||
public static final EntityEntry<SuperGlueEntity> SUPER_GLUE =
|
||||
register("super_glue", SuperGlueEntity::new, () -> SuperGlueRenderer::new, MobCategory.MISC, 10,
|
||||
|
|
|
@ -19,10 +19,10 @@ import javax.annotation.Nullable;
|
|||
import com.simibubi.create.Create;
|
||||
import com.simibubi.create.CreateClient;
|
||||
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.foundation.utility.Color;
|
||||
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.VecHelper;
|
||||
|
||||
|
@ -40,8 +40,8 @@ public class TrackGraph {
|
|||
|
||||
public static final AtomicInteger netIdGenerator = new AtomicInteger();
|
||||
|
||||
UUID id;
|
||||
Color color;
|
||||
public UUID id;
|
||||
public Color color;
|
||||
|
||||
Map<TrackNodeLocation, TrackNode> nodes;
|
||||
Map<Integer, TrackNode> nodesById;
|
||||
|
@ -58,7 +58,7 @@ public class TrackGraph {
|
|||
nodes = new HashMap<>();
|
||||
nodesById = new HashMap<>();
|
||||
connectionsByNode = new IdentityHashMap<>();
|
||||
color = Color.rainbowColor(new Random().nextInt());
|
||||
color = Color.rainbowColor(new Random(graphID.getLeastSignificantBits()).nextInt());
|
||||
stations = new HashMap<>();
|
||||
}
|
||||
|
||||
|
@ -85,6 +85,10 @@ public class TrackGraph {
|
|||
|
||||
//
|
||||
|
||||
public Set<TrackNodeLocation> getNodes() {
|
||||
return nodes.keySet();
|
||||
}
|
||||
|
||||
public TrackNode locateNode(Vec3 position) {
|
||||
return locateNode(new TrackNodeLocation(position));
|
||||
}
|
||||
|
@ -122,17 +126,32 @@ public class TrackGraph {
|
|||
return false;
|
||||
|
||||
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();
|
||||
GlobalStation globalStation = stations.get(uuid);
|
||||
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);
|
||||
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);
|
||||
if (!connectionsByNode.containsKey(removed))
|
||||
return true;
|
||||
|
@ -165,6 +184,16 @@ public class TrackGraph {
|
|||
nodes.clear();
|
||||
nodesById.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) {
|
||||
|
@ -214,12 +243,15 @@ public class TrackGraph {
|
|||
if (!connections.isEmpty()) {
|
||||
target.connectionsByNode.put(node, connections);
|
||||
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();
|
||||
GlobalStation globalStation = stations.get(uuid);
|
||||
Couple<TrackNodeLocation> loc = globalStation.edgeLocation;
|
||||
if (loc.getFirst().equals(location1) && loc.getSecond().equals(entry.getLocation())) {
|
||||
Debug.debugChat("Station " + globalStation.name + " migrated directly due to graph split");
|
||||
if (loc.getFirst()
|
||||
.equals(location1)
|
||||
&& loc.getSecond()
|
||||
.equals(entry.getLocation())) {
|
||||
target.addStation(globalStation);
|
||||
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);
|
||||
nodesById.remove(node.getNetId());
|
||||
connectionsByNode.remove(node);
|
||||
|
|
|
@ -9,8 +9,10 @@ import javax.annotation.Nullable;
|
|||
import org.apache.commons.lang3.mutable.MutableDouble;
|
||||
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.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.Couple;
|
||||
import com.simibubi.create.foundation.utility.VecHelper;
|
||||
|
@ -31,6 +33,7 @@ public class Carriage {
|
|||
public CarriageContraption contraption;
|
||||
public int bogeySpacing;
|
||||
public int id;
|
||||
public boolean blocked;
|
||||
|
||||
WeakReference<CarriageContraptionEntity> entity;
|
||||
Couple<CarriageBogey> bogeys;
|
||||
|
@ -40,6 +43,10 @@ public class Carriage {
|
|||
this.bogeys = Couple.create(bogey1, bogey2);
|
||||
this.entity = new WeakReference<>(null);
|
||||
this.id = netIdGenerator.incrementAndGet();
|
||||
|
||||
bogey1.carriage = this;
|
||||
if (bogey2 != null)
|
||||
bogey2.carriage = this;
|
||||
}
|
||||
|
||||
public void setTrain(Train train) {
|
||||
|
@ -51,11 +58,13 @@ public class Carriage {
|
|||
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 trailingAnchor = trailingBogey().anchorPosition;
|
||||
boolean onTwoBogeys = isOnTwoBogeys();
|
||||
double stress = onTwoBogeys ? bogeySpacing - leadingAnchor.distanceTo(trailingAnchor) : 0;
|
||||
blocked = false;
|
||||
|
||||
// positive stress: points should move apart
|
||||
// negative stress: points should move closer
|
||||
|
@ -65,7 +74,7 @@ public class Carriage {
|
|||
double leadingPointModifier = 0.5d;
|
||||
double trailingPointModifier = -0.5d;
|
||||
|
||||
MutableObject<MovingPoint> previous = new MutableObject<>();
|
||||
MutableObject<TravellingPoint> previous = new MutableObject<>();
|
||||
MutableDouble distanceMoved = new MutableDouble(distance);
|
||||
|
||||
bogeys.forEachWithContext((bogey, firstBogey) -> {
|
||||
|
@ -76,15 +85,16 @@ public class Carriage {
|
|||
double bogeyStress = bogey.getStress();
|
||||
|
||||
bogey.points.forEachWithContext((point, first) -> {
|
||||
MovingPoint prevPoint = previous.getValue();
|
||||
TravellingPoint prevPoint = previous.getValue();
|
||||
ITrackSelector trackSelector =
|
||||
prevPoint == null ? control == null ? point.random() : control.apply(point)
|
||||
: point.follow(prevPoint);
|
||||
|
||||
double correction = bogeyStress * (first ? leadingPointModifier : trailingPointModifier);
|
||||
double toMove = distanceMoved.getValue();
|
||||
double moved = point.travel(toMove, trackSelector);
|
||||
point.travel(correction + bogeyCorrection, trackSelector);
|
||||
double moved = point.travel(graph, toMove, trackSelector);
|
||||
point.travel(graph, correction + bogeyCorrection, trackSelector);
|
||||
blocked |= point.blocked;
|
||||
|
||||
distanceMoved.setValue(moved);
|
||||
previous.setValue(point);
|
||||
|
@ -158,11 +168,11 @@ public class Carriage {
|
|||
entity.discard();
|
||||
}
|
||||
|
||||
public MovingPoint getLeadingPoint() {
|
||||
public TravellingPoint getLeadingPoint() {
|
||||
return leadingBogey().leading();
|
||||
}
|
||||
|
||||
public MovingPoint getTrailingPoint() {
|
||||
public TravellingPoint getTrailingPoint() {
|
||||
return trailingBogey().trailing();
|
||||
}
|
||||
|
||||
|
@ -180,8 +190,9 @@ public class Carriage {
|
|||
|
||||
public static class CarriageBogey {
|
||||
|
||||
Carriage carriage;
|
||||
IBogeyBlock type;
|
||||
Couple<MovingPoint> points;
|
||||
Couple<TravellingPoint> points;
|
||||
Vec3 anchorPosition;
|
||||
|
||||
LerpedFloat wheelAngle;
|
||||
|
@ -191,13 +202,16 @@ public class Carriage {
|
|||
public Vec3 leadingCouplingAnchor;
|
||||
public Vec3 trailingCouplingAnchor;
|
||||
|
||||
public CarriageBogey(IBogeyBlock type, MovingPoint point, MovingPoint point2) {
|
||||
int derailAngle;
|
||||
|
||||
public CarriageBogey(IBogeyBlock type, TravellingPoint point, TravellingPoint point2) {
|
||||
this.type = type;
|
||||
points = Couple.create(point, point2);
|
||||
wheelAngle = LerpedFloat.angular();
|
||||
yaw = LerpedFloat.angular();
|
||||
pitch = LerpedFloat.angular();
|
||||
updateAnchorPosition();
|
||||
derailAngle = Create.RANDOM.nextInt(90) - 45;
|
||||
}
|
||||
|
||||
public void updateAngles(double distanceMoved) {
|
||||
|
@ -209,16 +223,20 @@ public class Carriage {
|
|||
double diffZ = positionVec.z - coupledVec.z;
|
||||
float yRot = AngleHelper.deg(Mth.atan2(diffZ, diffX)) + 90;
|
||||
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);
|
||||
pitch.setValue(xRot);
|
||||
yaw.setValue(-yRot);
|
||||
}
|
||||
|
||||
public MovingPoint leading() {
|
||||
public TravellingPoint leading() {
|
||||
return points.getFirst();
|
||||
}
|
||||
|
||||
public MovingPoint trailing() {
|
||||
public TravellingPoint trailing() {
|
||||
return points.getSecond();
|
||||
}
|
||||
|
||||
|
@ -240,16 +258,6 @@ public class Carriage {
|
|||
Vec3 thisOffset = type.getConnectorAnchorOffset();
|
||||
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, yaw.getValue(partialTicks), Axis.Y);
|
||||
thisOffset = VecHelper.rotate(thisOffset, -entityYRot - 90, Axis.Y);
|
||||
|
|
|
@ -3,7 +3,9 @@ package com.simibubi.create.content.logistics.trains.entity;
|
|||
import com.simibubi.create.AllEntityTypes;
|
||||
import com.simibubi.create.Create;
|
||||
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.level.Level;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
|
@ -44,13 +46,29 @@ public class CarriageContraptionEntity extends OrientedContraptionEntity {
|
|||
xo = getX();
|
||||
yo = getY();
|
||||
zo = getZ();
|
||||
|
||||
carriage.moveEntity(this);
|
||||
|
||||
double distanceTo = position().distanceTo(new Vec3(xo, yo, zo));
|
||||
carriage.bogeys.getFirst()
|
||||
.updateAngles(distanceTo);
|
||||
if (carriage.isOnTwoBogeys())
|
||||
carriage.bogeys.getSecond()
|
||||
.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
|
||||
|
|
|
@ -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.TrackNode;
|
||||
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.foundation.utility.Couple;
|
||||
import com.simibubi.create.foundation.utility.Pair;
|
||||
|
@ -24,18 +24,14 @@ import net.minecraft.world.phys.Vec3;
|
|||
|
||||
public class Navigation {
|
||||
|
||||
TrackGraph graph;
|
||||
Train train;
|
||||
|
||||
public GlobalStation destination;
|
||||
public double distanceToDestination;
|
||||
|
||||
List<TrackEdge> path;
|
||||
List<TrackEdge> currentPath;
|
||||
|
||||
public Navigation(Train train, TrackGraph graph) {
|
||||
this.train = train;
|
||||
this.graph = graph;
|
||||
path = new ArrayList<>();
|
||||
currentPath = new ArrayList<>();
|
||||
}
|
||||
|
||||
public void tick(Level level) {
|
||||
|
@ -47,7 +43,7 @@ public class Navigation {
|
|||
if (distanceToDestination < 1 / 32f) {
|
||||
distanceToDestination = 0;
|
||||
train.speed = 0;
|
||||
path.clear();
|
||||
currentPath.clear();
|
||||
train.arriveAt(destination);
|
||||
destination = null;
|
||||
return;
|
||||
|
@ -83,13 +79,13 @@ public class Navigation {
|
|||
return destination != null;
|
||||
}
|
||||
|
||||
public ITrackSelector control(MovingPoint mp) {
|
||||
return list -> {
|
||||
if (!path.isEmpty()) {
|
||||
TrackEdge target = path.get(0);
|
||||
public ITrackSelector control(TravellingPoint mp) {
|
||||
return (graph, list) -> {
|
||||
if (!currentPath.isEmpty()) {
|
||||
TrackEdge target = currentPath.get(0);
|
||||
for (Entry<TrackNode, TrackEdge> entry : list) {
|
||||
if (entry.getValue() == target) {
|
||||
path.remove(0);
|
||||
currentPath.remove(0);
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
|
@ -100,30 +96,50 @@ public class Navigation {
|
|||
|
||||
public void cancelNavigation() {
|
||||
distanceToDestination = 0;
|
||||
path.clear();
|
||||
currentPath.clear();
|
||||
if (destination == null)
|
||||
return;
|
||||
destination.cancelReservation(train);
|
||||
destination = null;
|
||||
train.runtime.transitInterrupted();
|
||||
}
|
||||
|
||||
public double startNavigation(GlobalStation destination, boolean simulate) {
|
||||
Pair<Double, List<TrackEdge>> pathTo = findPathTo(destination);
|
||||
|
||||
if (simulate)
|
||||
return pathTo.getFirst();
|
||||
|
||||
distanceToDestination = pathTo.getFirst();
|
||||
currentPath = pathTo.getSecond();
|
||||
if (distanceToDestination == -1) {
|
||||
distanceToDestination = 0;
|
||||
if (this.destination != null)
|
||||
cancelNavigation();
|
||||
return -1;
|
||||
}
|
||||
|
||||
public void setDestination(GlobalStation destination) {
|
||||
findPathTo(destination);
|
||||
if (distanceToDestination == 0)
|
||||
return;
|
||||
if (this.destination == destination)
|
||||
return;
|
||||
return 0;
|
||||
|
||||
train.leave();
|
||||
this.destination = destination;
|
||||
return distanceToDestination;
|
||||
}
|
||||
|
||||
private void findPathTo(GlobalStation destination) {
|
||||
path.clear();
|
||||
this.distanceToDestination = 0;
|
||||
private Pair<Double, List<TrackEdge>> findPathTo(GlobalStation destination) {
|
||||
TrackGraph graph = train.graph;
|
||||
List<TrackEdge> path = new ArrayList<>();
|
||||
double distanceToDestination = 0;
|
||||
|
||||
if (graph == null)
|
||||
return Pair.of(-1d, path);
|
||||
|
||||
Couple<TrackNodeLocation> target = destination.edgeLocation;
|
||||
PriorityQueue<Pair<Double, Pair<Couple<TrackNode>, TrackEdge>>> frontier =
|
||||
new PriorityQueue<>((p1, p2) -> Double.compare(p1.getFirst(), p2.getFirst()));
|
||||
|
||||
MovingPoint leadingPoint = train.carriages.get(0)
|
||||
TravellingPoint leadingPoint = train.carriages.get(0)
|
||||
.getLeadingPoint();
|
||||
Set<TrackEdge> visited = new HashSet<>();
|
||||
Map<TrackEdge, Pair<Boolean, TrackEdge>> reachedVia = new IdentityHashMap<>();
|
||||
|
@ -167,7 +183,7 @@ public class Navigation {
|
|||
else
|
||||
distanceToDestination += train.getTotalLength() + 2;
|
||||
distanceToDestination -= position;
|
||||
return;
|
||||
return Pair.of(distanceToDestination, path);
|
||||
}
|
||||
|
||||
for (Entry<TrackNode, TrackEdge> entry : graph.getConnectionsFrom(node2)
|
||||
|
@ -194,6 +210,8 @@ public class Navigation {
|
|||
Pair.of(Couple.create(node2, newNode), newEdge)));
|
||||
}
|
||||
}
|
||||
|
||||
return Pair.of(-1d, path);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,21 +1,37 @@
|
|||
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.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Consumer;
|
||||
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.CreateClient;
|
||||
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.GraphLocation;
|
||||
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 net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
|
||||
|
@ -28,28 +44,37 @@ public class Train {
|
|||
public double targetSpeed = 0;
|
||||
|
||||
public UUID id;
|
||||
public UUID owner;
|
||||
public TrackGraph graph;
|
||||
public Navigation navigation;
|
||||
public GlobalStation currentStation;
|
||||
public ScheduleRuntime runtime;
|
||||
public TrainIconType icon;
|
||||
public Component name;
|
||||
public TrainStatus status;
|
||||
|
||||
public UUID currentStation;
|
||||
|
||||
public boolean heldForAssembly;
|
||||
public boolean doubleEnded;
|
||||
public List<Carriage> carriages;
|
||||
public List<Integer> carriageSpacing;
|
||||
|
||||
List<TrainMigration> migratingPoints;
|
||||
public int migrationCooldown;
|
||||
public boolean derailed;
|
||||
|
||||
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.owner = owner;
|
||||
this.graph = graph;
|
||||
this.carriages = carriages;
|
||||
this.carriageSpacing = carriageSpacing;
|
||||
this.icon = TrainIconType.getDefault();
|
||||
this.stress = new double[carriageSpacing.size()];
|
||||
this.name = Lang.translate("train.unnamed");
|
||||
this.status = new TrainStatus(this);
|
||||
|
||||
carriages.forEach(c -> {
|
||||
c.setTrain(this);
|
||||
|
@ -60,12 +85,28 @@ public class Train {
|
|||
navigation = new Navigation(this, graph);
|
||||
runtime = new ScheduleRuntime(this);
|
||||
heldForAssembly = true;
|
||||
migratingPoints = new ArrayList<>();
|
||||
currentStation = null;
|
||||
}
|
||||
|
||||
public void tick(Level level) {
|
||||
status.tick(level);
|
||||
|
||||
if (graph == null) {
|
||||
if (!migratingPoints.isEmpty())
|
||||
reattachToTracks(level);
|
||||
return;
|
||||
}
|
||||
|
||||
runtime.tick(level);
|
||||
navigation.tick(level);
|
||||
|
||||
if (navigation.destination == null && speed > 0) {
|
||||
speed -= acceleration;
|
||||
if (speed <= 0)
|
||||
speed = 0;
|
||||
}
|
||||
|
||||
double distance = speed;
|
||||
Carriage previousCarriage = null;
|
||||
|
||||
|
@ -88,27 +129,38 @@ public class Train {
|
|||
double leadingModifier = approachingStation ? -0.75d : -0.5d;
|
||||
double trailingModifier = approachingStation ? 0d : 0.125d;
|
||||
|
||||
MovingPoint previous = null;
|
||||
TravellingPoint previous = null;
|
||||
boolean blocked = false;
|
||||
|
||||
for (int i = 0; i < carriages.size(); i++) {
|
||||
double leadingStress = i == 0 ? 0 : stress[i - 1] * leadingModifier;
|
||||
double trailingStress = i == stress.length ? 0 : stress[i] * trailingModifier;
|
||||
|
||||
Carriage carriage = carriages.get(i);
|
||||
MovingPoint toFollow = previous;
|
||||
Function<MovingPoint, ITrackSelector> control =
|
||||
TravellingPoint toFollow = previous;
|
||||
Function<TravellingPoint, ITrackSelector> control =
|
||||
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)
|
||||
distance = actualDistance;
|
||||
previous = carriage.getTrailingPoint();
|
||||
}
|
||||
|
||||
if (blocked) {
|
||||
speed = 0;
|
||||
navigation.cancelNavigation();
|
||||
runtime.tick(level);
|
||||
status.endOfTrack();
|
||||
} else if (speed > 0)
|
||||
status.trackOK();
|
||||
|
||||
if (navigation.destination != null) {
|
||||
boolean recalculate = navigation.distanceToDestination > 20;
|
||||
boolean recalculate = navigation.distanceToDestination % 100 > 20;
|
||||
navigation.distanceToDestination -= distance;
|
||||
if (recalculate && navigation.distanceToDestination <= 20)
|
||||
navigation.setDestination(navigation.destination);
|
||||
if (recalculate && navigation.distanceToDestination % 100 <= 20)
|
||||
navigation.startNavigation(navigation.destination, false);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -141,14 +193,87 @@ public class Train {
|
|||
offset += carriageSpacing.get(i);
|
||||
}
|
||||
|
||||
GlobalStation currentStation = getCurrentStation();
|
||||
if (currentStation != null)
|
||||
currentStation.cancelReservation(this);
|
||||
|
||||
Create.RAILWAYS.trains.remove(id);
|
||||
CreateClient.RAILWAYS.trains.remove(id);
|
||||
CreateClient.RAILWAYS.trains.remove(id); // TODO Thread breach
|
||||
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() {
|
||||
int length = 0;
|
||||
for (int i = 0; i < carriages.size(); i++) {
|
||||
|
@ -160,13 +285,38 @@ public class Train {
|
|||
}
|
||||
|
||||
public void leave() {
|
||||
GlobalStation currentStation = getCurrentStation();
|
||||
if (currentStation == null)
|
||||
return;
|
||||
currentStation.trainDeparted(this);
|
||||
currentStation = null;
|
||||
this.currentStation = null;
|
||||
}
|
||||
|
||||
public void arriveAt(GlobalStation station) {
|
||||
currentStation = station;
|
||||
setCurrentStation(station);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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))));
|
||||
}
|
||||
|
||||
}
|
|
@ -6,21 +6,22 @@ import java.util.List;
|
|||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
import java.util.Vector;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
import com.simibubi.create.Create;
|
||||
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.management.GraphLocation;
|
||||
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
|
||||
public class MovingPoint {
|
||||
public class TravellingPoint {
|
||||
|
||||
TrackGraph graph;
|
||||
TrackNode node1, node2;
|
||||
TrackEdge edge;
|
||||
double position;
|
||||
boolean blocked;
|
||||
|
||||
public static enum SteerDirection {
|
||||
NONE(0), LEFT(-1), RIGHT(1);
|
||||
|
@ -33,11 +34,10 @@ public class MovingPoint {
|
|||
}
|
||||
|
||||
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) {
|
||||
this.graph = graph;
|
||||
public TravellingPoint(TrackNode node1, TrackNode node2, TrackEdge edge, double position) {
|
||||
this.node1 = node1;
|
||||
this.node2 = node2;
|
||||
this.edge = edge;
|
||||
|
@ -45,11 +45,11 @@ public class MovingPoint {
|
|||
}
|
||||
|
||||
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) {
|
||||
return validTargets -> {
|
||||
public ITrackSelector follow(TravellingPoint other) {
|
||||
return (graph, validTargets) -> {
|
||||
TrackNode target = other.node1;
|
||||
|
||||
for (Entry<TrackNode, TrackEdge> entry : validTargets)
|
||||
|
@ -99,7 +99,7 @@ public class MovingPoint {
|
|||
}
|
||||
|
||||
public ITrackSelector steer(SteerDirection direction, Vec3 upNormal) {
|
||||
return validTargets -> {
|
||||
return (graph, validTargets) -> {
|
||||
double closest = Double.MAX_VALUE;
|
||||
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);
|
||||
if (distance == 0)
|
||||
return 0;
|
||||
|
||||
double traveled = distance;
|
||||
|
||||
double currentT = position / edgeLength;
|
||||
double incrementT = edge.incrementT(node1, node2, currentT, distance);
|
||||
position = incrementT * edgeLength;
|
||||
|
@ -159,11 +159,12 @@ public class MovingPoint {
|
|||
if (validTargets.isEmpty()) {
|
||||
traveled -= position - edgeLength;
|
||||
position = edgeLength;
|
||||
blocked = true;
|
||||
break;
|
||||
}
|
||||
|
||||
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;
|
||||
node2 = entry.getKey();
|
||||
|
@ -175,7 +176,7 @@ public class MovingPoint {
|
|||
return traveled;
|
||||
}
|
||||
|
||||
public void reverse() {
|
||||
public void reverse(TrackGraph graph) {
|
||||
TrackNode n = node1;
|
||||
node1 = node2;
|
||||
node2 = n;
|
||||
|
@ -191,4 +192,14 @@ public class MovingPoint {
|
|||
.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);
|
||||
}
|
||||
|
||||
}
|
|
@ -5,12 +5,9 @@ import java.util.UUID;
|
|||
|
||||
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.entity.Train;
|
||||
import com.simibubi.create.foundation.utility.Couple;
|
||||
import com.simibubi.create.foundation.utility.Debug;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
|
@ -49,21 +46,18 @@ public class GlobalStation {
|
|||
public void migrate(LevelAccessor level) {
|
||||
BlockEntity blockEntity = level.getBlockEntity(stationPos);
|
||||
if (blockEntity instanceof StationTileEntity station) {
|
||||
Debug.debugChat("Migrating Station " + name);
|
||||
station.migrate(this);
|
||||
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) {
|
||||
this.edgeLocation = nodes.map(TrackNode::getLocation);
|
||||
public void setLocation(Couple<TrackNodeLocation> nodes, double position) {
|
||||
this.edgeLocation = nodes;
|
||||
this.position = position;
|
||||
}
|
||||
|
||||
public void reserveFor(Train train) {
|
||||
Train nearestTrain = this.nearestTrain.get();
|
||||
Train nearestTrain = getNearestTrain();
|
||||
if (nearestTrain == null
|
||||
|| nearestTrain.navigation.distanceToDestination > train.navigation.distanceToDestination)
|
||||
this.nearestTrain = new WeakReference<>(train);
|
||||
|
@ -80,18 +74,18 @@ public class GlobalStation {
|
|||
|
||||
@Nullable
|
||||
public Train getPresentTrain() {
|
||||
Train nearestTrain = this.nearestTrain.get();
|
||||
if (nearestTrain == null || nearestTrain.currentStation != this)
|
||||
Train nearestTrain = getNearestTrain();
|
||||
if (nearestTrain == null || nearestTrain.getCurrentStation() != this)
|
||||
return null;
|
||||
return nearestTrain;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Train getImminentTrain() {
|
||||
Train nearestTrain = this.nearestTrain.get();
|
||||
Train nearestTrain = getNearestTrain();
|
||||
if (nearestTrain == null)
|
||||
return nearestTrain;
|
||||
if (nearestTrain.currentStation == this)
|
||||
if (nearestTrain.getCurrentStation() == this)
|
||||
return nearestTrain;
|
||||
if (!nearestTrain.navigation.isActive())
|
||||
return null;
|
||||
|
@ -100,6 +94,11 @@ public class GlobalStation {
|
|||
return nearestTrain;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Train getNearestTrain() {
|
||||
return this.nearestTrain.get();
|
||||
}
|
||||
|
||||
public CompoundTag write() {
|
||||
CompoundTag nbt = new CompoundTag();
|
||||
nbt.putUUID("Id", id);
|
||||
|
|
|
@ -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;
|
||||
|
||||
}
|
|
@ -15,17 +15,17 @@ import net.minecraft.world.level.Level;
|
|||
|
||||
public class ScheduleRuntime {
|
||||
|
||||
enum State {
|
||||
public enum State {
|
||||
PRE_TRANSIT, IN_TRANSIT, POST_TRANSIT
|
||||
}
|
||||
|
||||
Train train;
|
||||
Schedule schedule;
|
||||
|
||||
boolean paused;
|
||||
boolean isAutoSchedule;
|
||||
int currentEntry;
|
||||
State state;
|
||||
public boolean paused;
|
||||
public int currentEntry;
|
||||
public State state;
|
||||
|
||||
static final int INTERVAL = 40;
|
||||
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) {
|
||||
if (schedule == null)
|
||||
return;
|
||||
|
@ -76,16 +83,17 @@ public class ScheduleRuntime {
|
|||
|
||||
GlobalStation nextStation = findNextStation();
|
||||
if (nextStation == null) {
|
||||
train.status.failedNavigation();
|
||||
cooldown = INTERVAL;
|
||||
return;
|
||||
}
|
||||
if (nextStation == train.currentStation) {
|
||||
train.status.successfulNavigation();
|
||||
if (nextStation == train.getCurrentStation()) {
|
||||
state = State.IN_TRANSIT;
|
||||
destinationReached();
|
||||
return;
|
||||
}
|
||||
|
||||
train.navigation.setDestination(nextStation);
|
||||
if (train.navigation.startNavigation(nextStation, false) != -1)
|
||||
state = State.IN_TRANSIT;
|
||||
}
|
||||
|
||||
|
@ -113,12 +121,25 @@ public class ScheduleRuntime {
|
|||
public GlobalStation findNextStation() {
|
||||
ScheduleEntry entry = schedule.entries.get(currentEntry);
|
||||
ScheduleDestination destination = entry.destination;
|
||||
|
||||
if (destination instanceof FilteredDestination filtered) {
|
||||
String regex = filtered.nameFilter.replace("*", ".*");
|
||||
GlobalStation best = null;
|
||||
double bestCost = Double.MAX_VALUE;
|
||||
for (GlobalStation globalStation : train.graph.getStations()) {
|
||||
if (globalStation.name.equals(filtered.nameFilter))
|
||||
return globalStation;
|
||||
if (!globalStation.name.matches(regex))
|
||||
continue;
|
||||
double cost = train.navigation.startNavigation(globalStation, true);
|
||||
if (cost < 0)
|
||||
continue;
|
||||
if (cost > bestCost)
|
||||
continue;
|
||||
best = globalStation;
|
||||
bestCost = cost;
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ import com.simibubi.create.foundation.networking.TileEntityConfigurationPacket;
|
|||
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
|
||||
|
@ -66,7 +67,7 @@ public class StationEditPacket extends TileEntityConfigurationPacket<StationTile
|
|||
}
|
||||
|
||||
@Override
|
||||
protected void applySettings(StationTileEntity te) {
|
||||
protected void applySettings(ServerPlayer player, StationTileEntity te) {
|
||||
Level level = te.getLevel();
|
||||
BlockPos blockPos = te.getBlockPos();
|
||||
BlockState blockState = level.getBlockState(blockPos);
|
||||
|
@ -85,7 +86,7 @@ public class StationEditPacket extends TileEntityConfigurationPacket<StationTile
|
|||
if (!isAssemblyMode)
|
||||
return;
|
||||
if (tryAssemble)
|
||||
te.assemble();
|
||||
te.assemble(player.getUUID());
|
||||
else {
|
||||
if (disassembleAndEnterMode(te))
|
||||
te.refreshAssemblyInfo();
|
||||
|
@ -123,4 +124,7 @@ public class StationEditPacket extends TileEntityConfigurationPacket<StationTile
|
|||
return te.tryEnterAssemblyMode();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void applySettings(StationTileEntity te) {}
|
||||
|
||||
}
|
||||
|
|
|
@ -99,7 +99,7 @@ public class StationRenderer extends SafeTileEntityRenderer<StationTileEntity> {
|
|||
|
||||
@Override
|
||||
public int getViewDistance() {
|
||||
return 96;
|
||||
return 96 * 2;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -167,12 +167,12 @@ public class StationScreen extends AbstractStationScreen {
|
|||
return;
|
||||
}
|
||||
|
||||
if (train.navigation.destination != station && train.currentStation != station) {
|
||||
if (train.navigation.destination != station && train.getCurrentStation() != station) {
|
||||
leavingAnimation = 80;
|
||||
return;
|
||||
}
|
||||
|
||||
disassembleTrainButton.active = train.currentStation == station; // TODO te.canAssemble
|
||||
disassembleTrainButton.active = train.getCurrentStation() == station; // TODO te.canAssemble
|
||||
openScheduleButton.active = train.runtime.schedule != null;
|
||||
|
||||
float f = (float) (train.navigation.distanceToDestination / 30f);
|
||||
|
|
|
@ -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.CarriageBogey;
|
||||
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.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.TileEntityBehaviour;
|
||||
import com.simibubi.create.foundation.utility.Debug;
|
||||
import com.simibubi.create.foundation.utility.Iterate;
|
||||
import com.simibubi.create.foundation.utility.Lang;
|
||||
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);
|
||||
globalStation.setLocation(loc.edge, loc.position);
|
||||
loc.graph.addStation(globalStation);
|
||||
|
||||
if (toMigrate != null)
|
||||
Debug.debugChat("Migrated Station " + globalStation.name);
|
||||
toMigrate = null;
|
||||
setChanged();
|
||||
|
||||
|
@ -124,6 +119,7 @@ public class StationTileEntity extends SmartTileEntity {
|
|||
super.read(tag, clientPacket);
|
||||
if (tag.contains("ToMigrate"))
|
||||
toMigrate = tag.getCompound("ToMigrate");
|
||||
renderBounds = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -312,7 +308,7 @@ public class StationTileEntity extends SmartTileEntity {
|
|||
super.setRemovedNotDueToChunkUnload();
|
||||
}
|
||||
|
||||
public void assemble() {
|
||||
public void assemble(UUID playerUUID) {
|
||||
refreshAssemblyInfo();
|
||||
|
||||
if (bogeyLocations[0] != 0) {
|
||||
|
@ -349,7 +345,7 @@ public class StationTileEntity extends SmartTileEntity {
|
|||
pointOffsets.add(Double.valueOf(loc + .5 + bogeySize / 2));
|
||||
}
|
||||
|
||||
List<MovingPoint> points = new ArrayList<>();
|
||||
List<TravellingPoint> points = new ArrayList<>();
|
||||
Vec3 directionVec = Vec3.atLowerCornerOf(assemblyDirection.getNormal());
|
||||
TrackGraph graph = null;
|
||||
TrackNode secondNode = null;
|
||||
|
@ -402,7 +398,7 @@ public class StationTileEntity extends SmartTileEntity {
|
|||
return;
|
||||
}
|
||||
|
||||
points.add(new MovingPoint(graph, node, secondNode, edge, positionOnEdge));
|
||||
points.add(new TravellingPoint(node, secondNode, edge, positionOnEdge));
|
||||
}
|
||||
|
||||
secondNode = node;
|
||||
|
@ -483,9 +479,9 @@ public class StationTileEntity extends SmartTileEntity {
|
|||
.forEach(Carriage::discardEntity);
|
||||
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();
|
||||
train.currentStation = station;
|
||||
train.setCurrentStation(station);
|
||||
station.reserveFor(train);
|
||||
|
||||
Create.RAILWAYS.trains.put(train.id, train);
|
||||
|
@ -518,7 +514,7 @@ public class StationTileEntity extends SmartTileEntity {
|
|||
if (isAssembling())
|
||||
return INFINITE_EXTENT_AABB;
|
||||
if (renderBounds == null)
|
||||
renderBounds = new AABB(worldPosition, getTarget().getGlobalPosition());
|
||||
renderBounds = new AABB(worldPosition, getTarget().getGlobalPosition()).inflate(2);
|
||||
return renderBounds;
|
||||
}
|
||||
|
||||
|
|
|
@ -84,12 +84,6 @@ public class TrackTargetingBehaviour extends TileEntityBehaviour {
|
|||
return targetDirection;
|
||||
}
|
||||
|
||||
static class GraphLocation {
|
||||
public TrackGraph graph;
|
||||
public Couple<TrackNode> edge;
|
||||
public double position;
|
||||
}
|
||||
|
||||
public GraphLocation determineGraphLocation() {
|
||||
Level level = getWorld();
|
||||
BlockPos pos = getGlobalPosition();
|
||||
|
@ -152,7 +146,7 @@ public class TrackTargetingBehaviour extends TileEntityBehaviour {
|
|||
return null;
|
||||
|
||||
GraphLocation graphLocation = new GraphLocation();
|
||||
graphLocation.edge = Couple.create(backNode, frontNode);
|
||||
graphLocation.edge = Couple.create(backNode.getLocation(), frontNode.getLocation());
|
||||
graphLocation.position = position;
|
||||
graphLocation.graph = graph;
|
||||
return graphLocation;
|
||||
|
|
|
@ -3,6 +3,8 @@ package com.simibubi.create.content.logistics.trains.management.schedule;
|
|||
import java.util.List;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.simibubi.create.AllBlocks;
|
||||
import com.simibubi.create.Create;
|
||||
|
@ -76,6 +78,7 @@ public class FilteredDestination extends ScheduleDestination {
|
|||
editBox.setBordered(false);
|
||||
editBox.setTextColor(0xFFFFFF);
|
||||
editBox.setValue(nameFilter);
|
||||
editBox.setFilter(s -> StringUtils.countMatches(s, '*') <= 3);
|
||||
editBox.changeFocus(false);
|
||||
editBox.mouseClicked(0, 0, 0);
|
||||
editorSubWidgets
|
||||
|
|
|
@ -21,7 +21,7 @@ public class StationPoweredCondition extends ScheduleWaitCondition {
|
|||
|
||||
@Override
|
||||
public boolean tickCompletion(Level level, Train train, CompoundTag context) {
|
||||
GlobalStation currentStation = train.currentStation;
|
||||
GlobalStation currentStation = train.getCurrentStation();
|
||||
if (currentStation == null)
|
||||
return false;
|
||||
BlockPos stationPos = currentStation.stationPos;
|
||||
|
|
|
@ -21,7 +21,7 @@ public class StationUnloadedCondition extends ScheduleWaitCondition {
|
|||
|
||||
@Override
|
||||
public boolean tickCompletion(Level level, Train train, CompoundTag context) {
|
||||
GlobalStation currentStation = train.currentStation;
|
||||
GlobalStation currentStation = train.getCurrentStation();
|
||||
if (currentStation == null)
|
||||
return false;
|
||||
if (level instanceof ServerLevel serverLevel)
|
||||
|
|
|
@ -168,7 +168,7 @@ public class TrackRenderer extends SafeTileEntityRenderer<TrackTileEntity> {
|
|||
|
||||
@Override
|
||||
public int getViewDistance() {
|
||||
return 96;
|
||||
return 96 * 2;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@ public class AllCommands {
|
|||
.then(new ToggleDebugCommand().register())
|
||||
.then(FabulousWarningCommand.register())
|
||||
.then(OverlayConfigCommand.register())
|
||||
.then(DumpRailwaysCommand.register())
|
||||
.then(FixLightingCommand.register())
|
||||
.then(HighlightCommand.register())
|
||||
.then(CouplingCommand.register())
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -46,7 +46,7 @@ public abstract class TileEntityConfigurationPacket<TE extends SyncedTileEntity>
|
|||
return;
|
||||
BlockEntity tileEntity = world.getBlockEntity(pos);
|
||||
if (tileEntity instanceof SyncedTileEntity) {
|
||||
applySettings((TE) tileEntity);
|
||||
applySettings(player, (TE) tileEntity);
|
||||
((SyncedTileEntity) tileEntity).sendData();
|
||||
tileEntity.setChanged();
|
||||
}
|
||||
|
@ -60,6 +60,10 @@ public abstract class TileEntityConfigurationPacket<TE extends SyncedTileEntity>
|
|||
|
||||
protected abstract void readSettings(FriendlyByteBuf buffer);
|
||||
|
||||
protected void applySettings(ServerPlayer player, TE te) {
|
||||
applySettings(te);
|
||||
}
|
||||
|
||||
protected abstract void applySettings(TE te);
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue