Index: src/autoreplace_cmd.cpp =================================================================== --- src/autoreplace_cmd.cpp (revision 12038) +++ src/autoreplace_cmd.cpp (working copy) @@ -215,7 +215,7 @@ DoCommand(0, (front->index << 16) | new_v->index, 1, DC_EXEC, CMD_MOVE_RAIL_VEHICLE); } else { // copy/clone the orders - DoCommand(0, (old_v->index << 16) | new_v->index, IsOrderListShared(old_v) ? CO_SHARE : CO_COPY, DC_EXEC, CMD_CLONE_ORDER); + DoCommand(0, (old_v->index << 16) | new_v->index, old_v->IsOrderListShared() ? CO_SHARE : CO_COPY, DC_EXEC, CMD_CLONE_ORDER); new_v->cur_order_index = old_v->cur_order_index; ChangeVehicleViewWindow(old_v, new_v); new_v->profit_this_year = old_v->profit_this_year; Index: src/lang/english.txt =================================================================== --- src/lang/english.txt (revision 12038) +++ src/lang/english.txt (working copy) @@ -1130,6 +1130,7 @@ STR_CONFIG_PATCHES_DEFAULT_RAIL_TYPE_FIRST :First available STR_CONFIG_PATCHES_DEFAULT_RAIL_TYPE_LAST :Last available STR_CONFIG_PATCHES_DEFAULT_RAIL_TYPE_MOST_USED :Most used +STR_CONFIG_PATCHES_TIMETABLE_SEPARATION :{LTBLUE}Use timetable to ensure vehicles separation: {ORANGE}{STRING1} STR_CONFIG_PATCHES_ALWAYS_BUILD_INFRASTRUCTURE :{LTBLUE}Show building tools when no suitable vehicles are available: {ORANGE}{STRING1} STR_CONFIG_PATCHES_MAX_TRAINS :{LTBLUE}Max trains per player: {ORANGE}{STRING1} Index: src/order.h =================================================================== --- src/order.h (revision 12038) +++ src/order.h (working copy) @@ -212,7 +212,6 @@ bool VehicleHasDepotOrders(const Vehicle *v); void CheckOrders(const Vehicle*); void DeleteVehicleOrders(Vehicle *v); -bool IsOrderListShared(const Vehicle *v); void AssignOrder(Order *order, Order data); bool CheckForValidOrders(const Vehicle* v); Index: src/order_cmd.cpp =================================================================== --- src/order_cmd.cpp (revision 12038) +++ src/order_cmd.cpp (working copy) @@ -22,6 +22,7 @@ #include "window_func.h" #include "settings_type.h" #include "string_func.h" +#include "station_map.h" #include "table/strings.h" @@ -837,7 +838,7 @@ } /* make sure there are orders available */ - delta = IsOrderListShared(dst) ? src->num_orders + 1 : src->num_orders - dst->num_orders; + delta = dst->IsOrderListShared() ? src->num_orders + 1 : src->num_orders - dst->num_orders; if (!HasOrderPoolFree(delta)) return_cmd_error(STR_8831_NO_MORE_SPACE_FOR_ORDERS); @@ -939,7 +940,7 @@ if (v->name != NULL) bak->name = strdup(v->name); /* If we have shared orders, store it on a special way */ - if (IsOrderListShared(v)) { + if (v->IsOrderListShared()) { const Vehicle *u = (v->next_shared) ? v->next_shared : v->prev_shared; bak->clone = u->index; @@ -1209,7 +1210,7 @@ /* If we have a shared order-list, don't delete the list, but just remove our pointer */ - if (IsOrderListShared(v)) { + if (v->IsOrderListShared()) { Vehicle *u = v; v->orders = NULL; @@ -1257,24 +1258,10 @@ return (_patches.servint_ispercent) ? Clamp(index, MIN_SERVINT_PERCENT, MAX_SERVINT_PERCENT) : Clamp(index, MIN_SERVINT_DAYS, MAX_SERVINT_DAYS); } -/** - * - * Check if we share our orders with an other vehicle - * - * @return Returns the vehicle who has the same order - * - */ -bool IsOrderListShared(const Vehicle *v) -{ - return v->next_shared != NULL || v->prev_shared != NULL; -} - -/** - * - * Check if a vehicle has any valid orders - * +/** + * Checks if a vehicle has any valid orders + * @param The vehicle to inspect * @return false if there are no valid orders - * */ bool CheckForValidOrders(const Vehicle* v) { Index: src/order_gui.cpp =================================================================== --- src/order_gui.cpp (revision 12038) +++ src/order_gui.cpp (working copy) @@ -120,7 +120,7 @@ v = GetVehicle(w->window_number); - shared_orders = IsOrderListShared(v); + shared_orders = v->IsOrderListShared(); SetVScrollCount(w, v->num_orders + 1); Index: src/settings.cpp =================================================================== --- src/settings.cpp (revision 12038) +++ src/settings.cpp (working copy) @@ -1427,6 +1427,7 @@ SDT_CONDBOOL(Patches, disable_elrails, 38, SL_MAX_VERSION, 0, NN, false, STR_CONFIG_PATCHES_DISABLE_ELRAILS, SettingsDisableElrail), SDT_CONDVAR(Patches, freight_trains, SLE_UINT8, 39, SL_MAX_VERSION, 0,NN, 1, 1, 255, 1, STR_CONFIG_PATCHES_FREIGHT_TRAINS, NULL), SDT_CONDBOOL(Patches, timetabling, 67, SL_MAX_VERSION, 0, 0, true, STR_CONFIG_PATCHES_TIMETABLE_ALLOW, NULL), + SDT_CONDBOOL(Patches, timetable_separation, 67, SL_MAX_VERSION, 0, 0, true, STR_CONFIG_PATCHES_TIMETABLE_SEPARATION, NULL), /***************************************************************************/ /* Station section of the GUI-configure patches window */ Index: src/settings_gui.cpp =================================================================== --- src/settings_gui.cpp (revision 12038) +++ src/settings_gui.cpp (working copy) @@ -818,6 +818,7 @@ "disable_elrails", "freight_trains", "timetabling", + "timetable_separation", }; struct PatchEntry { @@ -1070,7 +1071,7 @@ { WWT_CLOSEBOX, RESIZE_NONE, 10, 0, 10, 0, 13, STR_00C5, STR_018B_CLOSE_WINDOW}, { WWT_CAPTION, RESIZE_NONE, 10, 11, 369, 0, 13, STR_CONFIG_PATCHES_CAPTION, STR_018C_WINDOW_TITLE_DRAG_THIS}, { WWT_PANEL, RESIZE_NONE, 10, 0, 369, 14, 41, 0x0, STR_NULL}, -{ WWT_PANEL, RESIZE_NONE, 10, 0, 369, 42, 380, 0x0, STR_NULL}, +{ WWT_PANEL, RESIZE_NONE, 10, 0, 369, 42, 390, 0x0, STR_NULL}, { WWT_TEXTBTN, RESIZE_NONE, 3, 10, 96, 16, 27, STR_CONFIG_PATCHES_GUI, STR_NULL}, { WWT_TEXTBTN, RESIZE_NONE, 3, 97, 183, 16, 27, STR_CONFIG_PATCHES_CONSTRUCTION, STR_NULL}, @@ -1082,7 +1083,7 @@ }; static const WindowDesc _patches_selection_desc = { - WDP_CENTER, WDP_CENTER, 370, 381, 370, 381, + WDP_CENTER, WDP_CENTER, 370, 391, 370, 391, WC_GAME_OPTIONS, WC_NONE, WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET, _patches_selection_widgets, Index: src/settings_type.h =================================================================== --- src/settings_type.h (revision 12038) +++ src/settings_type.h (working copy) @@ -217,6 +217,7 @@ bool timetabling; ///< Whether to allow timetabling. bool timetable_in_ticks; ///< Whether to show the timetable in ticks rather than days. + bool timetable_separation; // Whether to use automatic separation based on timetable. bool autoslope; ///< Allow terraforming under things. Index: src/timetable_cmd.cpp =================================================================== --- src/timetable_cmd.cpp (revision 12038) +++ src/timetable_cmd.cpp (working copy) @@ -13,6 +13,7 @@ #include "settings_type.h" #include "table/strings.h" +#include "station_map.h" static void ChangeTimetable(Vehicle *v, VehicleOrderID order_number, uint16 time, bool is_journey) { Index: src/vehicle.cpp =================================================================== --- src/vehicle.cpp (revision 12038) +++ src/vehicle.cpp (working copy) @@ -571,6 +571,146 @@ } } +/** + * Checks if the vehicle is part of the shared orders list + * @param *v The vehicle to check if member of the current vehicle shared orders list + * @return true if both vehicles share the same orders list + */ +bool Vehicle::IsVehicleInSharedOrdersList(Vehicle *v) +{ + for (Vehicle *v_shared = this->prev_shared; v_shared != NULL; v_shared = v_shared->prev_shared) { + if (v_shared == v) return true; + } + + for (Vehicle *v_shared = this->next_shared; v_shared != NULL; v_shared = v_shared->next_shared) { + if (v_shared == v) return true; + } + + return false; +} + +/** + * Counts the vehicles that share it's orders list + * @return The count of vehicles that use this vehicle shared orders list + */ +int Vehicle::CountSharedOrderdList() +{ + int count = 1; // Count this vehicle firts + for (Vehicle *v = this->prev_shared; v != NULL; v = v->prev_shared) count++; + for (Vehicle *v = this->next_shared; v != NULL; v = v->next_shared) count++; + return count; +} + +/** + * Finds this vehicle's position in the shared order list chain + * @return The 0 based position in the vehicle's shared order list + */ +int Vehicle::GetPositionInSharedOrderList() +{ + int count = 0; + for (Vehicle *v = this->prev_shared; v != NULL; v = v->prev_shared) count++; + return count; +} + +/** + * Gets the total timetable duration + * @return The sum of all orders durations (in ticks) in the vehicle's timetable + */ +int Vehicle::GetTimetableTotalDuration() +{ + int total = 0; + for (Order *o = this->orders; o != NULL; o = o->next) { + total += o->wait_time + o->travel_time; + } + return total; +} + +/** + * Get the current theorical separation with the first shared order vehicle. + * This includes its lateness_counter. + * The vehicle must be in the first order station for this function to work. + * @return The actual separation (in ticks) with the first vehicle + */ +int Vehicle::GetFirstSharedSeparation() +{ + /* If this is the first vehicle in the shared orders chain then separation is 0 */ + if (this->prev_shared == NULL) return 0; + + /* Else gets the first vehicle in chain... */ + Vehicle *first; + for (first = this->prev_shared; first->prev_shared != NULL; first = first->prev_shared); + /* ... then returns the separation between this vehicle and first vehicle, plus the first vehicle lateness counter */ + return this->GetVehicleSeparation(first, false, true) + first->lateness_counter; +} + +/** + * Get the current separation with the given vehicle. + * @param *v The vehicle to compare + * @param add_this_current_order_time Indicates if we have to take into account this vehicle current order time + * @param add_other_current_order_time Indicates if we have to take into account the compared vehicle current order time + * @return Separation duration (in ticks) between both vehicles + */ +int Vehicle::GetVehicleSeparation(Vehicle *v, bool add_this_current_order_time, bool add_other_current_order_time) +{ + /* If the compared vehicle is this vehicle, then the separation is 0, whatever the current order time */ + if (this == v) return 0; + + int this_position = this->GetTimetablePosition(add_this_current_order_time); + int other_position = v->GetTimetablePosition(add_other_current_order_time); + + if (this_position <= other_position) { + /* The other vehicle is in front of this one. It's really easy to compute. */ + return other_position - this_position; + } else { + /* The other vehicle is behind this one. We must add the total timetable duration minus the current order wait_time as we may start immediately. */ + int total_timetable = this->GetTimetableTotalDuration(); + return total_timetable + other_position - this_position; + } +} + +/** + * Get the theorical current vehicle position in the timetable. This position is not the position in the shared orders chain, + * but actually the theorical duration since the vehicle started the timetabled orders list based on the current order. + * @param add_current_order_time Indicates if we get the current order time into account. + * @return numbers of ticks from the begining of the timetable without lateness_counter. + */ +int Vehicle::GetTimetablePosition(bool add_current_order_time) +{ + int total = 0; + + /* Add current order time */ + if (add_current_order_time) total += current_order_time; + + Order *o = orders; + for (int i = 0; i < cur_order_index%num_orders; i++) { + total += o->travel_time + o->wait_time; + o = o->next; + } + + if (last_station_visited == o->dest) + { + total += o->travel_time; + } + + return total; +} + +/** + * Gets the first order index where the vehicle stops at a station + * @return First order index vhere the vehicle stops at a station + */ +VehicleOrderID Vehicle::FirstStopStationOrderID() +{ + VehicleOrderID index = 0; + for (Order *first = this->orders; first != NULL; first = first->next) { + if (first->type == OT_GOTO_STATION && first->wait_time > 0) { + return index; + } + index++; + } + return INVALID_VEH_ORDER_ID; +} + Vehicle::~Vehicle() { free(this->name); @@ -3121,12 +3261,61 @@ * that arrives at random stations is bad. */ this->current_order.flags |= OFB_NON_STOP; UpdateVehicleTimetable(this, true); + + /* We ensure the vehicle is well positionned in the shared order list + * This means the last vehicle from this shared order list that visited the + * station must be its ->prev_shared */ + if (_patches.timetabling && _patches.timetable_separation && this->prev_shared != NULL && this->IsOrderListShared() && this->FirstStopStationOrderID() == this->cur_order_index) { + /* Find the previous vehicle of the shared order that visited the station */ + int min_separation = -1; + int tmp_separation = 0; + Vehicle *previous = NULL; + /* Check for previous vehicles */ + for (Vehicle *v = this->prev_shared; v != NULL; v = v->prev_shared) { + tmp_separation = this->GetVehicleSeparation(v, false, true); + if (min_separation > 0 && tmp_separation < min_separation || min_separation == -1) { + min_separation = tmp_separation; + previous = v; + } + } + /* Check for next vehicles */ + for (Vehicle *v = this->next_shared; v != NULL; v = v->next_shared) { + tmp_separation = this->GetVehicleSeparation(v, false, true); + if (min_separation > 0 && tmp_separation < min_separation || min_separation == -1) { + min_separation = tmp_separation; + previous = v; + } + } + + /* Check if prev_shared vehicle is really the previous one and reorder the chain if needed */ + if (this->prev_shared != previous && previous != NULL) { + /* Remove this vehicle from the list */ + this->prev_shared->next_shared = this->next_shared; + if (this->next_shared != NULL) this->next_shared->prev_shared = this->prev_shared; + /* Insert in the list */ + this->prev_shared = previous; + this->next_shared = previous->next_shared; + /* Update previous and next */ + if (previous->next_shared != NULL) previous->next_shared->prev_shared = this; + previous->next_shared = this; + } + } } else { /* This is just an unordered intermediate stop */ this->current_order.flags = 0; } current_order.type = OT_LOADING; + + /* For shared orders vehicles other than the first in chain arriving to the + * first order station, we have to recompute the separation */ + if (_patches.timetabling && _patches.timetable_separation && this->IsOrderListShared() && this->prev_shared != NULL && this->cur_order_index == this->FirstStopStationOrderID()) { + /* This vehicle have to ensure separation + * Compute wait_time for auto-separation system + * The vehicle can leave the station. Fill the lateness counter with separation informations, and override the actual waiting time. */ + this->lateness_counter = -((GetTimetableTotalDuration() / CountSharedOrderdList()) - GetVehicleSeparation(prev_shared, false, true) - prev_shared->lateness_counter); + } + GetStation(this->last_station_visited)->loading_vehicles.push_back(this); VehiclePayment(this); @@ -3160,8 +3349,8 @@ { switch (this->current_order.type) { case OT_LOADING: { + /* Uses standard timetable system (no timetable, no shared orders, first vehicle, etc.) */ uint wait_time = max(this->current_order.wait_time - this->lateness_counter, 0); - /* Not the first call for this tick, or still loading */ if (mode || !HasBit(this->vehicle_flags, VF_LOADING_FINISHED) || (_patches.timetabling && this->current_order_time < wait_time)) return; Index: src/vehicle_base.h =================================================================== --- src/vehicle_base.h (revision 12038) +++ src/vehicle_base.h (working copy) @@ -461,6 +461,20 @@ * @return the first vehicle of the chain. */ inline Vehicle *First() const { return this->first; } + + /** + * Checks if the vehicle shares its orders with another vehicle + * @return true if the vehicle is in a shared orders chain + */ + bool IsOrderListShared() const { return this->prev_shared != NULL || this->next_shared != NULL; } + bool IsVehicleInSharedOrdersList(Vehicle *v); + int CountSharedOrderdList(); + int GetPositionInSharedOrderList(); + int GetTimetableTotalDuration(); + int GetFirstSharedSeparation(); + int GetVehicleSeparation(Vehicle *v, bool add_this_current_order_time, bool add_other_current_order_time); + int GetTimetablePosition(bool add_current_order_time); + VehicleOrderID FirstStopStationOrderID(); }; /**