2 * This file is part of OpenTTD.
3 * 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.
4 * 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.
5 * 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/>.
8 /** @file vehicle.cpp Base implementations of all vehicles. */
14 #include "spritecache.h"
15 #include "timetable.h"
16 #include "viewport_func.h"
17 #include "news_func.h"
18 #include "command_func.h"
19 #include "company_func.h"
22 #include "newgrf_debug.h"
23 #include "newgrf_sound.h"
24 #include "newgrf_station.h"
25 #include "group_gui.h"
26 #include "strings_func.h"
27 #include "zoom_func.h"
28 #include "date_func.h"
29 #include "vehicle_func.h"
30 #include "autoreplace_func.h"
31 #include "autoreplace_gui.h"
32 #include "station_base.h"
34 #include "depot_func.h"
35 #include "network/network.h"
36 #include "core/pool_func.hpp"
37 #include "economy_base.h"
38 #include "articulated_vehicles.h"
39 #include "roadstop_base.h"
40 #include "core/random_func.hpp"
41 #include "core/backup_type.hpp"
42 #include "order_backup.h"
43 #include "sound_func.h"
44 #include "effectvehicle_func.h"
45 #include "effectvehicle_base.h"
46 #include "vehiclelist.h"
47 #include "bridge_map.h"
48 #include "tunnel_map.h"
49 #include "depot_map.h"
51 #include "linkgraph/linkgraph.h"
52 #include "linkgraph/refresh.h"
53 #include "framerate_type.h"
55 #include "table/strings.h"
57 #include "safeguards.h"
59 /* Number of bits in the hash to use from each vehicle coord */
60 static const uint GEN_HASHX_BITS
= 6;
61 static const uint GEN_HASHY_BITS
= 6;
63 /* Size of each hash bucket */
64 static const uint GEN_HASHX_BUCKET_BITS
= 7;
65 static const uint GEN_HASHY_BUCKET_BITS
= 6;
67 /* Compute hash for vehicle coord */
68 #define GEN_HASHX(x) GB((x), GEN_HASHX_BUCKET_BITS + ZOOM_LVL_SHIFT, GEN_HASHX_BITS)
69 #define GEN_HASHY(y) (GB((y), GEN_HASHY_BUCKET_BITS + ZOOM_LVL_SHIFT, GEN_HASHY_BITS) << GEN_HASHX_BITS)
70 #define GEN_HASH(x, y) (GEN_HASHY(y) + GEN_HASHX(x))
72 /* Maximum size until hash repeats */
73 static const int GEN_HASHX_SIZE
= 1 << (GEN_HASHX_BUCKET_BITS
+ GEN_HASHX_BITS
+ ZOOM_LVL_SHIFT
);
74 static const int GEN_HASHY_SIZE
= 1 << (GEN_HASHY_BUCKET_BITS
+ GEN_HASHY_BITS
+ ZOOM_LVL_SHIFT
);
76 /* Increments to reach next bucket in hash table */
77 static const int GEN_HASHX_INC
= 1;
78 static const int GEN_HASHY_INC
= 1 << GEN_HASHX_BITS
;
80 /* Mask to wrap-around buckets */
81 static const uint GEN_HASHX_MASK
= (1 << GEN_HASHX_BITS
) - 1;
82 static const uint GEN_HASHY_MASK
= ((1 << GEN_HASHY_BITS
) - 1) << GEN_HASHX_BITS
;
84 VehicleID _new_vehicle_id
;
85 uint _returned_refit_capacity
; ///< Stores the capacity after a refit operation.
86 uint16 _returned_mail_refit_capacity
; ///< Stores the mail capacity after a refit operation (Aircraft only).
89 /** The pool with all our precious vehicles. */
90 VehiclePool
_vehicle_pool("Vehicle");
91 INSTANTIATE_POOL_METHODS(Vehicle
)
95 * Determine shared bounds of all sprites.
96 * @param[out] bounds Shared bounds.
98 void VehicleSpriteSeq::GetBounds(Rect
*bounds
) const
100 bounds
->left
= bounds
->top
= bounds
->right
= bounds
->bottom
= 0;
101 for (uint i
= 0; i
< this->count
; ++i
) {
102 const Sprite
*spr
= GetSprite(this->seq
[i
].sprite
, ST_NORMAL
);
104 bounds
->left
= spr
->x_offs
;
105 bounds
->top
= spr
->y_offs
;
106 bounds
->right
= spr
->width
+ spr
->x_offs
- 1;
107 bounds
->bottom
= spr
->height
+ spr
->y_offs
- 1;
109 if (spr
->x_offs
< bounds
->left
) bounds
->left
= spr
->x_offs
;
110 if (spr
->y_offs
< bounds
->top
) bounds
->top
= spr
->y_offs
;
111 int right
= spr
->width
+ spr
->x_offs
- 1;
112 int bottom
= spr
->height
+ spr
->y_offs
- 1;
113 if (right
> bounds
->right
) bounds
->right
= right
;
114 if (bottom
> bounds
->bottom
) bounds
->bottom
= bottom
;
120 * Draw the sprite sequence.
121 * @param x X position
122 * @param y Y position
123 * @param default_pal Vehicle palette
124 * @param force_pal Whether to ignore individual palettes, and draw everything with \a default_pal.
126 void VehicleSpriteSeq::Draw(int x
, int y
, PaletteID default_pal
, bool force_pal
) const
128 for (uint i
= 0; i
< this->count
; ++i
) {
129 PaletteID pal
= force_pal
|| !this->seq
[i
].pal
? default_pal
: this->seq
[i
].pal
;
130 DrawSprite(this->seq
[i
].sprite
, pal
, x
, y
);
135 * Function to tell if a vehicle needs to be autorenewed
136 * @param *c The vehicle owner
137 * @param use_renew_setting Should the company renew setting be considered?
138 * @return true if the vehicle is old enough for replacement
140 bool Vehicle::NeedsAutorenewing(const Company
*c
, bool use_renew_setting
) const
142 /* We can always generate the Company pointer when we have the vehicle.
143 * However this takes time and since the Company pointer is often present
144 * when this function is called then it's faster to pass the pointer as an
145 * argument rather than finding it again. */
146 assert(c
== Company::Get(this->owner
));
148 if (use_renew_setting
&& !c
->settings
.engine_renew
) return false;
149 if (this->age
- this->max_age
< (c
->settings
.engine_renew_months
* 30)) return false;
151 /* Only engines need renewing */
152 if (this->type
== VEH_TRAIN
&& !Train::From(this)->IsEngine()) return false;
158 * Service a vehicle and all subsequent vehicles in the consist
160 * @param *v The vehicle or vehicle chain being serviced
162 void VehicleServiceInDepot(Vehicle
*v
)
164 assert(v
!= nullptr);
165 SetWindowDirty(WC_VEHICLE_DETAILS
, v
->index
); // ensure that last service date and reliability are updated
168 v
->date_of_last_service
= _date
;
169 v
->breakdowns_since_last_service
= 0;
170 v
->reliability
= v
->GetEngine()->reliability
;
171 /* Prevent vehicles from breaking down directly after exiting the depot. */
172 v
->breakdown_chance
/= 4;
174 } while (v
!= nullptr && v
->HasEngineType());
178 * Check if the vehicle needs to go to a depot in near future (if a opportunity presents itself) for service or replacement.
180 * @see NeedsAutomaticServicing()
181 * @return true if the vehicle should go to a depot if a opportunity presents itself.
183 bool Vehicle::NeedsServicing() const
185 /* Stopped or crashed vehicles will not move, as such making unmovable
186 * vehicles to go for service is lame. */
187 if (this->vehstatus
& (VS_STOPPED
| VS_CRASHED
)) return false;
189 /* Are we ready for the next service cycle? */
190 const Company
*c
= Company::Get(this->owner
);
191 if (this->ServiceIntervalIsPercent() ?
192 (this->reliability
>= this->GetEngine()->reliability
* (100 - this->GetServiceInterval()) / 100) :
193 (this->date_of_last_service
+ this->GetServiceInterval() >= _date
)) {
197 /* If we're servicing anyway, because we have not disabled servicing when
198 * there are no breakdowns or we are playing with breakdowns, bail out. */
199 if (!_settings_game
.order
.no_servicing_if_no_breakdowns
||
200 _settings_game
.difficulty
.vehicle_breakdowns
!= 0) {
204 /* Test whether there is some pending autoreplace.
205 * Note: We do this after the service-interval test.
206 * There are a lot more reasons for autoreplace to fail than we can test here reasonably. */
207 bool pending_replace
= false;
208 Money needed_money
= c
->settings
.engine_renew_money
;
209 if (needed_money
> c
->money
) return false;
211 for (const Vehicle
*v
= this; v
!= nullptr; v
= (v
->type
== VEH_TRAIN
) ? Train::From(v
)->GetNextUnit() : nullptr) {
212 bool replace_when_old
= false;
213 EngineID new_engine
= EngineReplacementForCompany(c
, v
->engine_type
, v
->group_id
, &replace_when_old
);
215 /* Check engine availability */
216 if (new_engine
== INVALID_ENGINE
|| !HasBit(Engine::Get(new_engine
)->company_avail
, v
->owner
)) continue;
217 /* Is the vehicle old if we are not always replacing? */
218 if (replace_when_old
&& !v
->NeedsAutorenewing(c
, false)) continue;
220 /* Check refittability */
221 CargoTypes available_cargo_types
, union_mask
;
222 GetArticulatedRefitMasks(new_engine
, true, &union_mask
, &available_cargo_types
);
223 /* Is there anything to refit? */
224 if (union_mask
!= 0) {
226 /* We cannot refit to mixed cargoes in an automated way */
227 if (IsArticulatedVehicleCarryingDifferentCargoes(v
, &cargo_type
)) continue;
229 /* Did the old vehicle carry anything? */
230 if (cargo_type
!= CT_INVALID
) {
231 /* We can't refit the vehicle to carry the cargo we want */
232 if (!HasBit(available_cargo_types
, cargo_type
)) continue;
237 * We want 2*(the price of the new vehicle) without looking at the value of the vehicle we are going to sell. */
238 pending_replace
= true;
239 needed_money
+= 2 * Engine::Get(new_engine
)->GetCost();
240 if (needed_money
> c
->money
) return false;
243 return pending_replace
;
247 * Checks if the current order should be interrupted for a service-in-depot order.
248 * @see NeedsServicing()
249 * @return true if the current order should be interrupted.
251 bool Vehicle::NeedsAutomaticServicing() const
253 if (this->HasDepotOrder()) return false;
254 if (this->current_order
.IsType(OT_LOADING
)) return false;
255 if (this->current_order
.IsType(OT_GOTO_DEPOT
) && this->current_order
.GetDepotOrderType() != ODTFB_SERVICE
) return false;
256 return NeedsServicing();
259 uint
Vehicle::Crash(bool flooded
)
261 assert((this->vehstatus
& VS_CRASHED
) == 0);
262 assert(this->Previous() == nullptr); // IsPrimaryVehicle fails for free-wagon-chains
265 /* Stop the vehicle. */
266 if (this->IsPrimaryVehicle()) this->vehstatus
|= VS_STOPPED
;
267 /* crash all wagons, and count passengers */
268 for (Vehicle
*v
= this; v
!= nullptr; v
= v
->Next()) {
269 /* We do not transfer reserver cargo back, so TotalCount() instead of StoredCount() */
270 if (IsCargoInClass(v
->cargo_type
, CC_PASSENGERS
)) pass
+= v
->cargo
.TotalCount();
271 v
->vehstatus
|= VS_CRASHED
;
272 v
->MarkAllViewportsDirty();
275 /* Dirty some windows */
276 InvalidateWindowClassesData(GetWindowClassForVehicleType(this->type
), 0);
277 SetWindowWidgetDirty(WC_VEHICLE_VIEW
, this->index
, WID_VV_START_STOP
);
278 SetWindowDirty(WC_VEHICLE_DETAILS
, this->index
);
279 SetWindowDirty(WC_VEHICLE_DEPOT
, this->tile
);
281 delete this->cargo_payment
;
282 assert(this->cargo_payment
== nullptr); // cleared by ~CargoPayment
284 return RandomRange(pass
+ 1); // Randomise deceased passengers.
289 * Displays a "NewGrf Bug" error message for a engine, and pauses the game if not networking.
290 * @param engine The engine that caused the problem
291 * @param part1 Part 1 of the error message, taking the grfname as parameter 1
292 * @param part2 Part 2 of the error message, taking the engine as parameter 2
293 * @param bug_type Flag to check and set in grfconfig
294 * @param critical Shall the "OpenTTD might crash"-message be shown when the player tries to unpause?
296 void ShowNewGrfVehicleError(EngineID engine
, StringID part1
, StringID part2
, GRFBugs bug_type
, bool critical
)
298 const Engine
*e
= Engine::Get(engine
);
299 GRFConfig
*grfconfig
= GetGRFConfig(e
->GetGRFID());
301 /* Missing GRF. Nothing useful can be done in this situation. */
302 if (grfconfig
== nullptr) return;
304 if (!HasBit(grfconfig
->grf_bugs
, bug_type
)) {
305 SetBit(grfconfig
->grf_bugs
, bug_type
);
306 SetDParamStr(0, grfconfig
->GetName());
307 SetDParam(1, engine
);
308 ShowErrorMessage(part1
, part2
, WL_CRITICAL
);
309 if (!_networking
) DoCommand(0, critical
? PM_PAUSED_ERROR
: PM_PAUSED_NORMAL
, 1, DC_EXEC
, CMD_PAUSE
);
315 SetDParamStr(0, grfconfig
->GetName());
316 GetString(buffer
, part1
, lastof(buffer
));
317 DEBUG(grf
, 0, "%s", buffer
+ 3);
319 SetDParam(1, engine
);
320 GetString(buffer
, part2
, lastof(buffer
));
321 DEBUG(grf
, 0, "%s", buffer
+ 3);
325 * Logs a bug in GRF and shows a warning message if this
326 * is for the first time this happened.
327 * @param u first vehicle of chain
329 void VehicleLengthChanged(const Vehicle
*u
)
331 /* show a warning once for each engine in whole game and once for each GRF after each game load */
332 const Engine
*engine
= u
->GetEngine();
333 uint32 grfid
= engine
->grf_prop
.grffile
->grfid
;
334 GRFConfig
*grfconfig
= GetGRFConfig(grfid
);
335 if (GamelogGRFBugReverse(grfid
, engine
->grf_prop
.local_id
) || !HasBit(grfconfig
->grf_bugs
, GBUG_VEH_LENGTH
)) {
336 ShowNewGrfVehicleError(u
->engine_type
, STR_NEWGRF_BROKEN
, STR_NEWGRF_BROKEN_VEHICLE_LENGTH
, GBUG_VEH_LENGTH
, true);
341 * Vehicle constructor.
342 * @param type Type of the new vehicle.
344 Vehicle::Vehicle(VehicleType type
)
347 this->coord
.left
= INVALID_COORD
;
348 this->group_id
= DEFAULT_GROUP
;
349 this->fill_percent_te_id
= INVALID_TE_ID
;
351 this->colourmap
= PAL_NONE
;
352 this->cargo_age_counter
= 1;
353 this->last_station_visited
= INVALID_STATION
;
354 this->last_loading_station
= INVALID_STATION
;
358 * Get a value for a vehicle's random_bits.
359 * @return A random value from 0 to 255.
361 byte
VehicleRandomBits()
363 return GB(Random(), 0, 8);
366 /* Size of the hash, 6 = 64 x 64, 7 = 128 x 128. Larger sizes will (in theory) reduce hash
367 * lookup times at the expense of memory usage. */
368 const int HASH_BITS
= 7;
369 const int HASH_SIZE
= 1 << HASH_BITS
;
370 const int HASH_MASK
= HASH_SIZE
- 1;
371 const int TOTAL_HASH_SIZE
= 1 << (HASH_BITS
* 2);
372 const int TOTAL_HASH_MASK
= TOTAL_HASH_SIZE
- 1;
374 /* Resolution of the hash, 0 = 1*1 tile, 1 = 2*2 tiles, 2 = 4*4 tiles, etc.
375 * Profiling results show that 0 is fastest. */
376 const int HASH_RES
= 0;
378 static Vehicle
*_vehicle_tile_hash
[TOTAL_HASH_SIZE
];
380 static Vehicle
*VehicleFromTileHash(int xl
, int yl
, int xu
, int yu
, void *data
, VehicleFromPosProc
*proc
, bool find_first
)
382 for (int y
= yl
; ; y
= (y
+ (1 << HASH_BITS
)) & (HASH_MASK
<< HASH_BITS
)) {
383 for (int x
= xl
; ; x
= (x
+ 1) & HASH_MASK
) {
384 Vehicle
*v
= _vehicle_tile_hash
[(x
+ y
) & TOTAL_HASH_MASK
];
385 for (; v
!= nullptr; v
= v
->hash_tile_next
) {
386 Vehicle
*a
= proc(v
, data
);
387 if (find_first
&& a
!= nullptr) return a
;
399 * Helper function for FindVehicleOnPos/HasVehicleOnPos.
400 * @note Do not call this function directly!
401 * @param x The X location on the map
402 * @param y The Y location on the map
403 * @param data Arbitrary data passed to proc
404 * @param proc The proc that determines whether a vehicle will be "found".
405 * @param find_first Whether to return on the first found or iterate over
407 * @return the best matching or first vehicle (depending on find_first).
409 static Vehicle
*VehicleFromPosXY(int x
, int y
, void *data
, VehicleFromPosProc
*proc
, bool find_first
)
411 const int COLL_DIST
= 6;
413 /* Hash area to scan is from xl,yl to xu,yu */
414 int xl
= GB((x
- COLL_DIST
) / TILE_SIZE
, HASH_RES
, HASH_BITS
);
415 int xu
= GB((x
+ COLL_DIST
) / TILE_SIZE
, HASH_RES
, HASH_BITS
);
416 int yl
= GB((y
- COLL_DIST
) / TILE_SIZE
, HASH_RES
, HASH_BITS
) << HASH_BITS
;
417 int yu
= GB((y
+ COLL_DIST
) / TILE_SIZE
, HASH_RES
, HASH_BITS
) << HASH_BITS
;
419 return VehicleFromTileHash(xl
, yl
, xu
, yu
, data
, proc
, find_first
);
423 * Find a vehicle from a specific location. It will call proc for ALL vehicles
424 * on the tile and YOU must make SURE that the "best one" is stored in the
425 * data value and is ALWAYS the same regardless of the order of the vehicles
426 * where proc was called on!
427 * When you fail to do this properly you create an almost untraceable DESYNC!
428 * @note The return value of proc will be ignored.
429 * @note Use this when you have the intention that all vehicles
430 * should be iterated over.
431 * @param x The X location on the map
432 * @param y The Y location on the map
433 * @param data Arbitrary data passed to proc
434 * @param proc The proc that determines whether a vehicle will be "found".
436 void FindVehicleOnPosXY(int x
, int y
, void *data
, VehicleFromPosProc
*proc
)
438 VehicleFromPosXY(x
, y
, data
, proc
, false);
442 * Checks whether a vehicle in on a specific location. It will call proc for
443 * vehicles until it returns non-nullptr.
444 * @note Use FindVehicleOnPosXY when you have the intention that all vehicles
445 * should be iterated over.
446 * @param x The X location on the map
447 * @param y The Y location on the map
448 * @param data Arbitrary data passed to proc
449 * @param proc The proc that determines whether a vehicle will be "found".
450 * @return True if proc returned non-nullptr.
452 bool HasVehicleOnPosXY(int x
, int y
, void *data
, VehicleFromPosProc
*proc
)
454 return VehicleFromPosXY(x
, y
, data
, proc
, true) != nullptr;
458 * Helper function for FindVehicleOnPos/HasVehicleOnPos.
459 * @note Do not call this function directly!
460 * @param tile The location on the map
461 * @param data Arbitrary data passed to \a proc.
462 * @param proc The proc that determines whether a vehicle will be "found".
463 * @param find_first Whether to return on the first found or iterate over
465 * @return the best matching or first vehicle (depending on find_first).
467 static Vehicle
*VehicleFromPos(TileIndex tile
, void *data
, VehicleFromPosProc
*proc
, bool find_first
)
469 int x
= GB(TileX(tile
), HASH_RES
, HASH_BITS
);
470 int y
= GB(TileY(tile
), HASH_RES
, HASH_BITS
) << HASH_BITS
;
472 Vehicle
*v
= _vehicle_tile_hash
[(x
+ y
) & TOTAL_HASH_MASK
];
473 for (; v
!= nullptr; v
= v
->hash_tile_next
) {
474 if (v
->tile
!= tile
) continue;
476 Vehicle
*a
= proc(v
, data
);
477 if (find_first
&& a
!= nullptr) return a
;
484 * Find a vehicle from a specific location. It will call \a proc for ALL vehicles
485 * on the tile and YOU must make SURE that the "best one" is stored in the
486 * data value and is ALWAYS the same regardless of the order of the vehicles
487 * where proc was called on!
488 * When you fail to do this properly you create an almost untraceable DESYNC!
489 * @note The return value of \a proc will be ignored.
490 * @note Use this function when you have the intention that all vehicles
491 * should be iterated over.
492 * @param tile The location on the map
493 * @param data Arbitrary data passed to \a proc.
494 * @param proc The proc that determines whether a vehicle will be "found".
496 void FindVehicleOnPos(TileIndex tile
, void *data
, VehicleFromPosProc
*proc
)
498 VehicleFromPos(tile
, data
, proc
, false);
502 * Checks whether a vehicle is on a specific location. It will call \a proc for
503 * vehicles until it returns non-nullptr.
504 * @note Use #FindVehicleOnPos when you have the intention that all vehicles
505 * should be iterated over.
506 * @param tile The location on the map
507 * @param data Arbitrary data passed to \a proc.
508 * @param proc The \a proc that determines whether a vehicle will be "found".
509 * @return True if proc returned non-nullptr.
511 bool HasVehicleOnPos(TileIndex tile
, void *data
, VehicleFromPosProc
*proc
)
513 return VehicleFromPos(tile
, data
, proc
, true) != nullptr;
517 * Callback that returns 'real' vehicles lower or at height \c *(int*)data .
518 * @param v Vehicle to examine.
519 * @param data Pointer to height data.
520 * @return \a v if conditions are met, else \c nullptr.
522 static Vehicle
*EnsureNoVehicleProcZ(Vehicle
*v
, void *data
)
526 if (v
->type
== VEH_DISASTER
|| (v
->type
== VEH_AIRCRAFT
&& v
->subtype
== AIR_SHADOW
)) return nullptr;
527 if (v
->z_pos
> z
) return nullptr;
533 * Ensure there is no vehicle at the ground at the given position.
534 * @param tile Position to examine.
535 * @return Succeeded command (ground is free) or failed command (a vehicle is found).
537 CommandCost
EnsureNoVehicleOnGround(TileIndex tile
)
539 int z
= GetTileMaxPixelZ(tile
);
541 /* Value v is not safe in MP games, however, it is used to generate a local
542 * error message only (which may be different for different machines).
543 * Such a message does not affect MP synchronisation.
545 Vehicle
*v
= VehicleFromPos(tile
, &z
, &EnsureNoVehicleProcZ
, true);
546 if (v
!= nullptr) return_cmd_error(STR_ERROR_TRAIN_IN_THE_WAY
+ v
->type
);
547 return CommandCost();
550 /** Procedure called for every vehicle found in tunnel/bridge in the hash map */
551 static Vehicle
*GetVehicleTunnelBridgeProc(Vehicle
*v
, void *data
)
553 if (v
->type
!= VEH_TRAIN
&& v
->type
!= VEH_ROAD
&& v
->type
!= VEH_SHIP
) return nullptr;
554 if (v
== (const Vehicle
*)data
) return nullptr;
560 * Finds vehicle in tunnel / bridge
561 * @param tile first end
562 * @param endtile second end
563 * @param ignore Ignore this vehicle when searching
564 * @return Succeeded command (if tunnel/bridge is free) or failed command (if a vehicle is using the tunnel/bridge).
566 CommandCost
TunnelBridgeIsFree(TileIndex tile
, TileIndex endtile
, const Vehicle
*ignore
)
568 /* Value v is not safe in MP games, however, it is used to generate a local
569 * error message only (which may be different for different machines).
570 * Such a message does not affect MP synchronisation.
572 Vehicle
*v
= VehicleFromPos(tile
, const_cast<Vehicle
*>(ignore
), &GetVehicleTunnelBridgeProc
, true);
573 if (v
== nullptr) v
= VehicleFromPos(endtile
, const_cast<Vehicle
*>(ignore
), &GetVehicleTunnelBridgeProc
, true);
575 if (v
!= nullptr) return_cmd_error(STR_ERROR_TRAIN_IN_THE_WAY
+ v
->type
);
576 return CommandCost();
579 static Vehicle
*EnsureNoTrainOnTrackProc(Vehicle
*v
, void *data
)
581 TrackBits rail_bits
= *(TrackBits
*)data
;
583 if (v
->type
!= VEH_TRAIN
) return nullptr;
585 Train
*t
= Train::From(v
);
586 if ((t
->track
!= rail_bits
) && !TracksOverlap(t
->track
| rail_bits
)) return nullptr;
592 * Tests if a vehicle interacts with the specified track bits.
593 * All track bits interact except parallel #TRACK_BIT_HORZ or #TRACK_BIT_VERT.
595 * @param tile The tile.
596 * @param track_bits The track bits.
597 * @return \c true if no train that interacts, is found. \c false if a train is found.
599 CommandCost
EnsureNoTrainOnTrackBits(TileIndex tile
, TrackBits track_bits
)
601 /* Value v is not safe in MP games, however, it is used to generate a local
602 * error message only (which may be different for different machines).
603 * Such a message does not affect MP synchronisation.
605 Vehicle
*v
= VehicleFromPos(tile
, &track_bits
, &EnsureNoTrainOnTrackProc
, true);
606 if (v
!= nullptr) return_cmd_error(STR_ERROR_TRAIN_IN_THE_WAY
+ v
->type
);
607 return CommandCost();
610 static void UpdateVehicleTileHash(Vehicle
*v
, bool remove
)
612 Vehicle
**old_hash
= v
->hash_tile_current
;
618 int x
= GB(TileX(v
->tile
), HASH_RES
, HASH_BITS
);
619 int y
= GB(TileY(v
->tile
), HASH_RES
, HASH_BITS
) << HASH_BITS
;
620 new_hash
= &_vehicle_tile_hash
[(x
+ y
) & TOTAL_HASH_MASK
];
623 if (old_hash
== new_hash
) return;
625 /* Remove from the old position in the hash table */
626 if (old_hash
!= nullptr) {
627 if (v
->hash_tile_next
!= nullptr) v
->hash_tile_next
->hash_tile_prev
= v
->hash_tile_prev
;
628 *v
->hash_tile_prev
= v
->hash_tile_next
;
631 /* Insert vehicle at beginning of the new position in the hash table */
632 if (new_hash
!= nullptr) {
633 v
->hash_tile_next
= *new_hash
;
634 if (v
->hash_tile_next
!= nullptr) v
->hash_tile_next
->hash_tile_prev
= &v
->hash_tile_next
;
635 v
->hash_tile_prev
= new_hash
;
639 /* Remember current hash position */
640 v
->hash_tile_current
= new_hash
;
643 static Vehicle
*_vehicle_viewport_hash
[1 << (GEN_HASHX_BITS
+ GEN_HASHY_BITS
)];
645 static void UpdateVehicleViewportHash(Vehicle
*v
, int x
, int y
)
647 Vehicle
**old_hash
, **new_hash
;
648 int old_x
= v
->coord
.left
;
649 int old_y
= v
->coord
.top
;
651 new_hash
= (x
== INVALID_COORD
) ? nullptr : &_vehicle_viewport_hash
[GEN_HASH(x
, y
)];
652 old_hash
= (old_x
== INVALID_COORD
) ? nullptr : &_vehicle_viewport_hash
[GEN_HASH(old_x
, old_y
)];
654 if (old_hash
== new_hash
) return;
656 /* remove from hash table? */
657 if (old_hash
!= nullptr) {
658 if (v
->hash_viewport_next
!= nullptr) v
->hash_viewport_next
->hash_viewport_prev
= v
->hash_viewport_prev
;
659 *v
->hash_viewport_prev
= v
->hash_viewport_next
;
662 /* insert into hash table? */
663 if (new_hash
!= nullptr) {
664 v
->hash_viewport_next
= *new_hash
;
665 if (v
->hash_viewport_next
!= nullptr) v
->hash_viewport_next
->hash_viewport_prev
= &v
->hash_viewport_next
;
666 v
->hash_viewport_prev
= new_hash
;
671 void ResetVehicleHash()
673 for (Vehicle
*v
: Vehicle::Iterate()) { v
->hash_tile_current
= nullptr; }
674 memset(_vehicle_viewport_hash
, 0, sizeof(_vehicle_viewport_hash
));
675 memset(_vehicle_tile_hash
, 0, sizeof(_vehicle_tile_hash
));
678 void ResetVehicleColourMap()
680 for (Vehicle
*v
: Vehicle::Iterate()) { v
->colourmap
= PAL_NONE
; }
684 * List of vehicles that should check for autoreplace this tick.
685 * Mapping of vehicle -> leave depot immediately after autoreplace.
687 typedef SmallMap
<Vehicle
*, bool> AutoreplaceMap
;
688 static AutoreplaceMap _vehicles_to_autoreplace
;
690 void InitializeVehicles()
692 _vehicles_to_autoreplace
.clear();
693 _vehicles_to_autoreplace
.shrink_to_fit();
697 uint
CountVehiclesInChain(const Vehicle
*v
)
700 do count
++; while ((v
= v
->Next()) != nullptr);
705 * Check if a vehicle is counted in num_engines in each company struct
706 * @return true if the vehicle is counted in num_engines
708 bool Vehicle::IsEngineCountable() const
710 switch (this->type
) {
711 case VEH_AIRCRAFT
: return Aircraft::From(this)->IsNormalAircraft(); // don't count plane shadows and helicopter rotors
713 return !this->IsArticulatedPart() && // tenders and other articulated parts
714 !Train::From(this)->IsRearDualheaded(); // rear parts of multiheaded engines
715 case VEH_ROAD
: return RoadVehicle::From(this)->IsFrontEngine();
716 case VEH_SHIP
: return true;
717 default: return false; // Only count company buildable vehicles
722 * Check whether Vehicle::engine_type has any meaning.
723 * @return true if the vehicle has a usable engine type.
725 bool Vehicle::HasEngineType() const
727 switch (this->type
) {
728 case VEH_AIRCRAFT
: return Aircraft::From(this)->IsNormalAircraft();
731 case VEH_SHIP
: return true;
732 default: return false;
737 * Retrieves the engine of the vehicle.
738 * @return Engine of the vehicle.
739 * @pre HasEngineType() == true
741 const Engine
*Vehicle::GetEngine() const
743 return Engine::Get(this->engine_type
);
747 * Retrieve the NewGRF the vehicle is tied to.
748 * This is the GRF providing the Action 3 for the engine type.
749 * @return NewGRF associated to the vehicle.
751 const GRFFile
*Vehicle::GetGRF() const
753 return this->GetEngine()->GetGRF();
757 * Retrieve the GRF ID of the NewGRF the vehicle is tied to.
758 * This is the GRF providing the Action 3 for the engine type.
759 * @return GRF ID of the associated NewGRF.
761 uint32
Vehicle::GetGRFID() const
763 return this->GetEngine()->GetGRFID();
767 * Handle the pathfinding result, especially the lost status.
768 * If the vehicle is now lost and wasn't previously fire an
769 * event to the AIs and a news message to the user. If the
770 * vehicle is not lost anymore remove the news message.
771 * @param path_found Whether the vehicle has a path to its destination.
773 void Vehicle::HandlePathfindingResult(bool path_found
)
776 /* Route found, is the vehicle marked with "lost" flag? */
777 if (!HasBit(this->vehicle_flags
, VF_PATHFINDER_LOST
)) return;
779 /* Clear the flag as the PF's problem was solved. */
780 ClrBit(this->vehicle_flags
, VF_PATHFINDER_LOST
);
781 /* Delete the news item. */
782 DeleteVehicleNews(this->index
, STR_NEWS_VEHICLE_IS_LOST
);
786 /* Were we already lost? */
787 if (HasBit(this->vehicle_flags
, VF_PATHFINDER_LOST
)) return;
789 /* It is first time the problem occurred, set the "lost" flag. */
790 SetBit(this->vehicle_flags
, VF_PATHFINDER_LOST
);
791 /* Notify user about the event. */
792 AI::NewEvent(this->owner
, new ScriptEventVehicleLost(this->index
));
793 if (_settings_client
.gui
.lost_vehicle_warn
&& this->owner
== _local_company
) {
794 SetDParam(0, this->index
);
795 AddVehicleAdviceNewsItem(STR_NEWS_VEHICLE_IS_LOST
, this->index
);
799 /** Destroy all stuff that (still) needs the virtual functions to work properly */
800 void Vehicle::PreDestructor()
802 if (CleaningPool()) return;
804 if (Station::IsValidID(this->last_station_visited
)) {
805 Station
*st
= Station::Get(this->last_station_visited
);
806 st
->loading_vehicles
.remove(this);
808 HideFillingPercent(&this->fill_percent_te_id
);
809 this->CancelReservation(INVALID_STATION
, st
);
810 delete this->cargo_payment
;
811 assert(this->cargo_payment
== nullptr); // cleared by ~CargoPayment
814 if (this->IsEngineCountable()) {
815 GroupStatistics::CountEngine(this, -1);
816 if (this->IsPrimaryVehicle()) GroupStatistics::CountVehicle(this, -1);
817 GroupStatistics::UpdateAutoreplace(this->owner
);
819 if (this->owner
== _local_company
) InvalidateAutoreplaceWindow(this->engine_type
, this->group_id
);
820 DeleteGroupHighlightOfVehicle(this);
823 if (this->type
== VEH_AIRCRAFT
&& this->IsPrimaryVehicle()) {
824 Aircraft
*a
= Aircraft::From(this);
825 Station
*st
= GetTargetAirportIfValid(a
);
827 const AirportFTA
*layout
= st
->airport
.GetFTA()->layout
;
828 CLRBITS(st
->airport
.flags
, layout
[a
->previous_pos
].block
| layout
[a
->pos
].block
);
833 if (this->type
== VEH_ROAD
&& this->IsPrimaryVehicle()) {
834 RoadVehicle
*v
= RoadVehicle::From(this);
835 if (!(v
->vehstatus
& VS_CRASHED
) && IsInsideMM(v
->state
, RVSB_IN_DT_ROAD_STOP
, RVSB_IN_DT_ROAD_STOP_END
)) {
836 /* Leave the drive through roadstop, when you have not already left it. */
837 RoadStop::GetByTile(v
->tile
, GetRoadStopType(v
->tile
))->Leave(v
);
841 if (this->Previous() == nullptr) {
842 InvalidateWindowData(WC_VEHICLE_DEPOT
, this->tile
);
845 if (this->IsPrimaryVehicle()) {
846 DeleteWindowById(WC_VEHICLE_VIEW
, this->index
);
847 DeleteWindowById(WC_VEHICLE_ORDERS
, this->index
);
848 DeleteWindowById(WC_VEHICLE_REFIT
, this->index
);
849 DeleteWindowById(WC_VEHICLE_DETAILS
, this->index
);
850 DeleteWindowById(WC_VEHICLE_TIMETABLE
, this->index
);
851 SetWindowDirty(WC_COMPANY
, this->owner
);
852 OrderBackup::ClearVehicle(this);
854 InvalidateWindowClassesData(GetWindowClassForVehicleType(this->type
), 0);
856 this->cargo
.Truncate();
857 DeleteVehicleOrders(this);
858 DeleteDepotHighlightOfVehicle(this);
860 extern void StopGlobalFollowVehicle(const Vehicle
*v
);
861 StopGlobalFollowVehicle(this);
863 ReleaseDisastersTargetingVehicle(this->index
);
868 if (CleaningPool()) {
869 this->cargo
.OnCleanPool();
873 /* sometimes, eg. for disaster vehicles, when company bankrupts, when removing crashed/flooded vehicles,
874 * it may happen that vehicle chain is deleted when visible */
875 if (!(this->vehstatus
& VS_HIDDEN
)) this->MarkAllViewportsDirty();
877 Vehicle
*v
= this->Next();
878 this->SetNext(nullptr);
882 UpdateVehicleTileHash(this, true);
883 UpdateVehicleViewportHash(this, INVALID_COORD
, 0);
884 DeleteVehicleNews(this->index
, INVALID_STRING_ID
);
885 DeleteNewGRFInspectWindow(GetGrfSpecFeature(this->type
), this->index
);
889 * Adds a vehicle to the list of vehicles that visited a depot this tick
890 * @param *v vehicle to add
892 void VehicleEnteredDepotThisTick(Vehicle
*v
)
894 /* Vehicle should stop in the depot if it was in 'stopping' state */
895 _vehicles_to_autoreplace
[v
] = !(v
->vehstatus
& VS_STOPPED
);
897 /* We ALWAYS set the stopped state. Even when the vehicle does not plan on
898 * stopping in the depot, so we stop it to ensure that it will not reserve
899 * the path out of the depot before we might autoreplace it to a different
900 * engine. The new engine would not own the reserved path we store that we
901 * stopped the vehicle, so autoreplace can start it again */
902 v
->vehstatus
|= VS_STOPPED
;
906 * Increases the day counter for all vehicles and calls 1-day and 32-day handlers.
907 * Each tick, it processes vehicles with "index % DAY_TICKS == _date_fract",
908 * so each day, all vehicles are processes in DAY_TICKS steps.
910 static void RunVehicleDayProc()
912 if (_game_mode
!= GM_NORMAL
) return;
914 /* Run the day_proc for every DAY_TICKS vehicle starting at _date_fract. */
915 for (size_t i
= _date_fract
; i
< Vehicle::GetPoolSize(); i
+= DAY_TICKS
) {
916 Vehicle
*v
= Vehicle::Get(i
);
917 if (v
== nullptr) continue;
919 /* Call the 32-day callback if needed */
920 if ((v
->day_counter
& 0x1F) == 0 && v
->HasEngineType()) {
921 uint16 callback
= GetVehicleCallback(CBID_VEHICLE_32DAY_CALLBACK
, 0, 0, v
->engine_type
, v
);
922 if (callback
!= CALLBACK_FAILED
) {
923 if (HasBit(callback
, 0)) {
924 TriggerVehicle(v
, VEHICLE_TRIGGER_CALLBACK_32
); // Trigger vehicle trigger 10
927 /* After a vehicle trigger, the graphics and properties of the vehicle could change.
928 * Note: MarkDirty also invalidates the palette, which is the meaning of bit 1. So, nothing special there. */
929 if (callback
!= 0) v
->First()->MarkDirty();
931 if (callback
& ~3) ErrorUnknownCallbackResult(v
->GetGRFID(), CBID_VEHICLE_32DAY_CALLBACK
, callback
);
935 /* This is called once per day for each vehicle, but not in the first tick of the day */
940 void CallVehicleTicks()
942 _vehicles_to_autoreplace
.clear();
947 PerformanceMeasurer
framerate(PFE_GL_ECONOMY
);
948 for (Station
*st
: Station::Iterate()) LoadUnloadStation(st
);
950 PerformanceAccumulator::Reset(PFE_GL_TRAINS
);
951 PerformanceAccumulator::Reset(PFE_GL_ROADVEHS
);
952 PerformanceAccumulator::Reset(PFE_GL_SHIPS
);
953 PerformanceAccumulator::Reset(PFE_GL_AIRCRAFT
);
955 for (Vehicle
*v
: Vehicle::Iterate()) {
956 size_t vehicle_index
= v
->index
;
957 /* Vehicle could be deleted in this tick */
959 assert(Vehicle::Get(vehicle_index
) == nullptr);
963 assert(Vehicle::Get(vehicle_index
) == v
);
972 Vehicle
*front
= v
->First();
974 if (v
->vcache
.cached_cargo_age_period
!= 0) {
975 v
->cargo_age_counter
= min(v
->cargo_age_counter
, v
->vcache
.cached_cargo_age_period
);
976 if (--v
->cargo_age_counter
== 0) {
978 v
->cargo_age_counter
= v
->vcache
.cached_cargo_age_period
;
982 /* Do not play any sound when crashed */
983 if (front
->vehstatus
& VS_CRASHED
) continue;
985 /* Do not play any sound when in depot or tunnel */
986 if (v
->vehstatus
& VS_HIDDEN
) continue;
988 /* Do not play any sound when stopped */
989 if ((front
->vehstatus
& VS_STOPPED
) && (front
->type
!= VEH_TRAIN
|| front
->cur_speed
== 0)) continue;
991 /* Check vehicle type specifics */
994 if (Train::From(v
)->IsWagon()) continue;
998 if (!RoadVehicle::From(v
)->IsFrontEngine()) continue;
1002 if (!Aircraft::From(v
)->IsNormalAircraft()) continue;
1009 v
->motion_counter
+= front
->cur_speed
;
1010 /* Play a running sound if the motion counter passes 256 (Do we not skip sounds?) */
1011 if (GB(v
->motion_counter
, 0, 8) < front
->cur_speed
) PlayVehicleSound(v
, VSE_RUNNING
);
1013 /* Play an alternating running sound every 16 ticks */
1014 if (GB(v
->tick_counter
, 0, 4) == 0) {
1015 /* Play running sound when speed > 0 and not braking */
1016 bool running
= (front
->cur_speed
> 0) && !(front
->vehstatus
& (VS_STOPPED
| VS_TRAIN_SLOWING
));
1017 PlayVehicleSound(v
, running
? VSE_RUNNING_16
: VSE_STOPPED_16
);
1025 Backup
<CompanyID
> cur_company(_current_company
, FILE_LINE
);
1026 for (auto &it
: _vehicles_to_autoreplace
) {
1027 Vehicle
*v
= it
.first
;
1028 /* Autoreplace needs the current company set as the vehicle owner */
1029 cur_company
.Change(v
->owner
);
1031 /* Start vehicle if we stopped them in VehicleEnteredDepotThisTick()
1032 * We need to stop them between VehicleEnteredDepotThisTick() and here or we risk that
1033 * they are already leaving the depot again before being replaced. */
1034 if (it
.second
) v
->vehstatus
&= ~VS_STOPPED
;
1036 /* Store the position of the effect as the vehicle pointer will become invalid later */
1041 const Company
*c
= Company::Get(_current_company
);
1042 SubtractMoneyFromCompany(CommandCost(EXPENSES_NEW_VEHICLES
, (Money
)c
->settings
.engine_renew_money
));
1043 CommandCost res
= DoCommand(0, v
->index
, 0, DC_EXEC
, CMD_AUTOREPLACE_VEHICLE
);
1044 SubtractMoneyFromCompany(CommandCost(EXPENSES_NEW_VEHICLES
, -(Money
)c
->settings
.engine_renew_money
));
1046 if (!IsLocalCompany()) continue;
1048 if (res
.Succeeded()) {
1049 ShowCostOrIncomeAnimation(x
, y
, z
, res
.GetCost());
1053 StringID error_message
= res
.GetErrorMessage();
1054 if (error_message
== STR_ERROR_AUTOREPLACE_NOTHING_TO_DO
|| error_message
== INVALID_STRING_ID
) continue;
1056 if (error_message
== STR_ERROR_NOT_ENOUGH_CASH_REQUIRES_CURRENCY
) error_message
= STR_ERROR_AUTOREPLACE_MONEY_LIMIT
;
1059 if (error_message
== STR_ERROR_TRAIN_TOO_LONG_AFTER_REPLACEMENT
) {
1060 message
= error_message
;
1062 message
= STR_NEWS_VEHICLE_AUTORENEW_FAILED
;
1065 SetDParam(0, v
->index
);
1066 SetDParam(1, error_message
);
1067 AddVehicleAdviceNewsItem(message
, v
->index
);
1070 cur_company
.Restore();
1074 * Add vehicle sprite for drawing to the screen.
1075 * @param v Vehicle to draw.
1077 static void DoDrawVehicle(const Vehicle
*v
)
1079 PaletteID pal
= PAL_NONE
;
1081 if (v
->vehstatus
& VS_DEFPAL
) pal
= (v
->vehstatus
& VS_CRASHED
) ? PALETTE_CRASH
: GetVehiclePalette(v
);
1083 /* Check whether the vehicle shall be transparent due to the game state */
1084 bool shadowed
= (v
->vehstatus
& VS_SHADOW
) != 0;
1086 if (v
->type
== VEH_EFFECT
) {
1087 /* Check whether the vehicle shall be transparent/invisible due to GUI settings.
1088 * However, transparent smoke and bubbles look weird, so always hide them. */
1089 TransparencyOption to
= EffectVehicle::From(v
)->GetTransparencyOption();
1090 if (to
!= TO_INVALID
&& (IsTransparencySet(to
) || IsInvisibilitySet(to
))) return;
1093 StartSpriteCombine();
1094 for (uint i
= 0; i
< v
->sprite_seq
.count
; ++i
) {
1095 PaletteID pal2
= v
->sprite_seq
.seq
[i
].pal
;
1096 if (!pal2
|| (v
->vehstatus
& VS_CRASHED
)) pal2
= pal
;
1097 AddSortableSpriteToDraw(v
->sprite_seq
.seq
[i
].sprite
, pal2
, v
->x_pos
+ v
->x_offs
, v
->y_pos
+ v
->y_offs
,
1098 v
->x_extent
, v
->y_extent
, v
->z_extent
, v
->z_pos
, shadowed
, v
->x_bb_offs
, v
->y_bb_offs
);
1104 * Add the vehicle sprites that should be drawn at a part of the screen.
1105 * @param dpi Rectangle being drawn.
1107 void ViewportAddVehicles(DrawPixelInfo
*dpi
)
1109 /* The bounding rectangle */
1110 const int l
= dpi
->left
;
1111 const int r
= dpi
->left
+ dpi
->width
;
1112 const int t
= dpi
->top
;
1113 const int b
= dpi
->top
+ dpi
->height
;
1115 /* The hash area to scan */
1118 if (dpi
->width
+ (MAX_VEHICLE_PIXEL_X
* ZOOM_LVL_BASE
) < GEN_HASHX_SIZE
) {
1119 xl
= GEN_HASHX(l
- MAX_VEHICLE_PIXEL_X
* ZOOM_LVL_BASE
);
1122 /* scan whole hash row */
1124 xu
= GEN_HASHX_MASK
;
1127 if (dpi
->height
+ (MAX_VEHICLE_PIXEL_Y
* ZOOM_LVL_BASE
) < GEN_HASHY_SIZE
) {
1128 yl
= GEN_HASHY(t
- MAX_VEHICLE_PIXEL_Y
* ZOOM_LVL_BASE
);
1131 /* scan whole column */
1133 yu
= GEN_HASHY_MASK
;
1136 for (int y
= yl
;; y
= (y
+ GEN_HASHY_INC
) & GEN_HASHY_MASK
) {
1137 for (int x
= xl
;; x
= (x
+ GEN_HASHX_INC
) & GEN_HASHX_MASK
) {
1138 const Vehicle
*v
= _vehicle_viewport_hash
[x
+ y
]; // already masked & 0xFFF
1140 while (v
!= nullptr) {
1141 if (!(v
->vehstatus
& VS_HIDDEN
) &&
1142 l
<= v
->coord
.right
&&
1143 t
<= v
->coord
.bottom
&&
1144 r
>= v
->coord
.left
&&
1145 b
>= v
->coord
.top
) {
1148 v
= v
->hash_viewport_next
;
1159 * Find the vehicle close to the clicked coordinates.
1160 * @param vp Viewport clicked in.
1161 * @param x X coordinate in the viewport.
1162 * @param y Y coordinate in the viewport.
1163 * @return Closest vehicle, or \c nullptr if none found.
1165 Vehicle
*CheckClickOnVehicle(const ViewPort
*vp
, int x
, int y
)
1167 Vehicle
*found
= nullptr;
1168 uint dist
, best_dist
= UINT_MAX
;
1170 if ((uint
)(x
-= vp
->left
) >= (uint
)vp
->width
|| (uint
)(y
-= vp
->top
) >= (uint
)vp
->height
) return nullptr;
1172 x
= ScaleByZoom(x
, vp
->zoom
) + vp
->virtual_left
;
1173 y
= ScaleByZoom(y
, vp
->zoom
) + vp
->virtual_top
;
1175 for (Vehicle
*v
: Vehicle::Iterate()) {
1176 if ((v
->vehstatus
& (VS_HIDDEN
| VS_UNCLICKABLE
)) == 0 &&
1177 x
>= v
->coord
.left
&& x
<= v
->coord
.right
&&
1178 y
>= v
->coord
.top
&& y
<= v
->coord
.bottom
) {
1181 abs(((v
->coord
.left
+ v
->coord
.right
) >> 1) - x
),
1182 abs(((v
->coord
.top
+ v
->coord
.bottom
) >> 1) - y
)
1185 if (dist
< best_dist
) {
1196 * Decrease the value of a vehicle.
1197 * @param v %Vehicle to devaluate.
1199 void DecreaseVehicleValue(Vehicle
*v
)
1201 v
->value
-= v
->value
>> 8;
1202 SetWindowDirty(WC_VEHICLE_DETAILS
, v
->index
);
1205 static const byte _breakdown_chance
[64] = {
1206 3, 3, 3, 3, 3, 3, 3, 3,
1207 4, 4, 5, 5, 6, 6, 7, 7,
1208 8, 8, 9, 9, 10, 10, 11, 11,
1209 12, 13, 13, 13, 13, 14, 15, 16,
1210 17, 19, 21, 25, 28, 31, 34, 37,
1211 40, 44, 48, 52, 56, 60, 64, 68,
1212 72, 80, 90, 100, 110, 120, 130, 140,
1213 150, 170, 190, 210, 230, 250, 250, 250,
1216 void CheckVehicleBreakdown(Vehicle
*v
)
1220 /* decrease reliability */
1221 if (!_settings_game
.order
.no_servicing_if_no_breakdowns
||
1222 _settings_game
.difficulty
.vehicle_breakdowns
!= 0) {
1223 v
->reliability
= rel
= max((rel_old
= v
->reliability
) - v
->reliability_spd_dec
, 0);
1224 if ((rel_old
>> 8) != (rel
>> 8)) SetWindowDirty(WC_VEHICLE_DETAILS
, v
->index
);
1227 if (v
->breakdown_ctr
!= 0 || (v
->vehstatus
& VS_STOPPED
) ||
1228 _settings_game
.difficulty
.vehicle_breakdowns
< 1 ||
1229 v
->cur_speed
< 5 || _game_mode
== GM_MENU
) {
1233 uint32 r
= Random();
1235 /* increase chance of failure */
1236 int chance
= v
->breakdown_chance
+ 1;
1237 if (Chance16I(1, 25, r
)) chance
+= 25;
1238 v
->breakdown_chance
= min(255, chance
);
1240 /* calculate reliability value to use in comparison */
1241 rel
= v
->reliability
;
1242 if (v
->type
== VEH_SHIP
) rel
+= 0x6666;
1244 /* reduced breakdowns? */
1245 if (_settings_game
.difficulty
.vehicle_breakdowns
== 1) rel
+= 0x6666;
1247 /* check if to break down */
1248 if (_breakdown_chance
[(uint
)min(rel
, 0xffff) >> 10] <= v
->breakdown_chance
) {
1249 v
->breakdown_ctr
= GB(r
, 16, 6) + 0x3F;
1250 v
->breakdown_delay
= GB(r
, 24, 7) + 0x80;
1251 v
->breakdown_chance
= 0;
1256 * Handle all of the aspects of a vehicle breakdown
1257 * This includes adding smoke and sounds, and ending the breakdown when appropriate.
1258 * @return true iff the vehicle is stopped because of a breakdown
1259 * @note This function always returns false for aircraft, since these never stop for breakdowns
1261 bool Vehicle::HandleBreakdown()
1263 /* Possible states for Vehicle::breakdown_ctr
1264 * 0 - vehicle is running normally
1265 * 1 - vehicle is currently broken down
1266 * 2 - vehicle is going to break down now
1267 * >2 - vehicle is counting down to the actual breakdown event */
1268 switch (this->breakdown_ctr
) {
1273 this->breakdown_ctr
= 1;
1275 if (this->breakdowns_since_last_service
!= 255) {
1276 this->breakdowns_since_last_service
++;
1279 if (this->type
== VEH_AIRCRAFT
) {
1280 /* Aircraft just need this flag, the rest is handled elsewhere */
1281 this->vehstatus
|= VS_AIRCRAFT_BROKEN
;
1283 this->cur_speed
= 0;
1285 if (!PlayVehicleSound(this, VSE_BREAKDOWN
)) {
1286 bool train_or_ship
= this->type
== VEH_TRAIN
|| this->type
== VEH_SHIP
;
1287 SndPlayVehicleFx((_settings_game
.game_creation
.landscape
!= LT_TOYLAND
) ?
1288 (train_or_ship
? SND_10_TRAIN_BREAKDOWN
: SND_0F_VEHICLE_BREAKDOWN
) :
1289 (train_or_ship
? SND_3A_COMEDY_BREAKDOWN_2
: SND_35_COMEDY_BREAKDOWN
), this);
1292 if (!(this->vehstatus
& VS_HIDDEN
) && !HasBit(EngInfo(this->engine_type
)->misc_flags
, EF_NO_BREAKDOWN_SMOKE
)) {
1293 EffectVehicle
*u
= CreateEffectVehicleRel(this, 4, 4, 5, EV_BREAKDOWN_SMOKE
);
1294 if (u
!= nullptr) u
->animation_state
= this->breakdown_delay
* 2;
1298 this->MarkDirty(); // Update graphics after speed is zeroed
1299 SetWindowDirty(WC_VEHICLE_VIEW
, this->index
);
1300 SetWindowDirty(WC_VEHICLE_DETAILS
, this->index
);
1304 /* Aircraft breakdowns end only when arriving at the airport */
1305 if (this->type
== VEH_AIRCRAFT
) return false;
1307 /* For trains this function is called twice per tick, so decrease v->breakdown_delay at half the rate */
1308 if ((this->tick_counter
& (this->type
== VEH_TRAIN
? 3 : 1)) == 0) {
1309 if (--this->breakdown_delay
== 0) {
1310 this->breakdown_ctr
= 0;
1312 SetWindowDirty(WC_VEHICLE_VIEW
, this->index
);
1318 if (!this->current_order
.IsType(OT_LOADING
)) this->breakdown_ctr
--;
1324 * Update age of a vehicle.
1325 * @param v Vehicle to update.
1327 void AgeVehicle(Vehicle
*v
)
1329 if (v
->age
< MAX_DAY
) {
1331 if (v
->IsPrimaryVehicle() && v
->age
== VEHICLE_PROFIT_MIN_AGE
+ 1) GroupStatistics::VehicleReachedProfitAge(v
);
1334 if (!v
->IsPrimaryVehicle() && (v
->type
!= VEH_TRAIN
|| !Train::From(v
)->IsEngine())) return;
1336 int age
= v
->age
- v
->max_age
;
1337 if (age
== DAYS_IN_LEAP_YEAR
* 0 || age
== DAYS_IN_LEAP_YEAR
* 1 ||
1338 age
== DAYS_IN_LEAP_YEAR
* 2 || age
== DAYS_IN_LEAP_YEAR
* 3 || age
== DAYS_IN_LEAP_YEAR
* 4) {
1339 v
->reliability_spd_dec
<<= 1;
1342 SetWindowDirty(WC_VEHICLE_DETAILS
, v
->index
);
1344 /* Don't warn about non-primary or not ours vehicles or vehicles that are crashed */
1345 if (v
->Previous() != nullptr || v
->owner
!= _local_company
|| (v
->vehstatus
& VS_CRASHED
) != 0) return;
1347 const Company
*c
= Company::Get(v
->owner
);
1348 /* Don't warn if a renew is active */
1349 if (c
->settings
.engine_renew
&& v
->GetEngine()->company_avail
!= 0) return;
1350 /* Don't warn if a replacement is active */
1351 if (EngineHasReplacementForCompany(c
, v
->engine_type
, v
->group_id
)) return;
1354 if (age
== -DAYS_IN_LEAP_YEAR
) {
1355 str
= STR_NEWS_VEHICLE_IS_GETTING_OLD
;
1356 } else if (age
== 0) {
1357 str
= STR_NEWS_VEHICLE_IS_GETTING_VERY_OLD
;
1358 } else if (age
> 0 && (age
% DAYS_IN_LEAP_YEAR
) == 0) {
1359 str
= STR_NEWS_VEHICLE_IS_GETTING_VERY_OLD_AND
;
1364 SetDParam(0, v
->index
);
1365 AddVehicleAdviceNewsItem(str
, v
->index
);
1369 * Calculates how full a vehicle is.
1370 * @param front The front vehicle of the consist to check.
1371 * @param colour The string to show depending on if we are unloading or loading
1372 * @return A percentage of how full the Vehicle is.
1373 * Percentages are rounded towards 50%, so that 0% and 100% are only returned
1374 * if the vehicle is completely empty or full.
1375 * This is useful for both display and conditional orders.
1377 uint8
CalcPercentVehicleFilled(const Vehicle
*front
, StringID
*colour
)
1383 bool loading
= false;
1385 bool is_loading
= front
->current_order
.IsType(OT_LOADING
);
1387 /* The station may be nullptr when the (colour) string does not need to be set. */
1388 const Station
*st
= Station::GetIfValid(front
->last_station_visited
);
1389 assert(colour
== nullptr || (st
!= nullptr && is_loading
));
1391 bool order_no_load
= is_loading
&& (front
->current_order
.GetLoadType() & OLFB_NO_LOAD
);
1392 bool order_full_load
= is_loading
&& (front
->current_order
.GetLoadType() & OLFB_FULL_LOAD
);
1394 /* Count up max and used */
1395 for (const Vehicle
*v
= front
; v
!= nullptr; v
= v
->Next()) {
1396 count
+= v
->cargo
.StoredCount();
1397 max
+= v
->cargo_cap
;
1398 if (v
->cargo_cap
!= 0 && colour
!= nullptr) {
1399 unloading
+= HasBit(v
->vehicle_flags
, VF_CARGO_UNLOADING
) ? 1 : 0;
1400 loading
|= !order_no_load
&&
1401 (order_full_load
|| st
->goods
[v
->cargo_type
].HasRating()) &&
1402 !HasBit(v
->vehicle_flags
, VF_LOADING_FINISHED
) && !HasBit(v
->vehicle_flags
, VF_STOP_LOADING
);
1407 if (colour
!= nullptr) {
1408 if (unloading
== 0 && loading
) {
1409 *colour
= STR_PERCENT_UP
;
1410 } else if (unloading
== 0 && !loading
) {
1411 *colour
= STR_PERCENT_NONE
;
1412 } else if (cars
== unloading
|| !loading
) {
1413 *colour
= STR_PERCENT_DOWN
;
1415 *colour
= STR_PERCENT_UP_DOWN
;
1419 /* Train without capacity */
1420 if (max
== 0) return 100;
1422 /* Return the percentage */
1423 if (count
* 2 < max
) {
1424 /* Less than 50%; round up, so that 0% means really empty. */
1425 return CeilDiv(count
* 100, max
);
1427 /* More than 50%; round down, so that 100% means really full. */
1428 return (count
* 100) / max
;
1433 * Vehicle entirely entered the depot, update its status, orders, vehicle windows, service it, etc.
1434 * @param v Vehicle that entered a depot.
1436 void VehicleEnterDepot(Vehicle
*v
)
1438 /* Always work with the front of the vehicle */
1439 assert(v
== v
->First());
1443 Train
*t
= Train::From(v
);
1444 SetWindowClassesDirty(WC_TRAINS_LIST
);
1445 /* Clear path reservation */
1446 SetDepotReservation(t
->tile
, false);
1447 if (_settings_client
.gui
.show_track_reservation
) MarkTileDirtyByTile(t
->tile
);
1449 UpdateSignalsOnSegment(t
->tile
, INVALID_DIAGDIR
, t
->owner
);
1450 t
->wait_counter
= 0;
1451 t
->force_proceed
= TFP_NONE
;
1452 ClrBit(t
->flags
, VRF_TOGGLE_REVERSE
);
1453 t
->ConsistChanged(CCF_ARRANGE
);
1458 SetWindowClassesDirty(WC_ROADVEH_LIST
);
1462 SetWindowClassesDirty(WC_SHIPS_LIST
);
1463 Ship
*ship
= Ship::From(v
);
1464 ship
->state
= TRACK_BIT_DEPOT
;
1465 ship
->UpdateCache();
1466 ship
->UpdateViewport(true, true);
1467 SetWindowDirty(WC_VEHICLE_DEPOT
, v
->tile
);
1472 SetWindowClassesDirty(WC_AIRCRAFT_LIST
);
1473 HandleAircraftEnterHangar(Aircraft::From(v
));
1475 default: NOT_REACHED();
1477 SetWindowDirty(WC_VEHICLE_VIEW
, v
->index
);
1479 if (v
->type
!= VEH_TRAIN
) {
1480 /* Trains update the vehicle list when the first unit enters the depot and calls VehicleEnterDepot() when the last unit enters.
1481 * 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 */
1482 InvalidateWindowData(WC_VEHICLE_DEPOT
, v
->tile
);
1484 SetWindowDirty(WC_VEHICLE_DEPOT
, v
->tile
);
1486 v
->vehstatus
|= VS_HIDDEN
;
1489 VehicleServiceInDepot(v
);
1491 /* After a vehicle trigger, the graphics and properties of the vehicle could change. */
1492 TriggerVehicle(v
, VEHICLE_TRIGGER_DEPOT
);
1495 InvalidateWindowData(WC_VEHICLE_VIEW
, v
->index
);
1497 if (v
->current_order
.IsType(OT_GOTO_DEPOT
)) {
1498 SetWindowDirty(WC_VEHICLE_VIEW
, v
->index
);
1500 const Order
*real_order
= v
->GetOrder(v
->cur_real_order_index
);
1502 /* Test whether we are heading for this depot. If not, do nothing.
1503 * Note: The target depot for nearest-/manual-depot-orders is only updated on junctions, but we want to accept every depot. */
1504 if ((v
->current_order
.GetDepotOrderType() & ODTFB_PART_OF_ORDERS
) &&
1505 real_order
!= nullptr && !(real_order
->GetDepotActionType() & ODATFB_NEAREST_DEPOT
) &&
1506 (v
->type
== VEH_AIRCRAFT
? v
->current_order
.GetDestination() != GetStationIndex(v
->tile
) : v
->dest_tile
!= v
->tile
)) {
1507 /* We are heading for another depot, keep driving. */
1511 if (v
->current_order
.IsRefit()) {
1512 Backup
<CompanyID
> cur_company(_current_company
, v
->owner
, FILE_LINE
);
1513 CommandCost cost
= DoCommand(v
->tile
, v
->index
, v
->current_order
.GetRefitCargo() | 0xFF << 8, DC_EXEC
, GetCmdRefitVeh(v
));
1514 cur_company
.Restore();
1516 if (cost
.Failed()) {
1517 _vehicles_to_autoreplace
[v
] = false;
1518 if (v
->owner
== _local_company
) {
1519 /* Notify the user that we stopped the vehicle */
1520 SetDParam(0, v
->index
);
1521 AddVehicleAdviceNewsItem(STR_NEWS_ORDER_REFIT_FAILED
, v
->index
);
1523 } else if (cost
.GetCost() != 0) {
1524 v
->profit_this_year
-= cost
.GetCost() << 8;
1525 if (v
->owner
== _local_company
) {
1526 ShowCostOrIncomeAnimation(v
->x_pos
, v
->y_pos
, v
->z_pos
, cost
.GetCost());
1531 if (v
->current_order
.GetDepotOrderType() & ODTFB_PART_OF_ORDERS
) {
1532 /* Part of orders */
1533 v
->DeleteUnreachedImplicitOrders();
1534 UpdateVehicleTimetable(v
, true);
1535 v
->IncrementImplicitOrderIndex();
1537 if (v
->current_order
.GetDepotActionType() & ODATFB_HALT
) {
1538 /* Vehicles are always stopped on entering depots. Do not restart this one. */
1539 _vehicles_to_autoreplace
[v
] = false;
1540 /* Invalidate last_loading_station. As the link from the station
1541 * before the stop to the station after the stop can't be predicted
1542 * we shouldn't construct it when the vehicle visits the next stop. */
1543 v
->last_loading_station
= INVALID_STATION
;
1544 if (v
->owner
== _local_company
) {
1545 SetDParam(0, v
->index
);
1546 AddVehicleAdviceNewsItem(STR_NEWS_TRAIN_IS_WAITING
+ v
->type
, v
->index
);
1548 AI::NewEvent(v
->owner
, new ScriptEventVehicleWaitingInDepot(v
->index
));
1550 v
->current_order
.MakeDummy();
1556 * Update the position of the vehicle. This will update the hash that tells
1557 * which vehicles are on a tile.
1559 void Vehicle::UpdatePosition()
1561 UpdateVehicleTileHash(this, false);
1565 * Update the vehicle on the viewport, updating the right hash and setting the
1567 * @param dirty Mark the (new and old) coordinates of the vehicle as dirty.
1569 void Vehicle::UpdateViewport(bool dirty
)
1572 this->sprite_seq
.GetBounds(&new_coord
);
1574 Point pt
= RemapCoords(this->x_pos
+ this->x_offs
, this->y_pos
+ this->y_offs
, this->z_pos
);
1575 new_coord
.left
+= pt
.x
;
1576 new_coord
.top
+= pt
.y
;
1577 new_coord
.right
+= pt
.x
+ 2 * ZOOM_LVL_BASE
;
1578 new_coord
.bottom
+= pt
.y
+ 2 * ZOOM_LVL_BASE
;
1580 UpdateVehicleViewportHash(this, new_coord
.left
, new_coord
.top
);
1582 Rect old_coord
= this->coord
;
1583 this->coord
= new_coord
;
1586 if (old_coord
.left
== INVALID_COORD
) {
1587 this->MarkAllViewportsDirty();
1589 ::MarkAllViewportsDirty(
1590 min(old_coord
.left
, this->coord
.left
),
1591 min(old_coord
.top
, this->coord
.top
),
1592 max(old_coord
.right
, this->coord
.right
),
1593 max(old_coord
.bottom
, this->coord
.bottom
));
1599 * Update the position of the vehicle, and update the viewport.
1601 void Vehicle::UpdatePositionAndViewport()
1603 this->UpdatePosition();
1604 this->UpdateViewport(true);
1608 * Marks viewports dirty where the vehicle's image is.
1610 void Vehicle::MarkAllViewportsDirty() const
1612 ::MarkAllViewportsDirty(this->coord
.left
, this->coord
.top
, this->coord
.right
, this->coord
.bottom
);
1616 * Get position information of a vehicle when moving one pixel in the direction it is facing
1617 * @param v Vehicle to move
1618 * @return Position information after the move
1620 GetNewVehiclePosResult
GetNewVehiclePos(const Vehicle
*v
)
1622 static const int8 _delta_coord
[16] = {
1623 -1,-1,-1, 0, 1, 1, 1, 0, /* x */
1624 -1, 0, 1, 1, 1, 0,-1,-1, /* y */
1627 int x
= v
->x_pos
+ _delta_coord
[v
->direction
];
1628 int y
= v
->y_pos
+ _delta_coord
[v
->direction
+ 8];
1630 GetNewVehiclePosResult gp
;
1633 gp
.old_tile
= v
->tile
;
1634 gp
.new_tile
= TileVirtXY(x
, y
);
1638 static const Direction _new_direction_table
[] = {
1639 DIR_N
, DIR_NW
, DIR_W
,
1640 DIR_NE
, DIR_SE
, DIR_SW
,
1641 DIR_E
, DIR_SE
, DIR_S
1644 Direction
GetDirectionTowards(const Vehicle
*v
, int x
, int y
)
1648 if (y
>= v
->y_pos
) {
1649 if (y
!= v
->y_pos
) i
+= 3;
1653 if (x
>= v
->x_pos
) {
1654 if (x
!= v
->x_pos
) i
++;
1658 Direction dir
= v
->direction
;
1660 DirDiff dirdiff
= DirDifference(_new_direction_table
[i
], dir
);
1661 if (dirdiff
== DIRDIFF_SAME
) return dir
;
1662 return ChangeDir(dir
, dirdiff
> DIRDIFF_REVERSE
? DIRDIFF_45LEFT
: DIRDIFF_45RIGHT
);
1666 * Call the tile callback function for a vehicle entering a tile
1667 * @param v Vehicle entering the tile
1668 * @param tile Tile entered
1669 * @param x X position
1670 * @param y Y position
1671 * @return Some meta-data over the to be entered tile.
1672 * @see VehicleEnterTileStatus to see what the bits in the return value mean.
1674 VehicleEnterTileStatus
VehicleEnterTile(Vehicle
*v
, TileIndex tile
, int x
, int y
)
1676 return _tile_type_procs
[GetTileType(tile
)]->vehicle_enter_tile_proc(v
, tile
, x
, y
);
1680 * Initializes the structure. Vehicle unit numbers are supposed not to change after
1681 * struct initialization, except after each call to this->NextID() the returned value
1682 * is assigned to a vehicle.
1683 * @param type type of vehicle
1684 * @param owner owner of vehicles
1686 FreeUnitIDGenerator::FreeUnitIDGenerator(VehicleType type
, CompanyID owner
) : cache(nullptr), maxid(0), curid(0)
1689 for (const Vehicle
*v
: Vehicle::Iterate()) {
1690 if (v
->type
== type
&& v
->owner
== owner
) {
1691 this->maxid
= max
<UnitID
>(this->maxid
, v
->unitnumber
);
1695 if (this->maxid
== 0) return;
1697 /* Reserving 'maxid + 2' because we need:
1698 * - space for the last item (with v->unitnumber == maxid)
1699 * - one free slot working as loop terminator in FreeUnitIDGenerator::NextID() */
1700 this->cache
= CallocT
<bool>(this->maxid
+ 2);
1702 /* Fill the cache */
1703 for (const Vehicle
*v
: Vehicle::Iterate()) {
1704 if (v
->type
== type
&& v
->owner
== owner
) {
1705 this->cache
[v
->unitnumber
] = true;
1710 /** Returns next free UnitID. Supposes the last returned value was assigned to a vehicle. */
1711 UnitID
FreeUnitIDGenerator::NextID()
1713 if (this->maxid
<= this->curid
) return ++this->curid
;
1715 while (this->cache
[++this->curid
]) { } // it will stop, we reserved more space than needed
1721 * Get an unused unit number for a vehicle (if allowed).
1722 * @param type Type of vehicle
1723 * @return A unused unit number for the given type of vehicle if it is allowed to build one, else \c UINT16_MAX.
1725 UnitID
GetFreeUnitNumber(VehicleType type
)
1727 /* Check whether it is allowed to build another vehicle. */
1730 case VEH_TRAIN
: max_veh
= _settings_game
.vehicle
.max_trains
; break;
1731 case VEH_ROAD
: max_veh
= _settings_game
.vehicle
.max_roadveh
; break;
1732 case VEH_SHIP
: max_veh
= _settings_game
.vehicle
.max_ships
; break;
1733 case VEH_AIRCRAFT
: max_veh
= _settings_game
.vehicle
.max_aircraft
; break;
1734 default: NOT_REACHED();
1737 const Company
*c
= Company::Get(_current_company
);
1738 if (c
->group_all
[type
].num_vehicle
>= max_veh
) return UINT16_MAX
; // Currently already at the limit, no room to make a new one.
1740 FreeUnitIDGenerator
gen(type
, _current_company
);
1742 return gen
.NextID();
1747 * Check whether we can build infrastructure for the given
1748 * vehicle type. This to disable building stations etc. when
1749 * you are not allowed/able to have the vehicle type yet.
1750 * @param type the vehicle type to check this for
1751 * @return true if there is any reason why you may build
1752 * the infrastructure for the given vehicle type
1754 bool CanBuildVehicleInfrastructure(VehicleType type
, byte subtype
)
1756 assert(IsCompanyBuildableVehicleType(type
));
1758 if (!Company::IsValidID(_local_company
)) return false;
1759 if (!_settings_client
.gui
.disable_unsuitable_building
) return true;
1764 if (!HasAnyRailtypesAvail(_local_company
)) return false;
1765 max
= _settings_game
.vehicle
.max_trains
;
1768 if (!HasAnyRoadTypesAvail(_local_company
, (RoadTramType
)subtype
)) return false;
1769 max
= _settings_game
.vehicle
.max_roadveh
;
1771 case VEH_SHIP
: max
= _settings_game
.vehicle
.max_ships
; break;
1772 case VEH_AIRCRAFT
: max
= _settings_game
.vehicle
.max_aircraft
; break;
1773 default: NOT_REACHED();
1776 /* We can build vehicle infrastructure when we may build the vehicle type */
1778 /* Can we actually build the vehicle type? */
1779 for (const Engine
*e
: Engine::IterateType(type
)) {
1780 if (type
== VEH_ROAD
&& GetRoadTramType(e
->u
.road
.roadtype
) != (RoadTramType
)subtype
) continue;
1781 if (HasBit(e
->company_avail
, _local_company
)) return true;
1786 /* We should be able to build infrastructure when we have the actual vehicle type */
1787 for (const Vehicle
*v
: Vehicle::Iterate()) {
1788 if (v
->type
== VEH_ROAD
&& GetRoadTramType(RoadVehicle::From(v
)->roadtype
) != (RoadTramType
)subtype
) continue;
1789 if (v
->owner
== _local_company
&& v
->type
== type
) return true;
1797 * Determines the #LiveryScheme for a vehicle.
1798 * @param engine_type Engine of the vehicle.
1799 * @param parent_engine_type Engine of the front vehicle, #INVALID_ENGINE if vehicle is at front itself.
1800 * @param v the vehicle, \c nullptr if in purchase list etc.
1801 * @return livery scheme to use.
1803 LiveryScheme
GetEngineLiveryScheme(EngineID engine_type
, EngineID parent_engine_type
, const Vehicle
*v
)
1805 CargoID cargo_type
= v
== nullptr ? (CargoID
)CT_INVALID
: v
->cargo_type
;
1806 const Engine
*e
= Engine::Get(engine_type
);
1808 default: NOT_REACHED();
1810 if (v
!= nullptr && parent_engine_type
!= INVALID_ENGINE
&& (UsesWagonOverride(v
) || (v
->IsArticulatedPart() && e
->u
.rail
.railveh_type
!= RAILVEH_WAGON
))) {
1811 /* Wagonoverrides use the colour scheme of the front engine.
1812 * Articulated parts use the colour scheme of the first part. (Not supported for articulated wagons) */
1813 engine_type
= parent_engine_type
;
1814 e
= Engine::Get(engine_type
);
1815 /* Note: Luckily cargo_type is not needed for engines */
1818 if (cargo_type
== CT_INVALID
) cargo_type
= e
->GetDefaultCargoType();
1819 if (cargo_type
== CT_INVALID
) cargo_type
= CT_GOODS
; // The vehicle does not carry anything, let's pick some freight cargo
1820 if (e
->u
.rail
.railveh_type
== RAILVEH_WAGON
) {
1821 if (!CargoSpec::Get(cargo_type
)->is_freight
) {
1822 if (parent_engine_type
== INVALID_ENGINE
) {
1823 return LS_PASSENGER_WAGON_STEAM
;
1825 bool is_mu
= HasBit(EngInfo(parent_engine_type
)->misc_flags
, EF_RAIL_IS_MU
);
1826 switch (RailVehInfo(parent_engine_type
)->engclass
) {
1827 default: NOT_REACHED();
1828 case EC_STEAM
: return LS_PASSENGER_WAGON_STEAM
;
1829 case EC_DIESEL
: return is_mu
? LS_DMU
: LS_PASSENGER_WAGON_DIESEL
;
1830 case EC_ELECTRIC
: return is_mu
? LS_EMU
: LS_PASSENGER_WAGON_ELECTRIC
;
1831 case EC_MONORAIL
: return LS_PASSENGER_WAGON_MONORAIL
;
1832 case EC_MAGLEV
: return LS_PASSENGER_WAGON_MAGLEV
;
1836 return LS_FREIGHT_WAGON
;
1839 bool is_mu
= HasBit(e
->info
.misc_flags
, EF_RAIL_IS_MU
);
1841 switch (e
->u
.rail
.engclass
) {
1842 default: NOT_REACHED();
1843 case EC_STEAM
: return LS_STEAM
;
1844 case EC_DIESEL
: return is_mu
? LS_DMU
: LS_DIESEL
;
1845 case EC_ELECTRIC
: return is_mu
? LS_EMU
: LS_ELECTRIC
;
1846 case EC_MONORAIL
: return LS_MONORAIL
;
1847 case EC_MAGLEV
: return LS_MAGLEV
;
1852 /* Always use the livery of the front */
1853 if (v
!= nullptr && parent_engine_type
!= INVALID_ENGINE
) {
1854 engine_type
= parent_engine_type
;
1855 e
= Engine::Get(engine_type
);
1856 cargo_type
= v
->First()->cargo_type
;
1858 if (cargo_type
== CT_INVALID
) cargo_type
= e
->GetDefaultCargoType();
1859 if (cargo_type
== CT_INVALID
) cargo_type
= CT_GOODS
; // The vehicle does not carry anything, let's pick some freight cargo
1861 /* Important: Use Tram Flag of front part. Luckily engine_type refers to the front part here. */
1862 if (HasBit(e
->info
.misc_flags
, EF_ROAD_TRAM
)) {
1864 return IsCargoInClass(cargo_type
, CC_PASSENGERS
) ? LS_PASSENGER_TRAM
: LS_FREIGHT_TRAM
;
1867 return IsCargoInClass(cargo_type
, CC_PASSENGERS
) ? LS_BUS
: LS_TRUCK
;
1871 if (cargo_type
== CT_INVALID
) cargo_type
= e
->GetDefaultCargoType();
1872 if (cargo_type
== CT_INVALID
) cargo_type
= CT_GOODS
; // The vehicle does not carry anything, let's pick some freight cargo
1873 return IsCargoInClass(cargo_type
, CC_PASSENGERS
) ? LS_PASSENGER_SHIP
: LS_FREIGHT_SHIP
;
1876 switch (e
->u
.air
.subtype
) {
1877 case AIR_HELI
: return LS_HELICOPTER
;
1878 case AIR_CTOL
: return LS_SMALL_PLANE
;
1879 case AIR_CTOL
| AIR_FAST
: return LS_LARGE_PLANE
;
1880 default: NOT_REACHED();
1886 * Determines the livery for a vehicle.
1887 * @param engine_type EngineID of the vehicle
1888 * @param company Owner of the vehicle
1889 * @param parent_engine_type EngineID of the front vehicle. INVALID_VEHICLE if vehicle is at front itself.
1890 * @param v the vehicle. nullptr if in purchase list etc.
1891 * @param livery_setting The livery settings to use for acquiring the livery information.
1892 * @return livery to use
1894 const Livery
*GetEngineLivery(EngineID engine_type
, CompanyID company
, EngineID parent_engine_type
, const Vehicle
*v
, byte livery_setting
)
1896 const Company
*c
= Company::Get(company
);
1897 LiveryScheme scheme
= LS_DEFAULT
;
1899 if (livery_setting
== LIT_ALL
|| (livery_setting
== LIT_COMPANY
&& company
== _local_company
)) {
1901 const Group
*g
= Group::GetIfValid(v
->First()->group_id
);
1903 /* Traverse parents until we find a livery or reach the top */
1904 while (g
->livery
.in_use
== 0 && g
->parent
!= INVALID_GROUP
) {
1905 g
= Group::Get(g
->parent
);
1907 if (g
->livery
.in_use
!= 0) return &g
->livery
;
1911 /* The default livery is always available for use, but its in_use flag determines
1912 * whether any _other_ liveries are in use. */
1913 if (c
->livery
[LS_DEFAULT
].in_use
!= 0) {
1914 /* Determine the livery scheme to use */
1915 scheme
= GetEngineLiveryScheme(engine_type
, parent_engine_type
, v
);
1919 return &c
->livery
[scheme
];
1923 static PaletteID
GetEngineColourMap(EngineID engine_type
, CompanyID company
, EngineID parent_engine_type
, const Vehicle
*v
)
1925 PaletteID map
= (v
!= nullptr) ? v
->colourmap
: PAL_NONE
;
1927 /* Return cached value if any */
1928 if (map
!= PAL_NONE
) return map
;
1930 const Engine
*e
= Engine::Get(engine_type
);
1932 /* Check if we should use the colour map callback */
1933 if (HasBit(e
->info
.callback_mask
, CBM_VEHICLE_COLOUR_REMAP
)) {
1934 uint16 callback
= GetVehicleCallback(CBID_VEHICLE_COLOUR_MAPPING
, 0, 0, engine_type
, v
);
1935 /* Failure means "use the default two-colour" */
1936 if (callback
!= CALLBACK_FAILED
) {
1937 assert_compile(PAL_NONE
== 0); // Returning 0x4000 (resp. 0xC000) coincidences with default value (PAL_NONE)
1938 map
= GB(callback
, 0, 14);
1939 /* If bit 14 is set, then the company colours are applied to the
1940 * map else it's returned as-is. */
1941 if (!HasBit(callback
, 14)) {
1943 if (v
!= nullptr) const_cast<Vehicle
*>(v
)->colourmap
= map
;
1949 bool twocc
= HasBit(e
->info
.misc_flags
, EF_USES_2CC
);
1951 if (map
== PAL_NONE
) map
= twocc
? (PaletteID
)SPR_2CCMAP_BASE
: (PaletteID
)PALETTE_RECOLOUR_START
;
1953 /* Spectator has news shown too, but has invalid company ID - as well as dedicated server */
1954 if (!Company::IsValidID(company
)) return map
;
1956 const Livery
*livery
= GetEngineLivery(engine_type
, company
, parent_engine_type
, v
, _settings_client
.gui
.liveries
);
1958 map
+= livery
->colour1
;
1959 if (twocc
) map
+= livery
->colour2
* 16;
1962 if (v
!= nullptr) const_cast<Vehicle
*>(v
)->colourmap
= map
;
1967 * Get the colour map for an engine. This used for unbuilt engines in the user interface.
1968 * @param engine_type ID of engine
1969 * @param company ID of company
1970 * @return A ready-to-use palette modifier
1972 PaletteID
GetEnginePalette(EngineID engine_type
, CompanyID company
)
1974 return GetEngineColourMap(engine_type
, company
, INVALID_ENGINE
, nullptr);
1978 * Get the colour map for a vehicle.
1979 * @param v Vehicle to get colour map for
1980 * @return A ready-to-use palette modifier
1982 PaletteID
GetVehiclePalette(const Vehicle
*v
)
1984 if (v
->IsGroundVehicle()) {
1985 return GetEngineColourMap(v
->engine_type
, v
->owner
, v
->GetGroundVehicleCache()->first_engine
, v
);
1988 return GetEngineColourMap(v
->engine_type
, v
->owner
, INVALID_ENGINE
, v
);
1992 * Delete all implicit orders which were not reached.
1994 void Vehicle::DeleteUnreachedImplicitOrders()
1996 if (this->IsGroundVehicle()) {
1997 uint16
&gv_flags
= this->GetGroundVehicleFlags();
1998 if (HasBit(gv_flags
, GVF_SUPPRESS_IMPLICIT_ORDERS
)) {
1999 /* Do not delete orders, only skip them */
2000 ClrBit(gv_flags
, GVF_SUPPRESS_IMPLICIT_ORDERS
);
2001 this->cur_implicit_order_index
= this->cur_real_order_index
;
2002 InvalidateVehicleOrder(this, 0);
2007 const Order
*order
= this->GetOrder(this->cur_implicit_order_index
);
2008 while (order
!= nullptr) {
2009 if (this->cur_implicit_order_index
== this->cur_real_order_index
) break;
2011 if (order
->IsType(OT_IMPLICIT
)) {
2012 DeleteOrder(this, this->cur_implicit_order_index
);
2013 /* DeleteOrder does various magic with order_indices, so resync 'order' with 'cur_implicit_order_index' */
2014 order
= this->GetOrder(this->cur_implicit_order_index
);
2016 /* Skip non-implicit orders, e.g. service-orders */
2017 order
= order
->next
;
2018 this->cur_implicit_order_index
++;
2022 if (order
== nullptr) {
2023 order
= this->GetOrder(0);
2024 this->cur_implicit_order_index
= 0;
2030 * Prepare everything to begin the loading when arriving at a station.
2031 * @pre IsTileType(this->tile, MP_STATION) || this->type == VEH_SHIP.
2033 void Vehicle::BeginLoading()
2035 assert(IsTileType(this->tile
, MP_STATION
) || this->type
== VEH_SHIP
);
2037 if (this->current_order
.IsType(OT_GOTO_STATION
) &&
2038 this->current_order
.GetDestination() == this->last_station_visited
) {
2039 this->DeleteUnreachedImplicitOrders();
2041 /* Now both order indices point to the destination station, and we can start loading */
2042 this->current_order
.MakeLoading(true);
2043 UpdateVehicleTimetable(this, true);
2045 /* Furthermore add the Non Stop flag to mark that this station
2046 * is the actual destination of the vehicle, which is (for example)
2047 * necessary to be known for HandleTrainLoading to determine
2048 * whether the train is lost or not; not marking a train lost
2049 * that arrives at random stations is bad. */
2050 this->current_order
.SetNonStopType(ONSF_NO_STOP_AT_ANY_STATION
);
2053 /* We weren't scheduled to stop here. Insert an implicit order
2054 * to show that we are stopping here.
2055 * While only groundvehicles have implicit orders, e.g. aircraft might still enter
2056 * the 'wrong' terminal when skipping orders etc. */
2057 Order
*in_list
= this->GetOrder(this->cur_implicit_order_index
);
2058 if (this->IsGroundVehicle() &&
2059 (in_list
== nullptr || !in_list
->IsType(OT_IMPLICIT
) ||
2060 in_list
->GetDestination() != this->last_station_visited
)) {
2061 bool suppress_implicit_orders
= HasBit(this->GetGroundVehicleFlags(), GVF_SUPPRESS_IMPLICIT_ORDERS
);
2062 /* Do not create consecutive duplicates of implicit orders */
2063 Order
*prev_order
= this->cur_implicit_order_index
> 0 ? this->GetOrder(this->cur_implicit_order_index
- 1) : (this->GetNumOrders() > 1 ? this->GetLastOrder() : nullptr);
2064 if (prev_order
== nullptr ||
2065 (!prev_order
->IsType(OT_IMPLICIT
) && !prev_order
->IsType(OT_GOTO_STATION
)) ||
2066 prev_order
->GetDestination() != this->last_station_visited
) {
2068 /* Prefer deleting implicit orders instead of inserting new ones,
2069 * so test whether the right order follows later. In case of only
2070 * implicit orders treat the last order in the list like an
2071 * explicit one, except if the overall number of orders surpasses
2072 * IMPLICIT_ORDER_ONLY_CAP. */
2073 int target_index
= this->cur_implicit_order_index
;
2075 while (target_index
!= this->cur_real_order_index
|| this->GetNumManualOrders() == 0) {
2076 const Order
*order
= this->GetOrder(target_index
);
2077 if (order
== nullptr) break; // No orders.
2078 if (order
->IsType(OT_IMPLICIT
) && order
->GetDestination() == this->last_station_visited
) {
2083 if (target_index
>= this->orders
.list
->GetNumOrders()) {
2084 if (this->GetNumManualOrders() == 0 &&
2085 this->GetNumOrders() < IMPLICIT_ORDER_ONLY_CAP
) {
2090 if (target_index
== this->cur_implicit_order_index
) break; // Avoid infinite loop.
2094 if (suppress_implicit_orders
) {
2095 /* Skip to the found order */
2096 this->cur_implicit_order_index
= target_index
;
2097 InvalidateVehicleOrder(this, 0);
2099 /* Delete all implicit orders up to the station we just reached */
2100 const Order
*order
= this->GetOrder(this->cur_implicit_order_index
);
2101 while (!order
->IsType(OT_IMPLICIT
) || order
->GetDestination() != this->last_station_visited
) {
2102 if (order
->IsType(OT_IMPLICIT
)) {
2103 DeleteOrder(this, this->cur_implicit_order_index
);
2104 /* DeleteOrder does various magic with order_indices, so resync 'order' with 'cur_implicit_order_index' */
2105 order
= this->GetOrder(this->cur_implicit_order_index
);
2107 /* Skip non-implicit orders, e.g. service-orders */
2108 order
= order
->next
;
2109 this->cur_implicit_order_index
++;
2113 if (order
== nullptr) {
2114 order
= this->GetOrder(0);
2115 this->cur_implicit_order_index
= 0;
2117 assert(order
!= nullptr);
2120 } else if (!suppress_implicit_orders
&&
2121 ((this->orders
.list
== nullptr ? OrderList::CanAllocateItem() : this->orders
.list
->GetNumOrders() < MAX_VEH_ORDER_ID
)) &&
2122 Order::CanAllocateItem()) {
2123 /* Insert new implicit order */
2124 Order
*implicit_order
= new Order();
2125 implicit_order
->MakeImplicit(this->last_station_visited
);
2126 InsertOrder(this, implicit_order
, this->cur_implicit_order_index
);
2127 if (this->cur_implicit_order_index
> 0) --this->cur_implicit_order_index
;
2129 /* InsertOrder disabled creation of implicit orders for all vehicles with the same implicit order.
2130 * Reenable it for this vehicle */
2131 uint16
&gv_flags
= this->GetGroundVehicleFlags();
2132 ClrBit(gv_flags
, GVF_SUPPRESS_IMPLICIT_ORDERS
);
2136 this->current_order
.MakeLoading(false);
2139 if (this->last_loading_station
!= INVALID_STATION
&&
2140 this->last_loading_station
!= this->last_station_visited
&&
2141 ((this->current_order
.GetLoadType() & OLFB_NO_LOAD
) == 0 ||
2142 (this->current_order
.GetUnloadType() & OUFB_NO_UNLOAD
) == 0)) {
2143 IncreaseStats(Station::Get(this->last_loading_station
), this, this->last_station_visited
);
2146 PrepareUnload(this);
2148 SetWindowDirty(GetWindowClassForVehicleType(this->type
), this->owner
);
2149 SetWindowWidgetDirty(WC_VEHICLE_VIEW
, this->index
, WID_VV_START_STOP
);
2150 SetWindowDirty(WC_VEHICLE_DETAILS
, this->index
);
2151 SetWindowDirty(WC_STATION_VIEW
, this->last_station_visited
);
2153 Station::Get(this->last_station_visited
)->MarkTilesDirty(true);
2154 this->cur_speed
= 0;
2159 * Return all reserved cargo packets to the station and reset all packets
2160 * staged for transfer.
2161 * @param st the station where the reserved packets should go.
2163 void Vehicle::CancelReservation(StationID next
, Station
*st
)
2165 for (Vehicle
*v
= this; v
!= nullptr; v
= v
->next
) {
2166 VehicleCargoList
&cargo
= v
->cargo
;
2167 if (cargo
.ActionCount(VehicleCargoList::MTA_LOAD
) > 0) {
2168 DEBUG(misc
, 1, "cancelling cargo reservation");
2169 cargo
.Return(UINT_MAX
, &st
->goods
[v
->cargo_type
].cargo
, next
);
2170 cargo
.SetTransferLoadPlace(st
->xy
);
2177 * Perform all actions when leaving a station.
2178 * @pre this->current_order.IsType(OT_LOADING)
2180 void Vehicle::LeaveStation()
2182 assert(this->current_order
.IsType(OT_LOADING
));
2184 delete this->cargo_payment
;
2185 assert(this->cargo_payment
== nullptr); // cleared by ~CargoPayment
2187 /* Only update the timetable if the vehicle was supposed to stop here. */
2188 if (this->current_order
.GetNonStopType() != ONSF_STOP_EVERYWHERE
) UpdateVehicleTimetable(this, false);
2190 if ((this->current_order
.GetLoadType() & OLFB_NO_LOAD
) == 0 ||
2191 (this->current_order
.GetUnloadType() & OUFB_NO_UNLOAD
) == 0) {
2192 if (this->current_order
.CanLeaveWithCargo(this->last_loading_station
!= INVALID_STATION
)) {
2193 /* Refresh next hop stats to make sure we've done that at least once
2194 * during the stop and that refit_cap == cargo_cap for each vehicle in
2196 this->ResetRefitCaps();
2197 LinkRefresher::Run(this);
2199 /* if the vehicle could load here or could stop with cargo loaded set the last loading station */
2200 this->last_loading_station
= this->last_station_visited
;
2202 /* if the vehicle couldn't load and had to unload or transfer everything
2203 * set the last loading station to invalid as it will leave empty. */
2204 this->last_loading_station
= INVALID_STATION
;
2208 this->current_order
.MakeLeaveStation();
2209 Station
*st
= Station::Get(this->last_station_visited
);
2210 this->CancelReservation(INVALID_STATION
, st
);
2211 st
->loading_vehicles
.remove(this);
2213 HideFillingPercent(&this->fill_percent_te_id
);
2214 trip_occupancy
= CalcPercentVehicleFilled(this, nullptr);
2216 if (this->type
== VEH_TRAIN
&& !(this->vehstatus
& VS_CRASHED
)) {
2217 /* Trigger station animation (trains only) */
2218 if (IsTileType(this->tile
, MP_STATION
)) {
2219 TriggerStationRandomisation(st
, this->tile
, SRT_TRAIN_DEPARTS
);
2220 TriggerStationAnimation(st
, this->tile
, SAT_TRAIN_DEPARTS
);
2223 SetBit(Train::From(this)->flags
, VRF_LEAVING_STATION
);
2230 * Reset all refit_cap in the consist to cargo_cap.
2232 void Vehicle::ResetRefitCaps()
2234 for (Vehicle
*v
= this; v
!= nullptr; v
= v
->Next()) v
->refit_cap
= v
->cargo_cap
;
2238 * Handle the loading of the vehicle; when not it skips through dummy
2239 * orders and does nothing in all other cases.
2240 * @param mode is the non-first call for this vehicle in this tick?
2242 void Vehicle::HandleLoading(bool mode
)
2244 switch (this->current_order
.GetType()) {
2246 uint wait_time
= max(this->current_order
.GetTimetabledWait() - this->lateness_counter
, 0);
2248 /* Not the first call for this tick, or still loading */
2249 if (mode
|| !HasBit(this->vehicle_flags
, VF_LOADING_FINISHED
) || this->current_order_time
< wait_time
) return;
2251 this->PlayLeaveStationSound();
2253 this->LeaveStation();
2255 /* Only advance to next order if we just loaded at the current one */
2256 const Order
*order
= this->GetOrder(this->cur_implicit_order_index
);
2257 if (order
== nullptr ||
2258 (!order
->IsType(OT_IMPLICIT
) && !order
->IsType(OT_GOTO_STATION
)) ||
2259 order
->GetDestination() != this->last_station_visited
) {
2265 case OT_DUMMY
: break;
2270 this->IncrementImplicitOrderIndex();
2274 * Get a map of cargoes and free capacities in the consist.
2275 * @param capacities Map to be filled with cargoes and capacities.
2277 void Vehicle::GetConsistFreeCapacities(SmallMap
<CargoID
, uint
> &capacities
) const
2279 for (const Vehicle
*v
= this; v
!= nullptr; v
= v
->Next()) {
2280 if (v
->cargo_cap
== 0) continue;
2281 SmallPair
<CargoID
, uint
> *pair
= capacities
.Find(v
->cargo_type
);
2282 if (pair
== capacities
.End()) {
2283 capacities
.push_back({v
->cargo_type
, v
->cargo_cap
- v
->cargo
.StoredCount()});
2285 pair
->second
+= v
->cargo_cap
- v
->cargo
.StoredCount();
2290 uint
Vehicle::GetConsistTotalCapacity() const
2293 for (const Vehicle
*v
= this; v
!= nullptr; v
= v
->Next()) {
2294 result
+= v
->cargo_cap
;
2300 * Send this vehicle to the depot using the given command(s).
2301 * @param flags the command flags (like execute and such).
2302 * @param command the command to execute.
2303 * @return the cost of the depot action.
2305 CommandCost
Vehicle::SendToDepot(DoCommandFlag flags
, DepotCommand command
)
2307 CommandCost ret
= CheckOwnership(this->owner
);
2308 if (ret
.Failed()) return ret
;
2310 if (this->vehstatus
& VS_CRASHED
) return CMD_ERROR
;
2311 if (this->IsStoppedInDepot()) return CMD_ERROR
;
2313 if (this->current_order
.IsType(OT_GOTO_DEPOT
)) {
2314 bool halt_in_depot
= (this->current_order
.GetDepotActionType() & ODATFB_HALT
) != 0;
2315 if (!!(command
& DEPOT_SERVICE
) == halt_in_depot
) {
2316 /* We called with a different DEPOT_SERVICE setting.
2317 * Now we change the setting to apply the new one and let the vehicle head for the same depot.
2318 * Note: the if is (true for requesting service == true for ordered to stop in depot) */
2319 if (flags
& DC_EXEC
) {
2320 this->current_order
.SetDepotOrderType(ODTF_MANUAL
);
2321 this->current_order
.SetDepotActionType(halt_in_depot
? ODATF_SERVICE_ONLY
: ODATFB_HALT
);
2322 SetWindowWidgetDirty(WC_VEHICLE_VIEW
, this->index
, WID_VV_START_STOP
);
2324 return CommandCost();
2327 if (command
& DEPOT_DONT_CANCEL
) return CMD_ERROR
; // Requested no cancellation of depot orders
2328 if (flags
& DC_EXEC
) {
2329 /* If the orders to 'goto depot' are in the orders list (forced servicing),
2330 * then skip to the next order; effectively cancelling this forced service */
2331 if (this->current_order
.GetDepotOrderType() & ODTFB_PART_OF_ORDERS
) this->IncrementRealOrderIndex();
2333 if (this->IsGroundVehicle()) {
2334 uint16
&gv_flags
= this->GetGroundVehicleFlags();
2335 SetBit(gv_flags
, GVF_SUPPRESS_IMPLICIT_ORDERS
);
2338 this->current_order
.MakeDummy();
2339 SetWindowWidgetDirty(WC_VEHICLE_VIEW
, this->index
, WID_VV_START_STOP
);
2341 return CommandCost();
2345 DestinationID destination
;
2347 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
};
2348 if (!this->FindClosestDepot(&location
, &destination
, &reverse
)) return_cmd_error(no_depot
[this->type
]);
2350 if (flags
& DC_EXEC
) {
2351 if (this->current_order
.IsType(OT_LOADING
)) this->LeaveStation();
2353 if (this->IsGroundVehicle() && this->GetNumManualOrders() > 0) {
2354 uint16
&gv_flags
= this->GetGroundVehicleFlags();
2355 SetBit(gv_flags
, GVF_SUPPRESS_IMPLICIT_ORDERS
);
2358 this->SetDestTile(location
);
2359 this->current_order
.MakeGoToDepot(destination
, ODTF_MANUAL
);
2360 if (!(command
& DEPOT_SERVICE
)) this->current_order
.SetDepotActionType(ODATFB_HALT
);
2361 SetWindowWidgetDirty(WC_VEHICLE_VIEW
, this->index
, WID_VV_START_STOP
);
2363 /* If there is no depot in front and the train is not already reversing, reverse automatically (trains only) */
2364 if (this->type
== VEH_TRAIN
&& (reverse
^ HasBit(Train::From(this)->flags
, VRF_REVERSING
))) {
2365 DoCommand(this->tile
, this->index
, 0, DC_EXEC
, CMD_REVERSE_TRAIN_DIRECTION
);
2368 if (this->type
== VEH_AIRCRAFT
) {
2369 Aircraft
*a
= Aircraft::From(this);
2370 if (a
->state
== FLYING
&& a
->targetairport
!= destination
) {
2371 /* The aircraft is now heading for a different hangar than the next in the orders */
2372 extern void AircraftNextAirportPos_and_Order(Aircraft
*a
);
2373 AircraftNextAirportPos_and_Order(a
);
2378 return CommandCost();
2383 * Update the cached visual effect.
2384 * @param allow_power_change true if the wagon-is-powered-state may change.
2386 void Vehicle::UpdateVisualEffect(bool allow_power_change
)
2388 bool powered_before
= HasBit(this->vcache
.cached_vis_effect
, VE_DISABLE_WAGON_POWER
);
2389 const Engine
*e
= this->GetEngine();
2391 /* Evaluate properties */
2394 case VEH_TRAIN
: visual_effect
= e
->u
.rail
.visual_effect
; break;
2395 case VEH_ROAD
: visual_effect
= e
->u
.road
.visual_effect
; break;
2396 case VEH_SHIP
: visual_effect
= e
->u
.ship
.visual_effect
; break;
2397 default: visual_effect
= 1 << VE_DISABLE_EFFECT
; break;
2400 /* Check powered wagon / visual effect callback */
2401 if (HasBit(e
->info
.callback_mask
, CBM_VEHICLE_VISUAL_EFFECT
)) {
2402 uint16 callback
= GetVehicleCallback(CBID_VEHICLE_VISUAL_EFFECT
, 0, 0, this->engine_type
, this);
2404 if (callback
!= CALLBACK_FAILED
) {
2405 if (callback
>= 0x100 && e
->GetGRF()->grf_version
>= 8) ErrorUnknownCallbackResult(e
->GetGRFID(), CBID_VEHICLE_VISUAL_EFFECT
, callback
);
2407 callback
= GB(callback
, 0, 8);
2408 /* Avoid accidentally setting 'visual_effect' to the default value
2409 * Since bit 6 (disable effects) is set anyways, we can safely erase some bits. */
2410 if (callback
== VE_DEFAULT
) {
2411 assert(HasBit(callback
, VE_DISABLE_EFFECT
));
2412 SB(callback
, VE_TYPE_START
, VE_TYPE_COUNT
, 0);
2414 visual_effect
= callback
;
2418 /* Apply default values */
2419 if (visual_effect
== VE_DEFAULT
||
2420 (!HasBit(visual_effect
, VE_DISABLE_EFFECT
) && GB(visual_effect
, VE_TYPE_START
, VE_TYPE_COUNT
) == VE_TYPE_DEFAULT
)) {
2421 /* Only train engines have default effects.
2422 * Note: This is independent of whether the engine is a front engine or articulated part or whatever. */
2423 if (e
->type
!= VEH_TRAIN
|| e
->u
.rail
.railveh_type
== RAILVEH_WAGON
|| !IsInsideMM(e
->u
.rail
.engclass
, EC_STEAM
, EC_MONORAIL
)) {
2424 if (visual_effect
== VE_DEFAULT
) {
2425 visual_effect
= 1 << VE_DISABLE_EFFECT
;
2427 SetBit(visual_effect
, VE_DISABLE_EFFECT
);
2430 if (visual_effect
== VE_DEFAULT
) {
2431 /* Also set the offset */
2432 visual_effect
= (VE_OFFSET_CENTRE
- (e
->u
.rail
.engclass
== EC_STEAM
? 4 : 0)) << VE_OFFSET_START
;
2434 SB(visual_effect
, VE_TYPE_START
, VE_TYPE_COUNT
, e
->u
.rail
.engclass
- EC_STEAM
+ VE_TYPE_STEAM
);
2438 this->vcache
.cached_vis_effect
= visual_effect
;
2440 if (!allow_power_change
&& powered_before
!= HasBit(this->vcache
.cached_vis_effect
, VE_DISABLE_WAGON_POWER
)) {
2441 ToggleBit(this->vcache
.cached_vis_effect
, VE_DISABLE_WAGON_POWER
);
2442 ShowNewGrfVehicleError(this->engine_type
, STR_NEWGRF_BROKEN
, STR_NEWGRF_BROKEN_POWERED_WAGON
, GBUG_VEH_POWERED_WAGON
, false);
2446 static const int8 _vehicle_smoke_pos
[8] = {
2447 1, 1, 1, 0, -1, -1, -1, 0
2451 * Call CBID_VEHICLE_SPAWN_VISUAL_EFFECT and spawn requested effects.
2452 * @param v Vehicle to create effects for.
2454 static void SpawnAdvancedVisualEffect(const Vehicle
*v
)
2456 uint16 callback
= GetVehicleCallback(CBID_VEHICLE_SPAWN_VISUAL_EFFECT
, 0, Random(), v
->engine_type
, v
);
2457 if (callback
== CALLBACK_FAILED
) return;
2459 uint count
= GB(callback
, 0, 2);
2460 bool auto_center
= HasBit(callback
, 13);
2461 bool auto_rotate
= !HasBit(callback
, 14);
2465 /* For road vehicles: Compute offset from vehicle position to vehicle center */
2466 if (v
->type
== VEH_ROAD
) l_center
= -(int)(VEHICLE_LENGTH
- RoadVehicle::From(v
)->gcache
.cached_veh_length
) / 2;
2468 /* For trains: Compute offset from vehicle position to sprite position */
2469 if (v
->type
== VEH_TRAIN
) l_center
= (VEHICLE_LENGTH
- Train::From(v
)->gcache
.cached_veh_length
) / 2;
2472 Direction l_dir
= v
->direction
;
2473 if (v
->type
== VEH_TRAIN
&& HasBit(Train::From(v
)->flags
, VRF_REVERSE_DIRECTION
)) l_dir
= ReverseDir(l_dir
);
2474 Direction t_dir
= ChangeDir(l_dir
, DIRDIFF_90RIGHT
);
2476 int8 x_center
= _vehicle_smoke_pos
[l_dir
] * l_center
;
2477 int8 y_center
= _vehicle_smoke_pos
[t_dir
] * l_center
;
2479 for (uint i
= 0; i
< count
; i
++) {
2480 uint32 reg
= GetRegister(0x100 + i
);
2481 uint type
= GB(reg
, 0, 8);
2482 int8 x
= GB(reg
, 8, 8);
2483 int8 y
= GB(reg
, 16, 8);
2484 int8 z
= GB(reg
, 24, 8);
2489 x
= _vehicle_smoke_pos
[l_dir
] * l
+ _vehicle_smoke_pos
[t_dir
] * t
;
2490 y
= _vehicle_smoke_pos
[t_dir
] * l
- _vehicle_smoke_pos
[l_dir
] * t
;
2495 case 0xF1: CreateEffectVehicleRel(v
, x_center
+ x
, y_center
+ y
, z
, EV_STEAM_SMOKE
); break;
2496 case 0xF2: CreateEffectVehicleRel(v
, x_center
+ x
, y_center
+ y
, z
, EV_DIESEL_SMOKE
); break;
2497 case 0xF3: CreateEffectVehicleRel(v
, x_center
+ x
, y_center
+ y
, z
, EV_ELECTRIC_SPARK
); break;
2498 case 0xFA: CreateEffectVehicleRel(v
, x_center
+ x
, y_center
+ y
, z
, EV_BREAKDOWN_SMOKE_AIRCRAFT
); break;
2506 * Draw visual effects (smoke and/or sparks) for a vehicle chain.
2507 * @pre this->IsPrimaryVehicle()
2509 void Vehicle::ShowVisualEffect() const
2511 assert(this->IsPrimaryVehicle());
2514 /* Do not show any smoke when:
2515 * - vehicle smoke is disabled by the player
2516 * - the vehicle is slowing down or stopped (by the player)
2517 * - the vehicle is moving very slowly
2519 if (_settings_game
.vehicle
.smoke_amount
== 0 ||
2520 this->vehstatus
& (VS_TRAIN_SLOWING
| VS_STOPPED
) ||
2521 this->cur_speed
< 2) {
2525 /* Use the speed as limited by underground and orders. */
2526 uint max_speed
= this->GetCurrentMaxSpeed();
2528 if (this->type
== VEH_TRAIN
) {
2529 const Train
*t
= Train::From(this);
2530 /* For trains, do not show any smoke when:
2531 * - the train is reversing
2532 * - is entering a station with an order to stop there and its speed is equal to maximum station entering speed
2534 if (HasBit(t
->flags
, VRF_REVERSING
) ||
2535 (IsRailStationTile(t
->tile
) && t
->IsFrontEngine() && t
->current_order
.ShouldStopAtStation(t
, GetStationIndex(t
->tile
)) &&
2536 t
->cur_speed
>= max_speed
)) {
2541 const Vehicle
*v
= this;
2544 bool advanced
= HasBit(v
->vcache
.cached_vis_effect
, VE_ADVANCED_EFFECT
);
2545 int effect_offset
= GB(v
->vcache
.cached_vis_effect
, VE_OFFSET_START
, VE_OFFSET_COUNT
) - VE_OFFSET_CENTRE
;
2546 VisualEffectSpawnModel effect_model
= VESM_NONE
;
2548 effect_offset
= VE_OFFSET_CENTRE
;
2549 effect_model
= (VisualEffectSpawnModel
)GB(v
->vcache
.cached_vis_effect
, 0, VE_ADVANCED_EFFECT
);
2550 if (effect_model
>= VESM_END
) effect_model
= VESM_NONE
; // unknown spawning model
2552 effect_model
= (VisualEffectSpawnModel
)GB(v
->vcache
.cached_vis_effect
, VE_TYPE_START
, VE_TYPE_COUNT
);
2553 assert(effect_model
!= (VisualEffectSpawnModel
)VE_TYPE_DEFAULT
); // should have been resolved by UpdateVisualEffect
2554 assert_compile((uint
)VESM_STEAM
== (uint
)VE_TYPE_STEAM
);
2555 assert_compile((uint
)VESM_DIESEL
== (uint
)VE_TYPE_DIESEL
);
2556 assert_compile((uint
)VESM_ELECTRIC
== (uint
)VE_TYPE_ELECTRIC
);
2559 /* Show no smoke when:
2560 * - Smoke has been disabled for this vehicle
2561 * - The vehicle is not visible
2562 * - The vehicle is under a bridge
2563 * - The vehicle is on a depot tile
2564 * - The vehicle is on a tunnel tile
2565 * - The vehicle is a train engine that is currently unpowered */
2566 if (effect_model
== VESM_NONE
||
2567 v
->vehstatus
& VS_HIDDEN
||
2568 IsBridgeAbove(v
->tile
) ||
2569 IsDepotTile(v
->tile
) ||
2570 IsTunnelTile(v
->tile
) ||
2571 (v
->type
== VEH_TRAIN
&&
2572 !HasPowerOnRail(Train::From(v
)->railtype
, GetTileRailType(v
->tile
)))) {
2576 EffectVehicleType evt
= EV_END
;
2577 switch (effect_model
) {
2579 /* Steam smoke - amount is gradually falling until vehicle reaches its maximum speed, after that it's normal.
2580 * Details: while vehicle's current speed is gradually increasing, steam plumes' density decreases by one third each
2581 * third of its maximum speed spectrum. Steam emission finally normalises at very close to vehicle's maximum speed.
2583 * - instead of 1, 4 / 2^smoke_amount (max. 2) is used to provide sufficient regulation to steam puffs' amount. */
2584 if (GB(v
->tick_counter
, 0, ((4 >> _settings_game
.vehicle
.smoke_amount
) + ((this->cur_speed
* 3) / max_speed
))) == 0) {
2585 evt
= EV_STEAM_SMOKE
;
2590 /* Diesel smoke - thicker when vehicle is starting, gradually subsiding till it reaches its maximum speed
2591 * when smoke emission stops.
2592 * Details: Vehicle's (max.) speed spectrum is divided into 32 parts. When max. speed is reached, chance for smoke
2593 * emission erodes by 32 (1/4). For trains, power and weight come in handy too to either increase smoke emission in
2594 * 6 steps (1000HP each) if the power is low or decrease smoke emission in 6 steps (512 tonnes each) if the train
2595 * isn't overweight. Power and weight contributions are expressed in a way that neither extreme power, nor
2596 * extreme weight can ruin the balance (e.g. FreightWagonMultiplier) in the formula. When the vehicle reaches
2597 * maximum speed no diesel_smoke is emitted.
2599 * - up to which speed a diesel vehicle is emitting smoke (with reduced/small setting only until 1/2 of max_speed),
2600 * - in Chance16 - the last value is 512 / 2^smoke_amount (max. smoke when 128 = smoke_amount of 2). */
2601 int power_weight_effect
= 0;
2602 if (v
->type
== VEH_TRAIN
) {
2603 power_weight_effect
= (32 >> (Train::From(this)->gcache
.cached_power
>> 10)) - (32 >> (Train::From(this)->gcache
.cached_weight
>> 9));
2605 if (this->cur_speed
< (max_speed
>> (2 >> _settings_game
.vehicle
.smoke_amount
)) &&
2606 Chance16((64 - ((this->cur_speed
<< 5) / max_speed
) + power_weight_effect
), (512 >> _settings_game
.vehicle
.smoke_amount
))) {
2607 evt
= EV_DIESEL_SMOKE
;
2613 /* Electric train's spark - more often occurs when train is departing (more load)
2614 * Details: Electric locomotives are usually at least twice as powerful as their diesel counterparts, so spark
2615 * emissions are kept simple. Only when starting, creating huge force are sparks more likely to happen, but when
2616 * reaching its max. speed, quarter by quarter of it, chance decreases until the usual 2,22% at train's top speed.
2618 * - in Chance16 the last value is 360 / 2^smoke_amount (max. sparks when 90 = smoke_amount of 2). */
2619 if (GB(v
->tick_counter
, 0, 2) == 0 &&
2620 Chance16((6 - ((this->cur_speed
<< 2) / max_speed
)), (360 >> _settings_game
.vehicle
.smoke_amount
))) {
2621 evt
= EV_ELECTRIC_SPARK
;
2629 if (evt
!= EV_END
&& advanced
) {
2631 SpawnAdvancedVisualEffect(v
);
2632 } else if (evt
!= EV_END
) {
2635 /* The effect offset is relative to a point 4 units behind the vehicle's
2636 * front (which is the center of an 8/8 vehicle). Shorter vehicles need a
2637 * correction factor. */
2638 if (v
->type
== VEH_TRAIN
) effect_offset
+= (VEHICLE_LENGTH
- Train::From(v
)->gcache
.cached_veh_length
) / 2;
2640 int x
= _vehicle_smoke_pos
[v
->direction
] * effect_offset
;
2641 int y
= _vehicle_smoke_pos
[(v
->direction
+ 2) % 8] * effect_offset
;
2643 if (v
->type
== VEH_TRAIN
&& HasBit(Train::From(v
)->flags
, VRF_REVERSE_DIRECTION
)) {
2648 CreateEffectVehicleRel(v
, x
, y
, 10, evt
);
2650 } while ((v
= v
->Next()) != nullptr);
2652 if (sound
) PlayVehicleSound(this, VSE_VISUAL_EFFECT
);
2656 * Set the next vehicle of this vehicle.
2657 * @param next the next vehicle. nullptr removes the next vehicle.
2659 void Vehicle::SetNext(Vehicle
*next
)
2661 assert(this != next
);
2663 if (this->next
!= nullptr) {
2664 /* We had an old next vehicle. Update the first and previous pointers */
2665 for (Vehicle
*v
= this->next
; v
!= nullptr; v
= v
->Next()) {
2666 v
->first
= this->next
;
2668 this->next
->previous
= nullptr;
2673 if (this->next
!= nullptr) {
2674 /* A new next vehicle. Update the first and previous pointers */
2675 if (this->next
->previous
!= nullptr) this->next
->previous
->next
= nullptr;
2676 this->next
->previous
= this;
2677 for (Vehicle
*v
= this->next
; v
!= nullptr; v
= v
->Next()) {
2678 v
->first
= this->first
;
2684 * Adds this vehicle to a shared vehicle chain.
2685 * @param shared_chain a vehicle of the chain with shared vehicles.
2686 * @pre !this->IsOrderListShared()
2688 void Vehicle::AddToShared(Vehicle
*shared_chain
)
2690 assert(this->previous_shared
== nullptr && this->next_shared
== nullptr);
2692 if (shared_chain
->orders
.list
== nullptr) {
2693 assert(shared_chain
->previous_shared
== nullptr);
2694 assert(shared_chain
->next_shared
== nullptr);
2695 this->orders
.list
= shared_chain
->orders
.list
= new OrderList(nullptr, shared_chain
);
2698 this->next_shared
= shared_chain
->next_shared
;
2699 this->previous_shared
= shared_chain
;
2701 shared_chain
->next_shared
= this;
2703 if (this->next_shared
!= nullptr) this->next_shared
->previous_shared
= this;
2705 shared_chain
->orders
.list
->AddVehicle(this);
2709 * Removes the vehicle from the shared order list.
2711 void Vehicle::RemoveFromShared()
2713 /* Remember if we were first and the old window number before RemoveVehicle()
2714 * as this changes first if needed. */
2715 bool were_first
= (this->FirstShared() == this);
2716 VehicleListIdentifier
vli(VL_SHARED_ORDERS
, this->type
, this->owner
, this->FirstShared()->index
);
2718 this->orders
.list
->RemoveVehicle(this);
2721 /* We are not the first shared one, so only relink our previous one. */
2722 this->previous_shared
->next_shared
= this->NextShared();
2725 if (this->next_shared
!= nullptr) this->next_shared
->previous_shared
= this->previous_shared
;
2728 if (this->orders
.list
->GetNumVehicles() == 1) {
2729 /* When there is only one vehicle, remove the shared order list window. */
2730 DeleteWindowById(GetWindowClassForVehicleType(this->type
), vli
.Pack());
2731 InvalidateVehicleOrder(this->FirstShared(), VIWD_MODIFY_ORDERS
);
2732 } else if (were_first
) {
2733 /* If we were the first one, update to the new first one.
2734 * Note: FirstShared() is already the new first */
2735 InvalidateWindowData(GetWindowClassForVehicleType(this->type
), vli
.Pack(), this->FirstShared()->index
| (1U << 31));
2738 this->next_shared
= nullptr;
2739 this->previous_shared
= nullptr;
2742 void VehiclesYearlyLoop()
2744 for (Vehicle
*v
: Vehicle::Iterate()) {
2745 if (v
->IsPrimaryVehicle()) {
2746 /* show warning if vehicle is not generating enough income last 2 years (corresponds to a red icon in the vehicle list) */
2747 Money profit
= v
->GetDisplayProfitThisYear();
2748 if (v
->age
>= 730 && profit
< 0) {
2749 if (_settings_client
.gui
.vehicle_income_warn
&& v
->owner
== _local_company
) {
2750 SetDParam(0, v
->index
);
2751 SetDParam(1, profit
);
2752 AddVehicleAdviceNewsItem(STR_NEWS_VEHICLE_IS_UNPROFITABLE
, v
->index
);
2754 AI::NewEvent(v
->owner
, new ScriptEventVehicleUnprofitable(v
->index
));
2757 v
->profit_last_year
= v
->profit_this_year
;
2758 v
->profit_this_year
= 0;
2759 SetWindowDirty(WC_VEHICLE_DETAILS
, v
->index
);
2762 GroupStatistics::UpdateProfits();
2763 SetWindowClassesDirty(WC_TRAINS_LIST
);
2764 SetWindowClassesDirty(WC_SHIPS_LIST
);
2765 SetWindowClassesDirty(WC_ROADVEH_LIST
);
2766 SetWindowClassesDirty(WC_AIRCRAFT_LIST
);
2771 * Can this station be used by the given engine type?
2772 * @param engine_type the type of vehicles to test
2773 * @param st the station to test for
2774 * @return true if and only if the vehicle of the type can use this station.
2775 * @note For road vehicles the Vehicle is needed to determine whether it can
2776 * use the station. This function will return true for road vehicles
2777 * when at least one of the facilities is available.
2779 bool CanVehicleUseStation(EngineID engine_type
, const Station
*st
)
2781 const Engine
*e
= Engine::GetIfValid(engine_type
);
2782 assert(e
!= nullptr);
2786 return (st
->facilities
& FACIL_TRAIN
) != 0;
2789 /* For road vehicles we need the vehicle to know whether it can actually
2790 * use the station, but if it doesn't have facilities for RVs it is
2791 * certainly not possible that the station can be used. */
2792 return (st
->facilities
& (FACIL_BUS_STOP
| FACIL_TRUCK_STOP
)) != 0;
2795 return (st
->facilities
& FACIL_DOCK
) != 0;
2798 return (st
->facilities
& FACIL_AIRPORT
) != 0 &&
2799 (st
->airport
.GetFTA()->flags
& (e
->u
.air
.subtype
& AIR_CTOL
? AirportFTAClass::AIRPLANES
: AirportFTAClass::HELICOPTERS
)) != 0;
2807 * Can this station be used by the given vehicle?
2808 * @param v the vehicle to test
2809 * @param st the station to test for
2810 * @return true if and only if the vehicle can use this station.
2812 bool CanVehicleUseStation(const Vehicle
*v
, const Station
*st
)
2814 if (v
->type
== VEH_ROAD
) return st
->GetPrimaryRoadStop(RoadVehicle::From(v
)) != nullptr;
2816 return CanVehicleUseStation(v
->engine_type
, st
);
2820 * Access the ground vehicle cache of the vehicle.
2821 * @pre The vehicle is a #GroundVehicle.
2822 * @return #GroundVehicleCache of the vehicle.
2824 GroundVehicleCache
*Vehicle::GetGroundVehicleCache()
2826 assert(this->IsGroundVehicle());
2827 if (this->type
== VEH_TRAIN
) {
2828 return &Train::From(this)->gcache
;
2830 return &RoadVehicle::From(this)->gcache
;
2835 * Access the ground vehicle cache of the vehicle.
2836 * @pre The vehicle is a #GroundVehicle.
2837 * @return #GroundVehicleCache of the vehicle.
2839 const GroundVehicleCache
*Vehicle::GetGroundVehicleCache() const
2841 assert(this->IsGroundVehicle());
2842 if (this->type
== VEH_TRAIN
) {
2843 return &Train::From(this)->gcache
;
2845 return &RoadVehicle::From(this)->gcache
;
2850 * Access the ground vehicle flags of the vehicle.
2851 * @pre The vehicle is a #GroundVehicle.
2852 * @return #GroundVehicleFlags of the vehicle.
2854 uint16
&Vehicle::GetGroundVehicleFlags()
2856 assert(this->IsGroundVehicle());
2857 if (this->type
== VEH_TRAIN
) {
2858 return Train::From(this)->gv_flags
;
2860 return RoadVehicle::From(this)->gv_flags
;
2865 * Access the ground vehicle flags of the vehicle.
2866 * @pre The vehicle is a #GroundVehicle.
2867 * @return #GroundVehicleFlags of the vehicle.
2869 const uint16
&Vehicle::GetGroundVehicleFlags() const
2871 assert(this->IsGroundVehicle());
2872 if (this->type
== VEH_TRAIN
) {
2873 return Train::From(this)->gv_flags
;
2875 return RoadVehicle::From(this)->gv_flags
;
2880 * Calculates the set of vehicles that will be affected by a given selection.
2881 * @param[in,out] set Set of affected vehicles.
2882 * @param v First vehicle of the selection.
2883 * @param num_vehicles Number of vehicles in the selection (not counting articulated parts).
2884 * @pre \a set must be empty.
2885 * @post \a set will contain the vehicles that will be refitted.
2887 void GetVehicleSet(VehicleSet
&set
, Vehicle
*v
, uint8 num_vehicles
)
2889 if (v
->type
== VEH_TRAIN
) {
2890 Train
*u
= Train::From(v
);
2891 /* Only include whole vehicles, so start with the first articulated part */
2892 u
= u
->GetFirstEnginePart();
2894 /* Include num_vehicles vehicles, not counting articulated parts */
2895 for (; u
!= nullptr && num_vehicles
> 0; num_vehicles
--) {
2897 /* Include current vehicle in the selection. */
2898 include(set
, u
->index
);
2900 /* If the vehicle is multiheaded, add the other part too. */
2901 if (u
->IsMultiheaded()) include(set
, u
->other_multiheaded_part
->index
);
2904 } while (u
!= nullptr && u
->IsArticulatedPart());