mirror of
https://github.com/Creators-of-Create/Create.git
synced 2024-06-02 18:59:19 +02:00
472 lines
14 KiB
Java
472 lines
14 KiB
Java
package com.simibubi.create.content.trains.schedule;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.Collection;
|
|
import java.util.List;
|
|
import java.util.Objects;
|
|
|
|
import com.simibubi.create.AllItems;
|
|
import com.simibubi.create.content.trains.display.GlobalTrainDisplayData.TrainDeparturePrediction;
|
|
import com.simibubi.create.content.trains.entity.Carriage;
|
|
import com.simibubi.create.content.trains.entity.Navigation;
|
|
import com.simibubi.create.content.trains.entity.Train;
|
|
import com.simibubi.create.content.trains.graph.DiscoveredPath;
|
|
import com.simibubi.create.content.trains.graph.EdgePointType;
|
|
import com.simibubi.create.content.trains.schedule.condition.ScheduleWaitCondition;
|
|
import com.simibubi.create.content.trains.schedule.condition.ScheduledDelay;
|
|
import com.simibubi.create.content.trains.schedule.destination.ChangeThrottleInstruction;
|
|
import com.simibubi.create.content.trains.schedule.destination.ChangeTitleInstruction;
|
|
import com.simibubi.create.content.trains.schedule.destination.DestinationInstruction;
|
|
import com.simibubi.create.content.trains.schedule.destination.ScheduleInstruction;
|
|
import com.simibubi.create.content.trains.station.GlobalStation;
|
|
import com.simibubi.create.foundation.utility.Components;
|
|
import com.simibubi.create.foundation.utility.NBTHelper;
|
|
|
|
import net.minecraft.nbt.CompoundTag;
|
|
import net.minecraft.nbt.Tag;
|
|
import net.minecraft.network.chat.MutableComponent;
|
|
import net.minecraft.util.Mth;
|
|
import net.minecraft.world.item.ItemStack;
|
|
import net.minecraft.world.level.Level;
|
|
|
|
public class ScheduleRuntime {
|
|
|
|
private static final int TBD = -1;
|
|
private static final int INVALID = -2;
|
|
|
|
public enum State {
|
|
PRE_TRANSIT, IN_TRANSIT, POST_TRANSIT
|
|
}
|
|
|
|
Train train;
|
|
Schedule schedule;
|
|
|
|
public boolean isAutoSchedule;
|
|
public boolean paused;
|
|
public boolean completed;
|
|
public int currentEntry;
|
|
public State state;
|
|
|
|
static final int INTERVAL = 40;
|
|
int cooldown;
|
|
List<Integer> conditionProgress;
|
|
List<CompoundTag> conditionContext;
|
|
String currentTitle;
|
|
|
|
int ticksInTransit;
|
|
List<Integer> predictionTicks;
|
|
|
|
public boolean displayLinkUpdateRequested;
|
|
|
|
public ScheduleRuntime(Train train) {
|
|
this.train = train;
|
|
reset();
|
|
}
|
|
|
|
public void destinationReached() {
|
|
if (state != State.IN_TRANSIT)
|
|
return;
|
|
state = State.POST_TRANSIT;
|
|
conditionProgress.clear();
|
|
displayLinkUpdateRequested = true;
|
|
for (Carriage carriage : train.carriages)
|
|
carriage.storage.resetIdleCargoTracker();
|
|
|
|
if (ticksInTransit > 0) {
|
|
int current = predictionTicks.get(currentEntry);
|
|
if (current > 0)
|
|
ticksInTransit = (current + ticksInTransit) / 2;
|
|
predictionTicks.set(currentEntry, ticksInTransit);
|
|
}
|
|
|
|
if (currentEntry >= schedule.entries.size())
|
|
return;
|
|
List<List<ScheduleWaitCondition>> conditions = schedule.entries.get(currentEntry).conditions;
|
|
for (int i = 0; i < conditions.size(); i++) {
|
|
conditionProgress.add(0);
|
|
conditionContext.add(new CompoundTag());
|
|
}
|
|
}
|
|
|
|
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;
|
|
if (paused)
|
|
return;
|
|
if (train.derailed)
|
|
return;
|
|
if (train.navigation.destination != null) {
|
|
ticksInTransit++;
|
|
return;
|
|
}
|
|
if (currentEntry >= schedule.entries.size()) {
|
|
currentEntry = 0;
|
|
if (!schedule.cyclic) {
|
|
paused = true;
|
|
completed = true;
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (cooldown-- > 0)
|
|
return;
|
|
if (state == State.IN_TRANSIT)
|
|
return;
|
|
if (state == State.POST_TRANSIT) {
|
|
tickConditions(level);
|
|
return;
|
|
}
|
|
|
|
DiscoveredPath nextPath = startCurrentInstruction();
|
|
if (nextPath == null)
|
|
return;
|
|
|
|
train.status.successfulNavigation();
|
|
if (nextPath.destination == train.getCurrentStation()) {
|
|
state = State.IN_TRANSIT;
|
|
destinationReached();
|
|
return;
|
|
}
|
|
if (train.navigation.startNavigation(nextPath) != TBD) {
|
|
state = State.IN_TRANSIT;
|
|
ticksInTransit = 0;
|
|
}
|
|
}
|
|
|
|
public void tickConditions(Level level) {
|
|
List<List<ScheduleWaitCondition>> conditions = schedule.entries.get(currentEntry).conditions;
|
|
for (int i = 0; i < conditions.size(); i++) {
|
|
List<ScheduleWaitCondition> list = conditions.get(i);
|
|
int progress = conditionProgress.get(i);
|
|
|
|
if (progress >= list.size()) {
|
|
state = State.PRE_TRANSIT;
|
|
currentEntry++;
|
|
return;
|
|
}
|
|
|
|
CompoundTag tag = conditionContext.get(i);
|
|
ScheduleWaitCondition condition = list.get(progress);
|
|
int prevVersion = tag.getInt("StatusVersion");
|
|
|
|
if (condition.tickCompletion(level, train, tag)) {
|
|
conditionContext.set(i, new CompoundTag());
|
|
conditionProgress.set(i, progress + 1);
|
|
displayLinkUpdateRequested |= i == 0;
|
|
}
|
|
|
|
displayLinkUpdateRequested |= i == 0 && prevVersion != tag.getInt("StatusVersion");
|
|
}
|
|
|
|
for (Carriage carriage : train.carriages)
|
|
carriage.storage.tickIdleCargoTracker();
|
|
}
|
|
|
|
public DiscoveredPath startCurrentInstruction() {
|
|
ScheduleEntry entry = schedule.entries.get(currentEntry);
|
|
ScheduleInstruction instruction = entry.instruction;
|
|
|
|
if (instruction instanceof DestinationInstruction destination) {
|
|
String regex = destination.getFilterForRegex();
|
|
boolean anyMatch = false;
|
|
ArrayList<GlobalStation> validStations = new ArrayList<>();
|
|
|
|
if (!train.hasForwardConductor() && !train.hasBackwardConductor()) {
|
|
train.status.missingConductor();
|
|
cooldown = INTERVAL;
|
|
return null;
|
|
}
|
|
|
|
for (GlobalStation globalStation : train.graph.getPoints(EdgePointType.STATION)) {
|
|
if (!globalStation.name.matches(regex))
|
|
continue;
|
|
anyMatch = true;
|
|
validStations.add(globalStation);
|
|
}
|
|
DiscoveredPath best = train.navigation.findPathTo(validStations, Double.MAX_VALUE);
|
|
if (best == null) {
|
|
if (anyMatch)
|
|
train.status.failedNavigation();
|
|
else
|
|
train.status.failedNavigationNoTarget(destination.getFilter());
|
|
cooldown = INTERVAL;
|
|
return null;
|
|
}
|
|
|
|
return best;
|
|
}
|
|
|
|
if (instruction instanceof ChangeTitleInstruction title) {
|
|
currentTitle = title.getScheduleTitle();
|
|
state = State.PRE_TRANSIT;
|
|
currentEntry++;
|
|
return null;
|
|
}
|
|
|
|
if (instruction instanceof ChangeThrottleInstruction throttle) {
|
|
train.throttle = throttle.getThrottle();
|
|
state = State.PRE_TRANSIT;
|
|
currentEntry++;
|
|
return null;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public void setSchedule(Schedule schedule, boolean auto) {
|
|
reset();
|
|
this.schedule = schedule;
|
|
currentEntry = Mth.clamp(schedule.savedProgress, 0, schedule.entries.size() - 1);
|
|
paused = false;
|
|
isAutoSchedule = auto;
|
|
train.status.newSchedule();
|
|
predictionTicks = new ArrayList<>();
|
|
schedule.entries.forEach($ -> predictionTicks.add(TBD));
|
|
displayLinkUpdateRequested = true;
|
|
}
|
|
|
|
public Schedule getSchedule() {
|
|
return schedule;
|
|
}
|
|
|
|
public void discardSchedule() {
|
|
train.navigation.cancelNavigation();
|
|
reset();
|
|
}
|
|
|
|
private void reset() {
|
|
paused = true;
|
|
completed = false;
|
|
isAutoSchedule = false;
|
|
currentEntry = 0;
|
|
currentTitle = "";
|
|
schedule = null;
|
|
state = State.PRE_TRANSIT;
|
|
conditionProgress = new ArrayList<>();
|
|
conditionContext = new ArrayList<>();
|
|
predictionTicks = new ArrayList<>();
|
|
}
|
|
|
|
public Collection<TrainDeparturePrediction> submitPredictions() {
|
|
Collection<TrainDeparturePrediction> predictions = new ArrayList<>();
|
|
int entryCount = schedule.entries.size();
|
|
int accumulatedTime = 0;
|
|
int current = currentEntry;
|
|
|
|
// Current
|
|
if (state == State.POST_TRANSIT || current >= entryCount) {
|
|
GlobalStation currentStation = train.getCurrentStation();
|
|
if (currentStation != null)
|
|
predictions.add(createPrediction(current, currentStation.name, currentTitle, 0));
|
|
int departureTime = estimateStayDuration(current);
|
|
if (departureTime == INVALID)
|
|
accumulatedTime = INVALID;
|
|
else
|
|
accumulatedTime += departureTime;
|
|
|
|
} else {
|
|
GlobalStation destination = train.navigation.destination;
|
|
if (destination != null) {
|
|
double speed =
|
|
Math.min(train.throttle * train.maxSpeed(), (train.maxSpeed() + train.maxTurnSpeed()) / 2);
|
|
int timeRemaining = (int) (train.navigation.distanceToDestination / speed) * 2;
|
|
|
|
if (predictionTicks.size() > current && train.navigation.distanceStartedAt != 0) {
|
|
float predictedTime = predictionTicks.get(current);
|
|
if (predictedTime > 0) {
|
|
predictedTime *= Mth
|
|
.clamp(train.navigation.distanceToDestination / train.navigation.distanceStartedAt, 0, 1);
|
|
timeRemaining = (timeRemaining + (int) predictedTime) / 2;
|
|
}
|
|
}
|
|
|
|
accumulatedTime += timeRemaining;
|
|
predictions.add(createPrediction(current, destination.name, currentTitle, accumulatedTime));
|
|
|
|
int departureTime = estimateStayDuration(current);
|
|
if (departureTime != INVALID)
|
|
accumulatedTime += departureTime;
|
|
else
|
|
accumulatedTime = INVALID;
|
|
|
|
} else
|
|
predictForEntry(current, currentTitle, accumulatedTime, predictions);
|
|
}
|
|
|
|
// Upcoming
|
|
String currentTitle = this.currentTitle;
|
|
for (int i = 1; i < entryCount; i++) {
|
|
int index = (i + current) % entryCount;
|
|
if (index == 0 && !schedule.cyclic)
|
|
break;
|
|
|
|
if (schedule.entries.get(index).instruction instanceof ChangeTitleInstruction title) {
|
|
currentTitle = title.getScheduleTitle();
|
|
continue;
|
|
}
|
|
|
|
accumulatedTime = predictForEntry(index, currentTitle, accumulatedTime, predictions);
|
|
}
|
|
|
|
predictions.removeIf(Objects::isNull);
|
|
return predictions;
|
|
}
|
|
|
|
private int predictForEntry(int index, String currentTitle, int accumulatedTime,
|
|
Collection<TrainDeparturePrediction> predictions) {
|
|
ScheduleEntry entry = schedule.entries.get(index);
|
|
if (!(entry.instruction instanceof DestinationInstruction filter))
|
|
return accumulatedTime;
|
|
if (predictionTicks.size() <= currentEntry)
|
|
return accumulatedTime;
|
|
|
|
int departureTime = estimateStayDuration(index);
|
|
|
|
if (accumulatedTime < 0) {
|
|
predictions.add(createPrediction(index, filter.getFilter(), currentTitle, accumulatedTime));
|
|
return Math.min(accumulatedTime, departureTime);
|
|
}
|
|
|
|
int predictedTime = predictionTicks.get(index);
|
|
accumulatedTime += predictedTime;
|
|
|
|
if (predictedTime == TBD)
|
|
accumulatedTime = TBD;
|
|
|
|
predictions.add(createPrediction(index, filter.getFilter(), currentTitle, accumulatedTime));
|
|
|
|
if (accumulatedTime != TBD)
|
|
accumulatedTime += departureTime;
|
|
|
|
if (departureTime == INVALID)
|
|
accumulatedTime = INVALID;
|
|
|
|
return accumulatedTime;
|
|
}
|
|
|
|
private int estimateStayDuration(int index) {
|
|
if (index >= schedule.entries.size()) {
|
|
if (!schedule.cyclic)
|
|
return INVALID;
|
|
index = 0;
|
|
}
|
|
|
|
ScheduleEntry scheduleEntry = schedule.entries.get(index);
|
|
Columns: for (List<ScheduleWaitCondition> list : scheduleEntry.conditions) {
|
|
int total = 0;
|
|
for (ScheduleWaitCondition condition : list) {
|
|
if (!(condition instanceof ScheduledDelay wait))
|
|
continue Columns;
|
|
total += wait.totalWaitTicks();
|
|
}
|
|
return total;
|
|
}
|
|
|
|
return INVALID;
|
|
}
|
|
|
|
private TrainDeparturePrediction createPrediction(int index, String destination, String currentTitle, int time) {
|
|
if (time == INVALID)
|
|
return null;
|
|
|
|
int size = schedule.entries.size();
|
|
if (index >= size) {
|
|
if (!schedule.cyclic)
|
|
return new TrainDeparturePrediction(train, time, Components.literal(" "), destination);
|
|
index %= size;
|
|
}
|
|
|
|
String text = currentTitle;
|
|
if (text.isBlank()) {
|
|
for (int i = 1; i < size; i++) {
|
|
int j = (index + i) % size;
|
|
ScheduleEntry scheduleEntry = schedule.entries.get(j);
|
|
if (!(scheduleEntry.instruction instanceof DestinationInstruction instruction))
|
|
continue;
|
|
text = instruction.getFilter()
|
|
.replaceAll("\\*", "")
|
|
.trim();
|
|
break;
|
|
}
|
|
}
|
|
|
|
return new TrainDeparturePrediction(train, time, Components.literal(text), destination);
|
|
}
|
|
|
|
public CompoundTag write() {
|
|
CompoundTag tag = new CompoundTag();
|
|
tag.putInt("CurrentEntry", currentEntry);
|
|
tag.putBoolean("AutoSchedule", isAutoSchedule);
|
|
tag.putBoolean("Paused", paused);
|
|
tag.putBoolean("Completed", completed);
|
|
if (schedule != null)
|
|
tag.put("Schedule", schedule.write());
|
|
NBTHelper.writeEnum(tag, "State", state);
|
|
tag.putIntArray("ConditionProgress", conditionProgress);
|
|
tag.put("ConditionContext", NBTHelper.writeCompoundList(conditionContext, CompoundTag::copy));
|
|
tag.putIntArray("TransitTimes", predictionTicks);
|
|
return tag;
|
|
}
|
|
|
|
public void read(CompoundTag tag) {
|
|
reset();
|
|
paused = tag.getBoolean("Paused");
|
|
completed = tag.getBoolean("Completed");
|
|
isAutoSchedule = tag.getBoolean("AutoSchedule");
|
|
currentEntry = tag.getInt("CurrentEntry");
|
|
if (tag.contains("Schedule"))
|
|
schedule = Schedule.fromTag(tag.getCompound("Schedule"));
|
|
state = NBTHelper.readEnum(tag, "State", State.class);
|
|
for (int i : tag.getIntArray("ConditionProgress"))
|
|
conditionProgress.add(i);
|
|
NBTHelper.iterateCompoundList(tag.getList("ConditionContext", Tag.TAG_COMPOUND), conditionContext::add);
|
|
|
|
int[] readTransits = tag.getIntArray("TransitTimes");
|
|
if (schedule != null) {
|
|
schedule.entries.forEach($ -> predictionTicks.add(TBD));
|
|
if (readTransits.length == schedule.entries.size())
|
|
for (int i = 0; i < readTransits.length; i++)
|
|
predictionTicks.set(i, readTransits[i]);
|
|
}
|
|
}
|
|
|
|
public ItemStack returnSchedule() {
|
|
if (schedule == null)
|
|
return ItemStack.EMPTY;
|
|
ItemStack stack = AllItems.SCHEDULE.asStack();
|
|
CompoundTag nbt = stack.getOrCreateTag();
|
|
schedule.savedProgress = currentEntry;
|
|
nbt.put("Schedule", schedule.write());
|
|
stack = isAutoSchedule ? ItemStack.EMPTY : stack;
|
|
discardSchedule();
|
|
return stack;
|
|
}
|
|
|
|
public void setSchedulePresentClientside(boolean present) {
|
|
schedule = present ? new Schedule() : null;
|
|
}
|
|
|
|
public MutableComponent getWaitingStatus(Level level) {
|
|
List<List<ScheduleWaitCondition>> conditions = schedule.entries.get(currentEntry).conditions;
|
|
if (conditions.isEmpty() || conditionProgress.isEmpty() || conditionContext.isEmpty())
|
|
return Components.empty();
|
|
|
|
List<ScheduleWaitCondition> list = conditions.get(0);
|
|
int progress = conditionProgress.get(0);
|
|
if (progress >= list.size())
|
|
return Components.empty();
|
|
|
|
CompoundTag tag = conditionContext.get(0);
|
|
ScheduleWaitCondition condition = list.get(progress);
|
|
return condition.getWaitingStatus(level, train, tag);
|
|
}
|
|
|
|
}
|