# timetable_cmd.cpp

Go to the documentation of this file.

    00001 /* $Id$ */
    00002
    00003 /*
    00004  * This file is part of OpenTTD.
    00005  * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
    00006  * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
    00007  * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
    00008  */
    00009
    00012 #include "stdafx.h"
    00013 #include "command_func.h"
    00014 #include "company_func.h"
    00015 #include "date_func.h"
    00016 #include "window_func.h"
    00017 #include "vehicle_base.h"
    00018 #include "tile_cmd.h"
    00019 #include "settings_type.h"
    00020
    00021 #include "table/strings.h"
    00022
    00030 static void ChangeTimetable(Vehicle *v, VehicleOrderID order_number, uint16 time, bool is_journey)
    00031 {
    00032   Order *order = v->GetOrder(order_number);
    00033   int delta;
    00034
    00035   if (is_journey) {
    00036     delta = time - order->travel_time;
    00037     order->travel_time = time;
    00038   } else {
    00039     delta = time - order->wait_time;
    00040     order->wait_time = time;
    00041   }
    00042   v->orders.list->UpdateOrderTimetable(delta);
    00043
    00044   for (v = v->FirstShared(); v != NULL; v = v->NextShared()) {
    00045     if (v->cur_real_order_index == order_number && v->current_order.Equals(*order)) {
    00046       if (is_journey) {
    00047         v->current_order.travel_time = time;
    00048       } else {
    00049         v->current_order.wait_time = time;
    00050       }
    00051     }
    00052     SetWindowDirty(WC_VEHICLE_TIMETABLE, v->index);
    00053   }
    00054 }
    00055
    00070 CommandCost CmdChangeTimetable(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text)
    00071 {
    00072   if (!_settings_game.order.timetabling) return CMD_ERROR;
    00073
    00074   VehicleID veh = GB(p1, 0, 20);
    00075
    00076   Vehicle *v = Vehicle::GetIfValid(veh);
    00077   if (v == NULL || !v->IsPrimaryVehicle()) return CMD_ERROR;
    00078
    00079   CommandCost ret = CheckOwnership(v->owner);
    00080   if (ret.Failed()) return ret;
    00081
    00082   VehicleOrderID order_number = GB(p1, 20, 8);
    00083   Order *order = v->GetOrder(order_number);
    00084   if (order == NULL || order->IsType(OT_IMPLICIT)) return CMD_ERROR;
    00085
    00086   bool is_journey = HasBit(p1, 28);
    00087
    00088   int wait_time   = order->wait_time;
    00089   int travel_time = order->travel_time;
    00090   if (is_journey) {
    00091     travel_time = GB(p2, 0, 16);
    00092   } else {
    00093     wait_time   = GB(p2, 0, 16);
    00094   }
    00095
    00096   if (wait_time != order->wait_time) {
    00097     switch (order->GetType()) {
    00098       case OT_GOTO_STATION:
    00099         if (order->GetNonStopType() & ONSF_NO_STOP_AT_DESTINATION_STATION) return_cmd_error(STR_ERROR_TIMETABLE_NOT_STOPPING_HERE);
    00100         break;
    00101
    00102       case OT_CONDITIONAL:
    00103         break;
    00104
    00105       default: return_cmd_error(STR_ERROR_TIMETABLE_ONLY_WAIT_AT_STATIONS);
    00106     }
    00107   }
    00108
    00109   if (travel_time != order->travel_time && order->IsType(OT_CONDITIONAL)) return CMD_ERROR;
    00110
    00111   if (flags & DC_EXEC) {
    00112     if (wait_time   != order->wait_time)   ChangeTimetable(v, order_number, wait_time,   false);
    00113     if (travel_time != order->travel_time) ChangeTimetable(v, order_number, travel_time, true);
    00114   }
    00115
    00116   return CommandCost();
    00117 }
    00118
    00129 CommandCost CmdSetVehicleOnTime(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text)
    00130 {
    00131   if (!_settings_game.order.timetabling) return CMD_ERROR;
    00132
    00133   VehicleID veh = GB(p1, 0, 20);
    00134
    00135   Vehicle *v = Vehicle::GetIfValid(veh);
    00136   if (v == NULL || !v->IsPrimaryVehicle()) return CMD_ERROR;
    00137
    00138   CommandCost ret = CheckOwnership(v->owner);
    00139   if (ret.Failed()) return ret;
    00140
    00141   if (flags & DC_EXEC) {
    00142     v->lateness_counter = 0;
    00143     SetWindowDirty(WC_VEHICLE_TIMETABLE, v->index);
    00144   }
    00145
    00146   return CommandCost();
    00147 }
    00148
    00158 CommandCost CmdSetTimetableStart(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text)
    00159 {
    00160   if (!_settings_game.order.timetabling) return CMD_ERROR;
    00161
    00162   Vehicle *v = Vehicle::GetIfValid(GB(p1, 0, 20));
    00163   if (v == NULL || !v->IsPrimaryVehicle()) return CMD_ERROR;
    00164
    00165   CommandCost ret = CheckOwnership(v->owner);
    00166   if (ret.Failed()) return ret;
    00167
    00168   DateTicks start_date = (Date)p2 / DAY_TICKS;
    00169
    00170 #if WALLCLOCK_NETWORK_COMPATIBLE
    00171   /* Don't let a timetable start more than 15 years into the future or 1 year in the past. */
    00172   if (start_date < 0 || start_date > MAX_DAY) return CMD_ERROR;
    00173   if (start_date - _date > 15 * DAYS_IN_LEAP_YEAR) return CMD_ERROR;
    00174   if (_date - start_date > DAYS_IN_LEAP_YEAR) return CMD_ERROR;
    00175 #else
    00176   start_date = ((DateTicks)_date * DAY_TICKS) + _date_fract + (DateTicks)(int32)p2;
    00177 #endif
    00178
    00179   if (flags & DC_EXEC) {
    00180     v->lateness_counter = 0;
    00181     ClrBit(v->vehicle_flags, VF_TIMETABLE_STARTED);
    00182     v->timetable_start = start_date;
    00183
    00184     SetWindowDirty(WC_VEHICLE_TIMETABLE, v->index);
    00185   }
    00186
    00187   return CommandCost();
    00188 }
    00189
    00190
    00204 CommandCost CmdAutofillTimetable(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text)
    00205 {
    00206   if (!_settings_game.order.timetabling) return CMD_ERROR;
    00207
    00208   VehicleID veh = GB(p1, 0, 20);
    00209
    00210   Vehicle *v = Vehicle::GetIfValid(veh);
    00211   if (v == NULL || !v->IsPrimaryVehicle()) return CMD_ERROR;
    00212
    00213   CommandCost ret = CheckOwnership(v->owner);
    00214   if (ret.Failed()) return ret;
    00215
    00216   if (flags & DC_EXEC) {
    00217     if (HasBit(p2, 0)) {
    00218       /* Start autofilling the timetable, which clears the
    00219        * "timetable has started" bit. Times are not cleared anymore, but are
    00220        * overwritten when the order is reached now. */
    00221       SetBit(v->vehicle_flags, VF_AUTOFILL_TIMETABLE);
    00222       ClrBit(v->vehicle_flags, VF_TIMETABLE_STARTED);
    00223
    00224       /* Overwrite waiting times only if they got longer */
    00225       if (HasBit(p2, 1)) SetBit(v->vehicle_flags, VF_AUTOFILL_PRES_WAIT_TIME);
    00226
    00227       v->timetable_start = 0;
    00228       v->lateness_counter = 0;
    00229     } else {
    00230       ClrBit(v->vehicle_flags, VF_AUTOFILL_TIMETABLE);
    00231       ClrBit(v->vehicle_flags, VF_AUTOFILL_PRES_WAIT_TIME);
    00232     }
    00233
    00234     for (Vehicle *v2 = v->FirstShared(); v2 != NULL; v2 = v2->NextShared()) {
    00235       if (v2 != v) {
    00236         /* Stop autofilling; only one vehicle at a time can perform autofill */
    00237         ClrBit(v2->vehicle_flags, VF_AUTOFILL_TIMETABLE);
    00238         ClrBit(v2->vehicle_flags, VF_AUTOFILL_PRES_WAIT_TIME);
    00239       }
    00240       SetWindowDirty(WC_VEHICLE_TIMETABLE, v2->index);
    00241     }
    00242   }
    00243
    00244   return CommandCost();
    00245 }
    00246
    00258 CommandCost CmdAutomateTimetable(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text)
    00259 {
    00260   if (!_settings_game.order.timetable_automated) return CMD_ERROR;
    00261
    00262   VehicleID veh = GB(p1, 0, 16);
    00263
    00264   Vehicle *v = Vehicle::GetIfValid(veh);
    00265   if (v == NULL || !v->IsPrimaryVehicle()) return CMD_ERROR;
    00266
    00267   CommandCost ret = CheckOwnership(v->owner);
    00268   if (ret.Failed()) return ret;
    00269
    00270   if (flags & DC_EXEC) {
    00271     for (Vehicle *v2 = v->FirstShared(); v2 != NULL; v2 = v2->NextShared()) {
    00272       if (HasBit(p2, 0)) {
    00273         /* Automate timetable. Set flags and clear current times. */
    00274         SetBit(v2->vehicle_flags, VF_AUTOMATE_TIMETABLE);
    00275         ClrBit(v2->vehicle_flags, VF_AUTOFILL_TIMETABLE);
    00276         ClrBit(v2->vehicle_flags, VF_AUTOFILL_PRES_WAIT_TIME);
    00277         ClrBit(v2->vehicle_flags, VF_TIMETABLE_STARTED);
    00278         v2->timetable_start = 0;
    00279         v2->lateness_counter = 0;
    00280         v2->current_loading_time = 0;
    00281         v2->ClearSeparation();
    00282       } else {
    00283         /* De-automate timetable. Clear flags. */
    00284         ClrBit(v2->vehicle_flags, VF_AUTOMATE_TIMETABLE);
    00285         ClrBit(v2->vehicle_flags, VF_AUTOFILL_TIMETABLE);
    00286         ClrBit(v2->vehicle_flags, VF_AUTOFILL_PRES_WAIT_TIME);
    00287         v2->ClearSeparation();
    00288         if (!HasBit(p2, 1)) {
    00289           /* Ctrl wasn't pressed, so clear all timetabled times. */
    00290           SetBit(v2->vehicle_flags, VF_TIMETABLE_STARTED);
    00291           v2->timetable_start = 0;
    00292           v2->lateness_counter = 0;
    00293           v2->current_loading_time = 0;
    00294           OrderList *orders = v2->orders.list;
    00295           if (orders != NULL) {
    00296             for (int i = 0; i < orders->GetNumOrders(); i++) {
    00297               ChangeTimetable(v2, i, 0, false);
    00298               ChangeTimetable(v2, i, 0, true);
    00299             }
    00300           }
    00301         }
    00302       }
    00303       SetWindowDirty(WC_VEHICLE_TIMETABLE, v2->index);
    00304     }
    00305   }
    00306
    00307   return CommandCost();
    00308 }
    00309
    00310 int TimeToFinishOrder(Vehicle *v, int n)
    00311 {
    00312   int left;
    00313   Order *order = v->GetOrder(n);
    00314   assert(order != NULL);
    00315   if ((v->cur_real_order_index == n) && (v->last_station_visited == order->GetDestination())) {
    00316     if (order->wait_time == 0) return -1;
    00317     if (v->current_loading_time > 0) {
    00318       left = order->wait_time - v->current_order_time;
    00319     } else {
    00320       left = order->wait_time;
    00321     }
    00322     if (left < 0) left = 0;
    00323   } else {
    00324     left = order->travel_time;
    00325     if (v->cur_real_order_index == n) left -= v->current_order_time;
    00326     if (order->travel_time == 0 || order->wait_time == 0) return -1;
    00327     if (left < 0) left = 0;
    00328     left += order->wait_time;
    00329   }
    00330   return left;
    00331 }
    00332
    00333 int SeparationBetween(Vehicle *v1, Vehicle *v2)
    00334 {
    00335   if(v1 == v2) return -1;
    00336   int separation = 0;
    00337   int time;
    00338   int n = v1->cur_real_order_index;
    00339   while (n != v2->cur_real_order_index) {
    00340     time = TimeToFinishOrder(v1, n);
    00341     if (time == -1) return -1;
    00342     separation += time;
    00343     n++;
    00344     if (n >= v1->GetNumOrders()) n = 0;
    00345   }
    00346   int time1 = TimeToFinishOrder(v1, n);
    00347   int time2 = TimeToFinishOrder(v2, n);
    00348   if (time1 == -1 || time2 == -1) return -1;
    00349   time = time1 - time2;
    00350   if (time < 0) {
    00351     for (n = 0; n < v1->GetNumOrders(); n++) {
    00352       Order *order = v1->GetOrder(n);
    00353       if (order->travel_time == 0 || order->wait_time == 0) return -1;
    00354       time += order->travel_time + order->wait_time;
    00355     }
    00356   }
    00357   separation += time;
    00358   assert(separation >= 0);
    00359   if (separation == 0) return -1;
    00360   return separation;
    00361 }
    00362
    00363 void UpdateSeparationOrder(Vehicle *v_start)
    00364 {
    00365   /* First check if we have a vehicle ahead, and if not search for one. */
    00366   if (v_start->AheadSeparation() == NULL) {
    00367     v_start->InitSeparation();
    00368   }
    00369   if (v_start->AheadSeparation() == NULL) {
    00370     return;
    00371   }
    00372   /* Switch positions if necessary. */
    00373   int swaps = 0;
    00374   bool done = false;
    00375   while (!done) {
    00376     done = true;
    00377     int min_sep = SeparationBetween(v_start, v_start->AheadSeparation());
    00378     Vehicle *v = v_start;
    00379     do {
    00380       if (v != v_start) {
    00381         int tmp_sep = SeparationBetween(v_start, v);
    00382         if (tmp_sep < min_sep && tmp_sep != -1) {
    00383           swaps++;
    00384           if (swaps >= 50) {
    00385             return;
    00386           }
    00387           done = false;
    00388           v_start->ClearSeparation();
    00389           v_start->AddToSeparationBehind(v);
    00390           break;
    00391         }
    00392       }
    00393       int separation_ahead = SeparationBetween(v, v->AheadSeparation());
    00394       int separation_behind = SeparationBetween(v->BehindSeparation(), v);
    00395       v->lateness_counter = (separation_ahead - separation_behind) / 2;
    00396       if (separation_ahead == -1 || separation_behind == -1) v->lateness_counter = 0;
    00397       v = v->AheadSeparation();
    00398     } while (v != v_start);
    00399   }
    00400 }
    00401
    00407 void UpdateVehicleTimetable(Vehicle *v, bool travelling)
    00408 {
    00409   uint timetabled = travelling ? v->current_order.travel_time : v->current_order.wait_time;
    00410   if (!travelling) v->current_loading_time++; // +1 because this time is one tick behind
    00411   uint time_taken = v->current_order_time;
    00412   uint time_loading = v->current_loading_time;
    00413
    00414   v->current_order_time = 0;
    00415   v->current_loading_time = 0;
    00416
    00417   if (!_settings_game.order.timetabling) return;
    00418   if (v->current_order.IsType(OT_IMPLICIT)) return; // no timetabling of auto orders
    00419
    00420   VehicleOrderID first_manual_order = 0;
    00421   for (Order *o = v->GetFirstOrder(); o != NULL && o->IsType(OT_IMPLICIT); o = o->next) {
    00422     ++first_manual_order;
    00423   }
    00424
    00425   bool just_started = false;
    00426
    00427   /* Start automated timetables at first opportunity */
    00428   if (!HasBit(v->vehicle_flags, VF_TIMETABLE_STARTED) && HasBit(v->vehicle_flags, VF_AUTOMATE_TIMETABLE)) {
    00429     if (_settings_game.order.timetable_separation) v->ClearSeparation();
    00430     SetBit(v->vehicle_flags, VF_TIMETABLE_STARTED);
    00431     v->lateness_counter = 0;
    00432     if (_settings_game.order.timetable_separation) UpdateSeparationOrder(v);
    00433     for (v = v->FirstShared(); v != NULL; v = v->NextShared()) {
    00434       SetWindowDirty(WC_VEHICLE_TIMETABLE, v->index);
    00435     }
    00436     return;
    00437   }
    00438
    00439   /* This vehicle is arriving at the first destination in the timetable. */
    00440   if (v->cur_real_order_index == first_manual_order && travelling) {
    00441     /* If the start date hasn't been set, or it was set automatically when
    00442      * the vehicle last arrived at the first destination, update it to the
    00443      * current time. Otherwise set the late counter appropriately to when
    00444      * the vehicle should have arrived. */
    00445     just_started = !HasBit(v->vehicle_flags, VF_TIMETABLE_STARTED);
    00446
    00447     if (v->timetable_start != 0) {
    00448 #if WALLCLOCK_NETWORK_COMPATIBLE
    00449       v->lateness_counter = (_date - v->timetable_start) * DAY_TICKS + _date_fract;
    00450 #else
    00451       v->lateness_counter = (_date * DAY_TICKS) + _date_fract - v->timetable_start;
    00452 #endif
    00453       v->timetable_start = 0;
    00454     }
    00455
    00456     SetBit(v->vehicle_flags, VF_TIMETABLE_STARTED);
    00457     SetWindowDirty(WC_VEHICLE_TIMETABLE, v->index);
    00458   }
    00459
    00460   if (!HasBit(v->vehicle_flags, VF_TIMETABLE_STARTED)) return;
    00461
    00462   if (HasBit(v->vehicle_flags, VF_AUTOFILL_TIMETABLE)) {
    00463     if (travelling && !HasBit(v->vehicle_flags, VF_AUTOFILL_PRES_WAIT_TIME)) {
    00464       /* Need to clear that now as otherwise we are not able to reduce the wait time */
    00465       v->current_order.wait_time = 0;
    00466     }
    00467
    00468     if (just_started) return;
    00469
    00470     /* Modify station waiting time only if our new value is larger (this is
    00471      * always the case when we cleared the timetable). */
    00472     if (!v->current_order.IsType(OT_CONDITIONAL) && (travelling || time_taken > v->current_order.wait_time)) {
    00473       /* Round the time taken up to the nearest day, as this will avoid
    00474        * confusion for people who are timetabling in days, and can be
    00475        * adjusted later by people who aren't.
    00476        * For trains/aircraft multiple movement cycles are done in one
    00477        * tick. This makes it possible to leave the station and process
    00478        * e.g. a depot order in the same tick, causing it to not fill
    00479        * the timetable entry like is done for road vehicles/ships.
    00480        * Thus always make sure at least one tick is used between the
    00481        * processing of different orders when filling the timetable. */
    00482       time_taken = CeilDiv(max(time_taken, 1U), DATE_UNIT_SIZE) * DATE_UNIT_SIZE;
    00483
    00484       ChangeTimetable(v, v->cur_real_order_index, time_taken, travelling);
    00485     }
    00486
    00487     if (v->cur_real_order_index == first_manual_order && travelling) {
    00488       /* If we just started we would have returned earlier and have not reached
    00489        * this code. So obviously, we have completed our round: So turn autofill
    00490        * off again. */
    00491       ClrBit(v->vehicle_flags, VF_AUTOFILL_TIMETABLE);
    00492       ClrBit(v->vehicle_flags, VF_AUTOFILL_PRES_WAIT_TIME);
    00493     }
    00494     return;
    00495   }
    00496
    00497   /* If the vehicle's lateness is greater than the entire duration of its
    00498    * timetable, then make it 'skip' one run of its timetable. */
    00499   if (v->orders.list->IsCompleteTimetable()) {
    00500     Ticks duration = v->orders.list->GetTimetableTotalDuration();
    00501     while (v->lateness_counter > duration) {
    00502       v->lateness_counter -= duration;
    00503     }
    00504   }
    00505
    00506   if (just_started) return;
    00507
    00508   /* Update the timetable to gradually shift order times towards the actual travel times. */
    00509   if (timetabled != 0 && HasBit(v->vehicle_flags, VF_AUTOMATE_TIMETABLE)) {
    00510     int32 new_time;
    00511     if (travelling) {
    00512       new_time = time_taken;
    00513     } else {
    00514       new_time = time_loading;
    00515     }
    00516
    00517     /* Check for too large a difference from expected time, and if so don't average. */
    00518     if (!(new_time > (int32)timetabled * 2 || new_time < (int32)timetabled / 2)) {
    00519       int arrival_error = timetabled - new_time;
    00520       /* Compute running average, with sign conversion to avoid negative overflow */
    00521       new_time = ((int32)timetabled * 4 + new_time + 2) / 5;
    00522       /* Use arrival_error to finetune order ticks. */
    00523       if (arrival_error < 0) new_time++;
    00524       if (arrival_error > 0) new_time--;
    00525     } else if (new_time > (int32)timetabled * 10 && travelling) {
    00526       /* Possible jam, clear time and restart timetable for all vehicles.
    00527        * Otherwise we risk trains blocking 1-lane stations for long times. */
    00528       ChangeTimetable(v, v->cur_real_order_index, 0, travelling);
    00529       for (Vehicle *v2 = v->FirstShared(); v2 != NULL; v2 = v2->NextShared()) {
    00530         if (_settings_game.order.timetable_separation) v2->ClearSeparation();
    00531         ClrBit(v2->vehicle_flags, VF_TIMETABLE_STARTED);
    00532         SetWindowDirty(WC_VEHICLE_TIMETABLE, v2->index);
    00533       }
    00534       return;
    00535     }
    00536
    00537     if (new_time < 1) new_time = 1;
    00538     if (new_time != (int32)timetabled)
    00539       ChangeTimetable(v, v->cur_real_order_index, new_time, travelling);
    00540   } else if (timetabled == 0 && HasBit(v->vehicle_flags, VF_AUTOMATE_TIMETABLE)) {
    00541     /* Add times for orders that are not yet timetabled, even while not autofilling */
    00542     if (travelling)
    00543       ChangeTimetable(v, v->cur_real_order_index, time_taken, travelling);
    00544     else
    00545       ChangeTimetable(v, v->cur_real_order_index, time_loading, travelling);
    00546   }
    00547
    00548   /* Vehicles will wait at stations if they arrive early even if they are not
    00549    * timetabled to wait there, so make sure the lateness counter is updated
    00550    * when this happens. */
    00551   if (timetabled == 0 && (travelling || v->lateness_counter >= 0)) return;
    00552
    00553   if (_settings_game.order.timetable_separation && HasBit(v->vehicle_flags, VF_TIMETABLE_STARTED)) { //TODO: Adjust to allow for separation per group. (move separation setting away from advanced settings)
    00554     v->current_order_time = time_taken;
    00555     v->current_loading_time = time_loading;
    00556     UpdateSeparationOrder(v);
    00557     v->current_order_time = 0;
    00558     v->current_loading_time = 0;
    00559   } else {
    00560     v->lateness_counter -= (timetabled - time_taken);
    00561   }
    00562
    00563   /* When we are more late than this timetabled bit takes we (somewhat expensively)
    00564    * check how many ticks the (fully filled) timetable has. If a timetable cycle is
    00565    * shorter than the amount of ticks we are late we reduce the lateness by the
    00566    * length of a full cycle till lateness is less than the length of a timetable
    00567    * cycle. When the timetable isn't fully filled the cycle will be INVALID_TICKS. */
    00568   if (v->lateness_counter > (int)timetabled) {
    00569     Ticks cycle = v->orders.list->GetTimetableTotalDuration();
    00570     if (cycle != INVALID_TICKS && v->lateness_counter > cycle) {
    00571       v->lateness_counter %= cycle;
    00572     }
    00573   }
    00574
    00575   for (v = v->FirstShared(); v != NULL; v = v->NextShared()) {
    00576     SetWindowDirty(WC_VEHICLE_TIMETABLE, v->index);
    00577   }
    00578 }