Departures: Add scheduled dispatch 24 hour timetable mode

This commit is contained in:
Jonathan G Rennison 2024-09-08 00:16:58 +01:00
parent 3b5eada729
commit f1bf3e9ca2
6 changed files with 576 additions and 89 deletions

View File

@ -32,6 +32,7 @@
#include "departures_func.h"
#include "departures_type.h"
#include "tracerestrict.h"
#include "scope.h"
#include "3rdparty/cpp-btree/btree_set.h"
#include "3rdparty/cpp-btree/btree_map.h"
@ -115,13 +116,19 @@ bool DepartureCallingSettings::IsArrival(const Order *order, const DepartureOrde
});
}
static uint8_t GetDepartureConditionalOrderMode(const Order *order, const Vehicle *v, StateTicks eval_tick, const ScheduledDispatchVehicleRecords &records)
static uint8_t GetNonScheduleDepartureConditionalOrderMode(const Order *order, const Vehicle *v, StateTicks eval_tick)
{
if (order->GetConditionVariable() == OCV_UNCONDITIONALLY) return 1;
if (order->GetConditionVariable() == OCV_TIME_DATE) {
int value = GetTraceRestrictTimeDateValueFromStateTicks(static_cast<TraceRestrictTimeDateValueField>(order->GetConditionValue()), eval_tick);
return OrderConditionCompare(order->GetConditionComparator(), value, order->GetXData()) ? 1 : 2;
}
return _settings_client.gui.departure_conditionals;
}
static uint8_t GetDepartureConditionalOrderMode(const Order *order, const Vehicle *v, StateTicks eval_tick, const ScheduledDispatchVehicleRecords &records)
{
if (order->GetConditionVariable() == OCV_DISPATCH_SLOT) {
if (GB(order->GetConditionValue(), ODCB_SRC_START, ODCB_SRC_COUNT) == ODCS_VEH) {
auto record = records.find(std::make_pair(order->GetConditionDispatchScheduleID(), v->index));
@ -134,8 +141,9 @@ static uint8_t GetDepartureConditionalOrderMode(const Order *order, const Vehicl
extern bool EvaluateDispatchSlotConditionalOrder(const Order *order, const Vehicle *v, StateTicks state_ticks, bool *predicted);
return EvaluateDispatchSlotConditionalOrder(order, v, eval_tick, nullptr) ? 1 : 2;
} else {
return GetNonScheduleDepartureConditionalOrderMode(order, v, eval_tick);
}
return _settings_client.gui.departure_conditionals;
}
static bool VehicleSetNextDepartureTime(Ticks *previous_departure, Ticks *waiting_time, const StateTicks state_ticks_base,
@ -570,6 +578,71 @@ static bool IsIgnorableCallingAtOrder(const Order *order, DepartureCallingSettin
return false;
}
/**
* Process arrival history, returns true if a valid arrival was found.
*/
static bool ProcessArrivalHistory(Departure *d, std::span<const Order *> arrival_history, DepartureOrderDestinationDetector source, DepartureCallingSettings calling_settings)
{
std::vector<std::pair<StationID, uint>> possible_origins;
for (uint i = 0; i < (uint)arrival_history.size(); i++) {
const Order *o = arrival_history[i];
if ((o->GetType() == OT_GOTO_STATION ||
o->GetType() == OT_IMPLICIT) &&
(o->GetNonStopType() & ONSF_NO_STOP_AT_DESTINATION_STATION) == 0) {
if (source.StationMatches(o->GetDestination())) {
/* Remove all possible origins */
possible_origins.clear();
} else {
/* Remove all possible origins of this station */
for (auto &item : possible_origins) {
if (item.first == o->GetDestination()) {
item.first = INVALID_STATION;
}
}
}
}
if ((o->GetLoadType() != OLFB_NO_LOAD ||
calling_settings.show_all_stops) &&
(o->GetType() == OT_GOTO_STATION ||
o->GetType() == OT_IMPLICIT) &&
!source.StationMatches(o->GetDestination()) &&
(o->GetNonStopType() & ONSF_NO_STOP_AT_DESTINATION_STATION) == 0) {
possible_origins.push_back({ o->GetDestination(), i });
}
}
const Order *origin = nullptr;
uint origin_arrival_history_index = 0;
for (const auto &item : possible_origins) {
if (item.first != INVALID_STATION) {
origin_arrival_history_index = item.second;
origin = arrival_history[item.second];
break;
}
}
possible_origins.clear();
if (origin != nullptr) {
for (uint i = origin_arrival_history_index + 1; i < (uint)arrival_history.size(); i++) {
const Order *o = arrival_history[i];
if (o->GetType() == OT_GOTO_STATION &&
(o->GetLoadType() != OLFB_NO_LOAD ||
calling_settings.show_all_stops) &&
(o->GetNonStopType() & ONSF_NO_STOP_AT_DESTINATION_STATION) == 0) {
d->calling_at.push_back(CallAt((StationID)o->GetDestination()));
}
}
d->terminus = CallAt((StationID)origin->GetDestination());
return true;
}
return false;
}
/**
* Compute an up-to-date list of departures for a station.
* @param source the station/etc to compute the departures of
@ -578,7 +651,7 @@ static bool IsIgnorableCallingAtOrder(const Order *order, DepartureCallingSettin
* @param calling_settings departure calling settings
* @return a list of departures, which is empty if an error occurred
*/
DepartureList MakeDepartureList(DepartureOrderDestinationDetector source, const std::vector<const Vehicle *> &vehicles, DepartureType type, DepartureCallingSettings calling_settings)
static DepartureList MakeDepartureListLiveMode(DepartureOrderDestinationDetector source, const std::vector<const Vehicle *> &vehicles, DepartureType type, DepartureCallingSettings calling_settings)
{
/* This function is the meat of the departure boards functionality. */
/* As an overview, it works by repeatedly considering the best possible next departure to show. */
@ -729,14 +802,10 @@ DepartureList MakeDepartureList(DepartureOrderDestinationDetector source, const
c.station = (StationID)order->GetDestination();
/* We're not interested in this order any further if we're not calling at it. */
if (IsIgnorableCallingAtOrder(order, calling_settings)) {
if (c.scheduled_tick != 0) c.scheduled_tick += order->GetWaitTime();
order = lod.v->orders->GetNext(order);
continue;
if (!IsIgnorableCallingAtOrder(order, calling_settings)) {
if (via_state.HandleCallingPoint(d, order, c)) break;
}
if (via_state.HandleCallingPoint(d, order, c)) break;
if (c.scheduled_tick != 0) c.scheduled_tick += order->GetWaitTime();
/* Get the next order, which may be the vehicle's first order. */
@ -851,60 +920,7 @@ DepartureList MakeDepartureList(DepartureOrderDestinationDetector source, const
lod.arrival_history = std::move(new_history);
}
std::vector<std::pair<StationID, uint>> possible_origins;
for (uint i = 0; i < (uint)lod.arrival_history.size(); i++) {
const Order *o = lod.arrival_history[i];
if ((o->GetType() == OT_GOTO_STATION ||
o->GetType() == OT_IMPLICIT) &&
(o->GetNonStopType() & ONSF_NO_STOP_AT_DESTINATION_STATION) == 0) {
if (source.StationMatches(o->GetDestination())) {
/* Remove all possible origins */
possible_origins.clear();
} else {
/* Remove all possible origins of this station */
for (auto &item : possible_origins) {
if (item.first == o->GetDestination()) {
item.first = INVALID_STATION;
}
}
}
}
if ((o->GetLoadType() != OLFB_NO_LOAD ||
calling_settings.show_all_stops) &&
(o->GetType() == OT_GOTO_STATION ||
o->GetType() == OT_IMPLICIT) &&
!source.StationMatches(o->GetDestination()) &&
(o->GetNonStopType() & ONSF_NO_STOP_AT_DESTINATION_STATION) == 0) {
possible_origins.push_back({ o->GetDestination(), i });
}
}
const Order *origin = nullptr;
uint origin_arrival_history_index = 0;
for (const auto &item : possible_origins) {
if (item.first != INVALID_STATION) {
origin_arrival_history_index = item.second;
origin = lod.arrival_history[item.second];
break;
}
}
possible_origins.clear();
if (origin != nullptr) {
for (uint i = origin_arrival_history_index + 1; i < (uint)lod.arrival_history.size(); i++) {
const Order *o = lod.arrival_history[i];
if (o->GetType() == OT_GOTO_STATION &&
(o->GetLoadType() != OLFB_NO_LOAD ||
calling_settings.show_all_stops) &&
(o->GetNonStopType() & ONSF_NO_STOP_AT_DESTINATION_STATION) == 0) {
d->calling_at.push_back(CallAt((StationID)o->GetDestination()));
}
}
d->terminus = CallAt((StationID)origin->GetDestination());
if (ProcessArrivalHistory(d, lod.arrival_history, source, calling_settings)) {
bool duplicate = false;
if (_settings_client.gui.departure_merge_identical) {
@ -1034,3 +1050,414 @@ Ticks GetDeparturesMaxTicksAhead()
return _settings_client.gui.max_departure_time * DAY_TICKS * DayLengthFactor();
}
}
struct DepartureListScheduleModeSlotEvaluator {
struct DispatchScheduleAnno {
StateTicks original_start_tick;
int32_t original_last_dispatch;
uint repetition = 0;
bool usable = false;
};
DepartureList &result;
const Vehicle *v;
const Order *start_order;
DispatchSchedule &ds;
DispatchScheduleAnno &anno;
const uint schedule_index;
const DepartureOrderDestinationDetector &source;
DepartureType type;
DepartureCallingSettings calling_settings;
std::vector<const Order *> &arrival_history;
StateTicks slot{};
uint slot_index{};
bool departure_dependant_condition_found = false;
void EvaluateSlots();
private:
inline bool IsDepartureDependantConditionVariable(OrderConditionVariable ocv) const { return ocv == OCV_DISPATCH_SLOT || ocv == OCV_TIME_DATE; }
uint8_t EvaluateConditionalOrder(const Order *order, StateTicks eval_tick);
void EvaluateFromSourceOrder(const Order *source_order, StateTicks departure_tick);
void EvaluateSlotIndex(uint slot_index);
};
uint8_t DepartureListScheduleModeSlotEvaluator::EvaluateConditionalOrder(const Order *order, StateTicks eval_tick) {
if (order->GetConditionVariable() == OCV_TIME_DATE) {
TraceRestrictTimeDateValueField field = static_cast<TraceRestrictTimeDateValueField>(order->GetConditionValue());
if (field != TRTDVF_MINUTE && field != TRTDVF_HOUR && field != TRTDVF_HOUR_MINUTE) {
/* No reasonable way to handle this with a minutes schedule, give up */
return 0;
}
}
if (order->GetConditionVariable() == OCV_DISPATCH_SLOT) {
if (GB(order->GetConditionValue(), ODCB_SRC_START, ODCB_SRC_COUNT) == ODCS_VEH) {
if (order->GetConditionDispatchScheduleID() == this->schedule_index) {
extern LastDispatchRecord MakeLastDispatchRecord(const DispatchSchedule &ds, StateTicks slot, int slot_index);
extern bool EvaluateDispatchSlotConditionalOrderVehicleRecord(const Order *order, const LastDispatchRecord &record);
return EvaluateDispatchSlotConditionalOrderVehicleRecord(order, MakeLastDispatchRecord(this->ds, this->slot, this->slot_index)) ? 1 : 2;
} else {
/* Testing a different schedule index, handle as if there is no record */
return OrderConditionCompare(order->GetConditionComparator(), 0, 0);
}
}
extern bool EvaluateDispatchSlotConditionalOrder(const Order *order, const Vehicle *v, StateTicks state_ticks, bool *predicted);
return EvaluateDispatchSlotConditionalOrder(order, this->v, eval_tick, nullptr) ? 1 : 2;
} else {
return GetNonScheduleDepartureConditionalOrderMode(order, this->v, eval_tick);
}
}
void DepartureListScheduleModeSlotEvaluator::EvaluateFromSourceOrder(const Order *source_order, StateTicks departure_tick)
{
Departure d{};
d.scheduled_tick = departure_tick;
d.lateness = 0;
d.status = D_SCHEDULED;
d.vehicle = this->v;
d.type = this->type;
d.order = source_order;
d.scheduled_waiting_time = 0;
/* We'll be going through the order list later, so we need a separate variable for it. */
const Order *order = source_order;
const uint order_iteration_limit = this->v->GetNumOrders();
if (type == D_DEPARTURE) {
/* Computing departures: */
DepartureViaTerminusState via_state{};
order = this->v->orders->GetNext(order);
CallAt c = CallAt((StationID)order->GetDestination(), 0);
for (uint i = order_iteration_limit; i > 0; --i) {
/* If we reach the order at which the departure occurs again, then use the departure station as the terminus. */
if (order == source_order) {
/* If we're not calling anywhere, then skip this departure. */
via_state.found_terminus = (d.calling_at.size() > 0);
break;
}
/* If the order is a conditional branch, handle it. */
if (order->IsType(OT_CONDITIONAL)) {
if (this->IsDepartureDependantConditionVariable(order->GetConditionVariable())) this->departure_dependant_condition_found = true;
switch (this->EvaluateConditionalOrder(order, departure_tick)) {
case 0: {
/* Give up */
break;
}
case 1: {
/* Take the branch */
order = this->v->GetOrder(order->GetConditionSkipToOrder());
if (order == nullptr) {
break;
}
continue;
}
case 2: {
/* Do not take the branch */
order = this->v->orders->GetNext(order);
continue;
}
}
break;
}
if (via_state.CheckOrder(this->v, &d, order, this->source, this->calling_settings)) break;
c.station = (StationID)order->GetDestination();
/* We're not interested in this order any further if we're not calling at it. */
if (!IsIgnorableCallingAtOrder(order, this->calling_settings)) {
if (via_state.HandleCallingPoint(&d, order, c)) break;
}
if (order->IsScheduledDispatchOrder(true)) {
if (d.calling_at.size() > 0) {
via_state.found_terminus = true;
}
break;
}
/* Get the next order, which may be the vehicle's first order. */
order = this->v->orders->GetNext(order);
}
if (via_state.found_terminus) {
/* Add the departure to the result list. */
this->result.push_back(std::make_unique<Departure>(std::move(d)));
}
} else {
/* Computing arrivals: */
if (ProcessArrivalHistory(&d, this->arrival_history, this->source, this->calling_settings)) {
this->result.push_back(std::make_unique<Departure>(std::move(d)));
}
}
}
void DepartureListScheduleModeSlotEvaluator::EvaluateSlotIndex(uint slot_index)
{
this->slot_index = slot_index;
this->slot = this->ds.GetScheduledDispatchStartTick() + this->ds.GetScheduledDispatch()[slot_index].offset;
StateTicks departure_tick = this->slot;
this->arrival_history.clear();
/* The original last dispatch time will be restored in MakeDepartureListScheduleMode */
this->ds.SetScheduledDispatchLastDispatch(ds.GetScheduledDispatch()[slot_index].offset);
auto guard = scope_guard([&]() {
this->ds.SetScheduledDispatchLastDispatch(INVALID_SCHEDULED_DISPATCH_OFFSET);
});
if (type == D_DEPARTURE && calling_settings.IsDeparture(this->start_order, this->source)) {
this->EvaluateFromSourceOrder(this->start_order, departure_tick);
return;
}
if (type == D_ARRIVAL) {
this->arrival_history.push_back(this->start_order);
}
const Order *order = this->v->orders->GetNext(this->start_order);
bool require_travel_time = true;
/* Loop through the vehicle's orders until we've found a suitable order or we've determined that no such order exists. */
/* We only need to consider each order at most once. */
for (int i = this->v->GetNumOrders(); i > 0; --i) {
departure_tick += order->GetTravelTime();
if (type == D_ARRIVAL && this->calling_settings.IsArrival(order, this->source)) {
this->EvaluateFromSourceOrder(order, departure_tick);
break;
}
departure_tick += order->GetWaitTime();
if (order->IsScheduledDispatchOrder(true)) {
break;
}
if (type == D_DEPARTURE && this->calling_settings.IsDeparture(order, this->source)) {
this->EvaluateFromSourceOrder(order, departure_tick);
break;
}
/* If the order is a conditional branch, handle it. */
if (order->IsType(OT_CONDITIONAL)) {
if (this->IsDepartureDependantConditionVariable(order->GetConditionVariable())) this->departure_dependant_condition_found = true;
switch (this->EvaluateConditionalOrder(order, departure_tick)) {
case 0: {
/* Give up */
break;
}
case 1: {
/* Take the branch */
order = v->GetOrder(order->GetConditionSkipToOrder());
if (order == nullptr) {
break;
}
departure_tick -= order->GetTravelTime();
require_travel_time = false;
continue;
}
case 2: {
/* Do not take the branch */
departure_tick -= order->GetWaitTime(); /* Added previously above */
order = v->orders->GetNext(order);
require_travel_time = true;
continue;
}
}
break;
}
/* If an order has a 0 travel time, and it's not explictly set, then stop. */
if (require_travel_time && order->GetTravelTime() == 0 && !order->IsTravelTimetabled() && !order->IsType(OT_IMPLICIT)) {
break;
}
if (type == D_ARRIVAL) {
this->arrival_history.push_back(order);
}
order = v->orders->GetNext(order);
require_travel_time = true;
}
}
void DepartureListScheduleModeSlotEvaluator::EvaluateSlots()
{
const size_t start_number_departures = this->result.size();
this->departure_dependant_condition_found = false;
this->EvaluateSlotIndex(0);
const auto &slots = this->ds.GetScheduledDispatch();
if (this->departure_dependant_condition_found) {
/* Need to evaluate every slot individually */
for (size_t i = 1; i < slots.size(); i++) {
this->EvaluateSlotIndex(i);
}
if (this->anno.repetition > 1) {
const auto dispatch_start_tick = this->ds.GetScheduledDispatchStartTick();
auto guard = scope_guard([&]() {
this->ds.SetScheduledDispatchStartTick(dispatch_start_tick);
});
for (uint i = 1; i < this->anno.repetition; i++) {
this->ds.SetScheduledDispatchStartTick(this->ds.GetScheduledDispatchStartTick() + this->ds.GetScheduledDispatchDuration());
for (size_t j = 0; j < slots.size(); j++) {
this->EvaluateSlotIndex(j);
}
}
}
} else {
/* Trivially repeat found departures */
const size_t done_first_slot_departures = this->result.size();
if (done_first_slot_departures == start_number_departures) return;
const uint32_t first_offset = slots[0].offset;
for (size_t i = 1; i < slots.size(); i++) {
for (size_t j = start_number_departures; j != done_first_slot_departures; j++) {
std::unique_ptr<Departure> d = std::make_unique<Departure>(*this->result[j]); // Clone departure
d->scheduled_tick += slots[i].offset - first_offset;
this->result.push_back(std::move(d));
}
}
const size_t done_schedule_departures = this->result.size();
for (uint i = 1; i < this->anno.repetition; i++) {
for (size_t j = start_number_departures; j != done_schedule_departures; j++) {
std::unique_ptr<Departure> d = std::make_unique<Departure>(*this->result[j]); // Clone departure
d->scheduled_tick += this->ds.GetScheduledDispatchDuration() * i;
this->result.push_back(std::move(d));
}
}
}
}
static DepartureList MakeDepartureListScheduleMode(DepartureOrderDestinationDetector source, const std::vector<const Vehicle *> &vehicles, DepartureType type,
DepartureCallingSettings calling_settings, const StateTicks start_tick, const StateTicks end_tick, const uint max_departure_slots_per_schedule)
{
const Ticks tick_duration = (end_tick - start_tick).AsTicks();
std::vector<std::unique_ptr<Departure>> result;
std::vector<const Order *> arrival_history;
for (const Vehicle *veh : vehicles) {
if (!HasBit(veh->vehicle_flags, VF_SCHEDULED_DISPATCH)) continue;
const Vehicle *v = nullptr;
for (const Vehicle *u = veh->FirstShared(); u != nullptr; u = u->NextShared()) {
if (IsVehicleUsableForDepartures(u, calling_settings)) {
v = u;
break;
}
}
if (v == nullptr) continue;
std::vector<DepartureListScheduleModeSlotEvaluator::DispatchScheduleAnno> schedule_anno;
schedule_anno.resize(v->orders->GetScheduledDispatchScheduleCount());
for (uint i = 0; i < v->orders->GetScheduledDispatchScheduleCount(); i++) {
/* This is mutable so that parts can be backed up, modified and restored later */
DispatchSchedule &ds = const_cast<Vehicle *>(v)->orders->GetDispatchScheduleByIndex(i);
DepartureListScheduleModeSlotEvaluator::DispatchScheduleAnno &anno = schedule_anno[i];
anno.original_start_tick = ds.GetScheduledDispatchStartTick();
anno.original_last_dispatch = ds.GetScheduledDispatchLastDispatch();
const uint32_t duration = ds.GetScheduledDispatchDuration();
if (duration < _settings_time.ticks_per_minute || duration > (uint)tick_duration) continue; // Duration is obviously out of range
if (tick_duration % duration != 0) continue; // Duration does not evenly fit into range
const uint slot_count = (uint)ds.GetScheduledDispatch().size();
if (slot_count == 0) continue; // No departure slots
anno.repetition = tick_duration / ds.GetScheduledDispatchDuration();
if (anno.repetition * slot_count > max_departure_slots_per_schedule) continue;
StateTicks dispatch_tick = ds.GetScheduledDispatchStartTick();
if (dispatch_tick < start_tick) {
dispatch_tick += CeilDivT<StateTicksDelta>(start_tick - dispatch_tick, duration).AsTicks() * duration;
}
if (dispatch_tick > start_tick) {
StateTicksDelta delta = (dispatch_tick - start_tick);
dispatch_tick -= (delta / duration).AsTicksT<uint>() * duration;
}
ds.SetScheduledDispatchStartTick(dispatch_tick);
ds.SetScheduledDispatchLastDispatch(INVALID_SCHEDULED_DISPATCH_OFFSET);
anno.usable = true;
}
auto guard = scope_guard([&]() {
for (uint i = 0; i < v->orders->GetScheduledDispatchScheduleCount(); i++) {
/* Restore backup */
DispatchSchedule &ds = const_cast<Vehicle *>(v)->orders->GetDispatchScheduleByIndex(i);
const DepartureListScheduleModeSlotEvaluator::DispatchScheduleAnno &anno = schedule_anno[i];
ds.SetScheduledDispatchStartTick(anno.original_start_tick);
ds.SetScheduledDispatchLastDispatch(anno.original_last_dispatch);
}
});
for (const Order *start_order : v->Orders()) {
if (start_order->IsScheduledDispatchOrder(true)) {
const uint schedule_index = start_order->GetDispatchScheduleIndex();
DepartureListScheduleModeSlotEvaluator::DispatchScheduleAnno &anno = schedule_anno[schedule_index];
if (!anno.usable) continue;
DispatchSchedule &ds = const_cast<Vehicle *>(v)->orders->GetDispatchScheduleByIndex(schedule_index);
DepartureListScheduleModeSlotEvaluator evaluator{
result, v, start_order, ds, anno, schedule_index, source, type, calling_settings, arrival_history
};
evaluator.EvaluateSlots();
}
}
}
for (std::unique_ptr<Departure> &d : result) {
if (d->scheduled_tick < start_tick) {
d->scheduled_tick += CeilDivT<StateTicksDelta>(start_tick - d->scheduled_tick, tick_duration).AsTicks() * tick_duration;
}
if (d->scheduled_tick > start_tick) {
StateTicksDelta delta = (d->scheduled_tick - start_tick);
d->scheduled_tick -= (delta / tick_duration).AsTicksT<uint>() * tick_duration;
}
}
std::sort(result.begin(), result.end(), [](std::unique_ptr<Departure> &a, std::unique_ptr<Departure> &b) -> bool {
return a->scheduled_tick < b->scheduled_tick;
});
return result;
}
/**
* Compute an up-to-date list of departures for a station.
* @param source_mode the departure source mode to use
* @param source the station/etc to compute the departures of
* @param vehicles set of all the vehicles stopping at this station, of all vehicles types that we are interested in
* @param type the type of departures to get (departures or arrivals)
* @param calling_settings departure calling settings
* @return a list of departures, which is empty if an error occurred
*/
DepartureList MakeDepartureList(DeparturesSourceMode source_mode, DepartureOrderDestinationDetector source, const std::vector<const Vehicle *> &vehicles,
DepartureType type, DepartureCallingSettings calling_settings)
{
switch (source_mode) {
case DSM_LIVE:
return MakeDepartureListLiveMode(source, vehicles, type, calling_settings);
case DSM_SCHEDULE_24H: {
if (!_settings_time.time_in_minutes) return {};
TickMinutes start = _settings_time.NowInTickMinutes().ToSameDayClockTime(0, 0);
StateTicks start_tick = _settings_time.FromTickMinutes(start);
StateTicks end_tick = _settings_time.FromTickMinutes(start + (24 * 60));
/* Set maximum to 90 departures per hour per dispatch schedule, to prevent excessive numbers of departures */
return MakeDepartureListScheduleMode(source, vehicles, type, calling_settings, start_tick, end_tick, 90 * 24);
}
default:
NOT_REACHED();
}
}

View File

@ -17,7 +17,7 @@
#include <vector>
DepartureList MakeDepartureList(DepartureOrderDestinationDetector source, const std::vector<const Vehicle *> &vehicles, DepartureType type, DepartureCallingSettings calling_settings);
DepartureList MakeDepartureList(DeparturesSourceMode source_mode, DepartureOrderDestinationDetector source, const std::vector<const Vehicle *> &vehicles, DepartureType type, DepartureCallingSettings calling_settings);
Ticks GetDeparturesMaxTicksAhead();

View File

@ -52,10 +52,10 @@ static constexpr NWidgetPart _nested_departures_list[] = {
NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_DB_SCROLLBAR),
EndContainer(),
NWidget(NWID_HORIZONTAL),
NWidget(WWT_PANEL, COLOUR_GREY), SetMinimalSize(0, 12), SetResize(1, 0), SetFill(1, 1), EndContainer(),
NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_DB_CARGO_MODE), SetFill(0, 1), SetDataTip(STR_JUST_STRING, STR_DEPARTURES_CARGO_MODE_TOOLTIP),
NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_DB_DEPARTURE_MODE), SetFill(0, 1), SetDataTip(STR_JUST_STRING, STR_DEPARTURES_DEPARTURE_MODE_TOOLTIP),
NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_DB_CARGO_MODE), SetFill(1, 1), SetResize(1, 0), SetDataTip(STR_JUST_STRING, STR_DEPARTURES_CARGO_MODE_TOOLTIP),
NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_DB_SOURCE_MODE), SetFill(1, 1), SetResize(1, 0), SetDataTip(STR_JUST_STRING, STR_DEPARTURES_SOURCE_MODE_TOOLTIP),
NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_DB_DEPARTURE_MODE), SetFill(1, 1), SetResize(1, 0), SetDataTip(STR_JUST_STRING, STR_DEPARTURES_DEPARTURE_MODE_TOOLTIP),
NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_DB_SHOW_VIA), SetMinimalSize(11, 12), SetFill(0, 1), SetDataTip(STR_DEPARTURES_VIA_BUTTON, STR_DEPARTURES_VIA_TOOLTIP),
NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_DB_SHOW_TRAINS), SetMinimalSize(14, 12), SetFill(0, 1), SetDataTip(STR_TRAIN, STR_NULL),
NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_DB_SHOW_ROADVEHS), SetMinimalSize(14, 12), SetFill(0, 1), SetDataTip(STR_LORRY, STR_NULL),
@ -121,6 +121,12 @@ enum DepartureSourceType : uint8_t {
DST_DEPOT,
};
static const StringID _departure_source_mode_strings[DSM_END] = {
STR_DEPARTURES_SOURCE_MODE_LIVE,
STR_DEPARTURES_SOURCE_MODE_SCHEDULE_24_HOUR,
};
struct DeparturesWindow : public Window {
protected:
DepartureSourceType source_type{}; ///< Source type.
@ -135,6 +141,7 @@ protected:
bool show_types[4]; ///< The vehicle types to show in the departure list.
DeparturesCargoMode cargo_mode = DCF_ALL_CARGOES;
DeparturesMode mode = DM_DEPARTURES;
DeparturesSourceMode source_mode = DSM_LIVE;
bool show_via = false;
mutable bool scroll_refresh; ///< Whether the window should be refreshed when paused due to scrolling
uint min_width = 400; ///< The minimum width of this window.
@ -145,9 +152,10 @@ protected:
int toc_width; ///< current width of company field
std::array<uint32_t, 3> title_params{};///< title string parameters
virtual uint GetMinWidth() const;
uint GetScrollbarCapacity() const;
uint GetMinWidth() const;
static void RecomputeDateWidth();
virtual void DrawDeparturesListItems(const Rect &r) const;
void DrawDeparturesListItems(const Rect &r) const;
void FillVehicleList()
{
@ -166,10 +174,12 @@ protected:
}
for (const Vehicle *veh : Vehicle::IterateTypeMaskFrontOnly(vt_mask)) {
if (veh->IsPrimaryVehicle() && veh == veh->FirstShared()) {
if (this->source_mode != DSM_LIVE && !HasBit(veh->vehicle_flags, VF_SCHEDULED_DISPATCH)) continue;
for (const Order *order : veh->Orders()) {
if (this->source.OrderMatches(order)) {
if (this->source_mode != DSM_LIVE) this->vehicles.push_back(veh);
for (const Vehicle *v = veh; v != nullptr; v = v->NextShared()) {
this->vehicles.push_back(v);
if (this->source_mode == DSM_LIVE) this->vehicles.push_back(v);
if (_settings_client.gui.departure_show_vehicle) {
if (v->name.empty() && !(v->group_id != DEFAULT_GROUP && _settings_client.gui.vehicle_names != 0)) {
@ -357,6 +367,11 @@ public:
size.width = GetStringListWidth(_departure_mode_strings);
size.width += padding.width;
break;
case WID_DB_SOURCE_MODE:
size.width = GetStringListWidth(_departure_source_mode_strings);
size.width += padding.width;
break;
}
}
@ -379,6 +394,11 @@ public:
SetDParam(0, _departure_mode_strings[this->mode]);
break;
}
case WID_DB_SOURCE_MODE: {
SetDParam(0, _departure_source_mode_strings[this->source_mode]);
break;
}
}
}
@ -494,6 +514,13 @@ public:
case WID_DB_DEPARTURE_MODE:
ShowDropDownMenu(this, _departure_mode_strings, this->mode, WID_DB_DEPARTURE_MODE, 0, 0);
break;
case WID_DB_SOURCE_MODE: {
uint32_t disabled_mask = 0;
if (!_settings_time.time_in_minutes) SetBit(disabled_mask, DSM_SCHEDULE_24H);
ShowDropDownMenu(this, _departure_source_mode_strings, this->source_mode, WID_DB_SOURCE_MODE, disabled_mask, 0);
break;
}
}
}
@ -523,6 +550,20 @@ public:
this->SetWidgetDirty(widget);
break;
}
case WID_DB_SOURCE_MODE: {
if (this->source_mode != index) {
this->source_mode = static_cast<DeparturesSourceMode>(index);
if (!_settings_time.time_in_minutes && this->source_mode == DSM_SCHEDULE_24H) {
this->source_mode = DSM_LIVE;
}
this->vehicles_invalid = true;
this->calc_tick_countdown = 0;
if (_pause_mode != PM_UNPAUSED) this->OnGameTick();
}
this->SetWidgetDirty(widget);
break;
}
}
}
@ -562,17 +603,19 @@ public:
settings.show_freight = show_freight;
if (this->mode != DM_ARRIVALS) {
this->departures = MakeDepartureList(list_source, this->vehicles, D_DEPARTURE, settings);
this->departures = MakeDepartureList(this->source_mode, list_source, this->vehicles, D_DEPARTURE, settings);
} else {
this->departures.clear();
}
if (this->mode == DM_ARRIVALS || this->mode == DM_SEPARATE) {
this->arrivals = MakeDepartureList(list_source, this->vehicles, D_ARRIVAL, settings);
this->arrivals = MakeDepartureList(this->source_mode, list_source, this->vehicles, D_ARRIVAL, settings);
} else {
this->arrivals.clear();
}
this->departures_invalid = false;
this->vscroll->SetCount(this->GetScrollbarCapacity());
this->SetWidgetDirty(WID_DB_LIST);
this->SetWidgetDirty(WID_DB_SCROLLBAR);
}
uint new_width = this->GetMinWidth();
@ -601,12 +644,6 @@ public:
}
}
virtual void OnPaint() override
{
this->vscroll->SetCount(std::min<uint>(_settings_client.gui.max_departures, (uint)this->departures.size() + (uint)this->arrivals.size()));
this->DrawWidgets();
}
virtual void DrawWidget(const Rect &r, WidgetID widget) const override
{
switch (widget) {
@ -633,6 +670,10 @@ public:
this->vehicles_invalid = true;
this->departures_invalid = true;
if (data > 0) {
if (!_settings_time.time_in_minutes && this->source_mode == DSM_SCHEDULE_24H) {
this->source_mode = DSM_LIVE;
this->vehicles_invalid = true;
}
this->SetupValues();
this->ReInit();
if (_pause_mode != PM_UNPAUSED) this->OnGameTick();
@ -694,6 +735,13 @@ void DeparturesWindow::RecomputeDateWidth()
cached_date_width -= cached_date_arrow_width;
}
uint DeparturesWindow::GetScrollbarCapacity() const
{
uint count = (uint)this->departures.size() + (uint)this->arrivals.size();
if (this->source_mode == DSM_LIVE) count = std::min<uint>(_settings_client.gui.max_departures, count);
return count;
}
static int PadWidth(int width)
{
if (width > 0) width += WidgetDimensions::scaled.hsep_wide;
@ -734,11 +782,7 @@ void DeparturesWindow::DrawDeparturesListItems(const Rect &r) const
const int text_right = right - (rtl ? text_offset : 0);
int y = r.top + 1;
uint max_departures = std::min<uint>(this->vscroll->GetPosition() + this->vscroll->GetCapacity(), (uint)this->departures.size() + (uint)this->arrivals.size());
if (max_departures > _settings_client.gui.max_departures) {
max_departures = _settings_client.gui.max_departures;
}
uint max_departures = std::min<uint>(this->vscroll->GetPosition() + this->vscroll->GetCapacity(), this->GetScrollbarCapacity());
const int small_font_size = _settings_client.gui.departure_larger_font ? GetCharacterHeight(FS_NORMAL) : GetCharacterHeight(FS_SMALL);
@ -828,7 +872,7 @@ void DeparturesWindow::DrawDeparturesListItems(const Rect &r) const
if (this->mode == DM_COMBINED) {
time_str = STR_DEPARTURES_TIME_BOTH;
SetDParam(0, STR_JUST_TT_TIME_ABS);
SetDParam(1, d->scheduled_tick - (d->scheduled_waiting_time > 0 ? d->scheduled_waiting_time : d->order->GetWaitTime()));
SetDParam(1, d->scheduled_tick - d->EffectiveWaitingTime());
SetDParam(2, STR_JUST_TT_TIME_ABS);
SetDParam(3, d->scheduled_tick);
} else {
@ -978,10 +1022,13 @@ void DeparturesWindow::DrawDeparturesListItems(const Rect &r) const
if (d->status == D_ARRIVED) {
/* The vehicle has arrived. */
DrawString(status_left, status_right, y + 1, STR_DEPARTURES_ARRIVED);
} else if(d->status == D_CANCELLED) {
} else if (d->status == D_CANCELLED) {
/* The vehicle has been cancelled. */
DrawString(status_left, status_right, y + 1, STR_DEPARTURES_CANCELLED);
} else{
} else if (d->status == D_SCHEDULED) {
/* Display as scheduled. */
DrawString(status_left, status_right, y + 1, STR_DEPARTURES_SCHEDULED);
} else {
if (d->lateness <= TimetableAbsoluteDisplayUnitSize() && d->scheduled_tick > now_date) {
/* We have no evidence that the vehicle is late, so assume it is on time. */
DrawString(status_left, status_right, y + 1, STR_DEPARTURES_ON_TIME);

View File

@ -21,8 +21,9 @@
/** Whether or not a vehicle has arrived for a departure. */
enum DepartureStatus : uint8_t {
D_TRAVELLING = 0,
D_ARRIVED = 1,
D_CANCELLED = 2,
D_ARRIVED,
D_CANCELLED,
D_SCHEDULED,
};
/** The type of departures. */
@ -31,6 +32,13 @@ enum DepartureType : uint8_t {
D_ARRIVAL = 1,
};
enum DeparturesSourceMode : uint8_t {
DSM_LIVE,
DSM_SCHEDULE_24H,
DSM_END
};
struct CallAt {
StationID station;
StateTicks scheduled_tick;

View File

@ -1380,6 +1380,9 @@ STR_DEPARTURES_CALLING_AT_LIST_SMART_TERMINUS :{ORANGE}{RAW_ST
STR_DEPARTURES_TINY :{TINY_FONT}{STRING2}
STR_DEPARTURES_VIA_DESCRIPTOR :{STRING1}{STRING}
STR_DEPARTURES_VIA_AND :{STRING} and {STRING}
STR_DEPARTURES_SOURCE_MODE_TOOLTIP :{BLACK}Set whether to show live departures, or a 24 hour timetable of scheduled dispatch departures.
STR_DEPARTURES_SOURCE_MODE_LIVE :Live
STR_DEPARTURES_SOURCE_MODE_SCHEDULE_24_HOUR :24 hour schedule
STR_DEPARTURES_TYPE_TRAIN :{ORANGE}{TRAIN}
STR_DEPARTURES_TYPE_TRAIN_SILVER :{SILVER}{TRAIN}
@ -1402,6 +1405,7 @@ STR_DEPARTURES_ARRIVED :{GREEN}Arrived
STR_DEPARTURES_DELAYED :{YELLOW}Delayed
STR_DEPARTURES_EXPECTED :{YELLOW}Expt {STRING1}
STR_DEPARTURES_CANCELLED :{RED}Cancelled
STR_DEPARTURES_SCHEDULED :{GREEN}Scheduled
STR_CONFIG_SETTING_DEPARTUREBOARDS :{ORANGE}Departure boards
STR_CONFIG_MAX_DEPARTURES :Show at most {STRING2} departure board entries for each station

View File

@ -17,6 +17,7 @@ enum DeparturesWindowWidgets {
WID_DB_SCROLLBAR, ///< List scrollbar
WID_DB_CARGO_MODE, ///< Cargo filter mode
WID_DB_DEPARTURE_MODE, ///< Departure type mode
WID_DB_SOURCE_MODE, ///< Departure source mode
WID_DB_SHOW_VIA, ///< Toggle via button
WID_DB_SHOW_TRAINS, ///< Toggle trains button
WID_DB_SHOW_ROADVEHS, ///< Toggle road vehicles button