Index: src/autoreplace_cmd.cpp =================================================================== --- src/autoreplace_cmd.cpp (revision 12032) +++ 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 12032) +++ 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 12032) +++ 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 12032) +++ 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; @@ -1259,18 +1260,6 @@ /** * - * 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 * * @return false if there are no valid orders Index: src/order_gui.cpp =================================================================== --- src/order_gui.cpp (revision 12032) +++ 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 12032) +++ 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 12032) +++ 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 12032) +++ 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 show the timetable in ticks rather than days. */ bool autoslope; ///< Allow terraforming under things. Index: src/timetable_cmd.cpp =================================================================== --- src/timetable_cmd.cpp (revision 12032) +++ 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 12032) +++ src/vehicle.cpp (working copy) @@ -570,6 +570,139 @@ } } +/** + * Checks if the vehicle is part of the shared 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 + */ +int Vehicle::CountSharedOrderdList() +{ + int count = 1; /* Includes this one first */ + 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 position in the shared order list chain + */ +int Vehicle::GetPositionInSharedOrderList() +{ + int count = 0; /* Do not include this one */ + for (Vehicle *v = this->prev_shared; v != NULL; v = v->prev_shared) count++; + return count; +} + +/** + * Gets the total timetable duration + */ +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. + */ +int Vehicle::GetFirstSharedSeparation() +{ + if (this->prev_shared == NULL) return 0; + + Vehicle *first; + /* Gets the first vehicle in the chain */ + for (first = this->prev_shared; first->prev_shared != NULL; first = first->prev_shared); + + return this->GetVehicleSeparation(first, false, true) + first->lateness_counter; +} + +/** + * Get the current separation with the given vehicle. + */ +int Vehicle::GetVehicleSeparation(Vehicle *v, bool add_this_current_order_time, bool add_other_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 + * @param bool adds current_order_time + * @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%(this->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; +} + +/** + * Returns the vehicle order index where it first 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) { + /** + * Take it easy : if wait_time is 0 then it means the train won't wait here + * because user choice or because station type (waypoint, buoy) or because + * order type (non-stop, etc.) + * This test is quite filthy, but it should always work unlikely all my previous + * attempts of cleaner code. + */ + return index; + } + index++; + } + return INVALID_VEH_ORDER_ID; +} + Vehicle::~Vehicle() { free(this->name); @@ -3120,12 +3253,69 @@ * 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); @@ -3159,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 12032) +++ src/vehicle_base.h (working copy) @@ -456,6 +456,20 @@ * @return the first vehicle of the chain. */ inline Vehicle *First() const { return this->first; } + + /** + * Check if we share our orders with an other vehicle + * @return Returns the vehicle who has the same order + */ + 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(); }; /**