diff --git a/src/saveload/extended_ver_sl.cpp b/src/saveload/extended_ver_sl.cpp index 4944f2eced..7180d9de8e 100644 --- a/src/saveload/extended_ver_sl.cpp +++ b/src/saveload/extended_ver_sl.cpp @@ -80,7 +80,7 @@ const SlxiSubChunkInfo _sl_xv_sub_chunk_infos[] = { { XSLFI_IMPROVED_BREAKDOWNS, XSCF_NULL, 6, 6, "improved_breakdowns", nullptr, nullptr, nullptr }, { XSLFI_CONSIST_BREAKDOWN_FLAG, XSCF_NULL, 1, 1, "consist_breakdown_flag", nullptr, nullptr, nullptr }, { XSLFI_TT_WAIT_IN_DEPOT, XSCF_NULL, 1, 1, "tt_wait_in_depot", nullptr, nullptr, nullptr }, - { XSLFI_AUTO_TIMETABLE, XSCF_NULL, 4, 4, "auto_timetables", nullptr, nullptr, nullptr }, + { XSLFI_AUTO_TIMETABLE, XSCF_NULL, 5, 5, "auto_timetables", nullptr, nullptr, nullptr }, { XSLFI_VEHICLE_REPAIR_COST, XSCF_NULL, 2, 2, "vehicle_repair_cost", nullptr, nullptr, nullptr }, { XSLFI_ENH_VIEWPORT_PLANS, XSCF_IGNORABLE_ALL, 3, 3, "enh_viewport_plans", nullptr, nullptr, "PLAN" }, { XSLFI_INFRA_SHARING, XSCF_NULL, 2, 2, "infra_sharing", nullptr, nullptr, "CPDP" }, diff --git a/src/saveload/vehicle_sl.cpp b/src/saveload/vehicle_sl.cpp index 70c473690f..c3ec7e3aa5 100644 --- a/src/saveload/vehicle_sl.cpp +++ b/src/saveload/vehicle_sl.cpp @@ -609,6 +609,8 @@ static uint32 _cargo_loaded_at_xy; CargoPacketList _cpp_packets; std::map _veh_cpp_packets; +static uint32 _old_ahead_separation; + /** * Make it possible to make the saveload tables "friends" of other classes. * @param vt the vehicle type. Can be VEH_END for the common vehicle description data @@ -752,8 +754,8 @@ const SaveLoad *GetVehicleDescription(VehicleType vt) SLE_CONDVAR(Vehicle, random_bits, SLE_UINT8, SLV_2, SL_MAX_VERSION), SLE_CONDVAR(Vehicle, waiting_triggers, SLE_UINT8, SLV_2, SL_MAX_VERSION), - SLE_CONDREF_X(Vehicle, ahead_separation, REF_VEHICLE, SL_MIN_VERSION, SL_MAX_VERSION, SlXvFeatureTest(XSLFTO_AND, XSLFI_AUTO_TIMETABLE)), - SLE_CONDREF_X(Vehicle, behind_separation, REF_VEHICLE, SL_MIN_VERSION, SL_MAX_VERSION, SlXvFeatureTest(XSLFTO_AND, XSLFI_AUTO_TIMETABLE)), + SLEG_CONDVAR_X(_old_ahead_separation, SLE_UINT32, SL_MIN_VERSION, SL_MAX_VERSION, SlXvFeatureTest(XSLFTO_AND, XSLFI_AUTO_TIMETABLE, 1, 4)), + SLE_CONDNULL_X(4, SL_MIN_VERSION, SL_MAX_VERSION, SlXvFeatureTest(XSLFTO_AND, XSLFI_AUTO_TIMETABLE, 1, 4)), SLE_CONDREF(Vehicle, next_shared, REF_VEHICLE, SLV_2, SL_MAX_VERSION), SLE_CONDNULL(2, SLV_2, SLV_69), @@ -1053,6 +1055,10 @@ void Load_VEHS() _veh_cpp_packets[index] = std::move(_cpp_packets); _cpp_packets.clear(); } + + if (SlXvIsFeaturePresent(XSLFI_AUTO_TIMETABLE, 1, 4)) { + SB(v->vehicle_flags, VF_SEPARATION_ACTIVE, 1, _old_ahead_separation ? 1 : 0); + } } } diff --git a/src/table/newgrf_debug_data.h b/src/table/newgrf_debug_data.h index d3a052ddd4..13e0f5c518 100644 --- a/src/table/newgrf_debug_data.h +++ b/src/table/newgrf_debug_data.h @@ -11,6 +11,7 @@ #include "../newgrf_engine.h" #include "../newgrf_roadtype.h" #include "../date_func.h" +#include "../timetable.h" /* Helper for filling property tables */ #define NIP(prop, base, variable, type, name) { name, (ptrdiff_t)cpp_offsetof(base, variable), cpp_sizeof(base, variable), prop, type } @@ -131,18 +132,19 @@ class NIHVehicle : public NIHelper { print(buffer); } - extern int SeparationBetween(Vehicle *v1, Vehicle *v2); - if (v->AheadSeparation() != nullptr) { - b = buffer + seprintf(buffer, lastof(buffer), " Ahead separation: %d ticks, %u, ", SeparationBetween(v, v->AheadSeparation()), v->AheadSeparation()->index); - SetDParam(0, v->AheadSeparation()->index); - b = GetString(b, STR_VEHICLE_NAME, lastof(buffer)); - print(buffer); - } - if (v->BehindSeparation() != nullptr) { - b = buffer + seprintf(buffer, lastof(buffer), " Behind separation: %d ticks, %u, ", SeparationBetween(v->BehindSeparation(), v), v->BehindSeparation()->index); - SetDParam(0, v->BehindSeparation()->index); - b = GetString(b, STR_VEHICLE_NAME, lastof(buffer)); - print(buffer); + if (HasBit(v->vehicle_flags, VF_SEPARATION_ACTIVE)) { + std::vector progress_array = PopulateSeparationState(v); + if (!progress_array.empty()) { + print("Separation state:"); + } + for (const auto &info : progress_array) { + b = buffer + seprintf(buffer, lastof(buffer), " %s [%d, %d, %d], %u, ", + info.id == v->index ? "*" : " ", info.order_count, info.order_ticks, info.cumulative_ticks, info.id); + SetDParam(0, info.id); + b = GetString(b, STR_VEHICLE_NAME, lastof(buffer)); + b += seprintf(b, lastof(buffer), ", lateness: %d", Vehicle::Get(info.id)->lateness_counter); + print(buffer); + } } seprintf(buffer, lastof(buffer), " Engine: %u", v->engine_type); diff --git a/src/timetable.h b/src/timetable.h index 9f799bde18..61b05596ff 100644 --- a/src/timetable.h +++ b/src/timetable.h @@ -12,9 +12,23 @@ #include "date_type.h" #include "vehicle_type.h" +#include +#include void ShowTimetableWindow(const Vehicle *v); void UpdateVehicleTimetable(Vehicle *v, bool travelling); void SetTimetableParams(int first_param, Ticks ticks); +struct TimetableProgress { + VehicleID id; + int order_count; + int order_ticks; + int cumulative_ticks; + + bool IsValidForSeparation() const { return this->cumulative_ticks >= 0; } + bool operator<(const TimetableProgress& other) const { return std::tie(this->order_count, this->order_ticks) < std::tie(other.order_count, other.order_ticks); } +}; + +std::vector PopulateSeparationState(const Vehicle *v_start); + #endif /* TIMETABLE_H */ diff --git a/src/timetable_cmd.cpp b/src/timetable_cmd.cpp index 0dc35bf6c0..61374a6519 100644 --- a/src/timetable_cmd.cpp +++ b/src/timetable_cmd.cpp @@ -612,11 +612,6 @@ CommandCost CmdTimetableSeparation(TileIndex tile, DoCommandFlag flags, uint32 p static inline bool IsOrderUsableForSeparation(const Order *order) { - if (order->IsType(OT_CONDITIONAL)) { - // Auto separation is unlikely to useful work at all if one of these is present, so give up - return false; - } - if (order->GetWaitTime() == 0 && order->IsType(OT_GOTO_STATION) && !(order->GetNonStopType() & ONSF_NO_STOP_AT_DESTINATION_STATION)) { // non-station orders are permitted to have 0 wait times return false; @@ -624,112 +619,107 @@ static inline bool IsOrderUsableForSeparation(const Order *order) if (order->GetTravelTime() == 0 && !order->IsTravelTimetabled()) { // 0 travel times are permitted, if explicitly timetabled - // this is useful for depot service orders return false; } return true; } -int TimeToFinishOrder(Vehicle *v, int n) +std::vector PopulateSeparationState(const Vehicle *v_start) { - int left; - Order *order = v->GetOrder(n); - int wait_time = order->GetWaitTime(); - int travel_time = order->GetTravelTime(); - assert(order != nullptr); - if (!IsOrderUsableForSeparation(order)) return -1; - if ((v->cur_real_order_index == n) && (v->last_station_visited == order->GetDestination())) { - if (v->current_loading_time > 0) { - left = wait_time - v->current_order_time; - } else { - left = wait_time; + std::vector out; + for (const Vehicle *v = v_start->FirstShared(); v != nullptr; v = v->NextShared()) { + if (!HasBit(v->vehicle_flags, VF_SEPARATION_ACTIVE)) continue; + bool separation_valid = true; + const int n = v->cur_real_order_index; + int cumulative_ticks = 0; + bool vehicle_ok = true; + int order_count = n * 2; + for (int i = 0; i < n; i++) { + const Order *order = v->GetOrder(i); + if (order->IsType(OT_CONDITIONAL)) { + vehicle_ok = false; + break; + } + if (!IsOrderUsableForSeparation(order)) separation_valid = false; + cumulative_ticks += order->GetTravelTime() + order->GetWaitTime(); } - if (left < 0) left = 0; - } else { - left = travel_time; - if (v->cur_real_order_index == n) left -= v->current_order_time; - if (left < 0) left = 0; - left +=wait_time; - } - return left; -} + if (!vehicle_ok) continue; -int SeparationBetween(Vehicle *v1, Vehicle *v2) -{ - if (v1 == v2) return -1; - int separation = 0; - int time; - int n = v1->cur_real_order_index; - while (n != v2->cur_real_order_index) { - time = TimeToFinishOrder(v1, n); - if (time == -1) return -1; - separation += time; - n++; - if (n >= v1->GetNumOrders()) n = 0; - } - int time1 = TimeToFinishOrder(v1, n); - int time2 = TimeToFinishOrder(v2, n); - if (time1 == -1 || time2 == -1) return -1; - time = time1 - time2; - if (time < 0) { - for (n = 0; n < v1->GetNumOrders(); n++) { - Order *order = v1->GetOrder(n); - if (!IsOrderUsableForSeparation(order)) return -1; - time += order->GetTravelTime() + order->GetWaitTime(); + const Order *order = v->GetOrder(n); + if (order->IsType(OT_CONDITIONAL)) continue; + if (!IsOrderUsableForSeparation(order)) separation_valid = false; + if (order->IsType(OT_GOTO_DEPOT) && (order->GetDepotOrderType() & ODTFB_SERVICE || order->GetDepotActionType() & ODATFB_HALT)) { + // Do not try to separate vehicles on depot service or halt orders + separation_valid = false; } + int order_ticks; + if (order->GetType() == OT_GOTO_STATION && (v->current_order.IsType(OT_LOADING) || v->current_order.IsType(OT_LOADING_ADVANCE)) && + v->last_station_visited == order->GetDestination()) { + order_count++; + order_ticks = order->GetTravelTime() + v->current_loading_time; + cumulative_ticks += order->GetTravelTime() + min(v->current_loading_time, order->GetWaitTime()); + } else { + order_ticks = v->current_order_time; + cumulative_ticks += min(v->current_order_time, order->GetTravelTime()); + } + + out.push_back({ v->index, order_count, order_ticks, separation_valid ? cumulative_ticks : -1 }); } - separation += time; - assert(separation >= 0); - if (separation == 0) return -1; - return separation; + + std::sort(out.begin(), out.end()); + + return out; } void UpdateSeparationOrder(Vehicle *v_start) { - /* First check if we have a vehicle ahead, and if not search for one. */ - if (v_start->AheadSeparation() == nullptr) { - v_start->InitSeparation(); - } - if (v_start->AheadSeparation() == nullptr) { - return; - } - /* Switch positions if necessary. */ - int swaps = 0; - bool done = false; - while (!done) { - done = true; - int min_sep = SeparationBetween(v_start, v_start->AheadSeparation()); - Vehicle *v = v_start; - do { - if (v != v_start) { - int tmp_sep = SeparationBetween(v_start, v); - if (tmp_sep < min_sep && tmp_sep != -1) { - swaps++; - if (swaps >= 50) { - return; - } - done = false; - v_start->ClearSeparation(); - v_start->AddToSeparationBehind(v); - break; + SetBit(v_start->vehicle_flags, VF_SEPARATION_ACTIVE); + + std::vector progress_array = PopulateSeparationState(v_start); + if (progress_array.size() < 2) return; + + const uint duration = v_start->orders.list->GetTotalDuration(); + Vehicle *v = Vehicle::Get(progress_array.back().id); + Vehicle *v_ahead = Vehicle::Get(progress_array.front().id); + uint behind_index = progress_array.size() - 1; + for (uint i = 0; i < progress_array.size(); i++) { + const TimetableProgress &info_behind = progress_array[behind_index]; + behind_index = i; + Vehicle *v_behind = v; + + const TimetableProgress &info = progress_array[i]; + v = v_ahead; + + uint ahead_index = (i + 1 == progress_array.size()) ? 0 : i + 1; + const TimetableProgress &info_ahead = progress_array[ahead_index]; + v_ahead = Vehicle::Get(info_ahead.id); + + if (HasBit(v->vehicle_flags, VF_TIMETABLE_STARTED) && + HasBit(v_ahead->vehicle_flags, VF_TIMETABLE_STARTED) && + HasBit(v_behind->vehicle_flags, VF_TIMETABLE_STARTED)) { + if (info_behind.IsValidForSeparation() && info.IsValidForSeparation() && info_ahead.IsValidForSeparation()) { + /* + * The below is equivalent to: + * int separation_ahead = info_ahead.cumulative_ticks - info.cumulative_ticks; + * int separation_behind = info.cumulative_ticks - info_behind.cumulative_ticks; + * int separation_delta = separation_ahead - separation_behind; + */ + int separation_delta = info_ahead.cumulative_ticks + info_behind.cumulative_ticks - (2 * info.cumulative_ticks); + + if (i == 0) { + separation_delta -= duration; + } else if (ahead_index == 0) { + separation_delta += duration; } + + Company *owner = Company::GetIfValid(v->owner); + uint8 timetable_separation_rate = owner ? owner->settings.auto_timetable_separation_rate : 100; + int new_lateness = separation_delta / 2; + v->lateness_counter = (new_lateness * timetable_separation_rate + + v->lateness_counter * (100 - timetable_separation_rate)) / 100; } - if (HasBit(v->vehicle_flags, VF_TIMETABLE_STARTED) && - HasBit(v->AheadSeparation()->vehicle_flags, VF_TIMETABLE_STARTED) && - HasBit(v->BehindSeparation()->vehicle_flags, VF_TIMETABLE_STARTED)) { - int separation_ahead = SeparationBetween(v, v->AheadSeparation()); - int separation_behind = SeparationBetween(v->BehindSeparation(), v); - if (separation_ahead != -1 && separation_behind != -1) { - Company *owner = Company::GetIfValid(v->owner); - uint8 timetable_separation_rate = owner ? owner->settings.auto_timetable_separation_rate : 100; - int new_lateness = (separation_ahead - separation_behind) / 2; - v->lateness_counter = (new_lateness * timetable_separation_rate + - v->lateness_counter * (100 - timetable_separation_rate)) / 100; - } - } - v = v->AheadSeparation(); - } while (v != v_start); + } } } diff --git a/src/vehicle.cpp b/src/vehicle.cpp index 8679d17a3b..ee3480b5eb 100644 --- a/src/vehicle.cpp +++ b/src/vehicle.cpp @@ -3648,61 +3648,6 @@ void Vehicle::SetNext(Vehicle *next) } } -void Vehicle::ClearSeparation() -{ - if (this->ahead_separation == nullptr && this->behind_separation == nullptr) return; - - assert(this->ahead_separation != nullptr); - assert(this->behind_separation != nullptr); - - this->ahead_separation->behind_separation = this->behind_separation; - this->behind_separation->ahead_separation = this->ahead_separation; - - this->ahead_separation = nullptr; - this->behind_separation = nullptr; - - SetWindowDirty(WC_VEHICLE_TIMETABLE, this->index); -} - -void Vehicle::InitSeparation() -{ - extern int SeparationBetween(Vehicle *v1, Vehicle *v2); - - assert(this->ahead_separation == nullptr && this->behind_separation == nullptr); - - Vehicle *best_match = nullptr; - int lowest_separation = -1; - for (Vehicle *v_other = this->FirstShared(); v_other != nullptr; v_other = v_other->NextShared()) { - if (v_other->ahead_separation != nullptr && v_other != this) { - if (best_match == nullptr) { - best_match = v_other; - lowest_separation = SeparationBetween(this, v_other); - } else { - int temp_sep = SeparationBetween(this, v_other); - if ((lowest_separation == -1 || temp_sep < lowest_separation) && temp_sep != -1) { - best_match = v_other; - lowest_separation = temp_sep; - } - } - } - } - if (best_match != nullptr) { - this->AddToSeparationBehind(best_match); - } else { - this->ahead_separation = this->behind_separation = this; - } -} - -void Vehicle::AddToSeparationBehind(Vehicle *v_other) -{ - assert(v_other->ahead_separation != nullptr && v_other->behind_separation != nullptr); - - this->ahead_separation = v_other; - v_other->behind_separation->ahead_separation = this; - this->behind_separation = v_other->behind_separation; - v_other->behind_separation = this; -} - /** * Adds this vehicle to a shared vehicle chain. * @param shared_chain a vehicle of the chain with shared vehicles. @@ -3803,6 +3748,7 @@ char *Vehicle::DumpVehicleFlags(char *b, const char *last) const dump('L', HasBit(this->vehicle_flags, VF_PATHFINDER_LOST)); dump('c', HasBit(this->vehicle_flags, VF_SERVINT_IS_CUSTOM)); dump('p', HasBit(this->vehicle_flags, VF_SERVINT_IS_PERCENT)); + dump('z', HasBit(this->vehicle_flags, VF_SEPARATION_ACTIVE)); dump('D', HasBit(this->vehicle_flags, VF_SCHEDULED_DISPATCH)); dump('x', HasBit(this->vehicle_flags, VF_LAST_LOAD_ST_SEP)); dump('s', HasBit(this->vehicle_flags, VF_TIMETABLE_SEPARATION)); @@ -3853,8 +3799,6 @@ char *Vehicle::DumpVehicleFlags(char *b, const char *last) const TileIndex vtile = TileVirtXY(this->x_pos, this->y_pos); if (this->tile != vtile) b += seprintf(b, last, ", VirtXYTile: %X (%u x %u)", vtile, TileX(vtile), TileY(vtile)); if (this->cargo_payment) b += seprintf(b, last, ", CP"); - if (this->ahead_separation && this->behind_separation) b += seprintf(b, last, ", Sp"); - if (!!this->ahead_separation != !!this->behind_separation) b += seprintf(b, last, ", bad Sp"); return b; } diff --git a/src/vehicle_base.h b/src/vehicle_base.h index b4032cbac4..bb79ea02a7 100644 --- a/src/vehicle_base.h +++ b/src/vehicle_base.h @@ -57,9 +57,10 @@ enum VehicleFlags { // Additional flags not in trunk are added at the end to avoid clashing with any new // flags which get added in future trunk, and to avoid re-ordering flags which are in trunk already, // as this breaks savegame compatibility. + VF_SEPARATION_ACTIVE = 11, ///< Whether timetable auto-separation is currently active VF_SCHEDULED_DISPATCH = 12, ///< Whether the vehicle should follow a timetabled dispatching schedule VF_LAST_LOAD_ST_SEP = 13, ///< Each vehicle of this chain has its last_loading_station field set separately - VF_TIMETABLE_SEPARATION = 14,///< Whether the vehicle should manage the timetable automatically. + VF_TIMETABLE_SEPARATION = 14,///< Whether timetable auto-separation is enabled VF_AUTOMATE_TIMETABLE = 15, ///< Whether the vehicle should manage the timetable automatically. }; @@ -242,9 +243,6 @@ private: Vehicle *next_shared; ///< pointer to the next vehicle that shares the order Vehicle *previous_shared; ///< NOSAVE: pointer to the previous vehicle in the shared order chain - Vehicle *ahead_separation; - Vehicle *behind_separation; - public: friend const SaveLoad *GetVehicleDescription(VehicleType vt); ///< So we can use private/protected variables in the saveload code friend void FixOldVehicles(); @@ -701,35 +699,9 @@ public: inline Order *GetFirstOrder() const { return (this->orders.list == nullptr) ? nullptr : this->orders.list->GetFirstOrder(); } /** - * Get the vehicle ahead on track. - * @return the vehicle ahead on track or nullptr when there isn't one. + * Clears this vehicle's separation status */ - inline Vehicle *AheadSeparation() const { return this->ahead_separation; } - - /** - * Get the vehicle behind on track. - * @return the vehicle behind on track or nullptr when there isn't one. - */ - inline Vehicle *BehindSeparation() const { return this->behind_separation; } - - /** - * Clears a vehicle's separation status, removing it from any chain. - */ - void ClearSeparation(); - - /** - * Adds this vehicle to a shared vehicle separation chain. - * @param v_other a vehicle of the separation chain - * @pre !this->IsOrderListShared() - */ - void InitSeparation(); - - /** - * Adds this vehicle behind another in a separation chain. - * @param v_other a vehicle of the separation chain. - * @pre !this->IsOrderListShared() - */ - void AddToSeparationBehind(Vehicle *v_other); + inline void ClearSeparation() { ClrBit(this->vehicle_flags, VF_SEPARATION_ACTIVE); } void AddToShared(Vehicle *shared_chain); void RemoveFromShared();