diff --git a/src/departures.cpp b/src/departures.cpp index a9e06e20e8..8f756eb3a1 100644 --- a/src/departures.cpp +++ b/src/departures.cpp @@ -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(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 arrival_history, DepartureOrderDestinationDetector source, DepartureCallingSettings calling_settings) +{ + std::vector> 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 &vehicles, DepartureType type, DepartureCallingSettings calling_settings) +static DepartureList MakeDepartureListLiveMode(DepartureOrderDestinationDetector source, const std::vector &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> 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 &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(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(std::move(d))); + } + } else { + /* Computing arrivals: */ + + if (ProcessArrivalHistory(&d, this->arrival_history, this->source, this->calling_settings)) { + this->result.push_back(std::make_unique(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 d = std::make_unique(*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 d = std::make_unique(*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 &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> result; + std::vector 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 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(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(start_tick - dispatch_tick, duration).AsTicks() * duration; + } + if (dispatch_tick > start_tick) { + StateTicksDelta delta = (dispatch_tick - start_tick); + dispatch_tick -= (delta / duration).AsTicksT() * 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(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(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 &d : result) { + if (d->scheduled_tick < start_tick) { + d->scheduled_tick += CeilDivT(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() * tick_duration; + } + } + + std::sort(result.begin(), result.end(), [](std::unique_ptr &a, std::unique_ptr &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 &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(); + } +} diff --git a/src/departures_func.h b/src/departures_func.h index 1221e7796b..0c9c8b8d5c 100644 --- a/src/departures_func.h +++ b/src/departures_func.h @@ -17,7 +17,7 @@ #include -DepartureList MakeDepartureList(DepartureOrderDestinationDetector source, const std::vector &vehicles, DepartureType type, DepartureCallingSettings calling_settings); +DepartureList MakeDepartureList(DeparturesSourceMode source_mode, DepartureOrderDestinationDetector source, const std::vector &vehicles, DepartureType type, DepartureCallingSettings calling_settings); Ticks GetDeparturesMaxTicksAhead(); diff --git a/src/departures_gui.cpp b/src/departures_gui.cpp index 4ad8c1b045..d0af519a62 100644 --- a/src/departures_gui.cpp +++ b/src/departures_gui.cpp @@ -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 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(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(_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(_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(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(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); diff --git a/src/departures_type.h b/src/departures_type.h index 166c1f402b..919ae0f7d6 100644 --- a/src/departures_type.h +++ b/src/departures_type.h @@ -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; diff --git a/src/lang/extra/english.txt b/src/lang/extra/english.txt index 58bf6f8c4f..ff75afd9b0 100644 --- a/src/lang/extra/english.txt +++ b/src/lang/extra/english.txt @@ -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 diff --git a/src/widgets/departures_widget.h b/src/widgets/departures_widget.h index 2ffeda59b5..f04477c88b 100644 --- a/src/widgets/departures_widget.h +++ b/src/widgets/departures_widget.h @@ -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