timetable_cmd.cpp

Go to the documentation of this file.
00001 /* $Id: timetable_cmd.cpp 25617 2013-07-17 18:37:13Z rubidium $ */
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 "cmd_helper.h"
00019 #include "core/sort_func.hpp"
00020 
00021 #include "table/strings.h"
00022 
00030 static void ChangeTimetable(Vehicle *v, VehicleOrderID order_number, uint16 val, ModifyTimetableFlags mtf)
00031 {
00032   Order *order = v->GetOrder(order_number);
00033   int delta = 0;
00034 
00035   switch (mtf) {
00036     case MTF_WAIT_TIME:
00037       delta = val - order->wait_time;
00038       order->wait_time = val;
00039       break;
00040 
00041     case MTF_TRAVEL_TIME:
00042       delta = val - order->travel_time;
00043       order->travel_time = val;
00044       break;
00045 
00046     case MTF_TRAVEL_SPEED:
00047       order->max_speed = val;
00048       break;
00049 
00050     default:
00051       NOT_REACHED();
00052   }
00053   v->orders.list->UpdateOrderTimetable(delta);
00054 
00055   for (v = v->FirstShared(); v != NULL; v = v->NextShared()) {
00056     if (v->cur_real_order_index == order_number && v->current_order.Equals(*order)) {
00057       switch (mtf) {
00058         case MTF_WAIT_TIME:
00059           v->current_order.wait_time = val;
00060           break;
00061 
00062         case MTF_TRAVEL_TIME:
00063           v->current_order.travel_time = val;
00064           break;
00065 
00066         case MTF_TRAVEL_SPEED:
00067           v->current_order.max_speed = val;
00068           break;
00069 
00070         default:
00071           NOT_REACHED();
00072       }
00073     }
00074     SetWindowDirty(WC_VEHICLE_TIMETABLE, v->index);
00075   }
00076 }
00077 
00091 CommandCost CmdChangeTimetable(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text)
00092 {
00093   VehicleID veh = GB(p1, 0, 20);
00094 
00095   Vehicle *v = Vehicle::GetIfValid(veh);
00096   if (v == NULL || !v->IsPrimaryVehicle()) return CMD_ERROR;
00097 
00098   CommandCost ret = CheckOwnership(v->owner);
00099   if (ret.Failed()) return ret;
00100 
00101   VehicleOrderID order_number = GB(p1, 20, 8);
00102   Order *order = v->GetOrder(order_number);
00103   if (order == NULL || order->IsType(OT_IMPLICIT)) return CMD_ERROR;
00104 
00105   ModifyTimetableFlags mtf = Extract<ModifyTimetableFlags, 28, 2>(p1);
00106   if (mtf >= MTF_END) return CMD_ERROR;
00107 
00108   int wait_time   = order->wait_time;
00109   int travel_time = order->travel_time;
00110   int max_speed   = order->max_speed;
00111   switch (mtf) {
00112     case MTF_WAIT_TIME:
00113       wait_time = GB(p2, 0, 16);
00114       break;
00115 
00116     case MTF_TRAVEL_TIME:
00117       travel_time = GB(p2, 0, 16);
00118       break;
00119 
00120     case MTF_TRAVEL_SPEED:
00121       max_speed = GB(p2, 0, 16);
00122       if (max_speed == 0) max_speed = UINT16_MAX; // Disable speed limit.
00123       break;
00124 
00125     default:
00126       NOT_REACHED();
00127   }
00128 
00129   if (wait_time != order->wait_time) {
00130     switch (order->GetType()) {
00131       case OT_GOTO_STATION:
00132         if (order->GetNonStopType() & ONSF_NO_STOP_AT_DESTINATION_STATION) return_cmd_error(STR_ERROR_TIMETABLE_NOT_STOPPING_HERE);
00133         break;
00134 
00135       case OT_CONDITIONAL:
00136         break;
00137 
00138       default: return_cmd_error(STR_ERROR_TIMETABLE_ONLY_WAIT_AT_STATIONS);
00139     }
00140   }
00141 
00142   if (travel_time != order->travel_time && order->IsType(OT_CONDITIONAL)) return CMD_ERROR;
00143   if (max_speed != order->max_speed && (order->IsType(OT_CONDITIONAL) || v->type == VEH_AIRCRAFT)) return CMD_ERROR;
00144 
00145   if (flags & DC_EXEC) {
00146     if (wait_time   != order->wait_time)   ChangeTimetable(v, order_number, wait_time,   MTF_WAIT_TIME);
00147     if (travel_time != order->travel_time) ChangeTimetable(v, order_number, travel_time, MTF_TRAVEL_TIME);
00148     if (max_speed   != order->max_speed)   ChangeTimetable(v, order_number, max_speed,   MTF_TRAVEL_SPEED);
00149   }
00150 
00151   return CommandCost();
00152 }
00153 
00164 CommandCost CmdSetVehicleOnTime(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text)
00165 {
00166   VehicleID veh = GB(p1, 0, 20);
00167 
00168   Vehicle *v = Vehicle::GetIfValid(veh);
00169   if (v == NULL || !v->IsPrimaryVehicle() || v->orders.list == NULL) return CMD_ERROR;
00170 
00171   CommandCost ret = CheckOwnership(v->owner);
00172   if (ret.Failed()) return ret;
00173 
00174   if (flags & DC_EXEC) {
00175     v->lateness_counter = 0;
00176     SetWindowDirty(WC_VEHICLE_TIMETABLE, v->index);
00177   }
00178 
00179   return CommandCost();
00180 }
00181 
00190 static int CDECL VehicleTimetableSorter(Vehicle * const *ap, Vehicle * const *bp)
00191 {
00192   const Vehicle *a = *ap;
00193   const Vehicle *b = *bp;
00194 
00195   VehicleOrderID a_order = a->cur_real_order_index;
00196   VehicleOrderID b_order = b->cur_real_order_index;
00197   int j = (int)b_order - (int)a_order;
00198 
00199   /* Are we currently at an ordered station (un)loading? */
00200   bool a_load = a->current_order.IsType(OT_LOADING) && a->current_order.GetNonStopType() != ONSF_STOP_EVERYWHERE;
00201   bool b_load = b->current_order.IsType(OT_LOADING) && b->current_order.GetNonStopType() != ONSF_STOP_EVERYWHERE;
00202 
00203   /* If the current order is not loading at the ordered station, decrease the order index by one since we have
00204    * not yet arrived at the station (and thus the timetable entry; still in the travelling of the previous one).
00205    * Since the ?_order variables are unsigned the -1 will flow under and place the vehicles going to order #0 at
00206    * the begin of the list with vehicles arriving at #0. */
00207   if (!a_load) a_order--;
00208   if (!b_load) b_order--;
00209 
00210   /* First check the order index that accounted for loading, then just the raw one. */
00211   int i = (int)b_order - (int)a_order;
00212   if (i != 0) return i;
00213   if (j != 0) return j;
00214 
00215   /* Look at the time we spent in this order; the higher, the closer to its destination. */
00216   i = b->current_order_time - a->current_order_time;
00217   if (i != 0) return i;
00218 
00219   /* If all else is equal, use some unique index to sort it the same way. */
00220   return b->unitnumber - a->unitnumber;
00221 }
00222 
00234 CommandCost CmdSetTimetableStart(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text)
00235 {
00236   bool timetable_all = HasBit(p1, 20);
00237   Vehicle *v = Vehicle::GetIfValid(GB(p1, 0, 20));
00238   if (v == NULL || !v->IsPrimaryVehicle() || v->orders.list == NULL) return CMD_ERROR;
00239 
00240   CommandCost ret = CheckOwnership(v->owner);
00241   if (ret.Failed()) return ret;
00242 
00243   /* Don't let a timetable start more than 15 years into the future or 1 year in the past. */
00244   Date start_date = (Date)p2;
00245   if (start_date < 0 || start_date > MAX_DAY) return CMD_ERROR;
00246   if (start_date - _date > 15 * DAYS_IN_LEAP_YEAR) return CMD_ERROR;
00247   if (_date - start_date > DAYS_IN_LEAP_YEAR) return CMD_ERROR;
00248   if (timetable_all && !v->orders.list->IsCompleteTimetable()) return CMD_ERROR;
00249 
00250   if (flags & DC_EXEC) {
00251     SmallVector<Vehicle *, 8> vehs;
00252 
00253     if (timetable_all) {
00254       for (Vehicle *w = v->orders.list->GetFirstSharedVehicle(); w != NULL; w = w->NextShared()) {
00255         *vehs.Append() = w;
00256       }
00257     } else {
00258       *vehs.Append() = v;
00259     }
00260 
00261     int total_duration = v->orders.list->GetTimetableTotalDuration();
00262     int num_vehs = vehs.Length();
00263 
00264     if (num_vehs >= 2) {
00265       QSortT(vehs.Begin(), vehs.Length(), &VehicleTimetableSorter);
00266     }
00267 
00268     int base = vehs.FindIndex(v);
00269 
00270     for (Vehicle **viter = vehs.Begin(); viter != vehs.End(); viter++) {
00271       int idx = (viter - vehs.Begin()) - base;
00272       Vehicle *w = *viter;
00273 
00274       w->lateness_counter = 0;
00275       ClrBit(w->vehicle_flags, VF_TIMETABLE_STARTED);
00276       /* Do multiplication, then division to reduce rounding errors. */
00277       w->timetable_start = start_date + idx * total_duration / num_vehs / DAY_TICKS;
00278       SetWindowDirty(WC_VEHICLE_TIMETABLE, w->index);
00279     }
00280 
00281   }
00282 
00283   return CommandCost();
00284 }
00285 
00286 
00300 CommandCost CmdAutofillTimetable(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text)
00301 {
00302   VehicleID veh = GB(p1, 0, 20);
00303 
00304   Vehicle *v = Vehicle::GetIfValid(veh);
00305   if (v == NULL || !v->IsPrimaryVehicle() || v->orders.list == NULL) return CMD_ERROR;
00306 
00307   CommandCost ret = CheckOwnership(v->owner);
00308   if (ret.Failed()) return ret;
00309 
00310   if (flags & DC_EXEC) {
00311     if (HasBit(p2, 0)) {
00312       /* Start autofilling the timetable, which clears the
00313        * "timetable has started" bit. Times are not cleared anymore, but are
00314        * overwritten when the order is reached now. */
00315       SetBit(v->vehicle_flags, VF_AUTOFILL_TIMETABLE);
00316       ClrBit(v->vehicle_flags, VF_TIMETABLE_STARTED);
00317 
00318       /* Overwrite waiting times only if they got longer */
00319       if (HasBit(p2, 1)) SetBit(v->vehicle_flags, VF_AUTOFILL_PRES_WAIT_TIME);
00320 
00321       v->timetable_start = 0;
00322       v->lateness_counter = 0;
00323     } else {
00324       ClrBit(v->vehicle_flags, VF_AUTOFILL_TIMETABLE);
00325       ClrBit(v->vehicle_flags, VF_AUTOFILL_PRES_WAIT_TIME);
00326     }
00327 
00328     for (Vehicle *v2 = v->FirstShared(); v2 != NULL; v2 = v2->NextShared()) {
00329       if (v2 != v) {
00330         /* Stop autofilling; only one vehicle at a time can perform autofill */
00331         ClrBit(v2->vehicle_flags, VF_AUTOFILL_TIMETABLE);
00332         ClrBit(v2->vehicle_flags, VF_AUTOFILL_PRES_WAIT_TIME);
00333       }
00334       SetWindowDirty(WC_VEHICLE_TIMETABLE, v2->index);
00335     }
00336   }
00337 
00338   return CommandCost();
00339 }
00340 
00346 void UpdateVehicleTimetable(Vehicle *v, bool travelling)
00347 {
00348   uint timetabled = travelling ? v->current_order.travel_time : v->current_order.wait_time;
00349   uint time_taken = v->current_order_time;
00350 
00351   v->current_order_time = 0;
00352 
00353   if (v->current_order.IsType(OT_IMPLICIT)) return; // no timetabling of auto orders
00354 
00355   VehicleOrderID first_manual_order = 0;
00356   for (Order *o = v->GetFirstOrder(); o != NULL && o->IsType(OT_IMPLICIT); o = o->next) {
00357     ++first_manual_order;
00358   }
00359 
00360   bool just_started = false;
00361 
00362   /* This vehicle is arriving at the first destination in the timetable. */
00363   if (v->cur_real_order_index == first_manual_order && travelling) {
00364     /* If the start date hasn't been set, or it was set automatically when
00365      * the vehicle last arrived at the first destination, update it to the
00366      * current time. Otherwise set the late counter appropriately to when
00367      * the vehicle should have arrived. */
00368     just_started = !HasBit(v->vehicle_flags, VF_TIMETABLE_STARTED);
00369 
00370     if (v->timetable_start != 0) {
00371       v->lateness_counter = (_date - v->timetable_start) * DAY_TICKS + _date_fract;
00372       v->timetable_start = 0;
00373     }
00374 
00375     SetBit(v->vehicle_flags, VF_TIMETABLE_STARTED);
00376     SetWindowDirty(WC_VEHICLE_TIMETABLE, v->index);
00377   }
00378 
00379   if (!HasBit(v->vehicle_flags, VF_TIMETABLE_STARTED)) return;
00380 
00381   if (HasBit(v->vehicle_flags, VF_AUTOFILL_TIMETABLE)) {
00382     if (travelling && !HasBit(v->vehicle_flags, VF_AUTOFILL_PRES_WAIT_TIME)) {
00383       /* Need to clear that now as otherwise we are not able to reduce the wait time */
00384       v->current_order.wait_time = 0;
00385     }
00386 
00387     if (just_started) return;
00388 
00389     /* Modify station waiting time only if our new value is larger (this is
00390      * always the case when we cleared the timetable). */
00391     if (!v->current_order.IsType(OT_CONDITIONAL) && (travelling || time_taken > v->current_order.wait_time)) {
00392       /* Round the time taken up to the nearest day, as this will avoid
00393        * confusion for people who are timetabling in days, and can be
00394        * adjusted later by people who aren't.
00395        * For trains/aircraft multiple movement cycles are done in one
00396        * tick. This makes it possible to leave the station and process
00397        * e.g. a depot order in the same tick, causing it to not fill
00398        * the timetable entry like is done for road vehicles/ships.
00399        * Thus always make sure at least one tick is used between the
00400        * processing of different orders when filling the timetable. */
00401       time_taken = CeilDiv(max(time_taken, 1U), DAY_TICKS) * DAY_TICKS;
00402 
00403       ChangeTimetable(v, v->cur_real_order_index, time_taken, travelling ? MTF_TRAVEL_TIME : MTF_WAIT_TIME);
00404     }
00405 
00406     if (v->cur_real_order_index == first_manual_order && travelling) {
00407       /* If we just started we would have returned earlier and have not reached
00408        * this code. So obviously, we have completed our round: So turn autofill
00409        * off again. */
00410       ClrBit(v->vehicle_flags, VF_AUTOFILL_TIMETABLE);
00411       ClrBit(v->vehicle_flags, VF_AUTOFILL_PRES_WAIT_TIME);
00412     }
00413     return;
00414   }
00415 
00416   if (just_started) return;
00417 
00418   /* Vehicles will wait at stations if they arrive early even if they are not
00419    * timetabled to wait there, so make sure the lateness counter is updated
00420    * when this happens. */
00421   if (timetabled == 0 && (travelling || v->lateness_counter >= 0)) return;
00422 
00423   v->lateness_counter -= (timetabled - time_taken);
00424 
00425   /* When we are more late than this timetabled bit takes we (somewhat expensively)
00426    * check how many ticks the (fully filled) timetable has. If a timetable cycle is
00427    * shorter than the amount of ticks we are late we reduce the lateness by the
00428    * length of a full cycle till lateness is less than the length of a timetable
00429    * cycle. When the timetable isn't fully filled the cycle will be INVALID_TICKS. */
00430   if (v->lateness_counter > (int)timetabled) {
00431     Ticks cycle = v->orders.list->GetTimetableTotalDuration();
00432     if (cycle != INVALID_TICKS && v->lateness_counter > cycle) {
00433       v->lateness_counter %= cycle;
00434     }
00435   }
00436 
00437   for (v = v->FirstShared(); v != NULL; v = v->NextShared()) {
00438     SetWindowDirty(WC_VEHICLE_TIMETABLE, v->index);
00439   }
00440 }