4 * This file is part of OpenTTD.
5 * 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.
6 * 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.
7 * 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/>.
10 /** @file vehicle.cpp Base implementations of all vehicles. */
15 #include "articulated_vehicles.h"
16 #include "autoreplace_func.h"
17 #include "autoreplace_gui.h"
18 #include "bridge_map.h"
19 #include "command_func.h"
20 #include "company_func.h"
21 #include "date_func.h"
22 #include "depot_base.h"
23 #include "depot_func.h"
24 #include "depot_map.h"
25 #include "economy_base.h"
26 #include "effectvehicle_base.h"
27 #include "effectvehicle_func.h"
30 #include "group_gui.h"
31 #include "newgrf_debug.h"
32 #include "newgrf_sound.h"
33 #include "newgrf_station.h"
34 #include "news_func.h"
35 #include "order_backup.h"
36 #include "roadstop_base.h"
39 #include "sound_func.h"
40 #include "spritecache.h"
41 #include "station_base.h"
42 #include "strings_func.h"
43 #include "tbtr_template_vehicle_func.h"
44 #include "timetable.h"
45 #include "tracerestrict.h"
47 #include "tunnel_map.h"
48 #include "vehicle_func.h"
49 #include "vehiclelist.h"
50 #include "viewport_func.h"
51 #include "zoom_func.h"
54 #include "blitter/factory.hpp"
55 #include "core/backup_type.hpp"
56 #include "core/pool_func.hpp"
57 #include "core/random_func.hpp"
58 #include "linkgraph/linkgraph.h"
59 #include "linkgraph/refresh.h"
60 #include "network/network.h"
61 #include "table/strings.h"
65 #include "safeguards.h"
67 /* Number of bits in the hash to use from each vehicle coord */
68 static const uint GEN_HASHX_BITS
= 6;
69 static const uint GEN_HASHY_BITS
= 6;
71 /* Size of each hash bucket */
72 static const uint GEN_HASHX_BUCKET_BITS
= 7;
73 static const uint GEN_HASHY_BUCKET_BITS
= 6;
75 /* Compute hash for vehicle coord */
76 #define GEN_HASHX(x) GB((x), GEN_HASHX_BUCKET_BITS + ZOOM_LVL_SHIFT, GEN_HASHX_BITS)
77 #define GEN_HASHY(y) (GB((y), GEN_HASHY_BUCKET_BITS + ZOOM_LVL_SHIFT, GEN_HASHY_BITS) << GEN_HASHX_BITS)
78 #define GEN_HASH(x, y) (GEN_HASHY(y) + GEN_HASHX(x))
80 /* Maximum size until hash repeats */
81 static const int GEN_HASHX_SIZE
= 1 << (GEN_HASHX_BUCKET_BITS
+ GEN_HASHX_BITS
+ ZOOM_LVL_SHIFT
);
82 static const int GEN_HASHY_SIZE
= 1 << (GEN_HASHY_BUCKET_BITS
+ GEN_HASHY_BITS
+ ZOOM_LVL_SHIFT
);
84 /* Increments to reach next bucket in hash table */
85 static const int GEN_HASHX_INC
= 1;
86 static const int GEN_HASHY_INC
= 1 << GEN_HASHX_BITS
;
88 /* Mask to wrap-around buckets */
89 static const uint GEN_HASHX_MASK
= (1 << GEN_HASHX_BITS
) - 1;
90 static const uint GEN_HASHY_MASK
= ((1 << GEN_HASHY_BITS
) - 1) << GEN_HASHX_BITS
;
92 VehicleID _new_vehicle_id
;
93 uint16 _returned_refit_capacity
; ///< Stores the capacity after a refit operation.
94 uint16 _returned_mail_refit_capacity
; ///< Stores the mail capacity after a refit operation (Aircraft only).
97 /** The pool with all our precious vehicles. */
98 VehiclePool
_vehicle_pool("Vehicle");
99 INSTANTIATE_POOL_METHODS(Vehicle
)
103 * Determine shared bounds of all sprites.
104 * @param [out] bounds Shared bounds.
106 Rect16
VehicleSpriteSeq::GetBounds() const
109 bounds
.left
= bounds
.top
= bounds
.right
= bounds
.bottom
= 0;
111 for (uint i
= 0; i
< this->count
; ++i
) {
112 const Sprite
*spr
= GetSprite(this->seq
[i
].sprite
, ST_NORMAL
);
115 bounds
.left
= spr
->x_offs
;
116 bounds
.top
= spr
->y_offs
;
117 bounds
.right
= spr
->width
+ spr
->x_offs
- 1;
118 bounds
.bottom
= spr
->height
+ spr
->y_offs
- 1;
120 if (spr
->x_offs
< bounds
.left
) bounds
.left
= spr
->x_offs
;
121 if (spr
->y_offs
< bounds
.top
) bounds
.top
= spr
->y_offs
;
122 int right
= spr
->width
+ spr
->x_offs
- 1;
123 int bottom
= spr
->height
+ spr
->y_offs
- 1;
124 if (right
> bounds
.right
) bounds
.right
= right
;
125 if (bottom
> bounds
.bottom
) bounds
.bottom
= bottom
;
133 * Draw the sprite sequence.
134 * @param x X position
135 * @param y Y position
136 * @param default_pal Vehicle palette
137 * @param force_pal Whether to ignore individual palettes, and draw everything with \a default_pal.
139 void VehicleSpriteSeq::Draw(int x
, int y
, PaletteID default_pal
, bool force_pal
) const
141 for (uint i
= 0; i
< this->count
; ++i
) {
142 PaletteID pal
= force_pal
|| !this->seq
[i
].pal
? default_pal
: this->seq
[i
].pal
;
143 DrawSprite(this->seq
[i
].sprite
, pal
, x
, y
);
148 * Function to tell if a vehicle needs to be autorenewed
149 * @param *c The vehicle owner
150 * @param use_renew_setting Should the company renew setting be considered?
151 * @return true if the vehicle is old enough for replacement
153 bool Vehicle::NeedsAutorenewing(const Company
*c
, bool use_renew_setting
) const
155 /* We can always generate the Company pointer when we have the vehicle.
156 * However this takes time and since the Company pointer is often present
157 * when this function is called then it's faster to pass the pointer as an
158 * argument rather than finding it again. */
159 assert(c
== Company::Get(this->owner
));
161 if (use_renew_setting
&& !c
->settings
.engine_renew
) return false;
162 if (this->age
- this->max_age
< (c
->settings
.engine_renew_months
* 30)) return false;
164 /* Only engines need renewing */
165 if (this->type
== VEH_TRAIN
&& !Train::From(this)->IsEngine()) return false;
171 * Service a vehicle and all subsequent vehicles in the consist
173 * @param *v The vehicle or vehicle chain being serviced
175 void VehicleServiceInDepot(Vehicle
*v
)
177 assert(v
!= nullptr);
178 SetWindowDirty(WC_VEHICLE_DETAILS
, v
->index
); // ensure that last service date and reliability are updated
181 v
->date_of_last_service
= _date
;
182 v
->breakdowns_since_last_service
= 0;
183 v
->reliability
= v
->GetEngine()->reliability
;
184 /* Prevent vehicles from breaking down directly after exiting the depot. */
185 v
->breakdown_chance
= 0;
186 v
->breakdown_ctr
= 0;
188 } while (v
!= nullptr && v
->HasEngineType());
192 * Check if the vehicle needs to go to a depot in near future (if a opportunity presents itself) for service or replacement.
194 * @see NeedsAutomaticServicing()
195 * @return true if the vehicle should go to a depot if a opportunity presents itself.
197 bool Vehicle::NeedsServicing() const
199 /* Stopped or crashed vehicles will not move, as such making unmovable
200 * vehicles to go for service is lame. */
201 if (this->vehstatus
& (VS_STOPPED
| VS_CRASHED
)) return false;
203 /* Are we ready for the next service cycle? */
204 const Company
*c
= Company::Get(this->owner
);
206 if (this->ServiceIntervalIsPercent() ?
207 (this->reliability
>= this->GetEngine()->reliability
* (100 - this->GetServiceInterval()) / 100) :
208 (this->date_of_last_service
+ this->GetServiceInterval() >= _date
)) {
212 /* Do we even have any depots/hangars */
214 uint road_pieces
= 0;
216 for (unsigned int i
: c
->infrastructure
.rail
) rail_pices
+= i
;
218 for (uint i
= 0; i
< ROADSUBTYPE_END
; i
++) {
219 for (uint j
= 0; j
< ROADTYPE_END
; ++j
) {
220 road_pieces
+= c
->infrastructure
.road
[j
][i
];
224 if ((this->type
== VEH_TRAIN
&& rail_pices
== 0) ||
225 (this->type
== VEH_ROAD
&& road_pieces
== 0) ||
226 (this->type
== VEH_SHIP
&& c
->infrastructure
.water
== 0) ||
227 (this->type
== VEH_AIRCRAFT
&& c
->infrastructure
.airport
== 0)) {
231 /* If we're servicing anyway, because we have not disabled servicing when
232 * there are no breakdowns or we are playing with breakdowns, bail out. */
233 if (!_settings_game
.order
.no_servicing_if_no_breakdowns
||
234 _settings_game
.difficulty
.vehicle_breakdowns
!= 0) {
238 /* Is vehicle old and renewing is enabled */
239 if (this->NeedsAutorenewing(c
, true)) {
243 /* Test whether there is some pending autoreplace.
244 * Note: We do this after the service-interval test.
245 * There are a lot more reasons for autoreplace to fail than we can test here reasonably. */
246 bool pending_replace
= false;
247 Money needed_money
= c
->settings
.engine_renew_money
;
248 if (needed_money
> c
->money
) return false;
250 for (const Vehicle
*v
= this; v
!= nullptr; v
= (v
->type
== VEH_TRAIN
) ? Train::From(v
)->GetNextUnit() : nullptr) {
251 bool replace_when_old
= false;
252 EngineID new_engine
= EngineReplacementForCompany(c
, v
->engine_type
, v
->group_id
, &replace_when_old
);
254 /* Check engine availability */
255 if (new_engine
== INVALID_ENGINE
|| !HasBit(Engine::Get(new_engine
)->company_avail
, v
->owner
)) continue;
256 /* Is the vehicle old if we are not always replacing? */
257 if (replace_when_old
&& !v
->NeedsAutorenewing(c
, false)) continue;
259 /* Check refittability */
260 uint32 available_cargo_types
, union_mask
;
261 GetArticulatedRefitMasks(new_engine
, true, &union_mask
, &available_cargo_types
);
262 /* Is there anything to refit? */
263 if (union_mask
!= 0) {
265 /* We cannot refit to mixed cargoes in an automated way */
266 if (IsArticulatedVehicleCarryingDifferentCargoes(v
, &cargo_type
)) continue;
268 /* Did the old vehicle carry anything? */
269 if (cargo_type
!= CT_INVALID
) {
270 /* We can't refit the vehicle to carry the cargo we want */
271 if (!HasBit(available_cargo_types
, cargo_type
)) continue;
276 * We want 2*(the price of the new vehicle) without looking at the value of the vehicle we are going to sell. */
277 pending_replace
= true;
278 needed_money
+= 2 * Engine::Get(new_engine
)->GetCost();
279 if (needed_money
> c
->money
) return false;
282 return pending_replace
;
286 * Checks if the current order should be interrupted for a service-in-depot order.
287 * @see NeedsServicing()
288 * @return true if the current order should be interrupted.
290 bool Vehicle::NeedsAutomaticServicing() const
292 if (this->HasDepotOrder()) return false;
293 if (this->current_order
.IsType(OT_LOADING
)) return false;
294 if (this->current_order
.IsType(OT_GOTO_DEPOT
) && this->current_order
.GetDepotOrderType() != ODTFB_SERVICE
) return false;
296 return NeedsServicing();
299 uint
Vehicle::Crash(bool flooded
)
301 assert((this->vehstatus
& VS_CRASHED
) == 0);
302 assert(this->Previous() == nullptr); // IsPrimaryVehicle fails for free-wagon-chains
305 /* Stop the vehicle. */
306 if (this->IsPrimaryVehicle())
307 this->vehstatus
|= VS_STOPPED
;
308 /* crash all wagons, and count passengers */
309 for (Vehicle
*v
= this; v
!= nullptr; v
= v
->Next()) {
310 /* We do not transfer reserver cargo back, so TotalCount() instead of StoredCount() */
311 if (IsCargoInClass(v
->cargo_type
, CC_PASSENGERS
)) pass
+= v
->cargo
.TotalCount();
312 v
->vehstatus
|= VS_CRASHED
;
313 v
->MarkAllViewportsDirty();
316 /* Dirty some windows */
317 InvalidateWindowClassesData(GetWindowClassForVehicleType(this->type
), 0);
318 SetWindowWidgetDirty(WC_VEHICLE_VIEW
, this->index
, WID_VV_START_STOP
);
319 SetWindowDirty(WC_VEHICLE_DETAILS
, this->index
);
320 SetWindowDirty(WC_VEHICLE_DEPOT
, this->tile
);
322 delete this->cargo_payment
;
323 assert(this->cargo_payment
== nullptr); // cleared by ~CargoPayment
325 return RandomRange(pass
+ 1); // Randomise deceased passengers.
328 /** Marks the separation of this vehicle's order list invalid. */
329 void Vehicle::MarkSeparationInvalid()
331 if (this->orders
.list
!= nullptr) this->orders
.list
->MarkSeparationInvalid();
334 /** Sets new separation settings for this vehicle's shared orders. */
335 void Vehicle::SetSepSettings(TTSepMode Mode
, uint Parameter
)
337 if (this->orders
.list
!= nullptr) this->orders
.list
->SetSepSettings(Mode
, Parameter
);
341 * Get whether a the vehicle should be drawn (i.e. if it isn't hidden, or it is in a tunnel but being shown transparently)
342 * @return whether to show vehicle
344 bool Vehicle::IsDrawn() const
346 return !(HasBit(this->subtype
, GVSF_VIRTUAL
)) && (!(this->vehstatus
& VS_HIDDEN
) ||
347 (IsTransparencySet(TO_TUNNELS
) &&
348 ((this->type
== VEH_TRAIN
&& Train::From(this)->track
== TRACK_BIT_WORMHOLE
) ||
349 (this->type
== VEH_ROAD
&& RoadVehicle::From(this)->state
== RVSB_WORMHOLE
))));
353 * Displays a "NewGrf Bug" error message for a engine, and pauses the game if not networking.
354 * @param engine The engine that caused the problem
355 * @param part1 Part 1 of the error message, taking the grfname as parameter 1
356 * @param part2 Part 2 of the error message, taking the engine as parameter 2
357 * @param bug_type Flag to check and set in grfconfig
358 * @param critical Shall the "OpenTTD might crash"-message be shown when the player tries to unpause?
360 void ShowNewGrfVehicleError(EngineID engine
, StringID part1
, StringID part2
, GRFBugs bug_type
, bool critical
)
362 const Engine
*e
= Engine::Get(engine
);
363 GRFConfig
*grfconfig
= GetGRFConfig(e
->GetGRFID());
365 /* Missing GRF. Nothing useful can be done in this situation. */
366 if (grfconfig
== nullptr) return;
368 if (!HasBit(grfconfig
->grf_bugs
, bug_type
)) {
369 SetBit(grfconfig
->grf_bugs
, bug_type
);
370 SetDParamStr(0, grfconfig
->GetName());
371 SetDParam(1, engine
);
372 ShowErrorMessage(part1
, part2
, WL_CRITICAL
);
373 if (!_networking
) DoCommand(0, critical
? PM_PAUSED_ERROR
: PM_PAUSED_NORMAL
, 1, DC_EXEC
, CMD_PAUSE
);
379 SetDParamStr(0, grfconfig
->GetName());
380 GetString(buffer
, part1
, lastof(buffer
));
381 DEBUG(grf
, 0, "%s", buffer
+ 3);
383 SetDParam(1, engine
);
384 GetString(buffer
, part2
, lastof(buffer
));
385 DEBUG(grf
, 0, "%s", buffer
+ 3);
389 * Logs a bug in GRF and shows a warning message if this
390 * is for the first time this happened.
391 * @param u first vehicle of chain
393 void VehicleLengthChanged(const Vehicle
*u
)
395 /* show a warning once for each engine in whole game and once for each GRF after each game load */
396 const Engine
*engine
= u
->GetEngine();
397 uint32 grfid
= engine
->grf_prop
.grffile
->grfid
;
398 GRFConfig
*grfconfig
= GetGRFConfig(grfid
);
399 if (GamelogGRFBugReverse(grfid
, engine
->grf_prop
.local_id
) || !HasBit(grfconfig
->grf_bugs
, GBUG_VEH_LENGTH
)) {
400 ShowNewGrfVehicleError(u
->engine_type
, STR_NEWGRF_BROKEN
, STR_NEWGRF_BROKEN_VEHICLE_LENGTH
, GBUG_VEH_LENGTH
, true);
405 * Vehicle constructor.
406 * @param type Type of the new vehicle.
408 Vehicle::Vehicle(VehicleType type
)
411 this->coord
.left
= INVALID_COORD
;
412 this->group_id
= DEFAULT_GROUP
;
413 this->fill_percent_te_id
= INVALID_TE_ID
;
415 this->colourmap
= PAL_NONE
;
416 this->cargo_age_counter
= 1;
417 this->last_station_visited
= INVALID_STATION
;
418 this->last_loading_station
= INVALID_STATION
;
422 * Get a value for a vehicle's random_bits.
423 * @return A random value from 0 to 255.
425 byte
VehicleRandomBits()
427 return GB(Random(), 0, 8);
430 /* Size of the hash, 6 = 64 x 64, 7 = 128 x 128. Larger sizes will (in theory) reduce hash
431 * lookup times at the expense of memory usage. */
432 const int HASH_BITS
= 7;
433 const int HASH_SIZE
= 1 << HASH_BITS
;
434 const int HASH_MASK
= HASH_SIZE
- 1;
435 const int TOTAL_HASH_SIZE
= 1 << (HASH_BITS
* 2);
436 const int TOTAL_HASH_MASK
= TOTAL_HASH_SIZE
- 1;
438 /* Resolution of the hash, 0 = 1*1 tile, 1 = 2*2 tiles, 2 = 4*4 tiles, etc.
439 * Profiling results show that 0 is fastest. */
440 const int HASH_RES
= 0;
442 static Vehicle
*_vehicle_tile_hash
[TOTAL_HASH_SIZE
];
444 static Vehicle
*VehicleFromTileHash(int xl
, int yl
, int xu
, int yu
, void *data
, VehicleFromPosProc
*proc
, bool find_first
)
446 for (int y
= yl
; ; y
= (y
+ (1 << HASH_BITS
)) & (HASH_MASK
<< HASH_BITS
)) {
447 for (int x
= xl
; ; x
= (x
+ 1) & HASH_MASK
) {
448 Vehicle
*v
= _vehicle_tile_hash
[(x
+ y
) & TOTAL_HASH_MASK
];
449 for (; v
!= nullptr; v
= v
->hash_tile_next
) {
450 Vehicle
*a
= proc(v
, data
);
451 if (find_first
&& a
!= nullptr) return a
;
463 * Helper function for FindVehicleOnPos/HasVehicleOnPos.
464 * @note Do not call this function directly!
465 * @param x The X location on the map
466 * @param y The Y location on the map
467 * @param data Arbitrary data passed to proc
468 * @param proc The proc that determines whether a vehicle will be "found".
469 * @param find_first Whether to return on the first found or iterate over
471 * @return the best matching or first vehicle (depending on find_first).
473 static Vehicle
*VehicleFromPosXY(int x
, int y
, void *data
, VehicleFromPosProc
*proc
, bool find_first
)
475 const int COLL_DIST
= 6;
477 /* Hash area to scan is from xl,yl to xu,yu */
478 int xl
= GB((x
- COLL_DIST
) / TILE_SIZE
, HASH_RES
, HASH_BITS
);
479 int xu
= GB((x
+ COLL_DIST
) / TILE_SIZE
, HASH_RES
, HASH_BITS
);
480 int yl
= GB((y
- COLL_DIST
) / TILE_SIZE
, HASH_RES
, HASH_BITS
) << HASH_BITS
;
481 int yu
= GB((y
+ COLL_DIST
) / TILE_SIZE
, HASH_RES
, HASH_BITS
) << HASH_BITS
;
483 return VehicleFromTileHash(xl
, yl
, xu
, yu
, data
, proc
, find_first
);
487 * Find a vehicle from a specific location. It will call proc for ALL vehicles
488 * on the tile and YOU must make SURE that the "best one" is stored in the
489 * data value and is ALWAYS the same regardless of the order of the vehicles
490 * where proc was called on!
491 * When you fail to do this properly you create an almost untraceable DESYNC!
492 * @note The return value of proc will be ignored.
493 * @note Use this when you have the intention that all vehicles
494 * should be iterated over.
495 * @param x The X location on the map
496 * @param y The Y location on the map
497 * @param data Arbitrary data passed to proc
498 * @param proc The proc that determines whether a vehicle will be "found".
500 void FindVehicleOnPosXY(int x
, int y
, void *data
, VehicleFromPosProc
*proc
)
502 VehicleFromPosXY(x
, y
, data
, proc
, false);
506 * Checks whether a vehicle in on a specific location. It will call proc for
507 * vehicles until it returns non-nullptr.
508 * @note Use FindVehicleOnPosXY when you have the intention that all vehicles
509 * should be iterated over.
510 * @param x The X location on the map
511 * @param y The Y location on the map
512 * @param data Arbitrary data passed to proc
513 * @param proc The proc that determines whether a vehicle will be "found".
514 * @return True if proc returned non-nullptr.
516 bool HasVehicleOnPosXY(int x
, int y
, void *data
, VehicleFromPosProc
*proc
)
518 return VehicleFromPosXY(x
, y
, data
, proc
, true) != nullptr;
522 * Helper function for FindVehicleOnPos/HasVehicleOnPos.
523 * @note Do not call this function directly!
524 * @param tile The location on the map
525 * @param data Arbitrary data passed to \a proc.
526 * @param proc The proc that determines whether a vehicle will be "found".
527 * @param find_first Whether to return on the first found or iterate over
529 * @return the best matching or first vehicle (depending on find_first).
531 static Vehicle
*VehicleFromPos(TileIndex tile
, void *data
, VehicleFromPosProc
*proc
, bool find_first
)
533 int x
= GB(TileX(tile
), HASH_RES
, HASH_BITS
);
534 int y
= GB(TileY(tile
), HASH_RES
, HASH_BITS
) << HASH_BITS
;
536 Vehicle
*v
= _vehicle_tile_hash
[(x
+ y
) & TOTAL_HASH_MASK
];
537 for (; v
!= nullptr; v
= v
->hash_tile_next
) {
538 if (v
->tile
!= tile
) continue;
540 Vehicle
*a
= proc(v
, data
);
541 if (find_first
&& a
!= nullptr) return a
;
548 * Find a vehicle from a specific location. It will call \a proc for ALL vehicles
549 * on the tile and YOU must make SURE that the "best one" is stored in the
550 * data value and is ALWAYS the same regardless of the order of the vehicles
551 * where proc was called on!
552 * When you fail to do this properly you create an almost untraceable DESYNC!
553 * @note The return value of \a proc will be ignored.
554 * @note Use this function when you have the intention that all vehicles
555 * should be iterated over.
556 * @param tile The location on the map
557 * @param data Arbitrary data passed to \a proc.
558 * @param proc The proc that determines whether a vehicle will be "found".
560 void FindVehicleOnPos(TileIndex tile
, void *data
, VehicleFromPosProc
*proc
)
562 VehicleFromPos(tile
, data
, proc
, false);
566 * Checks whether a vehicle is on a specific location. It will call \a proc for
567 * vehicles until it returns non-nullptr.
568 * @note Use #FindVehicleOnPos when you have the intention that all vehicles
569 * should be iterated over.
570 * @param tile The location on the map
571 * @param data Arbitrary data passed to \a proc.
572 * @param proc The \a proc that determines whether a vehicle will be "found".
573 * @return True if proc returned non-nullptr.
575 bool HasVehicleOnPos(TileIndex tile
, void *data
, VehicleFromPosProc
*proc
)
577 return VehicleFromPos(tile
, data
, proc
, true) != nullptr;
581 * Callback that returns 'real' vehicles lower or at height \c *(int*)data .
582 * @param v Vehicle to examine.
583 * @param data Pointer to height data.
584 * @return \a v if conditions are met, else \c nullptr.
586 static Vehicle
*EnsureNoVehicleProcZ(Vehicle
*v
, void *data
)
590 if (v
->type
== VEH_DISASTER
|| (v
->type
== VEH_AIRCRAFT
&& v
->subtype
== AIR_SHADOW
)) return nullptr;
591 if (v
->z_pos
> z
) return nullptr;
597 * Ensure there is no vehicle at the ground at the given position.
598 * @param tile Position to examine.
599 * @return Succeeded command (ground is free) or failed command (a vehicle is found).
601 CommandCost
EnsureNoVehicleOnGround(TileIndex tile
)
603 int z
= GetTileMaxPixelZ(tile
);
605 /* Value v is not safe in MP games, however, it is used to generate a local
606 * error message only (which may be different for different machines).
607 * Such a message does not affect MP synchronisation.
609 Vehicle
*v
= VehicleFromPos(tile
, &z
, &EnsureNoVehicleProcZ
, true);
610 if (v
!= nullptr) return CommandError(STR_ERROR_TRAIN_IN_THE_WAY
+ v
->type
);
611 return CommandCost();
614 /** Procedure called for every vehicle found in tunnel/bridge in the hash map */
615 static Vehicle
*GetVehicleTunnelBridgeProc(Vehicle
*v
, void *data
)
617 if (v
->type
!= VEH_TRAIN
&& v
->type
!= VEH_ROAD
&& v
->type
!= VEH_SHIP
) return nullptr;
618 if (v
== (const Vehicle
*)data
) return nullptr;
624 * Finds vehicle in tunnel / bridge
625 * @param tile first end
626 * @param endtile second end
627 * @param ignore Ignore this vehicle when searching
628 * @return Succeeded command (if tunnel/bridge is free) or failed command (if a vehicle is using the tunnel/bridge).
630 CommandCost
TunnelBridgeIsFree(TileIndex tile
, TileIndex endtile
, const Vehicle
*ignore
)
632 /* Value v is not safe in MP games, however, it is used to generate a local
633 * error message only (which may be different for different machines).
634 * Such a message does not affect MP synchronisation.
636 Vehicle
*v
= VehicleFromPos(tile
, const_cast<Vehicle
*>(ignore
), &GetVehicleTunnelBridgeProc
, true);
637 if (v
== nullptr) v
= VehicleFromPos(endtile
, const_cast<Vehicle
*>(ignore
), &GetVehicleTunnelBridgeProc
, true);
639 if (v
!= nullptr) return CommandError(STR_ERROR_TRAIN_IN_THE_WAY
+ v
->type
);
640 return CommandCost();
643 static Vehicle
*EnsureNoTrainOnTrackProc(Vehicle
*v
, void *data
)
645 TrackBits rail_bits
= *(TrackBits
*)data
;
647 if (v
->type
!= VEH_TRAIN
) return nullptr;
649 Train
*t
= Train::From(v
);
650 if ((t
->track
!= rail_bits
) && !TracksOverlap(t
->track
| rail_bits
)) return nullptr;
656 * Tests if a vehicle interacts with the specified track bits.
657 * All track bits interact except parallel #TRACK_BIT_HORZ or #TRACK_BIT_VERT.
659 * @param tile The tile.
660 * @param track_bits The track bits.
661 * @return \c true if no train that interacts, is found. \c false if a train is found.
663 CommandCost
EnsureNoTrainOnTrackBits(TileIndex tile
, TrackBits track_bits
)
665 /* Value v is not safe in MP games, however, it is used to generate a local
666 * error message only (which may be different for different machines).
667 * Such a message does not affect MP synchronisation.
669 Vehicle
*v
= VehicleFromPos(tile
, &track_bits
, &EnsureNoTrainOnTrackProc
, true);
670 if (v
!= nullptr) return CommandError(STR_ERROR_TRAIN_IN_THE_WAY
+ v
->type
);
671 return CommandCost();
674 static void UpdateVehicleTileHash(Vehicle
*v
, bool remove
)
676 Vehicle
**old_hash
= v
->hash_tile_current
;
682 int x
= GB(TileX(v
->tile
), HASH_RES
, HASH_BITS
);
683 int y
= GB(TileY(v
->tile
), HASH_RES
, HASH_BITS
) << HASH_BITS
;
684 new_hash
= &_vehicle_tile_hash
[(x
+ y
) & TOTAL_HASH_MASK
];
687 if (old_hash
== new_hash
) return;
689 /* Remove from the old position in the hash table */
690 if (old_hash
!= nullptr) {
691 if (v
->hash_tile_next
!= nullptr) v
->hash_tile_next
->hash_tile_prev
= v
->hash_tile_prev
;
692 *v
->hash_tile_prev
= v
->hash_tile_next
;
695 /* Insert vehicle at beginning of the new position in the hash table */
696 if (new_hash
!= nullptr) {
697 v
->hash_tile_next
= *new_hash
;
698 if (v
->hash_tile_next
!= nullptr) v
->hash_tile_next
->hash_tile_prev
= &v
->hash_tile_next
;
699 v
->hash_tile_prev
= new_hash
;
703 /* Remember current hash position */
704 v
->hash_tile_current
= new_hash
;
707 static Vehicle
*_vehicle_viewport_hash
[1 << (GEN_HASHX_BITS
+ GEN_HASHY_BITS
)];
709 static void UpdateVehicleViewportHash(Vehicle
*v
, int x
, int y
)
711 Vehicle
**old_hash
, **new_hash
;
712 int old_x
= v
->coord
.left
;
713 int old_y
= v
->coord
.top
;
715 new_hash
= (x
== INVALID_COORD
) ? nullptr : &_vehicle_viewport_hash
[GEN_HASH(x
, y
)];
716 old_hash
= (old_x
== INVALID_COORD
) ? nullptr : &_vehicle_viewport_hash
[GEN_HASH(old_x
, old_y
)];
718 if (old_hash
== new_hash
) return;
720 /* remove from hash table? */
721 if (old_hash
!= nullptr) {
722 if (v
->hash_viewport_next
!= nullptr) v
->hash_viewport_next
->hash_viewport_prev
= v
->hash_viewport_prev
;
723 *v
->hash_viewport_prev
= v
->hash_viewport_next
;
726 /* insert into hash table? */
727 if (new_hash
!= nullptr) {
728 v
->hash_viewport_next
= *new_hash
;
729 if (v
->hash_viewport_next
!= nullptr) v
->hash_viewport_next
->hash_viewport_prev
= &v
->hash_viewport_next
;
730 v
->hash_viewport_prev
= new_hash
;
735 void ResetVehicleHash()
738 FOR_ALL_VEHICLES(v
) { v
->hash_tile_current
= nullptr; }
739 memset(_vehicle_viewport_hash
, 0, sizeof(_vehicle_viewport_hash
));
740 memset(_vehicle_tile_hash
, 0, sizeof(_vehicle_tile_hash
));
743 void ResetVehicleColourMap()
746 FOR_ALL_VEHICLES(v
) { v
->colourmap
= PAL_NONE
; }
750 * List of vehicles that should check for autoreplace this tick.
751 * Mapping of vehicle -> leave depot immediately after autoreplace.
753 typedef SmallMap
<Vehicle
*, bool, 4> AutoreplaceMap
;
754 static AutoreplaceMap _vehicles_to_autoreplace
;
757 * List of vehicles that are issued for template replacement this tick.
758 * Mapping is {vehicle : leave depot after replacement}
760 typedef SmallMap
<Train
*, bool, 4> TemplateReplacementMap
;
761 static TemplateReplacementMap _vehicles_to_templatereplace
;
763 void InitializeVehicles()
765 _vehicles_to_autoreplace
.Reset();
769 uint
CountVehiclesInChain(const Vehicle
*v
)
772 do count
++; while ((v
= v
->Next()) != nullptr);
777 * Check if a vehicle is counted in num_engines in each company struct
778 * @return true if the vehicle is counted in num_engines
780 bool Vehicle::IsEngineCountable() const
782 if (HasBit(this->subtype
, GVSF_VIRTUAL
)) return false;
783 switch (this->type
) {
784 case VEH_AIRCRAFT
: return Aircraft::From(this)->IsNormalAircraft(); // don't count plane shadows and helicopter rotors
786 return !this->IsArticulatedPart() && // tenders and other articulated parts
787 !Train::From(this)->IsRearDualheaded(); // rear parts of multiheaded engines
788 case VEH_ROAD
: return RoadVehicle::From(this)->IsFrontEngine();
789 case VEH_SHIP
: return true;
790 default: return false; // Only count company buildable vehicles
795 * Check whether Vehicle::engine_type has any meaning.
796 * @return true if the vehicle has a useable engine type.
798 bool Vehicle::HasEngineType() const
800 switch (this->type
) {
801 case VEH_AIRCRAFT
: return Aircraft::From(this)->IsNormalAircraft();
804 case VEH_SHIP
: return true;
805 default: return false;
810 * Retrieves the engine of the vehicle.
811 * @return Engine of the vehicle.
812 * @pre HasEngineType() == true
814 const Engine
*Vehicle::GetEngine() const
816 return Engine::Get(this->engine_type
);
820 * Retrieve the NewGRF the vehicle is tied to.
821 * This is the GRF providing the Action 3 for the engine type.
822 * @return NewGRF associated to the vehicle.
824 const GRFFile
*Vehicle::GetGRF() const
826 return this->GetEngine()->GetGRF();
830 * Retrieve the GRF ID of the NewGRF the vehicle is tied to.
831 * This is the GRF providing the Action 3 for the engine type.
832 * @return GRF ID of the associated NewGRF.
834 uint32
Vehicle::GetGRFID() const
836 return this->GetEngine()->GetGRFID();
840 * Handle the pathfinding result, especially the lost status.
841 * If the vehicle is now lost and wasn't previously fire an
842 * event to the AIs and a news message to the user. If the
843 * vehicle is not lost anymore remove the news message.
844 * @param path_found Whether the vehicle has a path to its destination.
846 void Vehicle::HandlePathfindingResult(bool path_found
)
849 /* Route found, is the vehicle marked with "lost" flag? */
850 if (!HasBit(this->vehicle_flags
, VF_PATHFINDER_LOST
)) return;
852 /* Clear the flag as the PF's problem was solved. */
853 ClrBit(this->vehicle_flags
, VF_PATHFINDER_LOST
);
854 /* Delete the news item. */
855 DeleteVehicleNews(this->index
, STR_NEWS_VEHICLE_IS_LOST
);
859 /* Were we already lost? */
860 if (HasBit(this->vehicle_flags
, VF_PATHFINDER_LOST
)) return;
862 /* It is first time the problem occurred, set the "lost" flag. */
863 SetBit(this->vehicle_flags
, VF_PATHFINDER_LOST
);
864 /* Notify user about the event. */
865 AI::NewEvent(this->owner
, new ScriptEventVehicleLost(this->index
));
866 if (_settings_client
.gui
.lost_vehicle_warn
&& this->owner
== _local_company
) {
867 SetDParam(0, this->index
);
868 AddVehicleAdviceNewsItem(STR_NEWS_VEHICLE_IS_LOST
, this->index
);
872 /** Destroy all stuff that (still) needs the virtual functions to work properly */
873 void Vehicle::PreDestructor()
875 if (CleaningPool()) return;
877 if (Station::IsValidID(this->last_station_visited
)) {
878 Station
*st
= Station::Get(this->last_station_visited
);
879 st
->loading_vehicles
.erase(std::remove(st
->loading_vehicles
.begin(), st
->loading_vehicles
.end(), this), st
->loading_vehicles
.end());
881 HideFillingPercent(&this->fill_percent_te_id
);
882 this->CancelReservation(INVALID_STATION
, st
);
883 delete this->cargo_payment
;
884 assert(this->cargo_payment
== nullptr); // cleared by ~CargoPayment
887 if (this->IsEngineCountable()) {
888 GroupStatistics::CountEngine(this, -1);
889 if (this->IsPrimaryVehicle()) GroupStatistics::CountVehicle(this, -1);
890 GroupStatistics::UpdateAutoreplace(this->owner
);
892 if (this->owner
== _local_company
) InvalidateAutoreplaceWindow(this->engine_type
, this->group_id
);
893 DeleteGroupHighlightOfVehicle(this);
894 if (this->type
== VEH_TRAIN
) {
895 extern void DeleteTraceRestrictSlotHighlightOfVehicle(const Vehicle
*v
);
897 DeleteTraceRestrictSlotHighlightOfVehicle(this);
901 if (this->type
== VEH_AIRCRAFT
&& this->IsPrimaryVehicle()) {
902 Aircraft
*a
= Aircraft::From(this);
903 Station
*st
= GetTargetAirportIfValid(a
);
905 const AirportFTA
*layout
= st
->airport
.GetFTA()->layout
;
906 CLRBITS(st
->airport
.flags
, layout
[a
->previous_pos
].block
| layout
[a
->pos
].block
);
911 if (this->type
== VEH_ROAD
&& this->IsPrimaryVehicle()) {
912 RoadVehicle
*v
= RoadVehicle::From(this);
913 if (!(v
->vehstatus
& VS_CRASHED
) && IsInsideMM(v
->state
, RVSB_IN_DT_ROAD_STOP
, RVSB_IN_DT_ROAD_STOP_END
)) {
914 /* Leave the drive through roadstop, when you have not already left it. */
915 RoadStop::GetByTile(v
->tile
, GetRoadStopType(v
->tile
))->Leave(v
);
919 if (this->type
== VEH_TRAIN
&& HasBit(Train::From(this)->flags
, VRF_HAVE_SLOT
)) {
920 TraceRestrictRemoveVehicleFromAllSlots(this->index
);
921 ClrBit(Train::From(this)->flags
, VRF_HAVE_SLOT
);
924 if (this->Previous() == nullptr) {
925 InvalidateWindowData(WC_VEHICLE_DEPOT
, this->tile
);
928 if (this->IsPrimaryVehicle()) {
929 DeleteWindowById(WC_VEHICLE_VIEW
, this->index
);
930 DeleteWindowById(WC_VEHICLE_ORDERS
, this->index
);
931 DeleteWindowById(WC_VEHICLE_REFIT
, this->index
);
932 DeleteWindowById(WC_VEHICLE_DETAILS
, this->index
);
933 DeleteWindowById(WC_VEHICLE_TIMETABLE
, this->index
);
934 DeleteWindowById(WC_VEHICLE_CARGO_TYPE_LOAD_ORDERS
, this->index
);
935 DeleteWindowById(WC_VEHICLE_CARGO_TYPE_UNLOAD_ORDERS
, this->index
);
936 SetWindowDirty(WC_COMPANY
, this->owner
);
937 OrderBackup::ClearVehicle(this);
939 InvalidateWindowClassesData(GetWindowClassForVehicleType(this->type
), 0);
941 this->cargo
.Truncate();
942 this->DeleteVehicleOrders();
943 DeleteDepotHighlightOfVehicle(this);
945 extern void StopGlobalFollowVehicle(const Vehicle
*v
);
946 StopGlobalFollowVehicle(this);
948 ReleaseDisastersTargetingVehicle(this->index
);
953 if (CleaningPool()) {
954 this->cargo
.OnCleanPool();
958 /* sometimes, eg. for disaster vehicles, when company bankrupts, when removing crashed/flooded vehicles,
959 * it may happen that vehicle chain is deleted when visible */
960 if (this->IsDrawn()) this->MarkAllViewportsDirty();
962 Vehicle
*v
= this->Next();
963 this->SetNext(nullptr);
967 UpdateVehicleTileHash(this, true);
968 UpdateVehicleViewportHash(this, INVALID_COORD
, 0);
969 DeleteVehicleNews(this->index
, INVALID_STRING_ID
);
970 DeleteNewGRFInspectWindow(GetGrfSpecFeature(this->type
), this->index
);
974 * Adds a vehicle to the list of vehicles that visited a depot this tick
975 * @param vehicle vehicle to add
977 void VehicleEnteredDepotThisTick(Vehicle
* vehicle
)
979 assert(vehicle
!= nullptr);
980 assert(vehicle
!= nullptr);
982 // Template Replacement Setup stuff
984 const bool stay_in_depot
= vehicle
->current_order
.GetType() == OT_GOTO_DEPOT
&&
985 vehicle
->current_order
.GetDepotActionType() != ODATF_SERVICE_ONLY
;
987 const TemplateReplacement
* template_replacement
= GetTemplateReplacementByGroupID(vehicle
->group_id
);
989 if (template_replacement
!= nullptr) {
990 _vehicles_to_templatereplace
[Train::From(vehicle
)] = stay_in_depot
;
992 // Moved the assignment for auto replacement here to prevent auto replacement
993 // from happening if template replacement is also scheduled.
994 // Vehicle should stop in the depot if it was in 'stopping' state.
995 _vehicles_to_autoreplace
[vehicle
] = !(vehicle
->vehstatus
& VS_STOPPED
);
999 // Manual depot order stuff
1001 const bool has_manual_depot_order
= HasBit(vehicle
->vehicle_flags
, VF_SHOULD_GOTO_DEPOT
) ||
1002 HasBit(vehicle
->vehicle_flags
, VF_SHOULD_SERVICE_AT_DEPOT
) ||
1003 (vehicle
->current_order
.GetType() == OT_GOTO_DEPOT
&&
1004 vehicle
->current_order
.GetDepotOrderType() == ODTF_MANUAL
);
1006 const Order
* real_current_order
= vehicle
->GetOrder(vehicle
->cur_real_order_index
);
1008 const bool current_order_needs_change
= real_current_order
== nullptr ||
1009 real_current_order
->GetType() != OT_GOTO_DEPOT
||
1010 real_current_order
->GetDestination() != vehicle
->current_order
.GetDestination();
1012 if (vehicle
->HasOrdersList() && has_manual_depot_order
&& current_order_needs_change
) {
1013 std::vector
<int> depot_order_indices
;
1015 for (int i
= 0; i
< vehicle
->GetNumOrders(); ++i
) {
1016 const Order
* order
= vehicle
->GetOrder(i
);
1018 assert(order
!= nullptr);
1020 if (order
->GetType() == OT_GOTO_DEPOT
&& order
->GetDestination() == vehicle
->current_order
.GetDestination()) {
1021 depot_order_indices
.push_back(i
);
1025 if (!depot_order_indices
.empty()) {
1026 const auto next_depot_order
= std::find_if(depot_order_indices
.begin(), depot_order_indices
.end(), [&](int index
)
1028 return index
> vehicle
->cur_real_order_index
;
1031 const int new_order_index
= next_depot_order
!= depot_order_indices
.end() ?
1033 *depot_order_indices
.begin();
1035 vehicle
->cur_implicit_order_index
= new_order_index
;
1036 vehicle
->cur_real_order_index
= new_order_index
;
1038 // We're skipping to the next depot order. Travel times will be wrong.
1039 vehicle
->cur_timetable_order_index
= INVALID_VEH_ORDER_ID
;
1041 InvalidateVehicleOrder(vehicle
, 0);
1045 ClrBit(vehicle
->vehicle_flags
, VF_SHOULD_GOTO_DEPOT
);
1046 ClrBit(vehicle
->vehicle_flags
, VF_SHOULD_SERVICE_AT_DEPOT
);
1049 // We ALWAYS set the stopped state. Even when the vehicle does not plan on
1050 // stopping in the depot, so we stop it to ensure that it will not reserve
1051 // the path out of the depot before we might autoreplace it to a different
1052 // engine. The new engine would not own the reserved path we store that we
1053 // stopped the vehicle, so autoreplace can start it again.
1054 vehicle
->vehstatus
|= VS_STOPPED
;
1058 * Increases the day counter for all vehicles and calls 1-day and 32-day handlers.
1059 * Each tick, it processes vehicles with "index % DAY_TICKS == _date_fract",
1060 * so each day, all vehicles are processes in DAY_TICKS steps.
1062 static void RunVehicleDayProc()
1064 if (_game_mode
!= GM_NORMAL
) return;
1066 /* Run the day_proc for every DAY_TICKS vehicle starting at _date_fract. */
1067 for (size_t i
= _date_fract
; i
< Vehicle::GetPoolSize(); i
+= DAY_TICKS
) {
1068 Vehicle
*v
= Vehicle::Get(i
);
1069 if (v
== nullptr) continue;
1071 /* Call the 32-day callback if needed */
1072 if ((v
->day_counter
& 0x1F) == 0 && v
->HasEngineType()) {
1073 uint16 callback
= GetVehicleCallback(CBID_VEHICLE_32DAY_CALLBACK
, 0, 0, v
->engine_type
, v
);
1074 if (callback
!= CALLBACK_FAILED
) {
1075 if (HasBit(callback
, 0)) {
1076 TriggerVehicle(v
, VEHICLE_TRIGGER_CALLBACK_32
); // Trigger vehicle trigger 10
1079 /* After a vehicle trigger, the graphics and properties of the vehicle could change.
1080 * Note: MarkDirty also invalidates the palette, which is the meaning of bit 1. So, nothing special there. */
1081 if (callback
!= 0) v
->First()->MarkDirty();
1083 if (callback
& ~3) ErrorUnknownCallbackResult(v
->GetGRFID(), CBID_VEHICLE_32DAY_CALLBACK
, callback
);
1087 /* This is called once per day for each vehicle, but not in the first tick of the day */
1092 static void ShowAutoReplaceAdviceMessage(const CommandCost
&res
, const Vehicle
*v
)
1094 StringID error_message
= res
.GetErrorMessage();
1095 if (error_message
== STR_ERROR_AUTOREPLACE_NOTHING_TO_DO
|| error_message
== INVALID_STRING_ID
) return;
1097 if (error_message
== STR_ERROR_NOT_ENOUGH_CASH_REQUIRES_CURRENCY
) error_message
= STR_ERROR_AUTOREPLACE_MONEY_LIMIT
;
1100 if (error_message
== STR_ERROR_TRAIN_TOO_LONG_AFTER_REPLACEMENT
) {
1101 message
= error_message
;
1103 message
= STR_NEWS_VEHICLE_AUTORENEW_FAILED
;
1106 SetDParam(0, v
->index
);
1107 SetDParam(1, error_message
);
1108 AddVehicleAdviceNewsItem(message
, v
->index
);
1111 void CallVehicleTicks()
1113 _vehicles_to_autoreplace
.Clear();
1114 _vehicles_to_templatereplace
.Clear();
1116 RunVehicleDayProc();
1119 FOR_ALL_STATIONS(st
) LoadUnloadStation(st
);
1122 FOR_ALL_VEHICLES(v
) {
1123 /* Vehicle could be deleted in this tick */
1125 assert(Vehicle::Get(vehicle_index
) == nullptr);
1129 assert(Vehicle::Get(vehicle_index
) == v
);
1138 Vehicle
*front
= v
->First();
1140 if (v
->vcache
.cached_cargo_age_period
!= 0) {
1141 v
->cargo_age_counter
= min(v
->cargo_age_counter
, v
->vcache
.cached_cargo_age_period
);
1142 if (--v
->cargo_age_counter
== 0) {
1143 v
->cargo
.AgeCargo();
1144 v
->cargo_age_counter
= v
->vcache
.cached_cargo_age_period
;
1148 /* Do not play any sound when crashed */
1149 if (front
->vehstatus
& VS_CRASHED
) continue;
1151 /* Do not play any sound when in depot or tunnel */
1152 if (v
->vehstatus
& VS_HIDDEN
) continue;
1154 /* Do not play any sound when stopped */
1155 if ((front
->vehstatus
& VS_STOPPED
) && (front
->type
!= VEH_TRAIN
|| front
->cur_speed
== 0)) continue;
1157 /* Check vehicle type specifics */
1160 if (Train::From(v
)->IsWagon()) continue;
1164 if (!RoadVehicle::From(v
)->IsFrontEngine()) continue;
1168 if (!Aircraft::From(v
)->IsNormalAircraft()) continue;
1175 v
->motion_counter
+= front
->cur_speed
;
1176 /* Play a running sound if the motion counter passes 256 (Do we not skip sounds?) */
1177 if (GB(v
->motion_counter
, 0, 8) < front
->cur_speed
) PlayVehicleSound(v
, VSE_RUNNING
);
1179 /* Play an alternating running sound every 16 ticks */
1180 if (GB(v
->tick_counter
, 0, 4) == 0) {
1181 /* Play running sound when speed > 0 and not braking */
1182 bool running
= (front
->cur_speed
> 0) && !(front
->vehstatus
& (VS_STOPPED
| VS_TRAIN_SLOWING
));
1183 PlayVehicleSound(v
, running
? VSE_RUNNING_16
: VSE_STOPPED_16
);
1191 /* do Template Replacement */
1192 Backup
<CompanyByte
> tmpl_cur_company(_current_company
, FILE_LINE
);
1193 for (TemplateReplacementMap::iterator it
= _vehicles_to_templatereplace
.Begin(); it
!= _vehicles_to_templatereplace
.End(); it
++) {
1194 Train
*t
= it
->first
;
1196 _vehicles_to_autoreplace
.Erase(t
);
1198 /* Store the position of the effect as the vehicle pointer will become invalid later */
1203 tmpl_cur_company
.Change(t
->owner
);
1205 bool stayInDepot
= it
->second
;
1207 it
->first
->vehstatus
|= VS_STOPPED
;
1208 CommandCost res
= DoCommand(t
->tile
, t
->index
, stayInDepot
? 1 : 0, DC_EXEC
, CMD_TEMPLATE_REPLACE_VEHICLE
);
1210 if (res
.Succeeded()) {
1211 VehicleID t_new
= _new_vehicle_id
;
1212 t
= Train::From(Vehicle::Get(t_new
));
1213 const Company
*c
= Company::Get(_current_company
);
1214 SubtractMoneyFromCompany(CommandCost(EXPENSES_NEW_VEHICLES
, (Money
)c
->settings
.engine_renew_money
));
1215 CommandCost res2
= DoCommand(0, t_new
, 1, DC_EXEC
, CMD_AUTOREPLACE_VEHICLE
);
1216 SubtractMoneyFromCompany(CommandCost(EXPENSES_NEW_VEHICLES
, -(Money
)c
->settings
.engine_renew_money
));
1217 if (res2
.Succeeded() || res
.GetCost() == 0) res
.AddCost(res2
);
1220 if (!IsLocalCompany()) continue;
1222 if (res
.Succeeded()) {
1223 if (res
.GetCost() != 0) {
1224 ShowCostOrIncomeAnimation(x
, y
, z
, res
.GetCost());
1229 ShowAutoReplaceAdviceMessage(res
, t
);
1231 tmpl_cur_company
.Restore();
1233 /* do Auto Replacement */
1234 Backup
<CompanyByte
> cur_company(_current_company
, FILE_LINE
);
1235 for (AutoreplaceMap::iterator it
= _vehicles_to_autoreplace
.Begin(); it
!= _vehicles_to_autoreplace
.End(); it
++) {
1237 /* Autoreplace needs the current company set as the vehicle owner */
1238 cur_company
.Change(v
->owner
);
1240 if (v
->type
== VEH_TRAIN
) {
1241 assert(!_vehicles_to_templatereplace
.Contains(Train::From(v
)));
1244 /* Start vehicle if we stopped them in VehicleEnteredDepotThisTick()
1245 * We need to stop them between VehicleEnteredDepotThisTick() and here or we risk that
1246 * they are already leaving the depot again before being replaced. */
1247 if (it
->second
) v
->vehstatus
&= ~VS_STOPPED
;
1249 /* Store the position of the effect as the vehicle pointer will become invalid later */
1254 const Company
*c
= Company::Get(_current_company
);
1255 SubtractMoneyFromCompany(CommandCost(EXPENSES_NEW_VEHICLES
, (Money
)c
->settings
.engine_renew_money
));
1256 CommandCost res
= DoCommand(0, v
->index
, 0, DC_EXEC
, CMD_AUTOREPLACE_VEHICLE
);
1257 SubtractMoneyFromCompany(CommandCost(EXPENSES_NEW_VEHICLES
, -(Money
)c
->settings
.engine_renew_money
));
1259 if (!IsLocalCompany()) continue;
1261 if (res
.Succeeded()) {
1262 ShowCostOrIncomeAnimation(x
, y
, z
, res
.GetCost());
1266 ShowAutoReplaceAdviceMessage(res
, v
);
1268 cur_company
.Restore();
1272 * Add vehicle sprite for drawing to the screen.
1273 * @param v Vehicle to draw.
1275 static void DoDrawVehicle(const Vehicle
*v
)
1277 PaletteID pal
= PAL_NONE
;
1279 if (v
->vehstatus
& VS_DEFPAL
) pal
= (v
->vehstatus
& VS_CRASHED
) ? PALETTE_CRASH
: GetVehiclePalette(v
);
1281 /* Check whether the vehicle shall be transparent due to the game state */
1282 bool shadowed
= (v
->vehstatus
& (VS_SHADOW
| VS_HIDDEN
)) != 0;
1284 if (v
->type
== VEH_EFFECT
) {
1285 /* Check whether the vehicle shall be transparent/invisible due to GUI settings.
1286 * However, transparent smoke and bubbles look weird, so always hide them. */
1287 TransparencyOption to
= EffectVehicle::From(v
)->GetTransparencyOption();
1288 if (to
!= TO_INVALID
&& (IsTransparencySet(to
) || IsInvisibilitySet(to
))) return;
1291 StartSpriteCombine();
1292 for (uint i
= 0; i
< v
->sprite_seq
.count
; ++i
) {
1293 PaletteID pal2
= v
->sprite_seq
.seq
[i
].pal
;
1294 if (!pal2
|| (v
->vehstatus
& VS_CRASHED
)) pal2
= pal
;
1295 AddSortableSpriteToDraw(v
->sprite_seq
.seq
[i
].sprite
, pal2
, v
->x_pos
+ v
->x_offs
, v
->y_pos
+ v
->y_offs
,
1296 v
->x_extent
, v
->y_extent
, v
->z_extent
, v
->z_pos
, shadowed
, v
->x_bb_offs
, v
->y_bb_offs
);
1301 struct ViewportHashBound
{
1305 static ViewportHashBound
GetViewportHashBound(int l
, int r
, int t
, int b
) {
1306 int xl
= (l
- (70 * ZOOM_LVL_BASE
)) >> (7 + ZOOM_LVL_SHIFT
);
1307 int xu
= (r
) >> (7 + ZOOM_LVL_SHIFT
);
1308 /* compare after shifting instead of before, so that lower bits don't affect comparison result */
1309 if (xu
- xl
< (1 << 6)) {
1313 /* scan whole hash row */
1318 int yl
= (t
- (70 * ZOOM_LVL_BASE
)) >> (6 + ZOOM_LVL_SHIFT
);
1319 int yu
= (b
) >> (6 + ZOOM_LVL_SHIFT
);
1320 /* compare after shifting instead of before, so that lower bits don't affect comparison result */
1321 if (yu
- yl
< (1 << 6)) {
1322 yl
= (yl
& 0x3F) << 6;
1323 yu
= (yu
& 0x3F) << 6;
1325 /* scan whole column */
1329 return { xl
, xu
, yl
, yu
};
1333 * Add the vehicle sprites that should be drawn at a part of the screen.
1334 * @param dpi Rectangle being drawn.
1336 void ViewportAddVehicles(DrawPixelInfo
*dpi
)
1338 /* The bounding rectangle */
1339 const int l
= dpi
->left
;
1340 const int r
= dpi
->left
+ dpi
->width
;
1341 const int t
= dpi
->top
;
1342 const int b
= dpi
->top
+ dpi
->height
;
1344 /* The hash area to scan */
1345 const ViewportHashBound vhb
= GetViewportHashBound(l
, r
, t
, b
);
1347 for (int y
= vhb
.yl
;; y
= (y
+ (1 << 6)) & (0x3F << 6)) {
1348 for (int x
= vhb
.xl
;; x
= (x
+ 1) & 0x3F) {
1349 const Vehicle
*v
= _vehicle_viewport_hash
[x
+ y
]; // already masked & 0xFFF
1351 while (v
!= nullptr) {
1353 l
<= v
->coord
.right
&&
1354 t
<= v
->coord
.bottom
&&
1355 r
>= v
->coord
.left
&&
1356 b
>= v
->coord
.top
) {
1359 v
= v
->hash_viewport_next
;
1362 if (x
== vhb
.xu
) break;
1365 if (y
== vhb
.yu
) break;
1369 void ViewportMapDrawVehicles(DrawPixelInfo
*dpi
)
1371 /* The bounding rectangle */
1372 const int l
= dpi
->left
;
1373 const int r
= dpi
->left
+ dpi
->width
;
1374 const int t
= dpi
->top
;
1375 const int b
= dpi
->top
+ dpi
->height
;
1377 /* The hash area to scan */
1378 const ViewportHashBound vhb
= GetViewportHashBound(l
, r
, t
, b
);
1380 const int w
= UnScaleByZoom(dpi
->width
, dpi
->zoom
);
1381 const int h
= UnScaleByZoom(dpi
->height
, dpi
->zoom
);
1382 Blitter
*blitter
= BlitterFactory::GetCurrentBlitter();
1383 for (int y
= vhb
.yl
;; y
= (y
+ (1 << 6)) & (0x3F << 6)) {
1384 for (int x
= vhb
.xl
;; x
= (x
+ 1) & 0x3F) {
1385 const Vehicle
*v
= _vehicle_viewport_hash
[x
+ y
]; // already masked & 0xFFF
1387 while (v
!= nullptr) {
1388 if (!(v
->vehstatus
& (VS_HIDDEN
| VS_UNCLICKABLE
)) && (v
->type
!= VEH_EFFECT
)) {
1389 Point pt
= RemapCoords(v
->x_pos
, v
->y_pos
, v
->z_pos
);
1390 const int pixel_x
= UnScaleByZoomLower(pt
.x
- dpi
->left
, dpi
->zoom
);
1391 if (IsInsideMM(pixel_x
, 0, w
)) {
1392 const int pixel_y
= UnScaleByZoomLower(pt
.y
- dpi
->top
, dpi
->zoom
);
1393 if (IsInsideMM(pixel_y
, 0, h
))
1394 blitter
->SetPixel(dpi
->dst_ptr
, pixel_x
, pixel_y
, PC_WHITE
);
1397 v
= v
->hash_viewport_next
;
1400 if (x
== vhb
.xu
) break;
1403 if (y
== vhb
.yu
) break;
1408 * Find the vehicle close to the clicked coordinates.
1409 * @param vp Viewport clicked in.
1410 * @param x X coordinate in the viewport.
1411 * @param y Y coordinate in the viewport.
1412 * @return Closest vehicle, or \c nullptr if none found.
1414 Vehicle
*CheckClickOnVehicle(const ViewPort
*vp
, int x
, int y
)
1416 Vehicle
*found
= nullptr, *v
;
1417 uint dist
, best_dist
= UINT_MAX
;
1419 if ((uint
)(x
-= vp
->left
) >= (uint
)vp
->width
|| (uint
)(y
-= vp
->top
) >= (uint
)vp
->height
) return nullptr;
1421 x
= ScaleByZoom(x
, vp
->zoom
) + vp
->virtual_left
;
1422 y
= ScaleByZoom(y
, vp
->zoom
) + vp
->virtual_top
;
1424 FOR_ALL_VEHICLES(v
) {
1425 if (((v
->vehstatus
& VS_UNCLICKABLE
) == 0) && v
->IsDrawn() &&
1426 x
>= v
->coord
.left
&& x
<= v
->coord
.right
&&
1427 y
>= v
->coord
.top
&& y
<= v
->coord
.bottom
) {
1430 abs(((v
->coord
.left
+ v
->coord
.right
) >> 1) - x
),
1431 abs(((v
->coord
.top
+ v
->coord
.bottom
) >> 1) - y
)
1434 if (dist
< best_dist
) {
1445 * Decrease the value of a vehicle.
1446 * @param v %Vehicle to devaluate.
1448 void DecreaseVehicleValue(Vehicle
*v
)
1450 v
->value
-= v
->value
>> 8;
1451 SetWindowDirty(WC_VEHICLE_DETAILS
, v
->index
);
1454 static const byte _breakdown_chance
[64] = {
1455 3, 3, 3, 3, 3, 3, 3, 3,
1456 4, 4, 5, 5, 6, 6, 7, 7,
1457 8, 8, 9, 9, 10, 10, 11, 11,
1458 12, 13, 13, 13, 13, 14, 15, 16,
1459 17, 19, 21, 25, 28, 31, 34, 37,
1460 40, 44, 48, 52, 56, 60, 64, 68,
1461 72, 80, 90, 100, 110, 120, 130, 140,
1462 150, 170, 190, 210, 230, 250, 250, 250,
1465 void CheckVehicleBreakdown(Vehicle
*v
)
1469 /* decrease reliability */
1470 v
->reliability
= rel
= max((rel_old
= v
->reliability
) - v
->reliability_spd_dec
, 0);
1471 if ((rel_old
>> 8) != (rel
>> 8)) SetWindowDirty(WC_VEHICLE_DETAILS
, v
->index
);
1473 if (v
->breakdown_ctr
!= 0 || (v
->vehstatus
& VS_STOPPED
) ||
1474 _settings_game
.difficulty
.vehicle_breakdowns
< 1 ||
1475 v
->cur_speed
< 5 || _game_mode
== GM_MENU
) {
1479 uint32 r
= Random();
1481 /* increase chance of failure */
1482 int chance
= v
->breakdown_chance
+ 1;
1483 if (Chance16I(1, 25, r
)) chance
+= 25;
1484 v
->breakdown_chance
= min(255, chance
);
1486 /* calculate reliability value to use in comparison */
1487 rel
= v
->reliability
;
1488 if (v
->type
== VEH_SHIP
) rel
+= 0x6666;
1490 /* reduced breakdowns? */
1491 if (_settings_game
.difficulty
.vehicle_breakdowns
== 1) rel
+= 0x6666;
1493 /* check if to break down */
1494 if (_breakdown_chance
[(uint
)min(rel
, 0xffff) >> 10] <= v
->breakdown_chance
) {
1495 v
->breakdown_ctr
= GB(r
, 16, 6) + 0x3F;
1496 v
->breakdown_delay
= GB(r
, 24, 7) + 0x80;
1497 v
->breakdown_chance
= 0;
1502 * Handle all of the aspects of a vehicle breakdown
1503 * This includes adding smoke and sounds, and ending the breakdown when appropriate.
1504 * @return true iff the vehicle is stopped because of a breakdown
1505 * @note This function always returns false for aircraft, since these never stop for breakdowns
1507 bool Vehicle::HandleBreakdown()
1509 /* Possible states for Vehicle::breakdown_ctr
1510 * 0 - vehicle is running normally
1511 * 1 - vehicle is currently broken down
1512 * 2 - vehicle is going to break down now
1513 * >2 - vehicle is counting down to the actual breakdown event */
1514 switch (this->breakdown_ctr
) {
1519 this->breakdown_ctr
= 1;
1521 if (this->breakdowns_since_last_service
!= 255) {
1522 this->breakdowns_since_last_service
++;
1525 if (this->type
== VEH_AIRCRAFT
) {
1526 /* Aircraft just need this flag, the rest is handled elsewhere */
1527 this->vehstatus
|= VS_AIRCRAFT_BROKEN
;
1529 this->cur_speed
= 0;
1531 if (!PlayVehicleSound(this, VSE_BREAKDOWN
)) {
1532 bool train_or_ship
= this->type
== VEH_TRAIN
|| this->type
== VEH_SHIP
;
1533 SndPlayVehicleFx((_settings_game
.game_creation
.landscape
!= LT_TOYLAND
) ?
1534 (train_or_ship
? SND_10_TRAIN_BREAKDOWN
: SND_0F_VEHICLE_BREAKDOWN
) :
1535 (train_or_ship
? SND_3A_COMEDY_BREAKDOWN_2
: SND_35_COMEDY_BREAKDOWN
), this);
1538 if (!(this->vehstatus
& VS_HIDDEN
) && !HasBit(EngInfo(this->engine_type
)->misc_flags
, EF_NO_BREAKDOWN_SMOKE
)) {
1539 EffectVehicle
*u
= CreateEffectVehicleRel(this, 4, 4, 5, EV_BREAKDOWN_SMOKE
);
1540 if (u
!= nullptr) u
->animation_state
= this->breakdown_delay
* 2;
1544 this->MarkDirty(); // Update graphics after speed is zeroed
1545 SetWindowDirty(WC_VEHICLE_VIEW
, this->index
);
1546 SetWindowDirty(WC_VEHICLE_DETAILS
, this->index
);
1550 /* Aircraft breakdowns end only when arriving at the airport */
1551 if (this->type
== VEH_AIRCRAFT
) return false;
1553 /* For trains this function is called twice per tick, so decrease v->breakdown_delay at half the rate */
1554 if ((this->tick_counter
& (this->type
== VEH_TRAIN
? 3 : 1)) == 0) {
1555 if (--this->breakdown_delay
== 0) {
1556 this->breakdown_ctr
= 0;
1558 SetWindowDirty(WC_VEHICLE_VIEW
, this->index
);
1564 if (!this->current_order
.IsType(OT_LOADING
)) this->breakdown_ctr
--;
1570 * Update age of a vehicle.
1571 * @param v Vehicle to update.
1573 void AgeVehicle(Vehicle
*v
)
1575 /* Stop if a virtual vehicle */
1576 if (HasBit(v
->subtype
, GVSF_VIRTUAL
)) return;
1578 if (v
->age
< MAX_DAY
) {
1580 if (v
->IsPrimaryVehicle() && v
->age
== VEHICLE_PROFIT_MIN_AGE
+ 1) GroupStatistics::VehicleReachedProfitAge(v
);
1583 if (!v
->IsPrimaryVehicle() && (v
->type
!= VEH_TRAIN
|| !Train::From(v
)->IsEngine())) return;
1585 int age
= v
->age
- v
->max_age
;
1586 if (age
== DAYS_IN_LEAP_YEAR
* 0 || age
== DAYS_IN_LEAP_YEAR
* 1 ||
1587 age
== DAYS_IN_LEAP_YEAR
* 2 || age
== DAYS_IN_LEAP_YEAR
* 3 || age
== DAYS_IN_LEAP_YEAR
* 4) {
1588 v
->reliability_spd_dec
<<= 1;
1591 SetWindowDirty(WC_VEHICLE_DETAILS
, v
->index
);
1593 /* Don't warn about non-primary or not ours vehicles or vehicles that are crashed */
1594 if (v
->Previous() != nullptr || v
->owner
!= _local_company
|| (v
->vehstatus
& VS_CRASHED
) != 0) return;
1596 /* Don't warn if a renew is active */
1597 if (Company::Get(v
->owner
)->settings
.engine_renew
&& v
->GetEngine()->company_avail
!= 0) return;
1600 if (age
== -DAYS_IN_LEAP_YEAR
) {
1601 str
= STR_NEWS_VEHICLE_IS_GETTING_OLD
;
1602 } else if (age
== 0) {
1603 str
= STR_NEWS_VEHICLE_IS_GETTING_VERY_OLD
;
1604 } else if (age
> 0 && (age
% DAYS_IN_LEAP_YEAR
) == 0) {
1605 str
= STR_NEWS_VEHICLE_IS_GETTING_VERY_OLD_AND
;
1610 SetDParam(0, v
->index
);
1611 AddVehicleAdviceNewsItem(str
, v
->index
);
1615 * Calculates how full a vehicle is.
1616 * @param front The front vehicle of the consist to check.
1617 * @param colour The string to show depending on if we are unloading or loading
1618 * @return A percentage of how full the Vehicle is.
1619 * Percentages are rounded towards 50%, so that 0% and 100% are only returned
1620 * if the vehicle is completely empty or full.
1621 * This is useful for both display and conditional orders.
1623 uint8
CalcPercentVehicleFilled(const Vehicle
*front
, StringID
*colour
)
1629 bool loading
= false;
1631 bool is_loading
= front
->current_order
.IsType(OT_LOADING
);
1633 /* The station may be nullptr when the (colour) string does not need to be set. */
1634 const Station
*st
= Station::GetIfValid(front
->last_station_visited
);
1635 assert(colour
== nullptr || (st
!= nullptr && is_loading
));
1637 bool order_no_load
= is_loading
&& (front
->current_order
.GetLoadType() & OLFB_NO_LOAD
);
1638 bool order_full_load
= is_loading
&& (front
->current_order
.GetLoadType() & OLFB_FULL_LOAD
);
1640 /* Count up max and used */
1641 for (const Vehicle
*v
= front
; v
!= nullptr; v
= v
->Next()) {
1642 count
+= v
->cargo
.StoredCount();
1643 max
+= v
->cargo_cap
;
1644 if (v
->cargo_cap
!= 0 && colour
!= nullptr) {
1645 unloading
+= HasBit(v
->vehicle_flags
, VF_CARGO_UNLOADING
) ? 1 : 0;
1646 loading
|= !order_no_load
&&
1647 (order_full_load
|| st
->goods
[v
->cargo_type
].HasRating()) &&
1648 !HasBit(v
->vehicle_flags
, VF_LOADING_FINISHED
) && !HasBit(v
->vehicle_flags
, VF_STOP_LOADING
);
1653 if (colour
!= nullptr) {
1654 if (unloading
== 0 && loading
) {
1655 *colour
= STR_PERCENT_UP
;
1656 } else if (unloading
== 0 && !loading
) {
1657 *colour
= STR_PERCENT_NONE
;
1658 } else if (cars
== unloading
|| !loading
) {
1659 *colour
= STR_PERCENT_DOWN
;
1661 *colour
= STR_PERCENT_UP_DOWN
;
1665 /* Train without capacity */
1666 if (max
== 0) return 100;
1668 /* Return the percentage */
1669 if (count
* 2 < max
) {
1670 /* Less than 50%; round up, so that 0% means really empty. */
1671 return CeilDiv(count
* 100, max
);
1673 /* More than 50%; round down, so that 100% means really full. */
1674 return (count
* 100) / max
;
1679 * Vehicle entirely entered the depot, update its status, orders, vehicle windows, service it, etc.
1680 * @param v Vehicle that entered a depot.
1682 void VehicleEnterDepot(Vehicle
*v
)
1684 /* Always work with the front of the vehicle */
1685 assert(v
== v
->First());
1689 Train
*t
= Train::From(v
);
1690 SetWindowClassesDirty(WC_TRAINS_LIST
);
1691 SetWindowClassesDirty(WC_TRACE_RESTRICT_SLOTS
);
1692 /* Clear path reservation */
1693 SetDepotReservation(t
->tile
, false);
1694 if (_settings_client
.gui
.show_track_reservation
) MarkTileDirtyByTile(t
->tile
, ZOOM_LVL_DRAW_MAP
);
1696 UpdateSignalsOnSegment(t
->tile
, INVALID_DIAGDIR
, t
->owner
);
1697 t
->wait_counter
= 0;
1698 t
->force_proceed
= TFP_NONE
;
1699 ClrBit(t
->flags
, VRF_TOGGLE_REVERSE
);
1700 t
->ConsistChanged(CCF_ARRANGE
);
1701 t
->reverse_distance
= 0;
1706 SetWindowClassesDirty(WC_ROADVEH_LIST
);
1710 SetWindowClassesDirty(WC_SHIPS_LIST
);
1711 Ship
*ship
= Ship::From(v
);
1712 ship
->state
= TRACK_BIT_DEPOT
;
1713 ship
->UpdateCache();
1714 ship
->UpdateViewport(true, true);
1715 SetWindowDirty(WC_VEHICLE_DEPOT
, v
->tile
);
1720 SetWindowClassesDirty(WC_AIRCRAFT_LIST
);
1721 HandleAircraftEnterHangar(Aircraft::From(v
));
1723 default: NOT_REACHED();
1725 SetWindowDirty(WC_VEHICLE_VIEW
, v
->index
);
1727 if (v
->type
!= VEH_TRAIN
) {
1728 /* Trains update the vehicle list when the first unit enters the depot and calls VehicleEnterDepot() when the last unit enters.
1729 * We only increase the number of vehicles when the first one enters, so we will not need to search for more vehicles in the depot */
1730 InvalidateWindowData(WC_VEHICLE_DEPOT
, v
->tile
);
1732 SetWindowDirty(WC_VEHICLE_DEPOT
, v
->tile
);
1734 v
->vehstatus
|= VS_HIDDEN
;
1737 VehicleServiceInDepot(v
);
1739 /* After a vehicle trigger, the graphics and properties of the vehicle could change. */
1740 TriggerVehicle(v
, VEHICLE_TRIGGER_DEPOT
);
1743 if (v
->current_order
.IsType(OT_GOTO_DEPOT
)) {
1744 SetWindowDirty(WC_VEHICLE_VIEW
, v
->index
);
1746 if (v
->type
!= VEH_TRAIN
&& v
->current_order
.GetDepotOrderType() == ODTF_MANUAL
) {
1747 // Check first if the vehicle has any depot in its order list. If yes then we're heading for a specific depot.
1748 // Don't stop if this one isn't it.
1749 bool has_depot_in_orders
= false;
1751 for (int i
= 0; i
< v
->GetNumOrders(); ++i
) {
1752 Order
* order
= v
->GetOrder(i
);
1754 bool isRegularOrder
= (order
->GetDepotOrderType() & ODTFB_PART_OF_ORDERS
) != 0;
1755 bool isDepotOrder
= order
->GetType() == OT_GOTO_DEPOT
;
1757 if (isRegularOrder
&& isDepotOrder
) {
1758 has_depot_in_orders
= true;
1763 if (has_depot_in_orders
)
1765 if ((v
->type
== VEH_AIRCRAFT
&& Station::GetByTile(v
->dest_tile
)->index
!= Station::GetByTile(v
->tile
)->index
) ||
1766 (v
->type
!= VEH_AIRCRAFT
&& v
->dest_tile
!= v
->tile
)) {
1767 /* We are heading for another depot, keep driving. */
1773 const Order
*real_order
= v
->GetOrder(v
->cur_real_order_index
);
1775 /* Test whether we are heading for this depot. If not, do nothing.
1776 * Note: The target depot for nearest-/manual-depot-orders is only updated on junctions, but we want to accept every depot. */
1777 if ((v
->current_order
.GetDepotOrderType() & ODTFB_PART_OF_ORDERS
) &&
1778 real_order
!= nullptr && !(real_order
->GetDepotActionType() & ODATFB_NEAREST_DEPOT
) &&
1779 (v
->type
== VEH_AIRCRAFT
? v
->current_order
.GetDestination() != GetStationIndex(v
->tile
) : v
->dest_tile
!= v
->tile
)) {
1780 /* We are heading for another depot, keep driving. */
1784 if (v
->current_order
.IsRefit()) {
1785 Backup
<CompanyByte
> cur_company(_current_company
, v
->owner
, FILE_LINE
);
1786 CommandCost cost
= DoCommand(v
->tile
, v
->index
, v
->current_order
.GetRefitCargo() | 0xFF << 8, DC_EXEC
, GetCmdRefitVeh(v
));
1787 cur_company
.Restore();
1789 if (cost
.Failed()) {
1790 _vehicles_to_autoreplace
[v
] = false;
1791 if (v
->owner
== _local_company
) {
1792 /* Notify the user that we stopped the vehicle */
1793 SetDParam(0, v
->index
);
1794 AddVehicleAdviceNewsItem(STR_NEWS_ORDER_REFIT_FAILED
, v
->index
);
1796 } else if (cost
.GetCost() != 0) {
1797 v
->profit_this_year
-= cost
.GetCost() << 8;
1798 if (v
->owner
== _local_company
) {
1799 ShowCostOrIncomeAnimation(v
->x_pos
, v
->y_pos
, v
->z_pos
, cost
.GetCost());
1804 /* Handle the ODTFB_PART_OF_ORDERS case. If there is a timetabled wait time, hold the vehicle, otherwise skip to the next order.
1805 Note that if there is a only a travel_time, but no wait_time defined for the order, and the vehicle arrives to the depot sooner as scheduled,
1806 he doesn't wait in it, as it would in stations. Thus, the original behavior is maintained if there's no defined wait_time.*/
1807 if (v
->current_order
.GetDepotOrderType() & ODTFB_PART_OF_ORDERS
) {
1808 v
->DeleteUnreachedImplicitOrders();
1809 UpdateVehicleTimetable(v
, true);
1810 if (v
->current_order
.IsWaitTimetabled() && !(v
->current_order
.GetDepotActionType() & ODATFB_HALT
)) {
1811 v
->current_order
.MakeWaiting();
1812 v
->HandleAutomaticTimetableSeparation();
1813 v
->current_order
.SetNonStopType(ONSF_NO_STOP_AT_ANY_STATION
);
1817 UpdateVehicleTimetable(v
, false);
1818 v
->IncrementImplicitOrderIndex();
1821 if (v
->current_order
.GetDepotActionType() & ODATFB_HALT
) {
1822 /* Vehicles are always stopped on entering depots. Do not restart this one. */
1823 _vehicles_to_autoreplace
[v
] = false;
1824 /* Invalidate last_loading_station. As the link from the station
1825 * before the stop to the station after the stop can't be predicted
1826 * we shouldn't construct it when the vehicle visits the next stop. */
1827 v
->last_loading_station
= INVALID_STATION
;
1828 ClrBit(v
->vehicle_flags
, VF_LAST_LOAD_ST_SEP
);
1829 if (v
->owner
== _local_company
) {
1830 SetDParam(0, v
->index
);
1831 AddVehicleAdviceNewsItem(STR_NEWS_TRAIN_IS_WAITING
+ v
->type
, v
->index
);
1833 AI::NewEvent(v
->owner
, new ScriptEventVehicleWaitingInDepot(v
->index
));
1834 v
->MarkSeparationInvalid();
1836 v
->current_order
.MakeDummy();
1842 * Update the position of the vehicle. This will update the hash that tells
1843 * which vehicles are on a tile.
1845 void Vehicle::UpdatePosition()
1847 UpdateVehicleTileHash(this, false);
1851 * Update the vehicle on the viewport, updating the right hash and setting the
1853 * @param dirty Mark the (new and old) coordinates of the vehicle as dirty.
1855 void Vehicle::UpdateViewport(bool dirty
)
1857 Rect new_coord
= ConvertRect
<Rect16
, Rect
>(this->sprite_seq_bounds
);
1859 Point pt
= RemapCoords(this->x_pos
+ this->x_offs
, this->y_pos
+ this->y_offs
, this->z_pos
);
1860 new_coord
.left
+= pt
.x
;
1861 new_coord
.top
+= pt
.y
;
1862 new_coord
.right
+= pt
.x
+ 2 * ZOOM_LVL_BASE
;
1863 new_coord
.bottom
+= pt
.y
+ 2 * ZOOM_LVL_BASE
;
1865 UpdateVehicleViewportHash(this, new_coord
.left
, new_coord
.top
);
1867 Rect old_coord
= this->coord
;
1868 this->coord
= new_coord
;
1871 if (old_coord
.left
== INVALID_COORD
) {
1872 this->MarkAllViewportsDirty();
1874 ::MarkAllViewportsDirty(
1875 min(old_coord
.left
, this->coord
.left
),
1876 min(old_coord
.top
, this->coord
.top
),
1877 max(old_coord
.right
, this->coord
.right
),
1878 max(old_coord
.bottom
, this->coord
.bottom
),
1879 this->type
!= VEH_EFFECT
? ZOOM_LVL_END
: ZOOM_LVL_DRAW_MAP
);
1885 * Update the position of the vehicle, and update the viewport.
1887 void Vehicle::UpdatePositionAndViewport()
1889 this->UpdatePosition();
1890 this->UpdateViewport(true);
1894 * Marks viewports dirty where the vehicle's image is.
1896 void Vehicle::MarkAllViewportsDirty() const
1898 ::MarkAllViewportsDirty(this->coord
.left
, this->coord
.top
, this->coord
.right
, this->coord
.bottom
, this->type
!= VEH_EFFECT
? ZOOM_LVL_END
: ZOOM_LVL_DRAW_MAP
);
1902 * Get position information of a vehicle when moving one pixel in the direction it is facing
1903 * @param v Vehicle to move
1904 * @return Position information after the move
1906 GetNewVehiclePosResult
GetNewVehiclePos(const Vehicle
*v
)
1908 static const int8 _delta_coord
[16] = {
1909 -1,-1,-1, 0, 1, 1, 1, 0, /* x */
1910 -1, 0, 1, 1, 1, 0,-1,-1, /* y */
1913 int x
= v
->x_pos
+ _delta_coord
[v
->direction
];
1914 int y
= v
->y_pos
+ _delta_coord
[v
->direction
+ 8];
1916 GetNewVehiclePosResult gp
;
1919 gp
.old_tile
= v
->tile
;
1920 gp
.new_tile
= TileVirtXY(x
, y
);
1924 static const Direction _new_direction_table
[] = {
1925 DIR_N
, DIR_NW
, DIR_W
,
1926 DIR_NE
, DIR_SE
, DIR_SW
,
1927 DIR_E
, DIR_SE
, DIR_S
1930 Direction
GetDirectionTowards(const Vehicle
*v
, int x
, int y
)
1934 if (y
>= v
->y_pos
) {
1935 if (y
!= v
->y_pos
) i
+= 3;
1939 if (x
>= v
->x_pos
) {
1940 if (x
!= v
->x_pos
) i
++;
1944 Direction dir
= v
->direction
;
1946 DirDiff dirdiff
= DirDifference(_new_direction_table
[i
], dir
);
1947 if (dirdiff
== DIRDIFF_SAME
) return dir
;
1948 return ChangeDir(dir
, dirdiff
> DIRDIFF_REVERSE
? DIRDIFF_45LEFT
: DIRDIFF_45RIGHT
);
1952 * Call the tile callback function for a vehicle entering a tile
1953 * @param v Vehicle entering the tile
1954 * @param tile Tile entered
1955 * @param x X position
1956 * @param y Y position
1957 * @return Some meta-data over the to be entered tile.
1958 * @see VehicleEnterTileStatus to see what the bits in the return value mean.
1960 VehicleEnterTileStatus
VehicleEnterTile(Vehicle
*v
, TileIndex tile
, int x
, int y
)
1962 return _tile_type_procs
[GetTileType(tile
)]->vehicle_enter_tile_proc(v
, tile
, x
, y
);
1966 * Initializes the structure. Vehicle unit numbers are supposed not to change after
1967 * struct initialization, except after each call to this->NextID() the returned value
1968 * is assigned to a vehicle.
1969 * @param type type of vehicle
1970 * @param owner owner of vehicles
1972 FreeUnitIDGenerator::FreeUnitIDGenerator(VehicleType type
, CompanyID owner
) : cache(nullptr), maxid(0), curid(0)
1976 FOR_ALL_VEHICLES(v
) {
1977 if (v
->type
== type
&& v
->owner
== owner
) {
1978 this->maxid
= max
<UnitID
>(this->maxid
, v
->unitnumber
);
1982 if (this->maxid
== 0) return;
1984 /* Reserving 'maxid + 2' because we need:
1985 * - space for the last item (with v->unitnumber == maxid)
1986 * - one free slot working as loop terminator in FreeUnitIDGenerator::NextID() */
1987 this->cache
= CallocT
<bool>(this->maxid
+ 2);
1989 /* Fill the cache */
1990 FOR_ALL_VEHICLES(v
) {
1991 if (v
->type
== type
&& v
->owner
== owner
) {
1992 this->cache
[v
->unitnumber
] = true;
1997 /** Returns next free UnitID. Supposes the last returned value was assigned to a vehicle. */
1998 UnitID
FreeUnitIDGenerator::NextID()
2000 if (this->maxid
<= this->curid
) return ++this->curid
;
2002 while (this->cache
[++this->curid
]) { } // it will stop, we reserved more space than needed
2008 * Get an unused unit number for a vehicle (if allowed).
2009 * @param type Type of vehicle
2010 * @return A unused unit number for the given type of vehicle if it is allowed to build one, else \c UINT16_MAX.
2012 UnitID
GetFreeUnitNumber(VehicleType type
)
2014 /* Check whether it is allowed to build another vehicle. */
2017 case VEH_TRAIN
: max_veh
= _settings_game
.vehicle
.max_trains
; break;
2018 case VEH_ROAD
: max_veh
= _settings_game
.vehicle
.max_roadveh
; break;
2019 case VEH_SHIP
: max_veh
= _settings_game
.vehicle
.max_ships
; break;
2020 case VEH_AIRCRAFT
: max_veh
= _settings_game
.vehicle
.max_aircraft
; break;
2021 default: NOT_REACHED();
2024 const Company
*c
= Company::Get(_current_company
);
2025 if (c
->group_all
[type
].num_vehicle
>= max_veh
) return UINT16_MAX
; // Currently already at the limit, no room to make a new one.
2027 FreeUnitIDGenerator
gen(type
, _current_company
);
2029 return gen
.NextID();
2034 * Check whether we can build infrastructure for the given
2035 * vehicle type. This to disable building stations etc. when
2036 * you are not allowed/able to have the vehicle type yet.
2037 * @param type the vehicle type to check this for
2038 * @return true if there is any reason why you may build
2039 * the infrastructure for the given vehicle type
2041 bool CanBuildVehicleInfrastructure(VehicleType type
)
2043 assert(type
!= VEH_ROAD
);
2044 assert(IsCompanyBuildableVehicleType(type
));
2046 if (!Company::IsValidID(_local_company
)) return false;
2047 if (!_settings_client
.gui
.disable_unsuitable_building
) return true;
2051 case VEH_TRAIN
: max
= _settings_game
.vehicle
.max_trains
; break;
2052 case VEH_SHIP
: max
= _settings_game
.vehicle
.max_ships
; break;
2053 case VEH_AIRCRAFT
: max
= _settings_game
.vehicle
.max_aircraft
; break;
2054 /* VEH_ROAD is not handled here, see CanBuildRoadTypeInfrastructure() */
2055 default: NOT_REACHED();
2058 /* We can build vehicle infrastructure when we may build the vehicle type */
2060 /* Can we actually build the vehicle type? */
2062 FOR_ALL_ENGINES_OF_TYPE(e
, type
) {
2063 if (HasBit(e
->company_avail
, _local_company
)) return true;
2068 /* We should be able to build infrastructure when we have the actual vehicle type */
2070 FOR_ALL_VEHICLES(v
) {
2071 if (v
->type
== type
&& v
->owner
== _local_company
) return true;
2078 * Determines the #LiveryScheme for a vehicle.
2079 * @param engine_type Engine of the vehicle.
2080 * @param parent_engine_type Engine of the front vehicle, #INVALID_ENGINE if vehicle is at front itself.
2081 * @param v the vehicle, \c nullptr if in purchase list etc.
2082 * @return livery scheme to use.
2084 LiveryScheme
GetEngineLiveryScheme(EngineID engine_type
, EngineID parent_engine_type
, const Vehicle
*v
)
2086 CargoID cargo_type
= v
== nullptr ? (CargoID
)CT_INVALID
: v
->cargo_type
;
2087 const Engine
*e
= Engine::Get(engine_type
);
2089 default: NOT_REACHED();
2091 if (v
!= nullptr && parent_engine_type
!= INVALID_ENGINE
&& (UsesWagonOverride(v
) || (v
->IsArticulatedPart() && e
->u
.rail
.railveh_type
!= RAILVEH_WAGON
))) {
2092 /* Wagonoverrides use the colour scheme of the front engine.
2093 * Articulated parts use the colour scheme of the first part. (Not supported for articulated wagons) */
2094 engine_type
= parent_engine_type
;
2095 e
= Engine::Get(engine_type
);
2096 /* Note: Luckily cargo_type is not needed for engines */
2099 if (cargo_type
== CT_INVALID
) cargo_type
= e
->GetDefaultCargoType();
2100 if (cargo_type
== CT_INVALID
) cargo_type
= CT_GOODS
; // The vehicle does not carry anything, let's pick some freight cargo
2101 if (e
->u
.rail
.railveh_type
== RAILVEH_WAGON
) {
2102 if (!CargoSpec::Get(cargo_type
)->is_freight
) {
2103 if (parent_engine_type
== INVALID_ENGINE
) {
2104 return LS_PASSENGER_WAGON_STEAM
;
2106 switch (RailVehInfo(parent_engine_type
)->engclass
) {
2107 default: NOT_REACHED();
2108 case EC_STEAM
: return LS_PASSENGER_WAGON_STEAM
;
2109 case EC_DIESEL
: return LS_PASSENGER_WAGON_DIESEL
;
2110 case EC_ELECTRIC
: return LS_PASSENGER_WAGON_ELECTRIC
;
2111 case EC_MONORAIL
: return LS_PASSENGER_WAGON_MONORAIL
;
2112 case EC_MAGLEV
: return LS_PASSENGER_WAGON_MAGLEV
;
2116 return LS_FREIGHT_WAGON
;
2119 bool is_mu
= HasBit(e
->info
.misc_flags
, EF_RAIL_IS_MU
);
2121 switch (e
->u
.rail
.engclass
) {
2122 default: NOT_REACHED();
2123 case EC_STEAM
: return LS_STEAM
;
2124 case EC_DIESEL
: return is_mu
? LS_DMU
: LS_DIESEL
;
2125 case EC_ELECTRIC
: return is_mu
? LS_EMU
: LS_ELECTRIC
;
2126 case EC_MONORAIL
: return LS_MONORAIL
;
2127 case EC_MAGLEV
: return LS_MAGLEV
;
2132 /* Always use the livery of the front */
2133 if (v
!= nullptr && parent_engine_type
!= INVALID_ENGINE
) {
2134 engine_type
= parent_engine_type
;
2135 e
= Engine::Get(engine_type
);
2136 cargo_type
= v
->First()->cargo_type
;
2138 if (cargo_type
== CT_INVALID
) cargo_type
= e
->GetDefaultCargoType();
2139 if (cargo_type
== CT_INVALID
) cargo_type
= CT_GOODS
; // The vehicle does not carry anything, let's pick some freight cargo
2141 /* Important: Use Tram Flag of front part. Luckily engine_type refers to the front part here. */
2142 if (HasBit(e
->info
.misc_flags
, EF_ROAD_TRAM
)) {
2144 return IsCargoInClass(cargo_type
, CC_PASSENGERS
) ? LS_PASSENGER_TRAM
: LS_FREIGHT_TRAM
;
2147 return IsCargoInClass(cargo_type
, CC_PASSENGERS
) ? LS_BUS
: LS_TRUCK
;
2151 if (cargo_type
== CT_INVALID
) cargo_type
= e
->GetDefaultCargoType();
2152 if (cargo_type
== CT_INVALID
) cargo_type
= CT_GOODS
; // The vehicle does not carry anything, let's pick some freight cargo
2153 return IsCargoInClass(cargo_type
, CC_PASSENGERS
) ? LS_PASSENGER_SHIP
: LS_FREIGHT_SHIP
;
2156 switch (e
->u
.air
.subtype
) {
2157 case AIR_HELI
: return LS_HELICOPTER
;
2158 case AIR_CTOL
: return LS_SMALL_PLANE
;
2159 case AIR_CTOL
| AIR_FAST
: return LS_LARGE_PLANE
;
2160 default: NOT_REACHED();
2166 * Determines the livery for a vehicle.
2167 * @param engine_type EngineID of the vehicle
2168 * @param company Owner of the vehicle
2169 * @param parent_engine_type EngineID of the front vehicle. INVALID_VEHICLE if vehicle is at front itself.
2170 * @param v the vehicle. nullptr if in purchase list etc.
2171 * @param livery_setting The livery settings to use for acquiring the livery information.
2172 * @return livery to use
2174 const Livery
*GetEngineLivery(EngineID engine_type
, CompanyID company
, EngineID parent_engine_type
, const Vehicle
*v
, byte livery_setting
)
2176 const Company
*c
= Company::Get(company
);
2177 LiveryScheme scheme
= LS_DEFAULT
;
2179 /* The default livery is always available for use, but its in_use flag determines
2180 * whether any _other_ liveries are in use. */
2181 if (c
->livery
[LS_DEFAULT
].in_use
&& (livery_setting
== LIT_ALL
|| (livery_setting
== LIT_COMPANY
&& company
== _local_company
))) {
2182 /* Determine the livery scheme to use */
2183 scheme
= GetEngineLiveryScheme(engine_type
, parent_engine_type
, v
);
2185 /* Switch back to the default scheme if the resolved scheme is not in use */
2186 if (!c
->livery
[scheme
].in_use
) scheme
= LS_DEFAULT
;
2189 return &c
->livery
[scheme
];
2193 static PaletteID
GetEngineColourMap(EngineID engine_type
, CompanyID company
, EngineID parent_engine_type
, const Vehicle
*v
)
2195 PaletteID map
= (v
!= nullptr) ? v
->colourmap
: PAL_NONE
;
2197 /* Return cached value if any */
2198 if (map
!= PAL_NONE
) return map
;
2200 const Engine
*e
= Engine::Get(engine_type
);
2202 /* Check if we should use the colour map callback */
2203 if (HasBit(e
->info
.callback_mask
, CBM_VEHICLE_COLOUR_REMAP
)) {
2204 uint16 callback
= GetVehicleCallback(CBID_VEHICLE_COLOUR_MAPPING
, 0, 0, engine_type
, v
);
2205 /* Failure means "use the default two-colour" */
2206 if (callback
!= CALLBACK_FAILED
) {
2207 assert_compile(PAL_NONE
== 0); // Returning 0x4000 (resp. 0xC000) coincidences with default value (PAL_NONE)
2208 map
= GB(callback
, 0, 14);
2209 /* If bit 14 is set, then the company colours are applied to the
2210 * map else it's returned as-is. */
2211 if (!HasBit(callback
, 14)) {
2213 if (v
!= nullptr) const_cast<Vehicle
*>(v
)->colourmap
= map
;
2219 bool twocc
= HasBit(e
->info
.misc_flags
, EF_USES_2CC
);
2221 if (map
== PAL_NONE
) map
= twocc
? (PaletteID
)SPR_2CCMAP_BASE
: (PaletteID
)PALETTE_RECOLOUR_START
;
2223 /* Spectator has news shown too, but has invalid company ID - as well as dedicated server */
2224 if (!Company::IsValidID(company
)) return map
;
2226 const Livery
*livery
= GetEngineLivery(engine_type
, company
, parent_engine_type
, v
, _settings_client
.gui
.liveries
);
2228 map
+= livery
->colour1
;
2229 if (twocc
) map
+= livery
->colour2
* 16;
2232 if (v
!= nullptr) const_cast<Vehicle
*>(v
)->colourmap
= map
;
2237 * Get the colour map for an engine. This used for unbuilt engines in the user interface.
2238 * @param engine_type ID of engine
2239 * @param company ID of company
2240 * @return A ready-to-use palette modifier
2242 PaletteID
GetEnginePalette(EngineID engine_type
, CompanyID company
)
2244 return GetEngineColourMap(engine_type
, company
, INVALID_ENGINE
, nullptr);
2248 * Get the colour map for a vehicle.
2249 * @param v Vehicle to get colour map for
2250 * @return A ready-to-use palette modifier
2252 PaletteID
GetVehiclePalette(const Vehicle
*v
)
2254 if (v
->IsGroundVehicle()) {
2255 return GetEngineColourMap(v
->engine_type
, v
->owner
, v
->GetGroundVehicleCache()->first_engine
, v
);
2258 return GetEngineColourMap(v
->engine_type
, v
->owner
, INVALID_ENGINE
, v
);
2262 * Delete all implicit orders which were not reached.
2264 void Vehicle::DeleteUnreachedImplicitOrders()
2266 if (this->IsGroundVehicle()) {
2267 uint16
&gv_flags
= this->GetGroundVehicleFlags();
2268 if (HasBit(gv_flags
, GVF_SUPPRESS_IMPLICIT_ORDERS
)) {
2269 /* Do not delete orders, only skip them */
2270 ClrBit(gv_flags
, GVF_SUPPRESS_IMPLICIT_ORDERS
);
2271 this->cur_implicit_order_index
= this->cur_real_order_index
;
2272 InvalidateVehicleOrder(this, 0);
2277 const Order
*order
= this->GetOrder(this->cur_implicit_order_index
);
2278 while (order
!= nullptr) {
2279 if (this->cur_implicit_order_index
== this->cur_real_order_index
) break;
2281 if (order
->IsType(OT_IMPLICIT
)) {
2282 DeleteOrder(this, this->cur_implicit_order_index
);
2283 /* DeleteOrder does various magic with order_indices, so resync 'order' with 'cur_implicit_order_index' */
2284 order
= this->GetOrder(this->cur_implicit_order_index
);
2286 /* Skip non-implicit orders, e.g. service-orders */
2287 order
= order
->next
;
2288 this->cur_implicit_order_index
++;
2292 if (order
== nullptr) {
2293 order
= this->GetOrder(0);
2294 this->cur_implicit_order_index
= 0;
2300 * Increase capacity for all link stats associated with vehicles in the given consist.
2301 * @param st Station to get the link stats from.
2302 * @param front First vehicle in the consist.
2303 * @param next_station_id Station the consist will be travelling to next.
2305 static void VehicleIncreaseStats(const Vehicle
*front
)
2307 for (const Vehicle
*v
= front
; v
!= nullptr; v
= v
->Next()) {
2308 StationID last_loading_station
= HasBit(front
->vehicle_flags
, VF_LAST_LOAD_ST_SEP
) ? v
->last_loading_station
: front
->last_loading_station
;
2309 if (v
->refit_cap
> 0 &&
2310 last_loading_station
!= INVALID_STATION
&&
2311 last_loading_station
!= front
->last_station_visited
&&
2312 ((front
->current_order
.GetCargoLoadType(v
->cargo_type
) & OLFB_NO_LOAD
) == 0 ||
2313 (front
->current_order
.GetCargoUnloadType(v
->cargo_type
) & OUFB_NO_UNLOAD
) == 0)) {
2314 /* The cargo count can indeed be higher than the refit_cap if
2315 * wagons have been auto-replaced and subsequently auto-
2316 * refitted to a higher capacity. The cargo gets redistributed
2317 * among the wagons in that case.
2318 * As usage is not such an important figure anyway we just
2319 * ignore the additional cargo then.*/
2320 IncreaseStats(Station::Get(last_loading_station
), v
->cargo_type
, front
->last_station_visited
, v
->refit_cap
,
2321 min(v
->refit_cap
, v
->cargo
.StoredCount()), EUM_INCREASE
);
2327 * Prepare everything to begin the loading when arriving at a station.
2328 * @pre IsTileType(this->tile, MP_STATION) || this->type == VEH_SHIP.
2330 void Vehicle::BeginLoading()
2332 assert(IsTileType(this->tile
, MP_STATION
) || this->type
== VEH_SHIP
);
2334 if (this->current_order
.IsType(OT_GOTO_STATION
) &&
2335 this->current_order
.GetDestination() == this->last_station_visited
) {
2336 this->DeleteUnreachedImplicitOrders();
2338 /* Now both order indices point to the destination station, and we can start loading */
2339 this->current_order
.MakeLoading(true);
2340 UpdateVehicleTimetable(this, true);
2342 /* Furthermore add the Non Stop flag to mark that this station
2343 * is the actual destination of the vehicle, which is (for example)
2344 * necessary to be known for HandleTrainLoading to determine
2345 * whether the train is lost or not; not marking a train lost
2346 * that arrives at random stations is bad. */
2347 this->current_order
.SetNonStopType(ONSF_NO_STOP_AT_ANY_STATION
);
2350 /* We weren't scheduled to stop here. Insert an implicit order
2351 * to show that we are stopping here.
2352 * While only groundvehicles have implicit orders, e.g. aircraft might still enter
2353 * the 'wrong' terminal when skipping orders etc. */
2354 Order
*in_list
= this->GetOrder(this->cur_implicit_order_index
);
2355 if (this->IsGroundVehicle() &&
2356 (in_list
== nullptr || !in_list
->IsType(OT_IMPLICIT
) ||
2357 in_list
->GetDestination() != this->last_station_visited
)) {
2358 bool suppress_implicit_orders
= HasBit(this->GetGroundVehicleFlags(), GVF_SUPPRESS_IMPLICIT_ORDERS
);
2359 /* Do not create consecutive duplicates of implicit orders */
2360 Order
*prev_order
= this->cur_implicit_order_index
> 0 ? this->GetOrder(this->cur_implicit_order_index
- 1) : (this->GetNumOrders() > 1 ? this->GetLastOrder() : nullptr);
2361 if (prev_order
== nullptr ||
2362 (!prev_order
->IsType(OT_IMPLICIT
) && !prev_order
->IsType(OT_GOTO_STATION
)) ||
2363 prev_order
->GetDestination() != this->last_station_visited
) {
2365 /* Prefer deleting implicit orders instead of inserting new ones,
2366 * so test whether the right order follows later. In case of only
2367 * implicit orders treat the last order in the list like an
2368 * explicit one, except if the overall number of orders surpasses
2369 * IMPLICIT_ORDER_ONLY_CAP. */
2370 int target_index
= this->cur_implicit_order_index
;
2372 while (target_index
!= this->cur_real_order_index
|| this->GetNumManualOrders() == 0) {
2373 const Order
*order
= this->GetOrder(target_index
);
2374 if (order
== nullptr) break; // No orders.
2375 if (order
->IsType(OT_IMPLICIT
) && order
->GetDestination() == this->last_station_visited
) {
2380 if (target_index
>= this->GetNumOrders()) {
2381 if (this->GetNumManualOrders() == 0 &&
2382 this->GetNumOrders() < IMPLICIT_ORDER_ONLY_CAP
) {
2387 if (target_index
== this->cur_implicit_order_index
) break; // Avoid infinite loop.
2391 if (suppress_implicit_orders
) {
2392 /* Skip to the found order */
2393 this->cur_implicit_order_index
= target_index
;
2394 InvalidateVehicleOrder(this, 0);
2396 /* Delete all implicit orders up to the station we just reached */
2397 const Order
*order
= this->GetOrder(this->cur_implicit_order_index
);
2398 while (!order
->IsType(OT_IMPLICIT
) || order
->GetDestination() != this->last_station_visited
) {
2399 if (order
->IsType(OT_IMPLICIT
)) {
2400 DeleteOrder(this, this->cur_implicit_order_index
);
2401 /* DeleteOrder does various magic with order_indices, so resync 'order' with 'cur_implicit_order_index' */
2402 order
= this->GetOrder(this->cur_implicit_order_index
);
2404 /* Skip non-implicit orders, e.g. service-orders */
2405 order
= order
->next
;
2406 this->cur_implicit_order_index
++;
2410 if (order
== nullptr) {
2411 order
= this->GetOrder(0);
2412 this->cur_implicit_order_index
= 0;
2414 assert(order
!= nullptr);
2417 } else if (!suppress_implicit_orders
&&
2418 (this->HasOrdersList() ? this->GetNumOrders() < MAX_VEH_ORDER_ID
: OrderList::CanAllocateItem()) &&
2419 Order::CanAllocateItem()) {
2420 /* Insert new implicit order */
2421 Order
*implicit_order
= new Order();
2422 implicit_order
->MakeImplicit(this->last_station_visited
);
2423 InsertOrder(this, implicit_order
, this->cur_implicit_order_index
);
2424 if (this->cur_implicit_order_index
> 0) --this->cur_implicit_order_index
;
2426 /* InsertOrder disabled creation of implicit orders for all vehicles with the same implicit order.
2427 * Reenable it for this vehicle */
2428 uint16
&gv_flags
= this->GetGroundVehicleFlags();
2429 ClrBit(gv_flags
, GVF_SUPPRESS_IMPLICIT_ORDERS
);
2434 this->current_order
.MakeLoading(false);
2437 this->HandleAutomaticTimetableSeparation();
2439 VehicleIncreaseStats(this);
2441 PrepareUnload(this);
2443 SetWindowDirty(GetWindowClassForVehicleType(this->type
), this->owner
);
2444 SetWindowWidgetDirty(WC_VEHICLE_VIEW
, this->index
, WID_VV_START_STOP
);
2445 SetWindowDirty(WC_VEHICLE_DETAILS
, this->index
);
2446 SetWindowDirty(WC_STATION_VIEW
, this->last_station_visited
);
2448 Station::Get(this->last_station_visited
)->MarkTilesDirty(true);
2449 this->cur_speed
= 0;
2454 * Handles the automatic timetable separation from initialization to setting of the lateness counter at the correct first order.
2456 void Vehicle::HandleAutomaticTimetableSeparation()
2458 /* If all requirements for separation are met, we can initialize it. */
2459 if (!_settings_game
.order
.automatic_timetable_separation
) return;
2460 if (!this->HasSharedOrdersList()) return;
2461 assert(this->orders
.list
!= nullptr);
2462 if (!this->orders
.list
->IsCompleteTimetable()) return;
2464 int first_wait_index
= -1;
2466 for (int i
= 0; i
< this->GetNumOrders(); ++i
) {
2467 Order
* order
= this->orders
.list
->GetOrderAt(i
);
2469 if (order
->IsWaitTimetabled() && !order
->IsType(OT_IMPLICIT
)) {
2470 first_wait_index
= i
;
2475 if (this->cur_implicit_order_index
!= first_wait_index
) return;
2477 if (!this->orders
.list
->IsSeparationValid()) {
2478 this->orders
.list
->InitializeSeparation();
2479 this->lateness_counter
= this->orders
.list
->SeparateVehicle();
2480 SetWindowDirty(WC_VEHICLE_TIMETABLE
, this->index
);
2484 this->lateness_counter
= this->orders
.list
->SeparateVehicle();
2486 if (this->lateness_counter
> 0)
2488 this->orders
.list
->InitializeSeparation();
2489 this->lateness_counter
= this->orders
.list
->SeparateVehicle();
2490 SetWindowDirty(WC_VEHICLE_TIMETABLE
, this->index
);
2494 SetBit(this->vehicle_flags
, VF_SEPARATION_IN_PROGRESS
);
2498 * Return all reserved cargo packets to the station and reset all packets
2499 * staged for transfer.
2500 * @param st the station where the reserved packets should go.
2502 void Vehicle::CancelReservation(StationID next
, Station
*st
)
2504 for (Vehicle
*v
= this; v
!= nullptr; v
= v
->next
) {
2505 VehicleCargoList
&cargo
= v
->cargo
;
2506 if (cargo
.ActionCount(VehicleCargoList::MTA_LOAD
) > 0) {
2507 DEBUG(misc
, 1, "cancelling cargo reservation");
2508 cargo
.Return(UINT_MAX
, &st
->goods
[v
->cargo_type
].cargo
, next
);
2509 cargo
.SetTransferLoadPlace(st
->xy
);
2515 uint32
Vehicle::GetLastLoadingStationValidCargoMask() const
2517 if (!HasBit(this->vehicle_flags
, VF_LAST_LOAD_ST_SEP
)) {
2518 return (this->last_loading_station
!= INVALID_STATION
) ? ~0 : 0;
2520 uint32 cargo_mask
= 0;
2521 for (const Vehicle
*u
= this; u
!= nullptr; u
= u
->Next()) {
2522 if (u
->cargo_type
< NUM_CARGO
&& u
->last_loading_station
!= INVALID_STATION
) {
2523 SetBit(cargo_mask
, u
->cargo_type
);
2531 * Perform all actions when leaving a station.
2532 * @pre this->current_order.IsType(OT_LOADING)
2534 void Vehicle::LeaveStation()
2536 assert(this->current_order
.IsType(OT_LOADING
));
2538 delete this->cargo_payment
;
2539 assert(this->cargo_payment
== nullptr); // cleared by ~CargoPayment
2541 /* Only update the timetable if the vehicle was supposed to stop here. */
2542 if (this->current_order
.GetNonStopType() != ONSF_STOP_EVERYWHERE
) UpdateVehicleTimetable(this, false);
2544 uint32 cargoes_can_load_unload
= this->current_order
.FilterLoadUnloadTypeCargoMask([&](const Order
*o
, CargoID cargo
) {
2545 return ((o
->GetCargoLoadType(cargo
) & OLFB_NO_LOAD
) == 0) || ((o
->GetCargoUnloadType(cargo
) & OUFB_NO_UNLOAD
) == 0);
2547 uint32 has_cargo_mask
= this->GetLastLoadingStationValidCargoMask();
2548 uint32 cargoes_can_leave_with_cargo
= FilterCargoMask([&](CargoID cargo
) {
2549 return this->current_order
.CanLeaveWithCargo(HasBit(has_cargo_mask
, cargo
), cargo
);
2550 }, cargoes_can_load_unload
);
2552 if (cargoes_can_load_unload
!= 0) {
2553 if (cargoes_can_leave_with_cargo
!= 0) {
2554 /* Refresh next hop stats to make sure we've done that at least once
2555 * during the stop and that refit_cap == cargo_cap for each vehicle in
2557 this->ResetRefitCaps();
2558 LinkRefresher::Run(this, true, false, cargoes_can_leave_with_cargo
);
2561 if (cargoes_can_leave_with_cargo
== (uint32
) ~0) {
2562 /* can leave with all cargoes */
2564 /* if the vehicle could load here or could stop with cargo loaded set the last loading station */
2565 this->last_loading_station
= this->last_station_visited
;
2566 ClrBit(this->vehicle_flags
, VF_LAST_LOAD_ST_SEP
);
2567 } else if (cargoes_can_leave_with_cargo
== 0) {
2568 /* can leave with no cargoes */
2570 /* if the vehicle couldn't load and had to unload or transfer everything
2571 * set the last loading station to invalid as it will leave empty. */
2572 this->last_loading_station
= INVALID_STATION
;
2573 ClrBit(this->vehicle_flags
, VF_LAST_LOAD_ST_SEP
);
2575 /* mix of cargoes loadable or could not leave with all cargoes */
2577 /* NB: this is saved here as we overwrite it on the first iteration of the loop below */
2578 StationID head_last_loading_station
= this->last_loading_station
;
2579 for (Vehicle
*u
= this; u
!= nullptr; u
= u
->Next()) {
2580 StationID last_loading_station
= HasBit(this->vehicle_flags
, VF_LAST_LOAD_ST_SEP
) ? u
->last_loading_station
: head_last_loading_station
;
2581 if (u
->cargo_type
< NUM_CARGO
&& HasBit(cargoes_can_load_unload
, u
->cargo_type
)) {
2582 if (HasBit(cargoes_can_leave_with_cargo
, u
->cargo_type
)) {
2583 u
->last_loading_station
= this->last_station_visited
;
2585 u
->last_loading_station
= INVALID_STATION
;
2588 u
->last_loading_station
= last_loading_station
;
2591 SetBit(this->vehicle_flags
, VF_LAST_LOAD_ST_SEP
);
2596 int last_loading_order_index
= -1;
2598 // Reverse iterate through the orders list and find the first (i.e. last) order that is of loading type.
2599 for (int i
= this->GetNumOrders() - 1; i
>= 0; --i
) {
2600 Order
* order
= this->orders
.list
->GetOrderAt(i
);
2602 bool can_load_or_unload
= false;
2604 if ((order
->IsType(OT_GOTO_STATION
) || order
->IsType(OT_IMPLICIT
)) &&
2605 (order
->GetNonStopType() & ONSF_NO_STOP_AT_DESTINATION_STATION
) == 0) {
2606 if (order
->GetUnloadType() == OUFB_CARGO_TYPE_UNLOAD
|| order
->GetLoadType() == OLFB_CARGO_TYPE_LOAD
) {
2607 can_load_or_unload
= true;
2608 } else if ((order
->GetLoadType() & OLFB_NO_LOAD
) == 0 || (order
->GetUnloadType() & OUFB_NO_UNLOAD
) == 0) {
2609 can_load_or_unload
= true;
2613 if (can_load_or_unload
&& !(order
->GetLoadType() & OLFB_NO_LOAD
)) {
2614 last_loading_order_index
= i
;
2619 if (last_loading_order_index
>= 0 && last_loading_order_index
< this->GetNumOrders()) {
2621 Order
* current_real_order
= this->orders
.list
->GetOrderAt(this->cur_real_order_index
);
2623 bool can_load_or_unload
= false;
2625 if ((current_real_order
->IsType(OT_GOTO_STATION
) || current_real_order
->IsType(OT_IMPLICIT
)) &&
2626 (current_real_order
->GetNonStopType() & ONSF_NO_STOP_AT_DESTINATION_STATION
) == 0) {
2627 if (current_real_order
->GetUnloadType() == OUFB_CARGO_TYPE_UNLOAD
|| current_real_order
->GetLoadType() == OLFB_CARGO_TYPE_LOAD
) {
2628 can_load_or_unload
= true;
2630 else if ((current_real_order
->GetLoadType() & OLFB_NO_LOAD
) == 0 || (current_real_order
->GetUnloadType() & OUFB_NO_UNLOAD
) == 0) {
2631 can_load_or_unload
= true;
2635 bool current_order_was_load_order
= (can_load_or_unload
&& !(current_real_order
->GetLoadType() & OLFB_NO_LOAD
));
2637 if (current_order_was_load_order
)
2639 int8 occupancy
= (int8
)CalcPercentVehicleFilled(this, nullptr);
2640 station_occupancies
.push_back(occupancy
);
2643 bool wasLastLoadingOrder
= this->cur_real_order_index
== last_loading_order_index
;
2645 if (wasLastLoadingOrder
)
2647 if (station_occupancies
.size() != 0)
2651 std::vector
<int8
>::const_iterator it
;
2652 for (it
= station_occupancies
.begin(); it
!= station_occupancies
.end(); ++it
)
2655 station_occupancies
.clear();
2660 station_occupancies
.clear();
2663 this->current_order
.MakeLeaveStation();
2664 Station
*st
= Station::Get(this->last_station_visited
);
2665 this->CancelReservation(INVALID_STATION
, st
);
2666 st
->loading_vehicles
.erase(std::remove(st
->loading_vehicles
.begin(), st
->loading_vehicles
.end(), this), st
->loading_vehicles
.end());
2668 HideFillingPercent(&this->fill_percent_te_id
);
2670 if (this->type
== VEH_TRAIN
&& !(this->vehstatus
& VS_CRASHED
)) {
2671 /* Trigger station animation (trains only) */
2672 if (IsTileType(this->tile
, MP_STATION
)) {
2673 TriggerStationRandomisation(st
, this->tile
, SRT_TRAIN_DEPARTS
);
2674 TriggerStationAnimation(st
, this->tile
, SAT_TRAIN_DEPARTS
);
2677 SetBit(Train::From(this)->flags
, VRF_LEAVING_STATION
);
2684 * Reset all refit_cap in the consist to cargo_cap.
2686 void Vehicle::ResetRefitCaps()
2688 for (Vehicle
*v
= this; v
!= nullptr; v
= v
->Next()) v
->refit_cap
= v
->cargo_cap
;
2692 * Handle the loading of the vehicle; when not it skips through dummy
2693 * orders and does nothing in all other cases.
2694 * @param mode is the non-first call for this vehicle in this tick?
2696 void Vehicle::HandleLoading(bool mode
)
2698 OrderType current_order_type
= this->current_order
.GetType();
2700 switch (current_order_type
) {
2702 uint wait_time
= max(this->current_order
.GetTimetabledWait() - this->lateness_counter
, 0);
2704 /* Save time just loading took since that is what goes into the timetable */
2705 if (!HasBit(this->vehicle_flags
, VF_LOADING_FINISHED
)) {
2706 this->current_loading_time
= this->current_order_time
;
2709 bool has_manual_depot_order
= (HasBit(this->vehicle_flags
, VF_SHOULD_GOTO_DEPOT
) || HasBit(this->vehicle_flags
, VF_SHOULD_SERVICE_AT_DEPOT
));
2711 if (!has_manual_depot_order
) {
2712 /* Not the first call for this tick, or still loading */
2713 if (mode
|| !HasBit(this->vehicle_flags
, VF_LOADING_FINISHED
) || this->current_order_time
< wait_time
) return;
2716 this->PlayLeaveStationSound();
2718 this->LeaveStation();
2720 /* Only advance to next order if we just loaded at the current one */
2721 const Order
*order
= this->GetOrder(this->cur_implicit_order_index
);
2722 if (order
== nullptr ||
2723 (!order
->IsType(OT_IMPLICIT
) && !order
->IsType(OT_GOTO_STATION
)) ||
2724 order
->GetDestination() != this->last_station_visited
) {
2730 case OT_DUMMY
: break;
2735 this->IncrementImplicitOrderIndex();
2739 * Handle the waiting time everywhere else as in stations (basically in depot but, eventually, also elsewhere ?)
2740 * Function is called when order's wait_time is defined.
2741 * @param stop_waiting should we stop waiting (or definitely avoid) even if there is still time left to wait ?
2743 void Vehicle::HandleWaiting(bool stop_waiting
)
2745 switch (this->current_order
.GetType()) {
2747 uint wait_time
= max(this->current_order
.GetTimetabledWait() - this->lateness_counter
, 0);
2748 /* Vehicles holds on until waiting Timetabled time expires. */
2749 if (!stop_waiting
&& this->current_order_time
< wait_time
) {
2753 /* When wait_time is expired, we move on. */
2754 UpdateVehicleTimetable(this, false);
2755 this->IncrementImplicitOrderIndex();
2756 this->current_order
.MakeDummy();
2766 * Send this vehicle to the depot using the given command(s).
2767 * @param flags the command flags (like execute and such).
2768 * @param command the command to execute.
2769 * @return the cost of the depot action.
2771 CommandCost
Vehicle::SendToDepot(DoCommandFlag flags
, DepotCommand command
)
2773 CommandCost ret
= CheckOwnership(this->owner
);
2774 if (ret
.Failed()) return ret
;
2776 if (this->vehstatus
& VS_CRASHED
) return CommandError();
2777 if (this->IsStoppedInDepot()) return CommandError();
2779 if (HasBit(this->vehicle_flags
, VF_SHOULD_GOTO_DEPOT
) || HasBit(this->vehicle_flags
, VF_SHOULD_SERVICE_AT_DEPOT
)) {
2780 bool service_only
= (command
& DEPOT_SERVICE
) != 0;
2781 if (service_only
!= HasBit(this->vehicle_flags
, VF_SHOULD_SERVICE_AT_DEPOT
)) {
2782 /* We called with a different DEPOT_SERVICE setting.
2783 * Now we change the setting to apply the new one and let the vehicle head for the same depot.
2784 * Note: the if is (true for requesting service == true for ordered to stop in depot) */
2785 if (flags
& DC_EXEC
) {
2786 ClrBit(this->vehicle_flags
, VF_SHOULD_GOTO_DEPOT
);
2787 ClrBit(this->vehicle_flags
, VF_SHOULD_SERVICE_AT_DEPOT
);
2788 SetBit(this->vehicle_flags
, service_only
? VF_SHOULD_SERVICE_AT_DEPOT
: VF_SHOULD_GOTO_DEPOT
);
2789 SetWindowWidgetDirty(WC_VEHICLE_VIEW
, this->index
, WID_VV_START_STOP
);
2791 return CommandCost();
2794 if (command
& DEPOT_DONT_CANCEL
) return CommandError(); // Requested no cancellation of depot orders
2796 if(flags
& DC_EXEC
) {
2797 ClrBit(this->vehicle_flags
, VF_SHOULD_GOTO_DEPOT
);
2798 ClrBit(this->vehicle_flags
, VF_SHOULD_SERVICE_AT_DEPOT
);
2799 SetWindowWidgetDirty(WC_VEHICLE_VIEW
, this->index
, WID_VV_START_STOP
);
2801 // Prevent any attempt to update timetable for current order, as actual travel time
2802 // will be incorrect due to depot command.
2803 this->cur_timetable_order_index
= INVALID_VEH_ORDER_ID
;
2805 return CommandCost();
2807 } else if (this->current_order
.IsType(OT_GOTO_DEPOT
) && this->current_order
.GetDepotOrderType() == ODTF_MANUAL
) {
2808 bool current_service_only
= !((this->current_order
.GetDepotActionType() & ODATFB_HALT
) != 0);
2809 bool service_only
= (command
& DEPOT_SERVICE
) != 0;
2810 if (current_service_only
!= service_only
) {
2811 /* We called with a different DEPOT_SERVICE setting.
2812 * Now we change the setting to apply the new one and let the vehicle head for the same depot.
2813 * Note: the if is (true for requesting service == true for ordered to stop in depot) */
2814 if (flags
& DC_EXEC
) {
2815 this->current_order
.SetDepotOrderType(ODTF_MANUAL
);
2816 this->current_order
.SetDepotActionType(service_only
? ODATF_SERVICE_ONLY
: ODATFB_HALT
);
2817 SetWindowWidgetDirty(WC_VEHICLE_VIEW
, this->index
, WID_VV_START_STOP
);
2819 return CommandCost();
2822 if (command
& DEPOT_DONT_CANCEL
) return CommandError(); // Requested no cancellation of depot orders
2823 if (flags
& DC_EXEC
) {
2824 /* If the orders to 'goto depot' are in the orders list (forced servicing),
2825 * then skip to the next order; effectively canceling this forced service */
2826 if (this->current_order
.GetDepotOrderType() & ODTFB_PART_OF_ORDERS
) this->IncrementRealOrderIndex();
2828 if (this->IsGroundVehicle()) {
2829 uint16
&gv_flags
= this->GetGroundVehicleFlags();
2830 SetBit(gv_flags
, GVF_SUPPRESS_IMPLICIT_ORDERS
);
2833 this->current_order
.MakeDummy();
2834 SetWindowWidgetDirty(WC_VEHICLE_VIEW
, this->index
, WID_VV_START_STOP
);
2836 // Prevent any attempt to update timetable for current order, as actual travel time
2837 // will be incorrect due to depot command.
2838 this->cur_timetable_order_index
= INVALID_VEH_ORDER_ID
;
2841 return CommandCost();
2845 DestinationID destination
;
2847 bool foundDepotInOrders
= false;
2848 static const StringID no_depot
[] = {STR_ERROR_UNABLE_TO_FIND_ROUTE_TO
, STR_ERROR_UNABLE_TO_FIND_LOCAL_DEPOT
, STR_ERROR_UNABLE_TO_FIND_LOCAL_DEPOT
, STR_ERROR_CAN_T_SEND_AIRCRAFT_TO_HANGAR
};
2850 // Check first if the vehicle has any depot in its order list. Prefer that over the closest one.
2852 for (int i
= 0; i
< this->GetNumOrders(); ++i
) {
2853 Order
* order
= this->orders
.list
->GetOrderAt(i
);
2855 bool isRegularOrder
= (order
->GetDepotOrderType() & ODTFB_PART_OF_ORDERS
) != 0;
2856 bool isDepotOrder
= order
->GetType() == OT_GOTO_DEPOT
;
2858 if (isRegularOrder
&& isDepotOrder
) {
2859 destination
= order
->GetDestination();
2860 if (this->type
== VEH_AIRCRAFT
) {
2861 Station
* st
= Station::Get(destination
);
2862 if (st
!= nullptr && st
->airport
.HasHangar() && CanVehicleUseStation(this, st
)) {
2864 foundDepotInOrders
= true;
2869 location
= Depot::Get(destination
)->xy
;
2871 foundDepotInOrders
= true;
2877 if (!foundDepotInOrders
&& !this->FindClosestDepot(&location
, &destination
, &reverse
)) return CommandError(no_depot
[this->type
]);
2879 if (flags
& DC_EXEC
) {
2880 if (!(foundDepotInOrders
&& this->type
== VEH_TRAIN
)) {
2881 // The OT_LOADING status of trains with depots in their order list will be handled separately in the HandleLoading() method.
2882 if (this->current_order
.IsType(OT_LOADING
)) this->LeaveStation();
2885 if (this->IsGroundVehicle() && this->GetNumManualOrders() > 0) {
2886 uint16
&gv_flags
= this->GetGroundVehicleFlags();
2887 SetBit(gv_flags
, GVF_SUPPRESS_IMPLICIT_ORDERS
);
2890 if (foundDepotInOrders
&& this->type
== VEH_TRAIN
) {
2891 bool service_only
= (command
& DEPOT_SERVICE
) != 0;
2892 SetBit(this->vehicle_flags
, service_only
? VF_SHOULD_SERVICE_AT_DEPOT
: VF_SHOULD_GOTO_DEPOT
);
2893 SetWindowWidgetDirty(WC_VEHICLE_VIEW
, this->index
, WID_VV_START_STOP
);
2895 this->dest_tile
= location
;
2896 this->current_order
.MakeGoToDepot(destination
, ODTF_MANUAL
);
2898 if (!(command
& DEPOT_SERVICE
)) this->current_order
.SetDepotActionType(ODATFB_HALT
);
2899 SetWindowWidgetDirty(WC_VEHICLE_VIEW
, this->index
, WID_VV_START_STOP
);
2901 /* If there is no depot in front, reverse automatically (trains only) */
2902 if (this->type
== VEH_TRAIN
&& reverse
) DoCommand(this->tile
, this->index
, 0, DC_EXEC
, CMD_REVERSE_TRAIN_DIRECTION
);
2904 if (this->type
== VEH_AIRCRAFT
) {
2905 Aircraft
*a
= Aircraft::From(this);
2906 if (a
->state
== FLYING
&& a
->targetairport
!= destination
) {
2907 /* The aircraft is now heading for a different hangar than the next in the orders */
2908 extern void AircraftNextAirportPos_and_Order(Aircraft
*a
);
2909 AircraftNextAirportPos_and_Order(a
);
2915 return CommandCost();
2920 * Update the cached visual effect.
2921 * @param allow_power_change true if the wagon-is-powered-state may change.
2923 void Vehicle::UpdateVisualEffect(bool allow_power_change
)
2925 bool powered_before
= HasBit(this->vcache
.cached_vis_effect
, VE_DISABLE_WAGON_POWER
);
2926 const Engine
*e
= this->GetEngine();
2928 /* Evaluate properties */
2931 case VEH_TRAIN
: visual_effect
= e
->u
.rail
.visual_effect
; break;
2932 case VEH_ROAD
: visual_effect
= e
->u
.road
.visual_effect
; break;
2933 case VEH_SHIP
: visual_effect
= e
->u
.ship
.visual_effect
; break;
2934 default: visual_effect
= 1 << VE_DISABLE_EFFECT
; break;
2937 /* Check powered wagon / visual effect callback */
2938 if (HasBit(e
->info
.callback_mask
, CBM_VEHICLE_VISUAL_EFFECT
) && !HasBit(this->subtype
, GVSF_VIRTUAL
)) {
2939 uint16 callback
= GetVehicleCallback(CBID_VEHICLE_VISUAL_EFFECT
, 0, 0, this->engine_type
, this);
2941 if (callback
!= CALLBACK_FAILED
) {
2942 if (callback
>= 0x100 && e
->GetGRF()->grf_version
>= 8) ErrorUnknownCallbackResult(e
->GetGRFID(), CBID_VEHICLE_VISUAL_EFFECT
, callback
);
2944 callback
= GB(callback
, 0, 8);
2945 /* Avoid accidentally setting 'visual_effect' to the default value
2946 * Since bit 6 (disable effects) is set anyways, we can safely erase some bits. */
2947 if (callback
== VE_DEFAULT
) {
2948 assert(HasBit(callback
, VE_DISABLE_EFFECT
));
2949 SB(callback
, VE_TYPE_START
, VE_TYPE_COUNT
, 0);
2951 visual_effect
= callback
;
2955 /* Apply default values */
2956 if (visual_effect
== VE_DEFAULT
||
2957 (!HasBit(visual_effect
, VE_DISABLE_EFFECT
) && GB(visual_effect
, VE_TYPE_START
, VE_TYPE_COUNT
) == VE_TYPE_DEFAULT
)) {
2958 /* Only train engines have default effects.
2959 * Note: This is independent of whether the engine is a front engine or articulated part or whatever. */
2960 if (e
->type
!= VEH_TRAIN
|| e
->u
.rail
.railveh_type
== RAILVEH_WAGON
|| !IsInsideMM(e
->u
.rail
.engclass
, EC_STEAM
, EC_MONORAIL
)) {
2961 if (visual_effect
== VE_DEFAULT
) {
2962 visual_effect
= 1 << VE_DISABLE_EFFECT
;
2964 SetBit(visual_effect
, VE_DISABLE_EFFECT
);
2967 if (visual_effect
== VE_DEFAULT
) {
2968 /* Also set the offset */
2969 visual_effect
= (VE_OFFSET_CENTRE
- (e
->u
.rail
.engclass
== EC_STEAM
? 4 : 0)) << VE_OFFSET_START
;
2971 SB(visual_effect
, VE_TYPE_START
, VE_TYPE_COUNT
, e
->u
.rail
.engclass
- EC_STEAM
+ VE_TYPE_STEAM
);
2975 this->vcache
.cached_vis_effect
= visual_effect
;
2977 if (!allow_power_change
&& powered_before
!= HasBit(this->vcache
.cached_vis_effect
, VE_DISABLE_WAGON_POWER
)) {
2978 ToggleBit(this->vcache
.cached_vis_effect
, VE_DISABLE_WAGON_POWER
);
2979 ShowNewGrfVehicleError(this->engine_type
, STR_NEWGRF_BROKEN
, STR_NEWGRF_BROKEN_POWERED_WAGON
, GBUG_VEH_POWERED_WAGON
, false);
2983 static const int8 _vehicle_smoke_pos
[8] = {
2984 1, 1, 1, 0, -1, -1, -1, 0
2988 * Call CBID_VEHICLE_SPAWN_VISUAL_EFFECT and spawn requested effects.
2989 * @param v Vehicle to create effects for.
2991 static void SpawnAdvancedVisualEffect(const Vehicle
*v
)
2993 uint16 callback
= GetVehicleCallback(CBID_VEHICLE_SPAWN_VISUAL_EFFECT
, 0, Random(), v
->engine_type
, v
);
2994 if (callback
== CALLBACK_FAILED
) return;
2996 uint count
= GB(callback
, 0, 2);
2997 bool auto_center
= HasBit(callback
, 13);
2998 bool auto_rotate
= !HasBit(callback
, 14);
3002 /* For road vehicles: Compute offset from vehicle position to vehicle center */
3003 if (v
->type
== VEH_ROAD
) l_center
= -(int)(VEHICLE_LENGTH
- RoadVehicle::From(v
)->gcache
.cached_veh_length
) / 2;
3005 /* For trains: Compute offset from vehicle position to sprite position */
3006 if (v
->type
== VEH_TRAIN
) l_center
= (VEHICLE_LENGTH
- Train::From(v
)->gcache
.cached_veh_length
) / 2;
3009 Direction l_dir
= v
->direction
;
3010 if (v
->type
== VEH_TRAIN
&& HasBit(Train::From(v
)->flags
, VRF_REVERSE_DIRECTION
)) l_dir
= ReverseDir(l_dir
);
3011 Direction t_dir
= ChangeDir(l_dir
, DIRDIFF_90RIGHT
);
3013 int8 x_center
= _vehicle_smoke_pos
[l_dir
] * l_center
;
3014 int8 y_center
= _vehicle_smoke_pos
[t_dir
] * l_center
;
3016 for (uint i
= 0; i
< count
; i
++) {
3017 uint32 reg
= GetRegister(0x100 + i
);
3018 uint type
= GB(reg
, 0, 8);
3019 int8 x
= GB(reg
, 8, 8);
3020 int8 y
= GB(reg
, 16, 8);
3021 int8 z
= GB(reg
, 24, 8);
3026 x
= _vehicle_smoke_pos
[l_dir
] * l
+ _vehicle_smoke_pos
[t_dir
] * t
;
3027 y
= _vehicle_smoke_pos
[t_dir
] * l
- _vehicle_smoke_pos
[l_dir
] * t
;
3032 case 0xF1: CreateEffectVehicleRel(v
, x_center
+ x
, y_center
+ y
, z
, EV_STEAM_SMOKE
); break;
3033 case 0xF2: CreateEffectVehicleRel(v
, x_center
+ x
, y_center
+ y
, z
, EV_DIESEL_SMOKE
); break;
3034 case 0xF3: CreateEffectVehicleRel(v
, x_center
+ x
, y_center
+ y
, z
, EV_ELECTRIC_SPARK
); break;
3035 case 0xFA: CreateEffectVehicleRel(v
, x_center
+ x
, y_center
+ y
, z
, EV_BREAKDOWN_SMOKE_AIRCRAFT
); break;
3042 uint16
ReversingDistanceTargetSpeed(const Train
*v
);
3045 * Draw visual effects (smoke and/or sparks) for a vehicle chain.
3046 * @pre this->IsPrimaryVehicle()
3048 void Vehicle::ShowVisualEffect() const
3050 assert(this->IsPrimaryVehicle());
3053 /* Do not show any smoke when:
3054 * - vehicle smoke is disabled by the player
3055 * - the vehicle is slowing down or stopped (by the player)
3056 * - the vehicle is moving very slowly
3058 if (_settings_game
.vehicle
.smoke_amount
== 0 ||
3059 this->vehstatus
& (VS_TRAIN_SLOWING
| VS_STOPPED
) ||
3060 this->cur_speed
< 2) {
3064 /* Use the speed as limited by underground and orders. */
3065 uint max_speed
= this->GetCurrentMaxSpeed();
3067 if (this->type
== VEH_TRAIN
) {
3068 const Train
*t
= Train::From(this);
3069 /* For trains, do not show any smoke when:
3070 * - the train is reversing
3071 * - is entering a station with an order to stop there and its speed is equal to maximum station entering speed
3072 * - is approaching a reversing point and its speed is equal to maximum approach speed
3074 if (HasBit(t
->flags
, VRF_REVERSING
) ||
3075 (IsRailStationTile(t
->tile
) && t
->IsFrontEngine() && t
->current_order
.ShouldStopAtStation(t
, GetStationIndex(t
->tile
)) &&
3076 t
->cur_speed
>= max_speed
) ||
3077 (t
->reverse_distance
>= 1 && t
->cur_speed
>= ReversingDistanceTargetSpeed(t
))) {
3082 const Vehicle
*v
= this;
3085 bool advanced
= HasBit(v
->vcache
.cached_vis_effect
, VE_ADVANCED_EFFECT
);
3086 int effect_offset
= GB(v
->vcache
.cached_vis_effect
, VE_OFFSET_START
, VE_OFFSET_COUNT
) - VE_OFFSET_CENTRE
;
3087 VisualEffectSpawnModel effect_model
= VESM_NONE
;
3089 effect_offset
= VE_OFFSET_CENTRE
;
3090 effect_model
= (VisualEffectSpawnModel
)GB(v
->vcache
.cached_vis_effect
, 0, VE_ADVANCED_EFFECT
);
3091 if (effect_model
>= VESM_END
) effect_model
= VESM_NONE
; // unknown spawning model
3093 effect_model
= (VisualEffectSpawnModel
)GB(v
->vcache
.cached_vis_effect
, VE_TYPE_START
, VE_TYPE_COUNT
);
3094 assert(effect_model
!= (VisualEffectSpawnModel
)VE_TYPE_DEFAULT
); // should have been resolved by UpdateVisualEffect
3095 assert_compile((uint
)VESM_STEAM
== (uint
)VE_TYPE_STEAM
);
3096 assert_compile((uint
)VESM_DIESEL
== (uint
)VE_TYPE_DIESEL
);
3097 assert_compile((uint
)VESM_ELECTRIC
== (uint
)VE_TYPE_ELECTRIC
);
3100 /* Show no smoke when:
3101 * - Smoke has been disabled for this vehicle
3102 * - The vehicle is not visible
3103 * - The vehicle is under a bridge
3104 * - The vehicle is on a depot tile
3105 * - The vehicle is on a tunnel tile
3106 * - The vehicle is a train engine that is currently unpowered */
3107 if (effect_model
== VESM_NONE
||
3108 v
->vehstatus
& VS_HIDDEN
||
3109 IsBridgeAbove(v
->tile
) ||
3110 IsDepotTile(v
->tile
) ||
3111 IsTunnelTile(v
->tile
) ||
3112 (v
->type
== VEH_TRAIN
&&
3113 !HasPowerOnRail(Train::From(v
)->railtype
, GetTileRailType(v
->tile
)))) {
3117 EffectVehicleType evt
= EV_END
;
3118 switch (effect_model
) {
3120 /* Steam smoke - amount is gradually falling until vehicle reaches its maximum speed, after that it's normal.
3121 * Details: while vehicle's current speed is gradually increasing, steam plumes' density decreases by one third each
3122 * third of its maximum speed spectrum. Steam emission finally normalises at very close to vehicle's maximum speed.
3124 * - instead of 1, 4 / 2^smoke_amount (max. 2) is used to provide sufficient regulation to steam puffs' amount. */
3125 if (GB(v
->tick_counter
, 0, ((4 >> _settings_game
.vehicle
.smoke_amount
) + ((this->cur_speed
* 3) / max_speed
))) == 0) {
3126 evt
= EV_STEAM_SMOKE
;
3131 /* Diesel smoke - thicker when vehicle is starting, gradually subsiding till it reaches its maximum speed
3132 * when smoke emission stops.
3133 * Details: Vehicle's (max.) speed spectrum is divided into 32 parts. When max. speed is reached, chance for smoke
3134 * emission erodes by 32 (1/4). For trains, power and weight come in handy too to either increase smoke emission in
3135 * 6 steps (1000HP each) if the power is low or decrease smoke emission in 6 steps (512 tonnes each) if the train
3136 * isn't overweight. Power and weight contributions are expressed in a way that neither extreme power, nor
3137 * extreme weight can ruin the balance (e.g. FreightWagonMultiplier) in the formula. When the vehicle reaches
3138 * maximum speed no diesel_smoke is emitted.
3140 * - up to which speed a diesel vehicle is emitting smoke (with reduced/small setting only until 1/2 of max_speed),
3141 * - in Chance16 - the last value is 512 / 2^smoke_amount (max. smoke when 128 = smoke_amount of 2). */
3142 int power_weight_effect
= 0;
3143 if (v
->type
== VEH_TRAIN
) {
3144 power_weight_effect
= (32 >> (Train::From(this)->gcache
.cached_power
>> 10)) - (32 >> (Train::From(this)->gcache
.cached_weight
>> 9));
3146 if (this->cur_speed
< (max_speed
>> (2 >> _settings_game
.vehicle
.smoke_amount
)) &&
3147 Chance16((64 - ((this->cur_speed
<< 5) / max_speed
) + power_weight_effect
), (512 >> _settings_game
.vehicle
.smoke_amount
))) {
3148 evt
= EV_DIESEL_SMOKE
;
3154 /* Electric train's spark - more often occurs when train is departing (more load)
3155 * Details: Electric locomotives are usually at least twice as powerful as their diesel counterparts, so spark
3156 * emissions are kept simple. Only when starting, creating huge force are sparks more likely to happen, but when
3157 * reaching its max. speed, quarter by quarter of it, chance decreases until the usual 2,22% at train's top speed.
3159 * - in Chance16 the last value is 360 / 2^smoke_amount (max. sparks when 90 = smoke_amount of 2). */
3160 if (GB(v
->tick_counter
, 0, 2) == 0 &&
3161 Chance16((6 - ((this->cur_speed
<< 2) / max_speed
)), (360 >> _settings_game
.vehicle
.smoke_amount
))) {
3162 evt
= EV_ELECTRIC_SPARK
;
3170 if (evt
!= EV_END
&& advanced
) {
3172 SpawnAdvancedVisualEffect(v
);
3173 } else if (evt
!= EV_END
) {
3176 /* The effect offset is relative to a point 4 units behind the vehicle's
3177 * front (which is the center of an 8/8 vehicle). Shorter vehicles need a
3178 * correction factor. */
3179 if (v
->type
== VEH_TRAIN
) effect_offset
+= (VEHICLE_LENGTH
- Train::From(v
)->gcache
.cached_veh_length
) / 2;
3181 int x
= _vehicle_smoke_pos
[v
->direction
] * effect_offset
;
3182 int y
= _vehicle_smoke_pos
[(v
->direction
+ 2) % 8] * effect_offset
;
3184 if (v
->type
== VEH_TRAIN
&& HasBit(Train::From(v
)->flags
, VRF_REVERSE_DIRECTION
)) {
3189 CreateEffectVehicleRel(v
, x
, y
, 10, evt
);
3191 } while ((v
= v
->Next()) != nullptr);
3193 if (sound
) PlayVehicleSound(this, VSE_VISUAL_EFFECT
);
3197 * Set the next vehicle of this vehicle.
3198 * @param next the next vehicle. nullptr removes the next vehicle.
3200 void Vehicle::SetNext(Vehicle
*next
)
3202 assert(this != next
);
3204 if (this->next
!= nullptr) {
3205 /* We had an old next vehicle. Update the first and previous pointers */
3206 for (Vehicle
*v
= this->next
; v
!= nullptr; v
= v
->Next()) {
3207 v
->first
= this->next
;
3209 this->next
->previous
= nullptr;
3214 if (this->next
!= nullptr) {
3215 /* A new next vehicle. Update the first and previous pointers */
3216 if (this->next
->previous
!= nullptr) this->next
->previous
->next
= nullptr;
3217 this->next
->previous
= this;
3218 for (Vehicle
*v
= this->next
; v
!= nullptr; v
= v
->Next()) {
3219 v
->first
= this->first
;
3225 * Adds this vehicle to a shared vehicle chain.
3226 * @param shared_chain a vehicle of the chain with shared vehicles.
3227 * @pre !this->IsOrderListShared()
3229 void Vehicle::AddToShared(Vehicle
*shared_chain
)
3231 assert(this->previous_shared
== nullptr && this->next_shared
== nullptr);
3233 if (shared_chain
->orders
.list
== nullptr) {
3234 assert(shared_chain
->previous_shared
== nullptr);
3235 assert(shared_chain
->next_shared
== nullptr);
3236 this->orders
.list
= shared_chain
->orders
.list
= new OrderList(nullptr, shared_chain
);
3239 this->next_shared
= shared_chain
->next_shared
;
3240 this->previous_shared
= shared_chain
;
3242 shared_chain
->next_shared
= this;
3244 if (this->next_shared
!= nullptr) this->next_shared
->previous_shared
= this;
3246 shared_chain
->orders
.list
->AddVehicle(this);
3247 shared_chain
->orders
.list
->MarkSeparationInvalid();
3251 * Removes the vehicle from the shared order list.
3253 void Vehicle::RemoveFromShared()
3255 /* Remember if we were first and the old window number before RemoveVehicle()
3256 * as this changes first if needed. */
3257 bool were_first
= (this->FirstShared() == this);
3258 VehicleListIdentifier
vli(VL_SHARED_ORDERS
, this->type
, this->owner
, this->FirstShared()->index
);
3260 this->orders
.list
->MarkSeparationInvalid();
3261 this->orders
.list
->RemoveVehicle(this);
3264 /* We are not the first shared one, so only relink our previous one. */
3265 this->previous_shared
->next_shared
= this->NextShared();
3268 if (this->next_shared
!= nullptr) this->next_shared
->previous_shared
= this->previous_shared
;
3271 if (this->orders
.list
->GetNumVehicles() == 1) {
3272 /* When there is only one vehicle, remove the shared order list window. */
3273 DeleteWindowById(GetWindowClassForVehicleType(this->type
), vli
.Pack());
3274 InvalidateVehicleOrder(this->FirstShared(), VIWD_MODIFY_ORDERS
);
3275 } else if (were_first
) {
3276 /* If we were the first one, update to the new first one.
3277 * Note: FirstShared() is already the new first */
3278 InvalidateWindowData(GetWindowClassForVehicleType(this->type
), vli
.Pack(), this->FirstShared()->index
| (1U << 31));
3281 this->next_shared
= nullptr;
3282 this->previous_shared
= nullptr;
3285 void VehiclesYearlyLoop()
3288 FOR_ALL_VEHICLES(v
) {
3289 if (v
->IsPrimaryVehicle()) {
3290 /* show warning if vehicle is not generating enough income last 2 years (corresponds to a red icon in the vehicle list) */
3291 Money profit
= v
->GetDisplayProfitThisYear();
3292 if (v
->age
>= 730 && profit
< 0) {
3293 if (_settings_client
.gui
.vehicle_income_warn
&& v
->owner
== _local_company
) {
3294 SetDParam(0, v
->index
);
3295 SetDParam(1, profit
);
3296 AddVehicleAdviceNewsItem(STR_NEWS_VEHICLE_IS_UNPROFITABLE
, v
->index
);
3298 AI::NewEvent(v
->owner
, new ScriptEventVehicleUnprofitable(v
->index
));
3301 v
->profit_last_year
= v
->profit_this_year
;
3302 v
->profit_lifetime
+= v
->profit_this_year
;
3303 v
->profit_this_year
= 0;
3304 SetWindowDirty(WC_VEHICLE_DETAILS
, v
->index
);
3307 GroupStatistics::UpdateProfits();
3308 SetWindowClassesDirty(WC_TRAINS_LIST
);
3309 SetWindowClassesDirty(WC_TRACE_RESTRICT_SLOTS
);
3310 SetWindowClassesDirty(WC_SHIPS_LIST
);
3311 SetWindowClassesDirty(WC_ROADVEH_LIST
);
3312 SetWindowClassesDirty(WC_AIRCRAFT_LIST
);
3317 * Can this station be used by the given engine type?
3318 * @param engine_type the type of vehicles to test
3319 * @param st the station to test for
3320 * @return true if and only if the vehicle of the type can use this station.
3321 * @note For road vehicles the Vehicle is needed to determine whether it can
3322 * use the station. This function will return true for road vehicles
3323 * when at least one of the facilities is available.
3325 bool CanVehicleUseStation(EngineID engine_type
, const Station
*st
)
3327 const Engine
*e
= Engine::GetIfValid(engine_type
);
3328 assert(e
!= nullptr);
3332 return (st
->facilities
& FACIL_TRAIN
) != 0;
3335 /* For road vehicles we need the vehicle to know whether it can actually
3336 * use the station, but if it doesn't have facilities for RVs it is
3337 * certainly not possible that the station can be used. */
3338 return (st
->facilities
& (FACIL_BUS_STOP
| FACIL_TRUCK_STOP
)) != 0;
3341 return (st
->facilities
& FACIL_DOCK
) != 0;
3344 return (st
->facilities
& FACIL_AIRPORT
) != 0 &&
3345 (st
->airport
.GetFTA()->flags
& (e
->u
.air
.subtype
& AIR_CTOL
? AirportFTAClass::AIRPLANES
: AirportFTAClass::HELICOPTERS
)) != 0;
3353 * Can this station be used by the given vehicle?
3354 * @param v the vehicle to test
3355 * @param st the station to test for
3356 * @return true if and only if the vehicle can use this station.
3358 bool CanVehicleUseStation(const Vehicle
*v
, const Station
*st
)
3360 if (v
->type
== VEH_ROAD
) return st
->GetPrimaryRoadStop(RoadVehicle::From(v
)) != nullptr;
3362 return CanVehicleUseStation(v
->engine_type
, st
);
3366 * Access the ground vehicle cache of the vehicle.
3367 * @pre The vehicle is a #GroundVehicle.
3368 * @return #GroundVehicleCache of the vehicle.
3370 GroundVehicleCache
*Vehicle::GetGroundVehicleCache()
3372 assert(this->IsGroundVehicle());
3373 if (this->type
== VEH_TRAIN
) {
3374 return &Train::From(this)->gcache
;
3376 return &RoadVehicle::From(this)->gcache
;
3381 * Access the ground vehicle cache of the vehicle.
3382 * @pre The vehicle is a #GroundVehicle.
3383 * @return #GroundVehicleCache of the vehicle.
3385 const GroundVehicleCache
*Vehicle::GetGroundVehicleCache() const
3387 assert(this->IsGroundVehicle());
3388 if (this->type
== VEH_TRAIN
) {
3389 return &Train::From(this)->gcache
;
3391 return &RoadVehicle::From(this)->gcache
;
3396 * Access the ground vehicle flags of the vehicle.
3397 * @pre The vehicle is a #GroundVehicle.
3398 * @return #GroundVehicleFlags of the vehicle.
3400 uint16
&Vehicle::GetGroundVehicleFlags()
3402 assert(this->IsGroundVehicle());
3403 if (this->type
== VEH_TRAIN
) {
3404 return Train::From(this)->gv_flags
;
3406 return RoadVehicle::From(this)->gv_flags
;
3411 * Access the ground vehicle flags of the vehicle.
3412 * @pre The vehicle is a #GroundVehicle.
3413 * @return #GroundVehicleFlags of the vehicle.
3415 const uint16
&Vehicle::GetGroundVehicleFlags() const
3417 assert(this->IsGroundVehicle());
3418 if (this->type
== VEH_TRAIN
) {
3419 return Train::From(this)->gv_flags
;
3421 return RoadVehicle::From(this)->gv_flags
;
3426 * Calculates the set of vehicles that will be affected by a given selection.
3427 * @param set [inout] Set of affected vehicles.
3428 * @param v First vehicle of the selection.
3429 * @param num_vehicles Number of vehicles in the selection (not counting articulated parts).
3430 * @pre \a set must be empty.
3431 * @post \a set will contain the vehicles that will be refitted.
3433 void GetVehicleSet(VehicleSet
&set
, Vehicle
*v
, uint8 num_vehicles
)
3435 if (v
->type
== VEH_TRAIN
) {
3436 Train
*u
= Train::From(v
);
3437 /* Only include whole vehicles, so start with the first articulated part */
3438 u
= u
->GetFirstEnginePart();
3440 /* Include num_vehicles vehicles, not counting articulated parts */
3441 for (; u
!= nullptr && num_vehicles
> 0; num_vehicles
--) {
3443 /* Include current vehicle in the selection. */
3444 set
.Include(u
->index
);
3446 /* If the vehicle is multiheaded, add the other part too. */
3447 if (u
->IsMultiheaded()) set
.Include(u
->other_multiheaded_part
->index
);
3450 } while (u
!= nullptr && u
->IsArticulatedPart());