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 }