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 "cmd_helper.h"
00019 
00020 #include "table/strings.h"
00021 
00029 static void ChangeTimetable(Vehicle *v, VehicleOrderID order_number, uint16 val, ModifyTimetableFlags mtf)
00030 {
00031   Order *order = v->GetOrder(order_number);
00032   int delta = 0;
00033 
00034   switch (mtf) {
00035     case MTF_WAIT_TIME:
00036       delta = val - order->wait_time;
00037       order->wait_time = val;
00038       break;
00039 
00040     case MTF_TRAVEL_TIME:
00041       delta = val - order->travel_time;
00042       order->travel_time = val;
00043       break;
00044 
00045     case MTF_TRAVEL_SPEED:
00046       order->max_speed = val;
00047       break;
00048 
00049     default:
00050       NOT_REACHED();
00051   }
00052   v->orders.list->UpdateOrderTimetable(delta);
00053 
00054   for (v = v->FirstShared(); v != NULL; v = v->NextShared()) {
00055     if (v->cur_real_order_index == order_number && v->current_order.Equals(*order)) {
00056       switch (mtf) {
00057         case MTF_WAIT_TIME:
00058           v->current_order.wait_time = val;
00059           break;
00060 
00061         case MTF_TRAVEL_TIME:
00062           v->current_order.travel_time = val;
00063           break;
00064 
00065         case MTF_TRAVEL_SPEED:
00066           v->current_order.max_speed = val;
00067           break;
00068 
00069         default:
00070           NOT_REACHED();
00071       }
00072     }
00073     SetWindowDirty(WC_VEHICLE_TIMETABLE, v->index);
00074   }
00075 }
00076 
00090 CommandCost CmdChangeTimetable(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text)
00091 {
00092   VehicleID veh = GB(p1, 0, 20);
00093 
00094   Vehicle *v = Vehicle::GetIfValid(veh);
00095   if (v == NULL || !v->IsPrimaryVehicle()) return CMD_ERROR;
00096 
00097   CommandCost ret = CheckOwnership(v->owner);
00098   if (ret.Failed()) return ret;
00099 
00100   VehicleOrderID order_number = GB(p1, 20, 8);
00101   Order *order = v->GetOrder(order_number);
00102   if (order == NULL || order->IsType(OT_IMPLICIT)) return CMD_ERROR;
00103 
00104   ModifyTimetableFlags mtf = Extract<ModifyTimetableFlags, 28, 2>(p1);
00105   if (mtf >= MTF_END) return CMD_ERROR;
00106 
00107   int wait_time   = order->wait_time;
00108   int travel_time = order->travel_time;
00109   int max_speed   = order->max_speed;
00110   switch (mtf) {
00111     case MTF_WAIT_TIME:
00112       wait_time = GB(p2, 0, 16);
00113       break;
00114 
00115     case MTF_TRAVEL_TIME:
00116       travel_time = GB(p2, 0, 16);
00117       break;
00118 
00119     case MTF_TRAVEL_SPEED:
00120       max_speed = GB(p2, 0, 16);
00121       if (max_speed == 0) max_speed = UINT16_MAX; // Disable speed limit.
00122       break;
00123 
00124     default:
00125       NOT_REACHED();
00126   }
00127 
00128   if (wait_time != order->wait_time) {
00129     switch (order->GetType()) {
00130       case OT_GOTO_STATION:
00131         if (order->GetNonStopType() & ONSF_NO_STOP_AT_DESTINATION_STATION) return_cmd_error(STR_ERROR_TIMETABLE_NOT_STOPPING_HERE);
00132         break;
00133 
00134       case OT_CONDITIONAL:
00135         break;
00136 
00137       default: return_cmd_error(STR_ERROR_TIMETABLE_ONLY_WAIT_AT_STATIONS);
00138     }
00139   }
00140 
00141   if (travel_time != order->travel_time && order->IsType(OT_CONDITIONAL)) return CMD_ERROR;
00142   if (max_speed != order->max_speed && (order->IsType(OT_CONDITIONAL) || v->type == VEH_AIRCRAFT)) return CMD_ERROR;
00143 
00144   if (flags & DC_EXEC) {
00145     if (wait_time   != order->wait_time)   ChangeTimetable(v, order_number, wait_time,   MTF_WAIT_TIME);
00146     if (travel_time != order->travel_time) ChangeTimetable(v, order_number, travel_time, MTF_TRAVEL_TIME);
00147     if (max_speed   != order->max_speed)   ChangeTimetable(v, order_number, max_speed,   MTF_TRAVEL_SPEED);
00148   }
00149 
00150   return CommandCost();
00151 }
00152 
00163 CommandCost CmdSetVehicleOnTime(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text)
00164 {
00165   VehicleID veh = GB(p1, 0, 20);
00166 
00167   Vehicle *v = Vehicle::GetIfValid(veh);
00168   if (v == NULL || !v->IsPrimaryVehicle()) return CMD_ERROR;
00169 
00170   CommandCost ret = CheckOwnership(v->owner);
00171   if (ret.Failed()) return ret;
00172 
00173   if (flags & DC_EXEC) {
00174     v->lateness_counter = 0;
00175     SetWindowDirty(WC_VEHICLE_TIMETABLE, v->index);
00176   }
00177 
00178   return CommandCost();
00179 }
00180 
00190 CommandCost CmdSetTimetableStart(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text)
00191 {
00192   Vehicle *v = Vehicle::GetIfValid(GB(p1, 0, 20));
00193   if (v == NULL || !v->IsPrimaryVehicle()) return CMD_ERROR;
00194 
00195   CommandCost ret = CheckOwnership(v->owner);
00196   if (ret.Failed()) return ret;
00197 
00198   /* Don't let a timetable start more than 15 years into the future or 1 year in the past. */
00199   Date start_date = (Date)p2;
00200   if (start_date < 0 || start_date > MAX_DAY) return CMD_ERROR;
00201   if (start_date - _date > 15 * DAYS_IN_LEAP_YEAR) return CMD_ERROR;
00202   if (_date - start_date > DAYS_IN_LEAP_YEAR) return CMD_ERROR;
00203 
00204   if (flags & DC_EXEC) {
00205     v->lateness_counter = 0;
00206     ClrBit(v->vehicle_flags, VF_TIMETABLE_STARTED);
00207     v->timetable_start = start_date;
00208 
00209     SetWindowDirty(WC_VEHICLE_TIMETABLE, v->index);
00210   }
00211 
00212   return CommandCost();
00213 }
00214 
00215 
00229 CommandCost CmdAutofillTimetable(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text)
00230 {
00231   VehicleID veh = GB(p1, 0, 20);
00232 
00233   Vehicle *v = Vehicle::GetIfValid(veh);
00234   if (v == NULL || !v->IsPrimaryVehicle()) return CMD_ERROR;
00235 
00236   CommandCost ret = CheckOwnership(v->owner);
00237   if (ret.Failed()) return ret;
00238 
00239   if (flags & DC_EXEC) {
00240     if (HasBit(p2, 0)) {
00241       /* Start autofilling the timetable, which clears the
00242        * "timetable has started" bit. Times are not cleared anymore, but are
00243        * overwritten when the order is reached now. */
00244       SetBit(v->vehicle_flags, VF_AUTOFILL_TIMETABLE);
00245       ClrBit(v->vehicle_flags, VF_TIMETABLE_STARTED);
00246 
00247       /* Overwrite waiting times only if they got longer */
00248       if (HasBit(p2, 1)) SetBit(v->vehicle_flags, VF_AUTOFILL_PRES_WAIT_TIME);
00249 
00250       v->timetable_start = 0;
00251       v->lateness_counter = 0;
00252     } else {
00253       ClrBit(v->vehicle_flags, VF_AUTOFILL_TIMETABLE);
00254       ClrBit(v->vehicle_flags, VF_AUTOFILL_PRES_WAIT_TIME);
00255     }
00256 
00257     for (Vehicle *v2 = v->FirstShared(); v2 != NULL; v2 = v2->NextShared()) {
00258       if (v2 != v) {
00259         /* Stop autofilling; only one vehicle at a time can perform autofill */
00260         ClrBit(v2->vehicle_flags, VF_AUTOFILL_TIMETABLE);
00261         ClrBit(v2->vehicle_flags, VF_AUTOFILL_PRES_WAIT_TIME);
00262       }
00263       SetWindowDirty(WC_VEHICLE_TIMETABLE, v2->index);
00264     }
00265   }
00266 
00267   return CommandCost();
00268 }
00269 
00275 void UpdateVehicleTimetable(Vehicle *v, bool travelling)
00276 {
00277   uint timetabled = travelling ? v->current_order.travel_time : v->current_order.wait_time;
00278   uint time_taken = v->current_order_time;
00279 
00280   v->current_order_time = 0;
00281 
00282   if (v->current_order.IsType(OT_IMPLICIT)) return; // no timetabling of auto orders
00283 
00284   VehicleOrderID first_manual_order = 0;
00285   for (Order *o = v->GetFirstOrder(); o != NULL && o->IsType(OT_IMPLICIT); o = o->next) {
00286     ++first_manual_order;
00287   }
00288 
00289   bool just_started = false;
00290 
00291   /* This vehicle is arriving at the first destination in the timetable. */
00292   if (v->cur_real_order_index == first_manual_order && travelling) {
00293     /* If the start date hasn't been set, or it was set automatically when
00294      * the vehicle last arrived at the first destination, update it to the
00295      * current time. Otherwise set the late counter appropriately to when
00296      * the vehicle should have arrived. */
00297     just_started = !HasBit(v->vehicle_flags, VF_TIMETABLE_STARTED);
00298 
00299     if (v->timetable_start != 0) {
00300       v->lateness_counter = (_date - v->timetable_start) * DAY_TICKS + _date_fract;
00301       v->timetable_start = 0;
00302     }
00303 
00304     SetBit(v->vehicle_flags, VF_TIMETABLE_STARTED);
00305     SetWindowDirty(WC_VEHICLE_TIMETABLE, v->index);
00306   }
00307 
00308   if (!HasBit(v->vehicle_flags, VF_TIMETABLE_STARTED)) return;
00309 
00310   if (HasBit(v->vehicle_flags, VF_AUTOFILL_TIMETABLE)) {
00311     if (travelling && !HasBit(v->vehicle_flags, VF_AUTOFILL_PRES_WAIT_TIME)) {
00312       /* Need to clear that now as otherwise we are not able to reduce the wait time */
00313       v->current_order.wait_time = 0;
00314     }
00315 
00316     if (just_started) return;
00317 
00318     /* Modify station waiting time only if our new value is larger (this is
00319      * always the case when we cleared the timetable). */
00320     if (!v->current_order.IsType(OT_CONDITIONAL) && (travelling || time_taken > v->current_order.wait_time)) {
00321       /* Round the time taken up to the nearest day, as this will avoid
00322        * confusion for people who are timetabling in days, and can be
00323        * adjusted later by people who aren't.
00324        * For trains/aircraft multiple movement cycles are done in one
00325        * tick. This makes it possible to leave the station and process
00326        * e.g. a depot order in the same tick, causing it to not fill
00327        * the timetable entry like is done for road vehicles/ships.
00328        * Thus always make sure at least one tick is used between the
00329        * processing of different orders when filling the timetable. */
00330       time_taken = CeilDiv(max(time_taken, 1U), DAY_TICKS) * DAY_TICKS;
00331 
00332       ChangeTimetable(v, v->cur_real_order_index, time_taken, travelling ? MTF_TRAVEL_TIME : MTF_WAIT_TIME);
00333     }
00334 
00335     if (v->cur_real_order_index == first_manual_order && travelling) {
00336       /* If we just started we would have returned earlier and have not reached
00337        * this code. So obviously, we have completed our round: So turn autofill
00338        * off again. */
00339       ClrBit(v->vehicle_flags, VF_AUTOFILL_TIMETABLE);
00340       ClrBit(v->vehicle_flags, VF_AUTOFILL_PRES_WAIT_TIME);
00341     }
00342     return;
00343   }
00344 
00345   if (just_started) return;
00346 
00347   /* Vehicles will wait at stations if they arrive early even if they are not
00348    * timetabled to wait there, so make sure the lateness counter is updated
00349    * when this happens. */
00350   if (timetabled == 0 && (travelling || v->lateness_counter >= 0)) return;
00351 
00352   v->lateness_counter -= (timetabled - time_taken);
00353 
00354   /* When we are more late than this timetabled bit takes we (somewhat expensively)
00355    * check how many ticks the (fully filled) timetable has. If a timetable cycle is
00356    * shorter than the amount of ticks we are late we reduce the lateness by the
00357    * length of a full cycle till lateness is less than the length of a timetable
00358    * cycle. When the timetable isn't fully filled the cycle will be INVALID_TICKS. */
00359   if (v->lateness_counter > (int)timetabled) {
00360     Ticks cycle = v->orders.list->GetTimetableTotalDuration();
00361     if (cycle != INVALID_TICKS && v->lateness_counter > cycle) {
00362       v->lateness_counter %= cycle;
00363     }
00364   }
00365 
00366   for (v = v->FirstShared(); v != NULL; v = v->NextShared()) {
00367     SetWindowDirty(WC_VEHICLE_TIMETABLE, v->index);
00368   }
00369 }