Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import net.tfminecraft.vehicleframework.tracks.TrackRegistry;
import net.tfminecraft.vehicleframework.tracks.TrackToolListener;
import net.tfminecraft.vehicleframework.vehicles.controller.GroundEngineLog;
import net.tfminecraft.vehicleframework.vehicles.handlers.TrainHandler;

public class VehicleFramework extends JavaPlugin{

Expand Down Expand Up @@ -63,6 +64,7 @@ public void onEnable() {
printBanner();
plugin = this;
trackRegistry = new TrackRegistry(getDataFolder());
trackRegistry.onRebuilt(TrainHandler::retrackTrains);
log = new LogWriter(getDataFolder());
VFLogger.info("Running checks...");
createFolders();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import org.bukkit.entity.Player;

import net.tfminecraft.vehicleframework.cache.Cache;
import net.tfminecraft.vehicleframework.managers.VehicleManager;
import net.tfminecraft.vehicleframework.permissions.Permissions;
import net.tfminecraft.vehicleframework.VehicleFramework;
import net.tfminecraft.vehicleframework.vehicles.ActiveVehicle;
Expand Down Expand Up @@ -343,8 +344,33 @@ public static void digAt(Player player, Location loc) {
if (loc.getWorld() == null) {
return;
}
applyDig(player, registry().dig(
loc.getWorld().getName(), loc.getX(), loc.getY(), loc.getZ(), loc.getWorld()), loc);
Optional<TrackRegistry.DigTarget> target = registry().digTarget(
loc.getWorld().getName(), loc.getX(), loc.getY(), loc.getZ());
if (target.isEmpty()) {
applyDig(player, DigResult.none(), loc);
return;
}
if (trainOn(target.get())) {
lastToolMs.put(player.getUniqueId(), System.currentTimeMillis());
player.sendMessage("搂cA train is on this track. Move it before removing the rail.");
return;
}
applyDig(player, registry().digAt(target.get().spline(), target.get().index(), loc.getWorld()), loc);
}

private static boolean trainOn(TrackRegistry.DigTarget target) {
VehicleManager vehicles = VehicleFramework.getVehicleManager();
if (vehicles == null) {
return false;
}
UUID trackId = target.spline().getId();
for (ActiveVehicle vehicle : vehicles.get().values()) {
if (vehicle.isTrain() && !vehicle.hasParent()
&& vehicle.getTrainHandler().occupies(trackId, target.centreS(), target.halfSpan())) {
return true;
}
}
return false;
}

private static boolean delete(Player player, String[] args) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BiConsumer;

import org.bukkit.World;

Expand All @@ -23,11 +24,23 @@ public final class TrackRegistry {
private final TrackStore store;
private final Map<UUID, TrackSpline> splines = new ConcurrentHashMap<>();
private final Map<UUID, TrackJunction> junctions = new ConcurrentHashMap<>();
private BiConsumer<TrackSpline, List<TrackSpline>> rebuilt = (old, next) -> {
};

public TrackRegistry(File dataFolder) {
this.store = new TrackStore(dataFolder);
}

/**
* Called when a spline's geometry is rebuilt, with the old spline and the
* splines that now carry its track. Arc lengths are not preserved, so
* anything bound to the old spline must re-find its position.
*/
public void onRebuilt(BiConsumer<TrackSpline, List<TrackSpline>> listener) {
rebuilt = listener == null ? (old, next) -> {
} : listener;
}

public void loadFromDisk() {
splines.clear();
junctions.clear();
Expand Down Expand Up @@ -147,11 +160,14 @@ public Optional<TrackEnd> findEnd(String world, double x, double y, double z) {
return Optional.ofNullable(best);
}

public DigResult dig(String world, double x, double y, double z) {
return dig(world, x, y, z, null);
/**
* The sample a dig at this point would remove, and the arc span of track
* it takes with it: {@code centreS} plus or minus {@code halfSpan}.
*/
public record DigTarget(TrackSpline spline, int index, double centreS, double halfSpan) {
}

public DigResult dig(String world, double x, double y, double z, World bukkitWorld) {
public Optional<DigTarget> digTarget(String world, double x, double y, double z) {
TrackSpline spline = null;
int index = -1;
double best = Math.max(Cache.trackJoinDistance, 2.0);
Expand All @@ -167,9 +183,25 @@ public DigResult dig(String world, double x, double y, double z, World bukkitWor
}
}
if (spline == null) {
return DigResult.none();
return Optional.empty();
}
return digAt(spline, index, bukkitWorld);
List<TrackSample> samples = spline.getSamples();
double digS = samples.get(index).s;
Optional<TrackJunction> turnout = turnoutDug(spline, digS);
if (turnout.isPresent()) {
// Digging a turnout removes the branch all the way back to the stem.
double turnoutEnd = turnout.get().turnoutEndS;
return Optional.of(new DigTarget(spline, index, turnoutEnd / 2,
turnoutEnd / 2 + TrackGenerate.STEP));
}
double before = index > 0 ? digS - samples.get(index - 1).s : 0;
double after = index < samples.size() - 1 ? samples.get(index + 1).s - digS : 0;
if (spline.isLoop() && (index == 0 || index == samples.size() - 1)) {
double seam = spline.length() - samples.get(samples.size() - 1).s;
before = index == 0 ? seam : before;
after = index == 0 ? after : seam;
}
return Optional.of(new DigTarget(spline, index, digS, Math.max(before, after)));
}

public DigResult digAt(TrackSpline spline, int index) {
Expand All @@ -182,14 +214,9 @@ public DigResult digAt(TrackSpline spline, int index, World bukkitWorld) {
return DigResult.none();
}
UUID id = spline.getId();
Optional<TrackJunction> asBranch = junctionByBranch(id);
if (asBranch.isPresent()) {
TrackJunction junction = asBranch.get();
double digS = spline.getSamples().get(index).s;
double turnoutEnd = junction.turnoutEndS;
if (turnoutEnd > 0 && digS <= turnoutEnd + 1e-9) {
return finishDig(removeJunctionTurnout(junction, bukkitWorld));
}
Optional<TrackJunction> turnout = turnoutDug(spline, spline.getSamples().get(index).s);
if (turnout.isPresent()) {
return finishDig(removeJunctionTurnout(turnout.get(), bukkitWorld));
}
List<TrackJunction> saved = List.copyOf(junctionsOn(id));
if (xyz.size() <= 2) {
Expand Down Expand Up @@ -227,7 +254,15 @@ public DigResult digAt(TrackSpline spline, int index, World bukkitWorld) {
dropJunctionsForSpline(id, spline.getWorld());
return finishDig(DigResult.deleted(id));
}
List<TrackSpline> pieces = new ArrayList<>();
if (start != null) {
pieces.add(start);
}
if (rest != null) {
pieces.add(rest);
}
rehomeJunctions(saved, spline, false, start, rest);
rebuilt.accept(spline, pieces);
if (start != null && rest != null) {
return finishDig(DigResult.split(start, rest));
}
Expand Down Expand Up @@ -339,9 +374,12 @@ private StrokeLay connect(TrackEnd from, TrackEnd to, World bukkitWorld) throws
from.spline.getWorld(),
TrackSpline.shouldLoop(merged, Cache.trackJoinDistance),
merged);
TrackSpline stored = replace(next);
TrackSpline stored = replaceQuietly(next);
rehomeJunctions(keepSaved, oldKeep, from.prepend, stored);
rehomeJunctions(dropSaved, oldDrop, !to.prepend, stored);
// Retrack only once junctions sit on the joined spline, so train routes survive.
rebuilt.accept(oldKeep, List.of(stored));
rebuilt.accept(oldDrop, List.of(stored));
return new StrokeLay(stored, extra, 0);
}

Expand Down Expand Up @@ -376,6 +414,15 @@ public Optional<TrackSpline> get(UUID id) {
}

public TrackSpline replace(TrackSpline spline) {
TrackSpline previous = splines.get(spline.getId());
TrackSpline next = replaceQuietly(spline);
if (previous != null && previous != next) {
rebuilt.accept(previous, List.of(next));
}
return next;
}

private TrackSpline replaceQuietly(TrackSpline spline) {
TrackSpline next = spline.promotedLoop(Cache.trackJoinDistance);
next.invalidateVisuals();
splines.put(next.getId(), next);
Expand Down Expand Up @@ -925,6 +972,11 @@ private TrackLayResult finishLay(TrackLayResult result) {
return result;
}

private Optional<TrackJunction> turnoutDug(TrackSpline spline, double digS) {
return junctionByBranch(spline.getId())
.filter(junction -> junction.turnoutEndS > 0 && digS <= junction.turnoutEndS + 1e-9);
}

private DigResult finishDig(DigResult result) {
if (result.kind != DigResult.Kind.NONE) {
pruneNestedShortTracks();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,12 @@
import net.tfminecraft.vehicleframework.database.ConsistData;
import net.tfminecraft.vehicleframework.database.PersistenceLog;
import net.tfminecraft.vehicleframework.enums.Direction;
import net.tfminecraft.vehicleframework.managers.VehicleManager;
import net.tfminecraft.vehicleframework.tracks.ThrottleTape;
import net.tfminecraft.vehicleframework.tracks.ThrottleTapeItems;
import net.tfminecraft.vehicleframework.tracks.TrackAdvance;
import net.tfminecraft.vehicleframework.tracks.TrainBlockCollision;
import net.tfminecraft.vehicleframework.tracks.TrackClearance;
import net.tfminecraft.vehicleframework.tracks.TrackFx;
import net.tfminecraft.vehicleframework.tracks.TrackJunction;
import net.tfminecraft.vehicleframework.tracks.TrackJunctionTravel;
Expand Down Expand Up @@ -773,6 +775,95 @@ private boolean keepBound() {
return true;
}

/** Moves every train on {@code old} onto the track that replaced it. */
public static void retrackTrains(TrackSpline old, List<TrackSpline> rebuilt) {
VehicleManager vehicles = VehicleFramework.getVehicleManager();
if (vehicles == null) {
return;
}
for (ActiveVehicle vehicle : vehicles.get().values()) {
if (vehicle.isTrain()) {
vehicle.getTrainHandler().retrack(old, rebuilt);
}
}
}

/**
* Keeps this car where it physically was after its track is rebuilt.
* Digging splits or trims a spline, which re-ids the far piece and shifts
* arc lengths; the stale {@code s} would otherwise teleport the train.
* If none of the rebuilt splines passes under the car it is left alone,
* and unbinds on its next tick if its spline is gone.
*/
public void retrack(TrackSpline old, List<TrackSpline> rebuilt) {
if (splineId == null || old == null || rebuilt == null || !splineId.equals(old.getId())) {
return;
}
TrackPose was = old.sampleAt(s);
TrackSpline best = null;
double bestS = 0;
double bestD = Double.POSITIVE_INFINITY;
for (TrackSpline candidate : rebuilt) {
double candidateS = candidate.nearestS(was.x, was.y, was.z);
TrackPose at = candidate.sampleAt(candidateS);
double horiz = Math.hypot(at.x - was.x, at.z - was.z);
double vert = Math.abs(at.y - was.y);
if (horiz > TrackClearance.OVERLAP_HORIZ || vert > TrackClearance.OVERLAP_VERT) {
continue;
}
double d = horiz * horiz + vert * vert;
if (d < bestD) {
best = candidate;
bestS = candidateS;
bestD = d;
}
}
if (best == null) {
return;
}
TrackRegistry registry = VehicleFramework.getTrackRegistry();
if (!best.getId().equals(splineId) && registry != null && !routeTouches(registry, best.getId())) {
routeJunctionId = null;
takeBranch = false;
}
PersistenceLog.append("RETRACK " + PersistenceLog.vehicle(v)
+ " from=" + splineId + "@" + s + " to=" + best.getId() + "@" + bestS);
splineId = best.getId();
s = bestS;
}

private boolean routeTouches(TrackRegistry registry, UUID trackId) {
if (routeJunctionId == null) {
return false;
}
TrackJunction route = registry.getJunction(routeJunctionId).orElse(null);
return route != null
&& (trackId.equals(route.stemSplineId) || trackId.equals(route.branchSplineId));
}

/**
* Whether any car of this consist sits within {@code halfSpan} of arc
* length {@code at} on the given track, counting each car out to its couplers.
*/
public boolean occupies(UUID trackId, double at, double halfSpan) {
if (trackId == null || v == null || v.hasParent() || boundSpline() == null) {
return false;
}
for (CarPlacement car : planCars()) {
if (!car.spline.getId().equals(trackId)) {
continue;
}
double d = Math.abs(car.s - at);
if (car.spline.isLoop()) {
d = Math.min(d, car.spline.length() - d);
}
if (d <= reach(car.vehicle.getTrainHandler()) + halfSpan) {
return true;
}
}
return false;
}

private boolean tryBindOrKeep() {
if (keepBound()) {
return true;
Expand Down Expand Up @@ -1116,18 +1207,24 @@ private static ActiveVehicle locoOf(ActiveVehicle car) {
}

private static double spacing(TrainHandler parent, TrainHandler child) {
double back = 0;
double front = 0;
double back = parent != null && parent.canHaveAttached() ? offsetLength(parent.getBack()) : 0;
double front = child != null && child.isAttachable() ? offsetLength(child.getFront()) : 0;
return TrackConsistMath.connectorSpacing(back, front);
}

// How far along the track a car extends from its centre to its couplers.
private static double reach(TrainHandler car) {
double front = car.isAttachable() ? offsetLength(car.getFront()) : 0;
double back = car.canHaveAttached() ? offsetLength(car.getBack()) : 0;
return Math.max(1.0, Math.max(front, back));
}

private static double offsetLength(Connector connector) {
try {
if (parent != null && parent.canHaveAttached()) {
back = parent.getBack().getOffset().length();
}
if (child != null && child.isAttachable()) {
front = child.getFront().getOffset().length();
}
return connector.getOffset().length();
} catch (Exception ignored) {
return TrackConsistMath.connectorSpacing(back, front);
// Offsets come from the live model and are unavailable until it loads.
return 0;
}
return TrackConsistMath.connectorSpacing(back, front);
}
}
Loading