From 7fd1d5e54d4e62a09f4f52597ef4cb534ed836af Mon Sep 17 00:00:00 2001 From: Jonathan G Rennison Date: Tue, 10 Sep 2024 19:56:03 +0100 Subject: [PATCH] Departures: Allow timetabled depot orders to be calling points --- src/departures.cpp | 59 +++++++++++++++++++++------------- src/departures_gui.cpp | 66 +++++++++++++++++++++----------------- src/departures_type.h | 39 ++++++++++++++++++---- src/lang/extra/english.txt | 14 ++++---- 4 files changed, 113 insertions(+), 65 deletions(-) diff --git a/src/departures.cpp b/src/departures.cpp index 9e242c7b60..93c4cc60f4 100644 --- a/src/departures.cpp +++ b/src/departures.cpp @@ -45,6 +45,13 @@ static constexpr Ticks INVALID_DEPARTURE_TICKS = INT32_MIN; using ScheduledDispatchCache = btree::btree_map>; using ScheduledDispatchVehicleRecords = btree::btree_map, LastDispatchRecord>; +CallAtTargetID CallAtTargetID::FromOrder(const Order *order) +{ + uint32_t id = order->GetDestination(); + if (order->IsType(OT_GOTO_DEPOT)) id |= DEPOT_TAG; + return CallAtTargetID(id); +} + struct ArrivalHistoryEntry { const Order *order; Ticks offset; @@ -323,7 +330,7 @@ static void ScheduledDispatchDepartureLocalFix(DepartureList &departure_list) static void ScheduledDispatchSmartTerminusDetection(DepartureList &departure_list, Ticks loop_duration = 0) { - btree::btree_map earliest_seen; + btree::btree_map earliest_seen; auto check_departure = [&](Departure *d) { size_t calling_at_size = d->calling_at.size(); @@ -336,12 +343,12 @@ static void ScheduledDispatchSmartTerminusDetection(DepartureList &departure_lis while (calling_at_size >= 2) { if (d->terminus.scheduled_tick != 0) { - auto iter = earliest_seen.find(d->terminus.station); + auto iter = earliest_seen.find(d->terminus.target); if (iter != earliest_seen.end() && iter->second <= d->terminus.scheduled_tick) { /* Terminus can be reached at same or earlier time on a later vehicle */ calling_at_size--; size_t new_terminus_offset = calling_at_size - 1; - d->terminus = CallAt(d->calling_at[new_terminus_offset]); + d->terminus = d->calling_at[new_terminus_offset]; auto remove_via = [&](StationID st) { if (d->via2 == st) d->via2 = INVALID_STATION; @@ -350,7 +357,9 @@ static void ScheduledDispatchSmartTerminusDetection(DepartureList &departure_lis d->via2 = INVALID_STATION; } }; - remove_via(d->terminus.station); + if (d->terminus.target.IsStationID()) { + remove_via(d->terminus.target.GetStationID()); + } for (const RemoveVia &rv : d->remove_vias) { if (rv.calling_at_offset == new_terminus_offset) { remove_via(rv.via); @@ -364,7 +373,7 @@ static void ScheduledDispatchSmartTerminusDetection(DepartureList &departure_lis for (const CallAt &c : d->calling_at) { if (c.scheduled_tick != 0) { - StateTicks &seen = earliest_seen[c.station]; + StateTicks &seen = earliest_seen[c.target]; if (seen == 0 || c.scheduled_tick < seen) seen = c.scheduled_tick; } } @@ -571,10 +580,11 @@ static void GetDepartureCandidateOrderDatesFromVehicle(std::vector &n } } -static bool IsStationIDCallingPointOrder(const Order *order) +static bool IsCallingPointTargetOrder(const Order *order) { if ((order->IsType(OT_GOTO_STATION) || order->IsType(OT_IMPLICIT)) && (order->GetNonStopType() & ONSF_NO_STOP_AT_DESTINATION_STATION) == 0) return true; if (order->IsType(OT_GOTO_WAYPOINT) && order->IsWaitTimetabled()) return true; + if (order->IsType(OT_GOTO_DEPOT) && ((order->GetDepotActionType() & ODATFB_NEAREST_DEPOT) == 0) && (order->IsWaitTimetabled() || (order->GetDepotActionType() & ODATFB_HALT) != 0)) return true; return false; } @@ -636,9 +646,9 @@ bool DepartureViaTerminusState::CheckOrder(const Vehicle *v, Departure *d, const bool DepartureViaTerminusState::HandleCallingPoint(Departure *d, const Order *order, CallAt c, DepartureCallingSettings calling_settings) { - if (!IsStationIDCallingPointOrder(order)) return false; + if (!IsCallingPointTargetOrder(order)) return false; - if (order->IsType(OT_GOTO_WAYPOINT)) { + if (order->IsType(OT_GOTO_WAYPOINT) || order->IsType(OT_GOTO_DEPOT)) { if (!calling_settings.ShowAllStops()) return false; } else { if (!calling_settings.ShowAllStops() && order->GetUnloadType() == OUFB_NO_UNLOAD) return false; @@ -650,6 +660,11 @@ bool DepartureViaTerminusState::HandleCallingPoint(Departure *d, const Order *or return true; } + d->terminus = c; + d->calling_at.push_back(c); + + if (order->IsType(OT_GOTO_DEPOT)) return (order->GetDepotActionType() & ODATFB_HALT) != 0; + /* Add the station to the calling at list and make it the candidate terminus. */ if (d->via == INVALID_STATION && pending_via != INVALID_STATION) { d->via = this->pending_via; @@ -658,8 +673,6 @@ bool DepartureViaTerminusState::HandleCallingPoint(Departure *d, const Order *or if (d->via == INVALID_STATION && this->candidate_via == (StationID)order->GetDestination()) { d->via = (StationID)order->GetDestination(); } - d->terminus = c; - d->calling_at.push_back(c); /* If we unload all at this station and departure load tests are not disabled, then it is the terminus. */ if (order->GetType() == OT_GOTO_STATION && order->GetUnloadType() == OUFB_UNLOAD && !calling_settings.DepartureNoLoadTest()) { @@ -692,7 +705,7 @@ static bool ProcessArrivalHistory(Departure *d, std::span a for (uint i = 0; i < (uint)arrival_history.size(); i++) { const Order *o = arrival_history[i].order; - if (IsStationIDCallingPointOrder(o)) { + if (IsCallingPointTargetOrder(o)) { if (source.StationMatches(o->GetDestination())) { /* Same as source order, remove all possible origins */ possible_origins.clear(); @@ -707,7 +720,7 @@ static bool ProcessArrivalHistory(Departure *d, std::span a } } - if (o->IsType(OT_GOTO_WAYPOINT)) { + if (o->IsType(OT_GOTO_WAYPOINT) || o->IsType(OT_GOTO_DEPOT)) { if (calling_settings.ShowAllStops()) possible_origins.push_back({ o->GetDestination(), i }); } else { if (calling_settings.ShowAllStops() || o->GetLoadType() != OLFB_NO_LOAD) possible_origins.push_back({ o->GetDestination(), i }); @@ -742,19 +755,19 @@ static bool ProcessArrivalHistory(Departure *d, std::span a auto make_call_at = [&](const ArrivalHistoryEntry &entry) -> CallAt { if (entry.offset == INVALID_DEPARTURE_TICKS) { - return CallAt((StationID)entry.order->GetDestination()); + return CallAt(entry.order); } else { - return CallAt((StationID)entry.order->GetDestination(), entry.offset + arrival_offset); + return CallAt(entry.order, entry.offset + arrival_offset); } }; for (uint i = origin_arrival_history_index + 1; i < (uint)arrival_history.size(); i++) { const Order *o = arrival_history[i].order; - if (IsStationIDCallingPointOrder(o)) { + if (IsCallingPointTargetOrder(o)) { check_order(o); if (o->IsType(OT_GOTO_STATION) && (o->GetLoadType() != OLFB_NO_LOAD || calling_settings.ShowAllStops())) { d->calling_at.push_back(make_call_at(arrival_history[i])); - } else if (o->IsType(OT_GOTO_WAYPOINT) && calling_settings.ShowAllStops()) { + } else if ((o->IsType(OT_GOTO_WAYPOINT) || o->IsType(OT_GOTO_DEPOT))&& calling_settings.ShowAllStops()) { d->calling_at.push_back(make_call_at(arrival_history[i])); } } @@ -880,7 +893,7 @@ static DepartureList MakeDepartureListLiveMode(DepartureOrderDestinationDetector order = lod.v->orders->GetNext(order); StateTicks departure_tick = d->scheduled_tick; bool travel_time_required = true; - CallAt c = CallAt((StationID)order->GetDestination(), departure_tick); + CallAt c = CallAt(order, departure_tick); 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 == lod.order) { @@ -927,7 +940,7 @@ static DepartureList MakeDepartureListLiveMode(DepartureOrderDestinationDetector } if (c.scheduled_tick != 0) c.scheduled_tick = departure_tick; - c.station = (StationID)order->GetDestination(); + c.target = CallAtTargetID::FromOrder(order); /* We're not interested in this order any further if we're not calling at it. */ if (via_state.HandleCallingPoint(d, order, c, calling_settings)) break; @@ -1013,7 +1026,7 @@ static DepartureList MakeDepartureListLiveMode(DepartureOrderDestinationDetector cumul += o->GetTravelTime() + o->GetWaitTime(); - if (o->GetType() == OT_GOTO_STATION || o->GetType() == OT_IMPLICIT || (o->IsType(OT_GOTO_WAYPOINT) && o->IsWaitTimetabled())) { + if (IsCallingPointTargetOrder(o)) { new_history.push_back({ o, cumul }); } } @@ -1266,7 +1279,7 @@ void DepartureListScheduleModeSlotEvaluator::EvaluateFromSourceOrder(const Order order = this->v->orders->GetNext(order); bool travel_time_required = true; - CallAt c = CallAt((StationID)order->GetDestination(), departure_tick); + CallAt c = CallAt(order, departure_tick); 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) { @@ -1313,7 +1326,7 @@ void DepartureListScheduleModeSlotEvaluator::EvaluateFromSourceOrder(const Order c.scheduled_tick = 0; } if (c.scheduled_tick != 0) c.scheduled_tick = departure_tick; - c.station = (StationID)order->GetDestination(); + c.target = CallAtTargetID::FromOrder(order); /* We're not interested in this order any further if we're not calling at it. */ if (via_state.HandleCallingPoint(&d, order, c, this->calling_settings)) break; @@ -1579,8 +1592,8 @@ static DepartureList MakeDepartureListScheduleMode(DepartureOrderDestinationDete std::sort(result.begin(), result.end(), [](std::unique_ptr &a, std::unique_ptr &b) -> bool { if (a->scheduled_tick == b->scheduled_tick) { - return std::tie(a->terminus.station, a->terminus.scheduled_tick, a->vehicle->index) - < std::tie(b->terminus.station, b->terminus.scheduled_tick, b->vehicle->index); + return std::tie(a->terminus.target, a->terminus.scheduled_tick, a->vehicle->index) + < std::tie(b->terminus.target, b->terminus.scheduled_tick, b->vehicle->index); } return a->scheduled_tick < b->scheduled_tick; }); diff --git a/src/departures_gui.cpp b/src/departures_gui.cpp index 9efd17b89c..970cc0b9ba 100644 --- a/src/departures_gui.cpp +++ b/src/departures_gui.cpp @@ -821,15 +821,21 @@ uint DeparturesWindow::GetMinWidth() const return result + ScaleGUITrad(140); } -/* Uses 2 parameters */ -static void FillBaseStationDParam(size_t n, StationID id) +/* Uses 3 parameters */ +static void FillCallingAtTargetDParam(size_t n, const Departure *d, CallAtTargetID target) { - if (Waypoint::IsValidID(id)) { - SetDParam(n, STR_WAYPOINT_NAME); + if (target.IsStationID()) { + if (Waypoint::IsValidID(target.GetStationID())) { + SetDParam(n, STR_WAYPOINT_NAME); + } else { + SetDParam(n, STR_STATION_NAME); + } + SetDParam(n + 1, target.GetStationID()); } else { - SetDParam(n, STR_STATION_NAME); + SetDParam(n, STR_DEPOT_NAME); + SetDParam(n + 1, d->vehicle->type); + SetDParam(n + 2, target.GetDepotDestinationID()); } - SetDParam(n + 1, id); } /** @@ -938,7 +944,7 @@ void DeparturesWindow::DrawDeparturesListItems(const Rect &r) const continue; } - if (d->terminus == INVALID_STATION) continue; + if (!d->terminus.IsValid()) continue; if (time_width > 0) { StringID time_str; @@ -1011,10 +1017,12 @@ void DeparturesWindow::DrawDeparturesListItems(const Rect &r) const /* The icons to show with the destination and via stations. */ StringID icon = STR_DEPARTURES_STATION_NONE; - if (_settings_client.gui.departure_destination_type) { - Station *t = Station::Get(d->terminus.station); + if (_settings_client.gui.departure_destination_type && d->terminus.target.IsStationID()) { + Station *t = Station::GetIfValid(d->terminus.target.GetStationID()); - if (t->facilities & FACIL_DOCK && + if (t == nullptr) { + /* No icon change */ + } else if (t->facilities & FACIL_DOCK && t->facilities & FACIL_AIRPORT && d->vehicle->type != VEH_SHIP && d->vehicle->type != VEH_AIRCRAFT) { @@ -1030,11 +1038,11 @@ void DeparturesWindow::DrawDeparturesListItems(const Rect &r) const StationID via = d->via; StationID via2 = d->via2; - if (via == d->terminus.station || this->source.StationMatches(via)) { + if (d->terminus.target.MatchesStationID(via) || this->source.StationMatches(via)) { via = via2; via2 = INVALID_STATION; } - if (via2 == d->terminus.station || this->source.StationMatches(via2)) via2 = INVALID_STATION; + if (d->terminus.target.MatchesStationID(via2) || this->source.StationMatches(via2)) via2 = INVALID_STATION; /* Arrival time */ if (arrival_time_width != 0 && d->terminus.scheduled_tick != 0) { @@ -1057,8 +1065,8 @@ void DeparturesWindow::DrawDeparturesListItems(const Rect &r) const if (via == INVALID_STATION) { /* Only show the terminus. */ - FillBaseStationDParam(0, d->terminus.station); - SetDParam(2, icon); + FillCallingAtTargetDParam(0, d, d->terminus.target); + SetDParam(3, icon); DrawString(dest_left, dest_right, y + 1, STR_DEPARTURES_TERMINUS); } else { auto set_via_dparams = [&](uint offset) { @@ -1096,16 +1104,16 @@ void DeparturesWindow::DrawDeparturesListItems(const Rect &r) const SetDParam(offset, SPECSTR_TEMP_START); }; /* Show the terminus and the via station. */ - FillBaseStationDParam(0, d->terminus.station); - SetDParam(2, icon); - set_via_dparams(3); + FillCallingAtTargetDParam(0, d, d->terminus.target); + SetDParam(3, icon); + set_via_dparams(4); int text_width = (GetStringBoundingBox(STR_DEPARTURES_TERMINUS_VIA_STATION)).width; if (dest_left + text_width < dest_right) { /* They will both fit, so show them both. */ - FillBaseStationDParam(0, d->terminus.station); - SetDParam(2, icon); - set_via_dparams(3); + FillCallingAtTargetDParam(0, d, d->terminus.target); + SetDParam(3, icon); + set_via_dparams(4); DrawString(dest_left, dest_right, y + 1, STR_DEPARTURES_TERMINUS_VIA_STATION); } else { /* They won't both fit, so switch between showing the terminus and the via station approximately every 4 seconds. */ @@ -1113,8 +1121,8 @@ void DeparturesWindow::DrawDeparturesListItems(const Rect &r) const set_via_dparams(0); DrawString(dest_left, dest_right, y + 1, STR_DEPARTURES_VIA); } else { - FillBaseStationDParam(0, d->terminus.station); - SetDParam(2, icon); + FillCallingAtTargetDParam(0, d, d->terminus.target); + SetDParam(3, icon); DrawString(dest_left, dest_right, y + 1, STR_DEPARTURES_TERMINUS_VIA); } this->scroll_refresh = true; @@ -1197,23 +1205,23 @@ void DeparturesWindow::DrawDeparturesListItems(const Rect &r) const /* STR_DEPARTURES_CALLING_AT_LAST_STATION :{STATION} & {RAW_STRING}*/ std::string buffer; - /* Uses 3 or 4 parameters */ + /* Uses 4 or 5 parameters */ auto fill_calling_at_dparam = [&](size_t n, const CallAt &c) { if (c.scheduled_tick != 0 && arrival_time_width > 0) { SetDParam(n, STR_DEPARTURES_CALLING_AT_STATION_WITH_TIME); n++; } - FillBaseStationDParam(n, c.station); - SetDParam(n + 2, c.scheduled_tick); + FillCallingAtTargetDParam(n, d, c.target); + SetDParam(n + 3, c.scheduled_tick); }; if (d->calling_at.size() != 0) { fill_calling_at_dparam(0, d->calling_at[0]); - std::string calling_at_buffer = GetString(STR_JUST_STRING3); + std::string calling_at_buffer = GetString(STR_JUST_STRING4); const CallAt *continues_to = nullptr; - if (d->calling_at[0].station == d->terminus.station && d->calling_at.size() > 1) { + if (d->calling_at[0].target == d->terminus.target && d->calling_at.size() > 1) { continues_to = &(d->calling_at[d->calling_at.size() - 1]); } else if (d->calling_at.size() > 1) { /* There's more than one stop. */ @@ -1221,8 +1229,8 @@ void DeparturesWindow::DrawDeparturesListItems(const Rect &r) const uint i; /* For all but the last station, write out ", ". */ for (i = 1; i < d->calling_at.size() - 1; ++i) { - StationID s = d->calling_at[i].station; - if (s == d->terminus.station) { + CallAtTargetID target = d->calling_at[i].target; + if (target == d->terminus.target) { continues_to = &(d->calling_at[d->calling_at.size() - 1]); break; } diff --git a/src/departures_type.h b/src/departures_type.h index 16165f67f6..86f82d079a 100644 --- a/src/departures_type.h +++ b/src/departures_type.h @@ -39,17 +39,44 @@ enum DeparturesSourceMode : uint8_t { DSM_END }; +struct CallAtTargetID { +private: + static constexpr uint32_t DEPOT_TAG = 1 << 31; + + uint32_t id; + + constexpr CallAtTargetID(uint32_t id) : id(id) {} + +public: + constexpr CallAtTargetID() : id(INVALID_STATION) {} + + static CallAtTargetID FromOrder(const Order *order); + static constexpr CallAtTargetID FromStation(StationID station) { return CallAtTargetID(station); } + + inline bool IsValid() const { return id != INVALID_STATION; } + inline bool IsStationID() const { return (id & DEPOT_TAG) == 0; } + inline StationID GetStationID() const { return (StationID)this->id; } + inline DestinationID GetDepotDestinationID() const { return this->id & ~DEPOT_TAG; } + inline bool MatchesStationID(StationID st) const { return this->IsStationID() && st == this->GetStationID(); } + + bool operator==(const CallAtTargetID& c) const = default; + auto operator<=>(const CallAtTargetID& c) const = default; +}; + struct CallAt { - StationID station; + CallAtTargetID target; StateTicks scheduled_tick; - CallAt(const StationID& s) : station(s), scheduled_tick(0) { } - CallAt(const StationID& s, StateTicks t) : station(s), scheduled_tick(t) { } - CallAt(const CallAt& c) : station(c.station), scheduled_tick(c.scheduled_tick) { } + CallAt(CallAtTargetID target) : target(target), scheduled_tick(0) {} + CallAt(CallAtTargetID target, StateTicks t) : target(target), scheduled_tick(t) {} + CallAt(const Order *order) : target(CallAtTargetID::FromOrder(order)), scheduled_tick(0) {} + CallAt(const Order *order, StateTicks t) : target(CallAtTargetID::FromOrder(order)), scheduled_tick(t) {} + + inline bool IsValid() const { return this->target.IsValid(); } inline bool operator==(const CallAt& c) const { - return this->station == c.station; + return this->target == c.target; } CallAt& operator=(const CallAt& c) = default; @@ -72,7 +99,7 @@ struct Departure { Ticks lateness = 0; ///< How delayed the departure is expected to be StationID via = INVALID_STATION; ///< The station the departure should list as going via StationID via2 = INVALID_STATION; ///< Secondary station the departure should list as going via - CallAt terminus = INVALID_STATION; ///< The station at which the vehicle will terminate following this departure + CallAt terminus = CallAtTargetID(); ///< The station at which the vehicle will terminate following this departure std::vector calling_at; ///< The stations both called at and unloaded at by the vehicle after this departure before it terminates std::vector remove_vias; ///< Vias to remove when using smart terminus. DepartureStatus status{}; ///< Whether the vehicle has arrived yet for this departure diff --git a/src/lang/extra/english.txt b/src/lang/extra/english.txt index 7e47d70862..295a6a41d7 100644 --- a/src/lang/extra/english.txt +++ b/src/lang/extra/english.txt @@ -1368,19 +1368,19 @@ STR_DEPARTURES_TIME :{COLOUR}{STRING STR_DEPARTURES_TIME_DEP :{COLOUR}{STRING1} {GREEN}{UP_ARROW} STR_DEPARTURES_TIME_ARR :{COLOUR}{STRING1} {RED}{DOWN_ARROW} STR_DEPARTURES_TIME_BOTH :{COLOUR}{STRING1} {RED}{DOWN_ARROW} {COLOUR}{STRING1} {GREEN}{UP_ARROW} -STR_DEPARTURES_TERMINUS :{ORANGE}{STRING1}{STRING} -STR_DEPARTURES_TERMINUS_VIA_STATION :{ORANGE}{STRING1}{STRING} via {STRING} -STR_DEPARTURES_TERMINUS_VIA :{ORANGE}{STRING1}{STRING} via +STR_DEPARTURES_TERMINUS :{ORANGE}{STRING2}{STRING} +STR_DEPARTURES_TERMINUS_VIA_STATION :{ORANGE}{STRING2}{STRING} via {STRING} +STR_DEPARTURES_TERMINUS_VIA :{ORANGE}{STRING2}{STRING} via STR_DEPARTURES_VIA :{ORANGE}via {STRING} STR_DEPARTURES_TOC :{ORANGE}{COMPANY} STR_DEPARTURES_GROUP :{ORANGE}{GROUP} STR_DEPARTURES_VEH :{ORANGE}{VEHICLE} STR_DEPARTURES_CALLING_AT :{ORANGE}Calling at: -STR_DEPARTURES_CALLING_AT_STATION :{RAW_STRING}, {STRING3} -STR_DEPARTURES_CALLING_AT_LAST_STATION :{RAW_STRING} and {STRING3} +STR_DEPARTURES_CALLING_AT_STATION :{RAW_STRING}, {STRING4} +STR_DEPARTURES_CALLING_AT_LAST_STATION :{RAW_STRING} and {STRING4} STR_DEPARTURES_CALLING_AT_LIST :{ORANGE}{RAW_STRING}. -STR_DEPARTURES_CALLING_AT_LIST_SMART_TERMINUS :{ORANGE}{RAW_STRING}. This service continues to {STRING3}. -STR_DEPARTURES_CALLING_AT_STATION_WITH_TIME :{STRING1} ({TT_TIME_ABS}) +STR_DEPARTURES_CALLING_AT_LIST_SMART_TERMINUS :{ORANGE}{RAW_STRING}. This service continues to {STRING4}. +STR_DEPARTURES_CALLING_AT_STATION_WITH_TIME :{STRING2} ({TT_TIME_ABS}) STR_DEPARTURES_TINY :{TINY_FONT}{STRING2} STR_DEPARTURES_VIA_DESCRIPTOR :{STRING1}{STRING} STR_DEPARTURES_VIA_AND :{STRING} and {STRING}