4 * This file is part of OpenTTD.
5 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
6 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
7 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
10 /** @file vehicle.cpp Base implementations of all vehicles. */
16 #include "spritecache.h"
17 #include "timetable.h"
18 #include "viewport_func.h"
19 #include "news_func.h"
20 #include "command_func.h"
21 #include "company_func.h"
24 #include "newgrf_debug.h"
25 #include "newgrf_sound.h"
26 #include "newgrf_station.h"
27 #include "group_gui.h"
28 #include "strings_func.h"
29 #include "zoom_func.h"
30 #include "date_func.h"
31 #include "vehicle_func.h"
32 #include "autoreplace_func.h"
33 #include "autoreplace_gui.h"
34 #include "station_base.h"
36 #include "depot_func.h"
37 #include "network/network.h"
38 #include "core/pool_func.hpp"
39 #include "economy_base.h"
40 #include "articulated_vehicles.h"
41 #include "roadstop_base.h"
42 #include "core/random_func.hpp"
43 #include "core/backup_type.hpp"
44 #include "order_backup.h"
45 #include "sound_func.h"
46 #include "effectvehicle_func.h"
47 #include "effectvehicle_base.h"
48 #include "vehiclelist.h"
49 #include "bridge_map.h"
50 #include "tunnel_map.h"
51 #include "depot_map.h"
53 #include "linkgraph/linkgraph.h"
54 #include "linkgraph/refresh.h"
56 #include "table/strings.h"
58 #include "safeguards.h"
60 #define GEN_HASH(x, y) ((GB((y), 6 + ZOOM_LVL_SHIFT, 6) << 6) + GB((x), 7 + ZOOM_LVL_SHIFT, 6))
62 VehicleID _new_vehicle_id
;
63 uint16 _returned_refit_capacity
; ///< Stores the capacity after a refit operation.
64 uint16 _returned_mail_refit_capacity
; ///< Stores the mail capacity after a refit operation (Aircraft only).
67 /** The pool with all our precious vehicles. */
68 VehiclePool
_vehicle_pool("Vehicle");
69 INSTANTIATE_POOL_METHODS(Vehicle
)
73 * Determine shared bounds of all sprites.
74 * @param [out] bounds Shared bounds.
76 void VehicleSpriteSeq::GetBounds(Rect
*bounds
) const
78 bounds
->left
= bounds
->top
= bounds
->right
= bounds
->bottom
= 0;
79 for (uint i
= 0; i
< this->count
; ++i
) {
80 const Sprite
*spr
= GetSprite(this->seq
[i
].sprite
, ST_NORMAL
);
82 bounds
->left
= spr
->x_offs
;
83 bounds
->top
= spr
->y_offs
;
84 bounds
->right
= spr
->width
+ spr
->x_offs
- 1;
85 bounds
->bottom
= spr
->height
+ spr
->y_offs
- 1;
87 if (spr
->x_offs
< bounds
->left
) bounds
->left
= spr
->x_offs
;
88 if (spr
->y_offs
< bounds
->top
) bounds
->top
= spr
->y_offs
;
89 int right
= spr
->width
+ spr
->x_offs
- 1;
90 int bottom
= spr
->height
+ spr
->y_offs
- 1;
91 if (right
> bounds
->right
) bounds
->right
= right
;
92 if (bottom
> bounds
->bottom
) bounds
->bottom
= bottom
;
98 * Draw the sprite sequence.
100 * @param y Y position
101 * @param default_pal Vehicle palette
102 * @param force_pal Whether to ignore individual palettes, and draw everything with \a default_pal.
104 void VehicleSpriteSeq::Draw(int x
, int y
, PaletteID default_pal
, bool force_pal
) const
106 for (uint i
= 0; i
< this->count
; ++i
) {
107 PaletteID pal
= force_pal
|| !this->seq
[i
].pal
? default_pal
: this->seq
[i
].pal
;
108 DrawSprite(this->seq
[i
].sprite
, pal
, x
, y
);
113 * Function to tell if a vehicle needs to be autorenewed
114 * @param *c The vehicle owner
115 * @param use_renew_setting Should the company renew setting be considered?
116 * @return true if the vehicle is old enough for replacement
118 bool Vehicle::NeedsAutorenewing(const Company
*c
, bool use_renew_setting
) const
120 /* We can always generate the Company pointer when we have the vehicle.
121 * However this takes time and since the Company pointer is often present
122 * when this function is called then it's faster to pass the pointer as an
123 * argument rather than finding it again. */
124 assert(c
== Company::Get(this->owner
));
126 if (use_renew_setting
&& !c
->settings
.engine_renew
) return false;
127 if (this->age
- this->max_age
< (c
->settings
.engine_renew_months
* 30)) return false;
129 /* Only engines need renewing */
130 if (this->type
== VEH_TRAIN
&& !Train::From(this)->IsEngine()) return false;
136 * Service a vehicle and all subsequent vehicles in the consist
138 * @param *v The vehicle or vehicle chain being serviced
140 void VehicleServiceInDepot(Vehicle
*v
)
143 SetWindowDirty(WC_VEHICLE_DETAILS
, v
->index
); // ensure that last service date and reliability are updated
146 v
->date_of_last_service
= _date
;
147 v
->breakdowns_since_last_service
= 0;
148 v
->reliability
= v
->GetEngine()->reliability
;
149 /* Prevent vehicles from breaking down directly after exiting the depot. */
150 v
->breakdown_chance
/= 4;
152 } while (v
!= NULL
&& v
->HasEngineType());
156 * Check if the vehicle needs to go to a depot in near future (if a opportunity presents itself) for service or replacement.
158 * @see NeedsAutomaticServicing()
159 * @return true if the vehicle should go to a depot if a opportunity presents itself.
161 bool Vehicle::NeedsServicing() const
163 /* Stopped or crashed vehicles will not move, as such making unmovable
164 * vehicles to go for service is lame. */
165 if (this->vehstatus
& (VS_STOPPED
| VS_CRASHED
)) return false;
167 /* Are we ready for the next service cycle? */
168 const Company
*c
= Company::Get(this->owner
);
169 if (this->ServiceIntervalIsPercent() ?
170 (this->reliability
>= this->GetEngine()->reliability
* (100 - this->GetServiceInterval()) / 100) :
171 (this->date_of_last_service
+ this->GetServiceInterval() >= _date
)) {
175 /* If we're servicing anyway, because we have not disabled servicing when
176 * there are no breakdowns or we are playing with breakdowns, bail out. */
177 if (!_settings_game
.order
.no_servicing_if_no_breakdowns
||
178 _settings_game
.difficulty
.vehicle_breakdowns
!= 0) {
182 /* Test whether there is some pending autoreplace.
183 * Note: We do this after the service-interval test.
184 * There are a lot more reasons for autoreplace to fail than we can test here reasonably. */
185 bool pending_replace
= false;
186 Money needed_money
= c
->settings
.engine_renew_money
;
187 if (needed_money
> c
->money
) return false;
189 for (const Vehicle
*v
= this; v
!= NULL
; v
= (v
->type
== VEH_TRAIN
) ? Train::From(v
)->GetNextUnit() : NULL
) {
190 bool replace_when_old
= false;
191 EngineID new_engine
= EngineReplacementForCompany(c
, v
->engine_type
, v
->group_id
, &replace_when_old
);
193 /* Check engine availability */
194 if (new_engine
== INVALID_ENGINE
|| !HasBit(Engine::Get(new_engine
)->company_avail
, v
->owner
)) continue;
195 /* Is the vehicle old if we are not always replacing? */
196 if (replace_when_old
&& !v
->NeedsAutorenewing(c
, false)) continue;
198 /* Check refittability */
199 uint32 available_cargo_types
, union_mask
;
200 GetArticulatedRefitMasks(new_engine
, true, &union_mask
, &available_cargo_types
);
201 /* Is there anything to refit? */
202 if (union_mask
!= 0) {
204 /* We cannot refit to mixed cargoes in an automated way */
205 if (IsArticulatedVehicleCarryingDifferentCargoes(v
, &cargo_type
)) continue;
207 /* Did the old vehicle carry anything? */
208 if (cargo_type
!= CT_INVALID
) {
209 /* We can't refit the vehicle to carry the cargo we want */
210 if (!HasBit(available_cargo_types
, cargo_type
)) continue;
215 * We want 2*(the price of the new vehicle) without looking at the value of the vehicle we are going to sell. */
216 pending_replace
= true;
217 needed_money
+= 2 * Engine::Get(new_engine
)->GetCost();
218 if (needed_money
> c
->money
) return false;
221 return pending_replace
;
225 * Checks if the current order should be interrupted for a service-in-depot order.
226 * @see NeedsServicing()
227 * @return true if the current order should be interrupted.
229 bool Vehicle::NeedsAutomaticServicing() const
231 if (this->HasDepotOrder()) return false;
232 if (this->current_order
.IsType(OT_LOADING
)) return false;
233 if (this->current_order
.IsType(OT_GOTO_DEPOT
) && this->current_order
.GetDepotOrderType() != ODTFB_SERVICE
) return false;
234 return NeedsServicing();
237 uint
Vehicle::Crash(bool flooded
)
239 assert((this->vehstatus
& VS_CRASHED
) == 0);
240 assert(this->Previous() == NULL
); // IsPrimaryVehicle fails for free-wagon-chains
243 /* Stop the vehicle. */
244 if (this->IsPrimaryVehicle()) this->vehstatus
|= VS_STOPPED
;
245 /* crash all wagons, and count passengers */
246 for (Vehicle
*v
= this; v
!= NULL
; v
= v
->Next()) {
247 /* We do not transfer reserver cargo back, so TotalCount() instead of StoredCount() */
248 if (IsCargoInClass(v
->cargo_type
, CC_PASSENGERS
)) pass
+= v
->cargo
.TotalCount();
249 v
->vehstatus
|= VS_CRASHED
;
250 v
->MarkAllViewportsDirty();
253 /* Dirty some windows */
254 InvalidateWindowClassesData(GetWindowClassForVehicleType(this->type
), 0);
255 SetWindowWidgetDirty(WC_VEHICLE_VIEW
, this->index
, WID_VV_START_STOP
);
256 SetWindowDirty(WC_VEHICLE_DETAILS
, this->index
);
257 SetWindowDirty(WC_VEHICLE_DEPOT
, this->tile
);
259 delete this->cargo_payment
;
260 assert(this->cargo_payment
== NULL
); // cleared by ~CargoPayment
262 return RandomRange(pass
+ 1); // Randomise deceased passengers.
267 * Displays a "NewGrf Bug" error message for a engine, and pauses the game if not networking.
268 * @param engine The engine that caused the problem
269 * @param part1 Part 1 of the error message, taking the grfname as parameter 1
270 * @param part2 Part 2 of the error message, taking the engine as parameter 2
271 * @param bug_type Flag to check and set in grfconfig
272 * @param critical Shall the "OpenTTD might crash"-message be shown when the player tries to unpause?
274 void ShowNewGrfVehicleError(EngineID engine
, StringID part1
, StringID part2
, GRFBugs bug_type
, bool critical
)
276 const Engine
*e
= Engine::Get(engine
);
277 GRFConfig
*grfconfig
= GetGRFConfig(e
->GetGRFID());
279 /* Missing GRF. Nothing useful can be done in this situation. */
280 if (grfconfig
== NULL
) return;
282 if (!HasBit(grfconfig
->grf_bugs
, bug_type
)) {
283 SetBit(grfconfig
->grf_bugs
, bug_type
);
284 SetDParamStr(0, grfconfig
->GetName());
285 SetDParam(1, engine
);
286 ShowErrorMessage(part1
, part2
, WL_CRITICAL
);
287 if (!_networking
) DoCommand(0, critical
? PM_PAUSED_ERROR
: PM_PAUSED_NORMAL
, 1, DC_EXEC
, CMD_PAUSE
);
293 SetDParamStr(0, grfconfig
->GetName());
294 GetString(buffer
, part1
, lastof(buffer
));
295 DEBUG(grf
, 0, "%s", buffer
+ 3);
297 SetDParam(1, engine
);
298 GetString(buffer
, part2
, lastof(buffer
));
299 DEBUG(grf
, 0, "%s", buffer
+ 3);
303 * Logs a bug in GRF and shows a warning message if this
304 * is for the first time this happened.
305 * @param u first vehicle of chain
307 void VehicleLengthChanged(const Vehicle
*u
)
309 /* show a warning once for each engine in whole game and once for each GRF after each game load */
310 const Engine
*engine
= u
->GetEngine();
311 uint32 grfid
= engine
->grf_prop
.grffile
->grfid
;
312 GRFConfig
*grfconfig
= GetGRFConfig(grfid
);
313 if (GamelogGRFBugReverse(grfid
, engine
->grf_prop
.local_id
) || !HasBit(grfconfig
->grf_bugs
, GBUG_VEH_LENGTH
)) {
314 ShowNewGrfVehicleError(u
->engine_type
, STR_NEWGRF_BROKEN
, STR_NEWGRF_BROKEN_VEHICLE_LENGTH
, GBUG_VEH_LENGTH
, true);
319 * Vehicle constructor.
320 * @param type Type of the new vehicle.
322 Vehicle::Vehicle(VehicleType type
)
325 this->coord
.left
= INVALID_COORD
;
326 this->group_id
= DEFAULT_GROUP
;
327 this->fill_percent_te_id
= INVALID_TE_ID
;
329 this->colourmap
= PAL_NONE
;
330 this->cargo_age_counter
= 1;
331 this->last_station_visited
= INVALID_STATION
;
332 this->last_loading_station
= INVALID_STATION
;
336 * Get a value for a vehicle's random_bits.
337 * @return A random value from 0 to 255.
339 byte
VehicleRandomBits()
341 return GB(Random(), 0, 8);
344 /* Size of the hash, 6 = 64 x 64, 7 = 128 x 128. Larger sizes will (in theory) reduce hash
345 * lookup times at the expense of memory usage. */
346 const int HASH_BITS
= 7;
347 const int HASH_SIZE
= 1 << HASH_BITS
;
348 const int HASH_MASK
= HASH_SIZE
- 1;
349 const int TOTAL_HASH_SIZE
= 1 << (HASH_BITS
* 2);
350 const int TOTAL_HASH_MASK
= TOTAL_HASH_SIZE
- 1;
352 /* Resolution of the hash, 0 = 1*1 tile, 1 = 2*2 tiles, 2 = 4*4 tiles, etc.
353 * Profiling results show that 0 is fastest. */
354 const int HASH_RES
= 0;
356 static Vehicle
*_vehicle_tile_hash
[TOTAL_HASH_SIZE
];
358 static Vehicle
*VehicleFromTileHash(int xl
, int yl
, int xu
, int yu
, void *data
, VehicleFromPosProc
*proc
, bool find_first
)
360 for (int y
= yl
; ; y
= (y
+ (1 << HASH_BITS
)) & (HASH_MASK
<< HASH_BITS
)) {
361 for (int x
= xl
; ; x
= (x
+ 1) & HASH_MASK
) {
362 Vehicle
*v
= _vehicle_tile_hash
[(x
+ y
) & TOTAL_HASH_MASK
];
363 for (; v
!= NULL
; v
= v
->hash_tile_next
) {
364 Vehicle
*a
= proc(v
, data
);
365 if (find_first
&& a
!= NULL
) return a
;
377 * Helper function for FindVehicleOnPos/HasVehicleOnPos.
378 * @note Do not call this function directly!
379 * @param x The X location on the map
380 * @param y The Y location on the map
381 * @param data Arbitrary data passed to proc
382 * @param proc The proc that determines whether a vehicle will be "found".
383 * @param find_first Whether to return on the first found or iterate over
385 * @return the best matching or first vehicle (depending on find_first).
387 static Vehicle
*VehicleFromPosXY(int x
, int y
, void *data
, VehicleFromPosProc
*proc
, bool find_first
)
389 const int COLL_DIST
= 6;
391 /* Hash area to scan is from xl,yl to xu,yu */
392 int xl
= GB((x
- COLL_DIST
) / TILE_SIZE
, HASH_RES
, HASH_BITS
);
393 int xu
= GB((x
+ COLL_DIST
) / TILE_SIZE
, HASH_RES
, HASH_BITS
);
394 int yl
= GB((y
- COLL_DIST
) / TILE_SIZE
, HASH_RES
, HASH_BITS
) << HASH_BITS
;
395 int yu
= GB((y
+ COLL_DIST
) / TILE_SIZE
, HASH_RES
, HASH_BITS
) << HASH_BITS
;
397 return VehicleFromTileHash(xl
, yl
, xu
, yu
, data
, proc
, find_first
);
401 * Find a vehicle from a specific location. It will call proc for ALL vehicles
402 * on the tile and YOU must make SURE that the "best one" is stored in the
403 * data value and is ALWAYS the same regardless of the order of the vehicles
404 * where proc was called on!
405 * When you fail to do this properly you create an almost untraceable DESYNC!
406 * @note The return value of proc will be ignored.
407 * @note Use this when you have the intention that all vehicles
408 * should be iterated over.
409 * @param x The X location on the map
410 * @param y The Y location on the map
411 * @param data Arbitrary data passed to proc
412 * @param proc The proc that determines whether a vehicle will be "found".
414 void FindVehicleOnPosXY(int x
, int y
, void *data
, VehicleFromPosProc
*proc
)
416 VehicleFromPosXY(x
, y
, data
, proc
, false);
420 * Checks whether a vehicle in on a specific location. It will call proc for
421 * vehicles until it returns non-NULL.
422 * @note Use FindVehicleOnPosXY when you have the intention that all vehicles
423 * should be iterated over.
424 * @param x The X location on the map
425 * @param y The Y location on the map
426 * @param data Arbitrary data passed to proc
427 * @param proc The proc that determines whether a vehicle will be "found".
428 * @return True if proc returned non-NULL.
430 bool HasVehicleOnPosXY(int x
, int y
, void *data
, VehicleFromPosProc
*proc
)
432 return VehicleFromPosXY(x
, y
, data
, proc
, true) != NULL
;
436 * Helper function for FindVehicleOnPos/HasVehicleOnPos.
437 * @note Do not call this function directly!
438 * @param tile The location on the map
439 * @param data Arbitrary data passed to \a proc.
440 * @param proc The proc that determines whether a vehicle will be "found".
441 * @param find_first Whether to return on the first found or iterate over
443 * @return the best matching or first vehicle (depending on find_first).
445 static Vehicle
*VehicleFromPos(TileIndex tile
, void *data
, VehicleFromPosProc
*proc
, bool find_first
)
447 int x
= GB(TileX(tile
), HASH_RES
, HASH_BITS
);
448 int y
= GB(TileY(tile
), HASH_RES
, HASH_BITS
) << HASH_BITS
;
450 Vehicle
*v
= _vehicle_tile_hash
[(x
+ y
) & TOTAL_HASH_MASK
];
451 for (; v
!= NULL
; v
= v
->hash_tile_next
) {
452 if (v
->tile
!= tile
) continue;
454 Vehicle
*a
= proc(v
, data
);
455 if (find_first
&& a
!= NULL
) return a
;
462 * Find a vehicle from a specific location. It will call \a proc for ALL vehicles
463 * on the tile and YOU must make SURE that the "best one" is stored in the
464 * data value and is ALWAYS the same regardless of the order of the vehicles
465 * where proc was called on!
466 * When you fail to do this properly you create an almost untraceable DESYNC!
467 * @note The return value of \a proc will be ignored.
468 * @note Use this function when you have the intention that all vehicles
469 * should be iterated over.
470 * @param tile The location on the map
471 * @param data Arbitrary data passed to \a proc.
472 * @param proc The proc that determines whether a vehicle will be "found".
474 void FindVehicleOnPos(TileIndex tile
, void *data
, VehicleFromPosProc
*proc
)
476 VehicleFromPos(tile
, data
, proc
, false);
480 * Checks whether a vehicle is on a specific location. It will call \a proc for
481 * vehicles until it returns non-NULL.
482 * @note Use #FindVehicleOnPos when you have the intention that all vehicles
483 * should be iterated over.
484 * @param tile The location on the map
485 * @param data Arbitrary data passed to \a proc.
486 * @param proc The \a proc that determines whether a vehicle will be "found".
487 * @return True if proc returned non-NULL.
489 bool HasVehicleOnPos(TileIndex tile
, void *data
, VehicleFromPosProc
*proc
)
491 return VehicleFromPos(tile
, data
, proc
, true) != NULL
;
495 * Callback that returns 'real' vehicles lower or at height \c *(int*)data .
496 * @param v Vehicle to examine.
497 * @param data Pointer to height data.
498 * @return \a v if conditions are met, else \c NULL.
500 static Vehicle
*EnsureNoVehicleProcZ(Vehicle
*v
, void *data
)
504 if (v
->type
== VEH_DISASTER
|| (v
->type
== VEH_AIRCRAFT
&& v
->subtype
== AIR_SHADOW
)) return NULL
;
505 if (v
->z_pos
> z
) return NULL
;
511 * Ensure there is no vehicle at the ground at the given position.
512 * @param tile Position to examine.
513 * @return Succeeded command (ground is free) or failed command (a vehicle is found).
515 CommandCost
EnsureNoVehicleOnGround(TileIndex tile
)
517 int z
= GetTileMaxPixelZ(tile
);
519 /* Value v is not safe in MP games, however, it is used to generate a local
520 * error message only (which may be different for different machines).
521 * Such a message does not affect MP synchronisation.
523 Vehicle
*v
= VehicleFromPos(tile
, &z
, &EnsureNoVehicleProcZ
, true);
524 if (v
!= NULL
) return_cmd_error(STR_ERROR_TRAIN_IN_THE_WAY
+ v
->type
);
525 return CommandCost();
528 /** Procedure called for every vehicle found in tunnel/bridge in the hash map */
529 static Vehicle
*GetVehicleTunnelBridgeProc(Vehicle
*v
, void *data
)
531 if (v
->type
!= VEH_TRAIN
&& v
->type
!= VEH_ROAD
&& v
->type
!= VEH_SHIP
) return NULL
;
532 if (v
== (const Vehicle
*)data
) return NULL
;
538 * Finds vehicle in tunnel / bridge
539 * @param tile first end
540 * @param endtile second end
541 * @param ignore Ignore this vehicle when searching
542 * @return Succeeded command (if tunnel/bridge is free) or failed command (if a vehicle is using the tunnel/bridge).
544 CommandCost
TunnelBridgeIsFree(TileIndex tile
, TileIndex endtile
, const Vehicle
*ignore
)
546 /* Value v is not safe in MP games, however, it is used to generate a local
547 * error message only (which may be different for different machines).
548 * Such a message does not affect MP synchronisation.
550 Vehicle
*v
= VehicleFromPos(tile
, const_cast<Vehicle
*>(ignore
), &GetVehicleTunnelBridgeProc
, true);
551 if (v
== NULL
) v
= VehicleFromPos(endtile
, const_cast<Vehicle
*>(ignore
), &GetVehicleTunnelBridgeProc
, true);
553 if (v
!= NULL
) return_cmd_error(STR_ERROR_TRAIN_IN_THE_WAY
+ v
->type
);
554 return CommandCost();
557 static Vehicle
*EnsureNoTrainOnTrackProc(Vehicle
*v
, void *data
)
559 TrackBits rail_bits
= *(TrackBits
*)data
;
561 if (v
->type
!= VEH_TRAIN
) return NULL
;
563 Train
*t
= Train::From(v
);
564 if ((t
->track
!= rail_bits
) && !TracksOverlap(t
->track
| rail_bits
)) return NULL
;
570 * Tests if a vehicle interacts with the specified track bits.
571 * All track bits interact except parallel #TRACK_BIT_HORZ or #TRACK_BIT_VERT.
573 * @param tile The tile.
574 * @param track_bits The track bits.
575 * @return \c true if no train that interacts, is found. \c false if a train is found.
577 CommandCost
EnsureNoTrainOnTrackBits(TileIndex tile
, TrackBits track_bits
)
579 /* Value v is not safe in MP games, however, it is used to generate a local
580 * error message only (which may be different for different machines).
581 * Such a message does not affect MP synchronisation.
583 Vehicle
*v
= VehicleFromPos(tile
, &track_bits
, &EnsureNoTrainOnTrackProc
, true);
584 if (v
!= NULL
) return_cmd_error(STR_ERROR_TRAIN_IN_THE_WAY
+ v
->type
);
585 return CommandCost();
588 static void UpdateVehicleTileHash(Vehicle
*v
, bool remove
)
590 Vehicle
**old_hash
= v
->hash_tile_current
;
596 int x
= GB(TileX(v
->tile
), HASH_RES
, HASH_BITS
);
597 int y
= GB(TileY(v
->tile
), HASH_RES
, HASH_BITS
) << HASH_BITS
;
598 new_hash
= &_vehicle_tile_hash
[(x
+ y
) & TOTAL_HASH_MASK
];
601 if (old_hash
== new_hash
) return;
603 /* Remove from the old position in the hash table */
604 if (old_hash
!= NULL
) {
605 if (v
->hash_tile_next
!= NULL
) v
->hash_tile_next
->hash_tile_prev
= v
->hash_tile_prev
;
606 *v
->hash_tile_prev
= v
->hash_tile_next
;
609 /* Insert vehicle at beginning of the new position in the hash table */
610 if (new_hash
!= NULL
) {
611 v
->hash_tile_next
= *new_hash
;
612 if (v
->hash_tile_next
!= NULL
) v
->hash_tile_next
->hash_tile_prev
= &v
->hash_tile_next
;
613 v
->hash_tile_prev
= new_hash
;
617 /* Remember current hash position */
618 v
->hash_tile_current
= new_hash
;
621 static Vehicle
*_vehicle_viewport_hash
[0x1000];
623 static void UpdateVehicleViewportHash(Vehicle
*v
, int x
, int y
)
625 Vehicle
**old_hash
, **new_hash
;
626 int old_x
= v
->coord
.left
;
627 int old_y
= v
->coord
.top
;
629 new_hash
= (x
== INVALID_COORD
) ? NULL
: &_vehicle_viewport_hash
[GEN_HASH(x
, y
)];
630 old_hash
= (old_x
== INVALID_COORD
) ? NULL
: &_vehicle_viewport_hash
[GEN_HASH(old_x
, old_y
)];
632 if (old_hash
== new_hash
) return;
634 /* remove from hash table? */
635 if (old_hash
!= NULL
) {
636 if (v
->hash_viewport_next
!= NULL
) v
->hash_viewport_next
->hash_viewport_prev
= v
->hash_viewport_prev
;
637 *v
->hash_viewport_prev
= v
->hash_viewport_next
;
640 /* insert into hash table? */
641 if (new_hash
!= NULL
) {
642 v
->hash_viewport_next
= *new_hash
;
643 if (v
->hash_viewport_next
!= NULL
) v
->hash_viewport_next
->hash_viewport_prev
= &v
->hash_viewport_next
;
644 v
->hash_viewport_prev
= new_hash
;
649 void ResetVehicleHash()
652 FOR_ALL_VEHICLES(v
) { v
->hash_tile_current
= NULL
; }
653 memset(_vehicle_viewport_hash
, 0, sizeof(_vehicle_viewport_hash
));
654 memset(_vehicle_tile_hash
, 0, sizeof(_vehicle_tile_hash
));
657 void ResetVehicleColourMap()
660 FOR_ALL_VEHICLES(v
) { v
->colourmap
= PAL_NONE
; }
664 * List of vehicles that should check for autoreplace this tick.
665 * Mapping of vehicle -> leave depot immediately after autoreplace.
667 typedef SmallMap
<Vehicle
*, bool, 4> AutoreplaceMap
;
668 static AutoreplaceMap _vehicles_to_autoreplace
;
670 void InitializeVehicles()
672 _vehicles_to_autoreplace
.Reset();
676 uint
CountVehiclesInChain(const Vehicle
*v
)
679 do count
++; while ((v
= v
->Next()) != NULL
);
684 * Check if a vehicle is counted in num_engines in each company struct
685 * @return true if the vehicle is counted in num_engines
687 bool Vehicle::IsEngineCountable() const
689 switch (this->type
) {
690 case VEH_AIRCRAFT
: return Aircraft::From(this)->IsNormalAircraft(); // don't count plane shadows and helicopter rotors
692 return !this->IsArticulatedPart() && // tenders and other articulated parts
693 !Train::From(this)->IsRearDualheaded(); // rear parts of multiheaded engines
694 case VEH_ROAD
: return RoadVehicle::From(this)->IsFrontEngine();
695 case VEH_SHIP
: return true;
696 default: return false; // Only count company buildable vehicles
701 * Check whether Vehicle::engine_type has any meaning.
702 * @return true if the vehicle has a useable engine type.
704 bool Vehicle::HasEngineType() const
706 switch (this->type
) {
707 case VEH_AIRCRAFT
: return Aircraft::From(this)->IsNormalAircraft();
710 case VEH_SHIP
: return true;
711 default: return false;
716 * Retrieves the engine of the vehicle.
717 * @return Engine of the vehicle.
718 * @pre HasEngineType() == true
720 const Engine
*Vehicle::GetEngine() const
722 return Engine::Get(this->engine_type
);
726 * Retrieve the NewGRF the vehicle is tied to.
727 * This is the GRF providing the Action 3 for the engine type.
728 * @return NewGRF associated to the vehicle.
730 const GRFFile
*Vehicle::GetGRF() const
732 return this->GetEngine()->GetGRF();
736 * Retrieve the GRF ID of the NewGRF the vehicle is tied to.
737 * This is the GRF providing the Action 3 for the engine type.
738 * @return GRF ID of the associated NewGRF.
740 uint32
Vehicle::GetGRFID() const
742 return this->GetEngine()->GetGRFID();
746 * Handle the pathfinding result, especially the lost status.
747 * If the vehicle is now lost and wasn't previously fire an
748 * event to the AIs and a news message to the user. If the
749 * vehicle is not lost anymore remove the news message.
750 * @param path_found Whether the vehicle has a path to its destination.
752 void Vehicle::HandlePathfindingResult(bool path_found
)
755 /* Route found, is the vehicle marked with "lost" flag? */
756 if (!HasBit(this->vehicle_flags
, VF_PATHFINDER_LOST
)) return;
758 /* Clear the flag as the PF's problem was solved. */
759 ClrBit(this->vehicle_flags
, VF_PATHFINDER_LOST
);
760 /* Delete the news item. */
761 DeleteVehicleNews(this->index
, STR_NEWS_VEHICLE_IS_LOST
);
765 /* Were we already lost? */
766 if (HasBit(this->vehicle_flags
, VF_PATHFINDER_LOST
)) return;
768 /* It is first time the problem occurred, set the "lost" flag. */
769 SetBit(this->vehicle_flags
, VF_PATHFINDER_LOST
);
770 /* Notify user about the event. */
771 AI::NewEvent(this->owner
, new ScriptEventVehicleLost(this->index
));
772 if (_settings_client
.gui
.lost_vehicle_warn
&& this->owner
== _local_company
) {
773 SetDParam(0, this->index
);
774 AddVehicleAdviceNewsItem(STR_NEWS_VEHICLE_IS_LOST
, this->index
);
778 /** Destroy all stuff that (still) needs the virtual functions to work properly */
779 void Vehicle::PreDestructor()
781 if (CleaningPool()) return;
783 if (Station::IsValidID(this->last_station_visited
)) {
784 Station
*st
= Station::Get(this->last_station_visited
);
785 st
->loading_vehicles
.remove(this);
787 HideFillingPercent(&this->fill_percent_te_id
);
788 this->CancelReservation(INVALID_STATION
, st
);
789 delete this->cargo_payment
;
790 assert(this->cargo_payment
== NULL
); // cleared by ~CargoPayment
793 if (this->IsEngineCountable()) {
794 GroupStatistics::CountEngine(this, -1);
795 if (this->IsPrimaryVehicle()) GroupStatistics::CountVehicle(this, -1);
796 GroupStatistics::UpdateAutoreplace(this->owner
);
798 if (this->owner
== _local_company
) InvalidateAutoreplaceWindow(this->engine_type
, this->group_id
);
799 DeleteGroupHighlightOfVehicle(this);
802 if (this->type
== VEH_AIRCRAFT
&& this->IsPrimaryVehicle()) {
803 Aircraft
*a
= Aircraft::From(this);
804 Station
*st
= GetTargetAirportIfValid(a
);
806 const AirportFTA
*layout
= st
->airport
.GetFTA()->layout
;
807 CLRBITS(st
->airport
.flags
, layout
[a
->previous_pos
].block
| layout
[a
->pos
].block
);
812 if (this->type
== VEH_ROAD
&& this->IsPrimaryVehicle()) {
813 RoadVehicle
*v
= RoadVehicle::From(this);
814 if (!(v
->vehstatus
& VS_CRASHED
) && IsInsideMM(v
->state
, RVSB_IN_DT_ROAD_STOP
, RVSB_IN_DT_ROAD_STOP_END
)) {
815 /* Leave the drive through roadstop, when you have not already left it. */
816 RoadStop::GetByTile(v
->tile
, GetRoadStopType(v
->tile
))->Leave(v
);
820 if (this->Previous() == NULL
) {
821 InvalidateWindowData(WC_VEHICLE_DEPOT
, this->tile
);
824 if (this->IsPrimaryVehicle()) {
825 DeleteWindowById(WC_VEHICLE_VIEW
, this->index
);
826 DeleteWindowById(WC_VEHICLE_ORDERS
, this->index
);
827 DeleteWindowById(WC_VEHICLE_REFIT
, this->index
);
828 DeleteWindowById(WC_VEHICLE_DETAILS
, this->index
);
829 DeleteWindowById(WC_VEHICLE_TIMETABLE
, this->index
);
830 SetWindowDirty(WC_COMPANY
, this->owner
);
831 OrderBackup::ClearVehicle(this);
833 InvalidateWindowClassesData(GetWindowClassForVehicleType(this->type
), 0);
835 this->cargo
.Truncate();
836 DeleteVehicleOrders(this);
837 DeleteDepotHighlightOfVehicle(this);
839 extern void StopGlobalFollowVehicle(const Vehicle
*v
);
840 StopGlobalFollowVehicle(this);
842 ReleaseDisastersTargetingVehicle(this->index
);
847 if (CleaningPool()) {
848 this->cargo
.OnCleanPool();
852 /* sometimes, eg. for disaster vehicles, when company bankrupts, when removing crashed/flooded vehicles,
853 * it may happen that vehicle chain is deleted when visible */
854 if (!(this->vehstatus
& VS_HIDDEN
)) this->MarkAllViewportsDirty();
856 Vehicle
*v
= this->Next();
861 UpdateVehicleTileHash(this, true);
862 UpdateVehicleViewportHash(this, INVALID_COORD
, 0);
863 DeleteVehicleNews(this->index
, INVALID_STRING_ID
);
864 DeleteNewGRFInspectWindow(GetGrfSpecFeature(this->type
), this->index
);
868 * Adds a vehicle to the list of vehicles that visited a depot this tick
869 * @param *v vehicle to add
871 void VehicleEnteredDepotThisTick(Vehicle
*v
)
873 /* Vehicle should stop in the depot if it was in 'stopping' state */
874 _vehicles_to_autoreplace
[v
] = !(v
->vehstatus
& VS_STOPPED
);
876 /* We ALWAYS set the stopped state. Even when the vehicle does not plan on
877 * stopping in the depot, so we stop it to ensure that it will not reserve
878 * the path out of the depot before we might autoreplace it to a different
879 * engine. The new engine would not own the reserved path we store that we
880 * stopped the vehicle, so autoreplace can start it again */
881 v
->vehstatus
|= VS_STOPPED
;
885 * Increases the day counter for all vehicles and calls 1-day and 32-day handlers.
886 * Each tick, it processes vehicles with "index % DAY_TICKS == _date_fract",
887 * so each day, all vehicles are processes in DAY_TICKS steps.
889 static void RunVehicleDayProc()
891 if (_game_mode
!= GM_NORMAL
) return;
893 /* Run the day_proc for every DAY_TICKS vehicle starting at _date_fract. */
894 for (size_t i
= _date_fract
; i
< Vehicle::GetPoolSize(); i
+= DAY_TICKS
) {
895 Vehicle
*v
= Vehicle::Get(i
);
896 if (v
== NULL
) continue;
898 /* Call the 32-day callback if needed */
899 if ((v
->day_counter
& 0x1F) == 0 && v
->HasEngineType()) {
900 uint16 callback
= GetVehicleCallback(CBID_VEHICLE_32DAY_CALLBACK
, 0, 0, v
->engine_type
, v
);
901 if (callback
!= CALLBACK_FAILED
) {
902 if (HasBit(callback
, 0)) {
903 TriggerVehicle(v
, VEHICLE_TRIGGER_CALLBACK_32
); // Trigger vehicle trigger 10
906 /* After a vehicle trigger, the graphics and properties of the vehicle could change.
907 * Note: MarkDirty also invalidates the palette, which is the meaning of bit 1. So, nothing special there. */
908 if (callback
!= 0) v
->First()->MarkDirty();
910 if (callback
& ~3) ErrorUnknownCallbackResult(v
->GetGRFID(), CBID_VEHICLE_32DAY_CALLBACK
, callback
);
914 /* This is called once per day for each vehicle, but not in the first tick of the day */
919 void CallVehicleTicks()
921 _vehicles_to_autoreplace
.Clear();
926 FOR_ALL_STATIONS(st
) LoadUnloadStation(st
);
929 FOR_ALL_VEHICLES(v
) {
930 /* Vehicle could be deleted in this tick */
932 assert(Vehicle::Get(vehicle_index
) == NULL
);
936 assert(Vehicle::Get(vehicle_index
) == v
);
945 Vehicle
*front
= v
->First();
947 if (v
->vcache
.cached_cargo_age_period
!= 0) {
948 v
->cargo_age_counter
= min(v
->cargo_age_counter
, v
->vcache
.cached_cargo_age_period
);
949 if (--v
->cargo_age_counter
== 0) {
951 v
->cargo_age_counter
= v
->vcache
.cached_cargo_age_period
;
955 /* Do not play any sound when crashed */
956 if (front
->vehstatus
& VS_CRASHED
) continue;
958 /* Do not play any sound when in depot or tunnel */
959 if (v
->vehstatus
& VS_HIDDEN
) continue;
961 /* Do not play any sound when stopped */
962 if ((front
->vehstatus
& VS_STOPPED
) && (front
->type
!= VEH_TRAIN
|| front
->cur_speed
== 0)) continue;
964 /* Check vehicle type specifics */
967 if (Train::From(v
)->IsWagon()) continue;
971 if (!RoadVehicle::From(v
)->IsFrontEngine()) continue;
975 if (!Aircraft::From(v
)->IsNormalAircraft()) continue;
982 v
->motion_counter
+= front
->cur_speed
;
983 /* Play a running sound if the motion counter passes 256 (Do we not skip sounds?) */
984 if (GB(v
->motion_counter
, 0, 8) < front
->cur_speed
) PlayVehicleSound(v
, VSE_RUNNING
);
986 /* Play an alternating running sound every 16 ticks */
987 if (GB(v
->tick_counter
, 0, 4) == 0) {
988 /* Play running sound when speed > 0 and not braking */
989 bool running
= (front
->cur_speed
> 0) && !(front
->vehstatus
& (VS_STOPPED
| VS_TRAIN_SLOWING
));
990 PlayVehicleSound(v
, running
? VSE_RUNNING_16
: VSE_STOPPED_16
);
998 Backup
<CompanyByte
> cur_company(_current_company
, FILE_LINE
);
999 for (AutoreplaceMap::iterator it
= _vehicles_to_autoreplace
.Begin(); it
!= _vehicles_to_autoreplace
.End(); it
++) {
1001 /* Autoreplace needs the current company set as the vehicle owner */
1002 cur_company
.Change(v
->owner
);
1004 /* Start vehicle if we stopped them in VehicleEnteredDepotThisTick()
1005 * We need to stop them between VehicleEnteredDepotThisTick() and here or we risk that
1006 * they are already leaving the depot again before being replaced. */
1007 if (it
->second
) v
->vehstatus
&= ~VS_STOPPED
;
1009 /* Store the position of the effect as the vehicle pointer will become invalid later */
1014 const Company
*c
= Company::Get(_current_company
);
1015 SubtractMoneyFromCompany(CommandCost(EXPENSES_NEW_VEHICLES
, (Money
)c
->settings
.engine_renew_money
));
1016 CommandCost res
= DoCommand(0, v
->index
, 0, DC_EXEC
, CMD_AUTOREPLACE_VEHICLE
);
1017 SubtractMoneyFromCompany(CommandCost(EXPENSES_NEW_VEHICLES
, -(Money
)c
->settings
.engine_renew_money
));
1019 if (!IsLocalCompany()) continue;
1021 if (res
.Succeeded()) {
1022 ShowCostOrIncomeAnimation(x
, y
, z
, res
.GetCost());
1026 StringID error_message
= res
.GetErrorMessage();
1027 if (error_message
== STR_ERROR_AUTOREPLACE_NOTHING_TO_DO
|| error_message
== INVALID_STRING_ID
) continue;
1029 if (error_message
== STR_ERROR_NOT_ENOUGH_CASH_REQUIRES_CURRENCY
) error_message
= STR_ERROR_AUTOREPLACE_MONEY_LIMIT
;
1032 if (error_message
== STR_ERROR_TRAIN_TOO_LONG_AFTER_REPLACEMENT
) {
1033 message
= error_message
;
1035 message
= STR_NEWS_VEHICLE_AUTORENEW_FAILED
;
1038 SetDParam(0, v
->index
);
1039 SetDParam(1, error_message
);
1040 AddVehicleAdviceNewsItem(message
, v
->index
);
1043 cur_company
.Restore();
1047 * Add vehicle sprite for drawing to the screen.
1048 * @param v Vehicle to draw.
1050 static void DoDrawVehicle(const Vehicle
*v
)
1052 PaletteID pal
= PAL_NONE
;
1054 if (v
->vehstatus
& VS_DEFPAL
) pal
= (v
->vehstatus
& VS_CRASHED
) ? PALETTE_CRASH
: GetVehiclePalette(v
);
1056 /* Check whether the vehicle shall be transparent due to the game state */
1057 bool shadowed
= (v
->vehstatus
& VS_SHADOW
) != 0;
1059 if (v
->type
== VEH_EFFECT
) {
1060 /* Check whether the vehicle shall be transparent/invisible due to GUI settings.
1061 * However, transparent smoke and bubbles look weird, so always hide them. */
1062 TransparencyOption to
= EffectVehicle::From(v
)->GetTransparencyOption();
1063 if (to
!= TO_INVALID
&& (IsTransparencySet(to
) || IsInvisibilitySet(to
))) return;
1066 StartSpriteCombine();
1067 for (uint i
= 0; i
< v
->sprite_seq
.count
; ++i
) {
1068 PaletteID pal2
= v
->sprite_seq
.seq
[i
].pal
;
1069 if (!pal2
|| (v
->vehstatus
& VS_CRASHED
)) pal2
= pal
;
1070 AddSortableSpriteToDraw(v
->sprite_seq
.seq
[i
].sprite
, pal2
, v
->x_pos
+ v
->x_offs
, v
->y_pos
+ v
->y_offs
,
1071 v
->x_extent
, v
->y_extent
, v
->z_extent
, v
->z_pos
, shadowed
, v
->x_bb_offs
, v
->y_bb_offs
);
1077 * Add the vehicle sprites that should be drawn at a part of the screen.
1078 * @param dpi Rectangle being drawn.
1080 void ViewportAddVehicles(DrawPixelInfo
*dpi
)
1082 /* The bounding rectangle */
1083 const int l
= dpi
->left
;
1084 const int r
= dpi
->left
+ dpi
->width
;
1085 const int t
= dpi
->top
;
1086 const int b
= dpi
->top
+ dpi
->height
;
1088 /* The hash area to scan */
1091 if (dpi
->width
+ (70 * ZOOM_LVL_BASE
) < (1 << (7 + 6 + ZOOM_LVL_SHIFT
))) {
1092 xl
= GB(l
- (70 * ZOOM_LVL_BASE
), 7 + ZOOM_LVL_SHIFT
, 6);
1093 xu
= GB(r
, 7 + ZOOM_LVL_SHIFT
, 6);
1095 /* scan whole hash row */
1100 if (dpi
->height
+ (70 * ZOOM_LVL_BASE
) < (1 << (6 + 6 + ZOOM_LVL_SHIFT
))) {
1101 yl
= GB(t
- (70 * ZOOM_LVL_BASE
), 6 + ZOOM_LVL_SHIFT
, 6) << 6;
1102 yu
= GB(b
, 6 + ZOOM_LVL_SHIFT
, 6) << 6;
1104 /* scan whole column */
1109 for (int y
= yl
;; y
= (y
+ (1 << 6)) & (0x3F << 6)) {
1110 for (int x
= xl
;; x
= (x
+ 1) & 0x3F) {
1111 const Vehicle
*v
= _vehicle_viewport_hash
[x
+ y
]; // already masked & 0xFFF
1114 if (!(v
->vehstatus
& VS_HIDDEN
) &&
1115 l
<= v
->coord
.right
&&
1116 t
<= v
->coord
.bottom
&&
1117 r
>= v
->coord
.left
&&
1118 b
>= v
->coord
.top
) {
1121 v
= v
->hash_viewport_next
;
1132 * Find the vehicle close to the clicked coordinates.
1133 * @param vp Viewport clicked in.
1134 * @param x X coordinate in the viewport.
1135 * @param y Y coordinate in the viewport.
1136 * @return Closest vehicle, or \c NULL if none found.
1138 Vehicle
*CheckClickOnVehicle(const ViewPort
*vp
, int x
, int y
)
1140 Vehicle
*found
= NULL
, *v
;
1141 uint dist
, best_dist
= UINT_MAX
;
1143 if ((uint
)(x
-= vp
->left
) >= (uint
)vp
->width
|| (uint
)(y
-= vp
->top
) >= (uint
)vp
->height
) return NULL
;
1145 x
= ScaleByZoom(x
, vp
->zoom
) + vp
->virtual_left
;
1146 y
= ScaleByZoom(y
, vp
->zoom
) + vp
->virtual_top
;
1148 FOR_ALL_VEHICLES(v
) {
1149 if ((v
->vehstatus
& (VS_HIDDEN
| VS_UNCLICKABLE
)) == 0 &&
1150 x
>= v
->coord
.left
&& x
<= v
->coord
.right
&&
1151 y
>= v
->coord
.top
&& y
<= v
->coord
.bottom
) {
1154 abs(((v
->coord
.left
+ v
->coord
.right
) >> 1) - x
),
1155 abs(((v
->coord
.top
+ v
->coord
.bottom
) >> 1) - y
)
1158 if (dist
< best_dist
) {
1169 * Decrease the value of a vehicle.
1170 * @param v %Vehicle to devaluate.
1172 void DecreaseVehicleValue(Vehicle
*v
)
1174 v
->value
-= v
->value
>> 8;
1175 SetWindowDirty(WC_VEHICLE_DETAILS
, v
->index
);
1178 static const byte _breakdown_chance
[64] = {
1179 3, 3, 3, 3, 3, 3, 3, 3,
1180 4, 4, 5, 5, 6, 6, 7, 7,
1181 8, 8, 9, 9, 10, 10, 11, 11,
1182 12, 13, 13, 13, 13, 14, 15, 16,
1183 17, 19, 21, 25, 28, 31, 34, 37,
1184 40, 44, 48, 52, 56, 60, 64, 68,
1185 72, 80, 90, 100, 110, 120, 130, 140,
1186 150, 170, 190, 210, 230, 250, 250, 250,
1189 void CheckVehicleBreakdown(Vehicle
*v
)
1193 /* decrease reliability */
1194 v
->reliability
= rel
= max((rel_old
= v
->reliability
) - v
->reliability_spd_dec
, 0);
1195 if ((rel_old
>> 8) != (rel
>> 8)) SetWindowDirty(WC_VEHICLE_DETAILS
, v
->index
);
1197 if (v
->breakdown_ctr
!= 0 || (v
->vehstatus
& VS_STOPPED
) ||
1198 _settings_game
.difficulty
.vehicle_breakdowns
< 1 ||
1199 v
->cur_speed
< 5 || _game_mode
== GM_MENU
) {
1203 uint32 r
= Random();
1205 /* increase chance of failure */
1206 int chance
= v
->breakdown_chance
+ 1;
1207 if (Chance16I(1, 25, r
)) chance
+= 25;
1208 v
->breakdown_chance
= min(255, chance
);
1210 /* calculate reliability value to use in comparison */
1211 rel
= v
->reliability
;
1212 if (v
->type
== VEH_SHIP
) rel
+= 0x6666;
1214 /* reduced breakdowns? */
1215 if (_settings_game
.difficulty
.vehicle_breakdowns
== 1) rel
+= 0x6666;
1217 /* check if to break down */
1218 if (_breakdown_chance
[(uint
)min(rel
, 0xffff) >> 10] <= v
->breakdown_chance
) {
1219 v
->breakdown_ctr
= GB(r
, 16, 6) + 0x3F;
1220 v
->breakdown_delay
= GB(r
, 24, 7) + 0x80;
1221 v
->breakdown_chance
= 0;
1226 * Handle all of the aspects of a vehicle breakdown
1227 * This includes adding smoke and sounds, and ending the breakdown when appropriate.
1228 * @return true iff the vehicle is stopped because of a breakdown
1229 * @note This function always returns false for aircraft, since these never stop for breakdowns
1231 bool Vehicle::HandleBreakdown()
1233 /* Possible states for Vehicle::breakdown_ctr
1234 * 0 - vehicle is running normally
1235 * 1 - vehicle is currently broken down
1236 * 2 - vehicle is going to break down now
1237 * >2 - vehicle is counting down to the actual breakdown event */
1238 switch (this->breakdown_ctr
) {
1243 this->breakdown_ctr
= 1;
1245 if (this->breakdowns_since_last_service
!= 255) {
1246 this->breakdowns_since_last_service
++;
1249 if (this->type
== VEH_AIRCRAFT
) {
1250 /* Aircraft just need this flag, the rest is handled elsewhere */
1251 this->vehstatus
|= VS_AIRCRAFT_BROKEN
;
1253 this->cur_speed
= 0;
1255 if (!PlayVehicleSound(this, VSE_BREAKDOWN
)) {
1256 bool train_or_ship
= this->type
== VEH_TRAIN
|| this->type
== VEH_SHIP
;
1257 SndPlayVehicleFx((_settings_game
.game_creation
.landscape
!= LT_TOYLAND
) ?
1258 (train_or_ship
? SND_10_TRAIN_BREAKDOWN
: SND_0F_VEHICLE_BREAKDOWN
) :
1259 (train_or_ship
? SND_3A_COMEDY_BREAKDOWN_2
: SND_35_COMEDY_BREAKDOWN
), this);
1262 if (!(this->vehstatus
& VS_HIDDEN
) && !HasBit(EngInfo(this->engine_type
)->misc_flags
, EF_NO_BREAKDOWN_SMOKE
)) {
1263 EffectVehicle
*u
= CreateEffectVehicleRel(this, 4, 4, 5, EV_BREAKDOWN_SMOKE
);
1264 if (u
!= NULL
) u
->animation_state
= this->breakdown_delay
* 2;
1268 this->MarkDirty(); // Update graphics after speed is zeroed
1269 SetWindowDirty(WC_VEHICLE_VIEW
, this->index
);
1270 SetWindowDirty(WC_VEHICLE_DETAILS
, this->index
);
1274 /* Aircraft breakdowns end only when arriving at the airport */
1275 if (this->type
== VEH_AIRCRAFT
) return false;
1277 /* For trains this function is called twice per tick, so decrease v->breakdown_delay at half the rate */
1278 if ((this->tick_counter
& (this->type
== VEH_TRAIN
? 3 : 1)) == 0) {
1279 if (--this->breakdown_delay
== 0) {
1280 this->breakdown_ctr
= 0;
1282 SetWindowDirty(WC_VEHICLE_VIEW
, this->index
);
1288 if (!this->current_order
.IsType(OT_LOADING
)) this->breakdown_ctr
--;
1294 * Update age of a vehicle.
1295 * @param v Vehicle to update.
1297 void AgeVehicle(Vehicle
*v
)
1299 if (v
->age
< MAX_DAY
) {
1301 if (v
->IsPrimaryVehicle() && v
->age
== VEHICLE_PROFIT_MIN_AGE
+ 1) GroupStatistics::VehicleReachedProfitAge(v
);
1304 if (!v
->IsPrimaryVehicle() && (v
->type
!= VEH_TRAIN
|| !Train::From(v
)->IsEngine())) return;
1306 int age
= v
->age
- v
->max_age
;
1307 if (age
== DAYS_IN_LEAP_YEAR
* 0 || age
== DAYS_IN_LEAP_YEAR
* 1 ||
1308 age
== DAYS_IN_LEAP_YEAR
* 2 || age
== DAYS_IN_LEAP_YEAR
* 3 || age
== DAYS_IN_LEAP_YEAR
* 4) {
1309 v
->reliability_spd_dec
<<= 1;
1312 SetWindowDirty(WC_VEHICLE_DETAILS
, v
->index
);
1314 /* Don't warn about non-primary or not ours vehicles or vehicles that are crashed */
1315 if (v
->Previous() != NULL
|| v
->owner
!= _local_company
|| (v
->vehstatus
& VS_CRASHED
) != 0) return;
1317 /* Don't warn if a renew is active */
1318 if (Company::Get(v
->owner
)->settings
.engine_renew
&& v
->GetEngine()->company_avail
!= 0) return;
1321 if (age
== -DAYS_IN_LEAP_YEAR
) {
1322 str
= STR_NEWS_VEHICLE_IS_GETTING_OLD
;
1323 } else if (age
== 0) {
1324 str
= STR_NEWS_VEHICLE_IS_GETTING_VERY_OLD
;
1325 } else if (age
> 0 && (age
% DAYS_IN_LEAP_YEAR
) == 0) {
1326 str
= STR_NEWS_VEHICLE_IS_GETTING_VERY_OLD_AND
;
1331 SetDParam(0, v
->index
);
1332 AddVehicleAdviceNewsItem(str
, v
->index
);
1336 * Calculates how full a vehicle is.
1337 * @param front The front vehicle of the consist to check.
1338 * @param colour The string to show depending on if we are unloading or loading
1339 * @return A percentage of how full the Vehicle is.
1340 * Percentages are rounded towards 50%, so that 0% and 100% are only returned
1341 * if the vehicle is completely empty or full.
1342 * This is useful for both display and conditional orders.
1344 uint8
CalcPercentVehicleFilled(const Vehicle
*front
, StringID
*colour
)
1350 bool loading
= false;
1352 bool is_loading
= front
->current_order
.IsType(OT_LOADING
);
1354 /* The station may be NULL when the (colour) string does not need to be set. */
1355 const Station
*st
= Station::GetIfValid(front
->last_station_visited
);
1356 assert(colour
== NULL
|| (st
!= NULL
&& is_loading
));
1358 bool order_no_load
= is_loading
&& (front
->current_order
.GetLoadType() & OLFB_NO_LOAD
);
1359 bool order_full_load
= is_loading
&& (front
->current_order
.GetLoadType() & OLFB_FULL_LOAD
);
1361 /* Count up max and used */
1362 for (const Vehicle
*v
= front
; v
!= NULL
; v
= v
->Next()) {
1363 count
+= v
->cargo
.StoredCount();
1364 max
+= v
->cargo_cap
;
1365 if (v
->cargo_cap
!= 0 && colour
!= NULL
) {
1366 unloading
+= HasBit(v
->vehicle_flags
, VF_CARGO_UNLOADING
) ? 1 : 0;
1367 loading
|= !order_no_load
&&
1368 (order_full_load
|| st
->goods
[v
->cargo_type
].HasRating()) &&
1369 !HasBit(v
->vehicle_flags
, VF_LOADING_FINISHED
) && !HasBit(v
->vehicle_flags
, VF_STOP_LOADING
);
1374 if (colour
!= NULL
) {
1375 if (unloading
== 0 && loading
) {
1376 *colour
= STR_PERCENT_UP
;
1377 } else if (unloading
== 0 && !loading
) {
1378 *colour
= STR_PERCENT_NONE
;
1379 } else if (cars
== unloading
|| !loading
) {
1380 *colour
= STR_PERCENT_DOWN
;
1382 *colour
= STR_PERCENT_UP_DOWN
;
1386 /* Train without capacity */
1387 if (max
== 0) return 100;
1389 /* Return the percentage */
1390 if (count
* 2 < max
) {
1391 /* Less than 50%; round up, so that 0% means really empty. */
1392 return CeilDiv(count
* 100, max
);
1394 /* More than 50%; round down, so that 100% means really full. */
1395 return (count
* 100) / max
;
1400 * Vehicle entirely entered the depot, update its status, orders, vehicle windows, service it, etc.
1401 * @param v Vehicle that entered a depot.
1403 void VehicleEnterDepot(Vehicle
*v
)
1405 /* Always work with the front of the vehicle */
1406 assert(v
== v
->First());
1410 Train
*t
= Train::From(v
);
1411 SetWindowClassesDirty(WC_TRAINS_LIST
);
1412 /* Clear path reservation */
1413 SetDepotReservation(t
->tile
, false);
1414 if (_settings_client
.gui
.show_track_reservation
) MarkTileDirtyByTile(t
->tile
);
1416 UpdateSignalsOnSegment(t
->tile
, INVALID_DIAGDIR
, t
->owner
);
1417 t
->wait_counter
= 0;
1418 t
->force_proceed
= TFP_NONE
;
1419 ClrBit(t
->flags
, VRF_TOGGLE_REVERSE
);
1420 t
->ConsistChanged(CCF_ARRANGE
);
1425 SetWindowClassesDirty(WC_ROADVEH_LIST
);
1429 SetWindowClassesDirty(WC_SHIPS_LIST
);
1430 Ship
*ship
= Ship::From(v
);
1431 ship
->state
= TRACK_BIT_DEPOT
;
1432 ship
->UpdateCache();
1433 ship
->UpdateViewport(true, true);
1434 SetWindowDirty(WC_VEHICLE_DEPOT
, v
->tile
);
1439 SetWindowClassesDirty(WC_AIRCRAFT_LIST
);
1440 HandleAircraftEnterHangar(Aircraft::From(v
));
1442 default: NOT_REACHED();
1444 SetWindowDirty(WC_VEHICLE_VIEW
, v
->index
);
1446 if (v
->type
!= VEH_TRAIN
) {
1447 /* Trains update the vehicle list when the first unit enters the depot and calls VehicleEnterDepot() when the last unit enters.
1448 * 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 */
1449 InvalidateWindowData(WC_VEHICLE_DEPOT
, v
->tile
);
1451 SetWindowDirty(WC_VEHICLE_DEPOT
, v
->tile
);
1453 v
->vehstatus
|= VS_HIDDEN
;
1456 VehicleServiceInDepot(v
);
1458 /* After a vehicle trigger, the graphics and properties of the vehicle could change. */
1459 TriggerVehicle(v
, VEHICLE_TRIGGER_DEPOT
);
1462 if (v
->current_order
.IsType(OT_GOTO_DEPOT
)) {
1463 SetWindowDirty(WC_VEHICLE_VIEW
, v
->index
);
1465 const Order
*real_order
= v
->GetOrder(v
->cur_real_order_index
);
1467 /* Test whether we are heading for this depot. If not, do nothing.
1468 * Note: The target depot for nearest-/manual-depot-orders is only updated on junctions, but we want to accept every depot. */
1469 if ((v
->current_order
.GetDepotOrderType() & ODTFB_PART_OF_ORDERS
) &&
1470 real_order
!= NULL
&& !(real_order
->GetDepotActionType() & ODATFB_NEAREST_DEPOT
) &&
1471 (v
->type
== VEH_AIRCRAFT
? v
->current_order
.GetDestination() != GetStationIndex(v
->tile
) : v
->dest_tile
!= v
->tile
)) {
1472 /* We are heading for another depot, keep driving. */
1476 if (v
->current_order
.IsRefit()) {
1477 Backup
<CompanyByte
> cur_company(_current_company
, v
->owner
, FILE_LINE
);
1478 CommandCost cost
= DoCommand(v
->tile
, v
->index
, v
->current_order
.GetRefitCargo() | 0xFF << 8, DC_EXEC
, GetCmdRefitVeh(v
));
1479 cur_company
.Restore();
1481 if (cost
.Failed()) {
1482 _vehicles_to_autoreplace
[v
] = false;
1483 if (v
->owner
== _local_company
) {
1484 /* Notify the user that we stopped the vehicle */
1485 SetDParam(0, v
->index
);
1486 AddVehicleAdviceNewsItem(STR_NEWS_ORDER_REFIT_FAILED
, v
->index
);
1488 } else if (cost
.GetCost() != 0) {
1489 v
->profit_this_year
-= cost
.GetCost() << 8;
1490 if (v
->owner
== _local_company
) {
1491 ShowCostOrIncomeAnimation(v
->x_pos
, v
->y_pos
, v
->z_pos
, cost
.GetCost());
1496 if (v
->current_order
.GetDepotOrderType() & ODTFB_PART_OF_ORDERS
) {
1497 /* Part of orders */
1498 v
->DeleteUnreachedImplicitOrders();
1499 UpdateVehicleTimetable(v
, true);
1500 v
->IncrementImplicitOrderIndex();
1502 if (v
->current_order
.GetDepotActionType() & ODATFB_HALT
) {
1503 /* Vehicles are always stopped on entering depots. Do not restart this one. */
1504 _vehicles_to_autoreplace
[v
] = false;
1505 /* Invalidate last_loading_station. As the link from the station
1506 * before the stop to the station after the stop can't be predicted
1507 * we shouldn't construct it when the vehicle visits the next stop. */
1508 v
->last_loading_station
= INVALID_STATION
;
1509 if (v
->owner
== _local_company
) {
1510 SetDParam(0, v
->index
);
1511 AddVehicleAdviceNewsItem(STR_NEWS_TRAIN_IS_WAITING
+ v
->type
, v
->index
);
1513 AI::NewEvent(v
->owner
, new ScriptEventVehicleWaitingInDepot(v
->index
));
1515 v
->current_order
.MakeDummy();
1521 * Update the position of the vehicle. This will update the hash that tells
1522 * which vehicles are on a tile.
1524 void Vehicle::UpdatePosition()
1526 UpdateVehicleTileHash(this, false);
1530 * Update the vehicle on the viewport, updating the right hash and setting the
1532 * @param dirty Mark the (new and old) coordinates of the vehicle as dirty.
1534 void Vehicle::UpdateViewport(bool dirty
)
1537 this->sprite_seq
.GetBounds(&new_coord
);
1539 Point pt
= RemapCoords(this->x_pos
+ this->x_offs
, this->y_pos
+ this->y_offs
, this->z_pos
);
1540 new_coord
.left
+= pt
.x
;
1541 new_coord
.top
+= pt
.y
;
1542 new_coord
.right
+= pt
.x
+ 2 * ZOOM_LVL_BASE
;
1543 new_coord
.bottom
+= pt
.y
+ 2 * ZOOM_LVL_BASE
;
1545 UpdateVehicleViewportHash(this, new_coord
.left
, new_coord
.top
);
1547 Rect old_coord
= this->coord
;
1548 this->coord
= new_coord
;
1551 if (old_coord
.left
== INVALID_COORD
) {
1552 this->MarkAllViewportsDirty();
1554 ::MarkAllViewportsDirty(
1555 min(old_coord
.left
, this->coord
.left
),
1556 min(old_coord
.top
, this->coord
.top
),
1557 max(old_coord
.right
, this->coord
.right
),
1558 max(old_coord
.bottom
, this->coord
.bottom
));
1564 * Update the position of the vehicle, and update the viewport.
1566 void Vehicle::UpdatePositionAndViewport()
1568 this->UpdatePosition();
1569 this->UpdateViewport(true);
1573 * Marks viewports dirty where the vehicle's image is.
1575 void Vehicle::MarkAllViewportsDirty() const
1577 ::MarkAllViewportsDirty(this->coord
.left
, this->coord
.top
, this->coord
.right
, this->coord
.bottom
);
1581 * Get position information of a vehicle when moving one pixel in the direction it is facing
1582 * @param v Vehicle to move
1583 * @return Position information after the move
1585 GetNewVehiclePosResult
GetNewVehiclePos(const Vehicle
*v
)
1587 static const int8 _delta_coord
[16] = {
1588 -1,-1,-1, 0, 1, 1, 1, 0, /* x */
1589 -1, 0, 1, 1, 1, 0,-1,-1, /* y */
1592 int x
= v
->x_pos
+ _delta_coord
[v
->direction
];
1593 int y
= v
->y_pos
+ _delta_coord
[v
->direction
+ 8];
1595 GetNewVehiclePosResult gp
;
1598 gp
.old_tile
= v
->tile
;
1599 gp
.new_tile
= TileVirtXY(x
, y
);
1603 static const Direction _new_direction_table
[] = {
1604 DIR_N
, DIR_NW
, DIR_W
,
1605 DIR_NE
, DIR_SE
, DIR_SW
,
1606 DIR_E
, DIR_SE
, DIR_S
1609 Direction
GetDirectionTowards(const Vehicle
*v
, int x
, int y
)
1613 if (y
>= v
->y_pos
) {
1614 if (y
!= v
->y_pos
) i
+= 3;
1618 if (x
>= v
->x_pos
) {
1619 if (x
!= v
->x_pos
) i
++;
1623 Direction dir
= v
->direction
;
1625 DirDiff dirdiff
= DirDifference(_new_direction_table
[i
], dir
);
1626 if (dirdiff
== DIRDIFF_SAME
) return dir
;
1627 return ChangeDir(dir
, dirdiff
> DIRDIFF_REVERSE
? DIRDIFF_45LEFT
: DIRDIFF_45RIGHT
);
1631 * Call the tile callback function for a vehicle entering a tile
1632 * @param v Vehicle entering the tile
1633 * @param tile Tile entered
1634 * @param x X position
1635 * @param y Y position
1636 * @return Some meta-data over the to be entered tile.
1637 * @see VehicleEnterTileStatus to see what the bits in the return value mean.
1639 VehicleEnterTileStatus
VehicleEnterTile(Vehicle
*v
, TileIndex tile
, int x
, int y
)
1641 return _tile_type_procs
[GetTileType(tile
)]->vehicle_enter_tile_proc(v
, tile
, x
, y
);
1645 * Initializes the structure. Vehicle unit numbers are supposed not to change after
1646 * struct initialization, except after each call to this->NextID() the returned value
1647 * is assigned to a vehicle.
1648 * @param type type of vehicle
1649 * @param owner owner of vehicles
1651 FreeUnitIDGenerator::FreeUnitIDGenerator(VehicleType type
, CompanyID owner
) : cache(NULL
), maxid(0), curid(0)
1655 FOR_ALL_VEHICLES(v
) {
1656 if (v
->type
== type
&& v
->owner
== owner
) {
1657 this->maxid
= max
<UnitID
>(this->maxid
, v
->unitnumber
);
1661 if (this->maxid
== 0) return;
1663 /* Reserving 'maxid + 2' because we need:
1664 * - space for the last item (with v->unitnumber == maxid)
1665 * - one free slot working as loop terminator in FreeUnitIDGenerator::NextID() */
1666 this->cache
= CallocT
<bool>(this->maxid
+ 2);
1668 /* Fill the cache */
1669 FOR_ALL_VEHICLES(v
) {
1670 if (v
->type
== type
&& v
->owner
== owner
) {
1671 this->cache
[v
->unitnumber
] = true;
1676 /** Returns next free UnitID. Supposes the last returned value was assigned to a vehicle. */
1677 UnitID
FreeUnitIDGenerator::NextID()
1679 if (this->maxid
<= this->curid
) return ++this->curid
;
1681 while (this->cache
[++this->curid
]) { } // it will stop, we reserved more space than needed
1687 * Get an unused unit number for a vehicle (if allowed).
1688 * @param type Type of vehicle
1689 * @return A unused unit number for the given type of vehicle if it is allowed to build one, else \c UINT16_MAX.
1691 UnitID
GetFreeUnitNumber(VehicleType type
)
1693 /* Check whether it is allowed to build another vehicle. */
1696 case VEH_TRAIN
: max_veh
= _settings_game
.vehicle
.max_trains
; break;
1697 case VEH_ROAD
: max_veh
= _settings_game
.vehicle
.max_roadveh
; break;
1698 case VEH_SHIP
: max_veh
= _settings_game
.vehicle
.max_ships
; break;
1699 case VEH_AIRCRAFT
: max_veh
= _settings_game
.vehicle
.max_aircraft
; break;
1700 default: NOT_REACHED();
1703 const Company
*c
= Company::Get(_current_company
);
1704 if (c
->group_all
[type
].num_vehicle
>= max_veh
) return UINT16_MAX
; // Currently already at the limit, no room to make a new one.
1706 FreeUnitIDGenerator
gen(type
, _current_company
);
1708 return gen
.NextID();
1713 * Check whether we can build infrastructure for the given
1714 * vehicle type. This to disable building stations etc. when
1715 * you are not allowed/able to have the vehicle type yet.
1716 * @param type the vehicle type to check this for
1717 * @return true if there is any reason why you may build
1718 * the infrastructure for the given vehicle type
1720 bool CanBuildVehicleInfrastructure(VehicleType type
)
1722 assert(IsCompanyBuildableVehicleType(type
));
1724 if (!Company::IsValidID(_local_company
)) return false;
1725 if (!_settings_client
.gui
.disable_unsuitable_building
) return true;
1729 case VEH_TRAIN
: max
= _settings_game
.vehicle
.max_trains
; break;
1730 case VEH_ROAD
: max
= _settings_game
.vehicle
.max_roadveh
; break;
1731 case VEH_SHIP
: max
= _settings_game
.vehicle
.max_ships
; break;
1732 case VEH_AIRCRAFT
: max
= _settings_game
.vehicle
.max_aircraft
; break;
1733 default: NOT_REACHED();
1736 /* We can build vehicle infrastructure when we may build the vehicle type */
1738 /* Can we actually build the vehicle type? */
1740 FOR_ALL_ENGINES_OF_TYPE(e
, type
) {
1741 if (HasBit(e
->company_avail
, _local_company
)) return true;
1746 /* We should be able to build infrastructure when we have the actual vehicle type */
1748 FOR_ALL_VEHICLES(v
) {
1749 if (v
->owner
== _local_company
&& v
->type
== type
) return true;
1757 * Determines the #LiveryScheme for a vehicle.
1758 * @param engine_type Engine of the vehicle.
1759 * @param parent_engine_type Engine of the front vehicle, #INVALID_ENGINE if vehicle is at front itself.
1760 * @param v the vehicle, \c NULL if in purchase list etc.
1761 * @return livery scheme to use.
1763 LiveryScheme
GetEngineLiveryScheme(EngineID engine_type
, EngineID parent_engine_type
, const Vehicle
*v
)
1765 CargoID cargo_type
= v
== NULL
? (CargoID
)CT_INVALID
: v
->cargo_type
;
1766 const Engine
*e
= Engine::Get(engine_type
);
1768 default: NOT_REACHED();
1770 if (v
!= NULL
&& parent_engine_type
!= INVALID_ENGINE
&& (UsesWagonOverride(v
) || (v
->IsArticulatedPart() && e
->u
.rail
.railveh_type
!= RAILVEH_WAGON
))) {
1771 /* Wagonoverrides use the colour scheme of the front engine.
1772 * Articulated parts use the colour scheme of the first part. (Not supported for articulated wagons) */
1773 engine_type
= parent_engine_type
;
1774 e
= Engine::Get(engine_type
);
1775 /* Note: Luckily cargo_type is not needed for engines */
1778 if (cargo_type
== CT_INVALID
) cargo_type
= e
->GetDefaultCargoType();
1779 if (cargo_type
== CT_INVALID
) cargo_type
= CT_GOODS
; // The vehicle does not carry anything, let's pick some freight cargo
1780 if (e
->u
.rail
.railveh_type
== RAILVEH_WAGON
) {
1781 if (!CargoSpec::Get(cargo_type
)->is_freight
) {
1782 if (parent_engine_type
== INVALID_ENGINE
) {
1783 return LS_PASSENGER_WAGON_STEAM
;
1785 switch (RailVehInfo(parent_engine_type
)->engclass
) {
1786 default: NOT_REACHED();
1787 case EC_STEAM
: return LS_PASSENGER_WAGON_STEAM
;
1788 case EC_DIESEL
: return LS_PASSENGER_WAGON_DIESEL
;
1789 case EC_ELECTRIC
: return LS_PASSENGER_WAGON_ELECTRIC
;
1790 case EC_MONORAIL
: return LS_PASSENGER_WAGON_MONORAIL
;
1791 case EC_MAGLEV
: return LS_PASSENGER_WAGON_MAGLEV
;
1795 return LS_FREIGHT_WAGON
;
1798 bool is_mu
= HasBit(e
->info
.misc_flags
, EF_RAIL_IS_MU
);
1800 switch (e
->u
.rail
.engclass
) {
1801 default: NOT_REACHED();
1802 case EC_STEAM
: return LS_STEAM
;
1803 case EC_DIESEL
: return is_mu
? LS_DMU
: LS_DIESEL
;
1804 case EC_ELECTRIC
: return is_mu
? LS_EMU
: LS_ELECTRIC
;
1805 case EC_MONORAIL
: return LS_MONORAIL
;
1806 case EC_MAGLEV
: return LS_MAGLEV
;
1811 /* Always use the livery of the front */
1812 if (v
!= NULL
&& parent_engine_type
!= INVALID_ENGINE
) {
1813 engine_type
= parent_engine_type
;
1814 e
= Engine::Get(engine_type
);
1815 cargo_type
= v
->First()->cargo_type
;
1817 if (cargo_type
== CT_INVALID
) cargo_type
= e
->GetDefaultCargoType();
1818 if (cargo_type
== CT_INVALID
) cargo_type
= CT_GOODS
; // The vehicle does not carry anything, let's pick some freight cargo
1820 /* Important: Use Tram Flag of front part. Luckily engine_type refers to the front part here. */
1821 if (HasBit(e
->info
.misc_flags
, EF_ROAD_TRAM
)) {
1823 return IsCargoInClass(cargo_type
, CC_PASSENGERS
) ? LS_PASSENGER_TRAM
: LS_FREIGHT_TRAM
;
1826 return IsCargoInClass(cargo_type
, CC_PASSENGERS
) ? LS_BUS
: LS_TRUCK
;
1830 if (cargo_type
== CT_INVALID
) cargo_type
= e
->GetDefaultCargoType();
1831 if (cargo_type
== CT_INVALID
) cargo_type
= CT_GOODS
; // The vehicle does not carry anything, let's pick some freight cargo
1832 return IsCargoInClass(cargo_type
, CC_PASSENGERS
) ? LS_PASSENGER_SHIP
: LS_FREIGHT_SHIP
;
1835 switch (e
->u
.air
.subtype
) {
1836 case AIR_HELI
: return LS_HELICOPTER
;
1837 case AIR_CTOL
: return LS_SMALL_PLANE
;
1838 case AIR_CTOL
| AIR_FAST
: return LS_LARGE_PLANE
;
1839 default: NOT_REACHED();
1845 * Determines the livery for a vehicle.
1846 * @param engine_type EngineID of the vehicle
1847 * @param company Owner of the vehicle
1848 * @param parent_engine_type EngineID of the front vehicle. INVALID_VEHICLE if vehicle is at front itself.
1849 * @param v the vehicle. NULL if in purchase list etc.
1850 * @param livery_setting The livery settings to use for acquiring the livery information.
1851 * @return livery to use
1853 const Livery
*GetEngineLivery(EngineID engine_type
, CompanyID company
, EngineID parent_engine_type
, const Vehicle
*v
, byte livery_setting
)
1855 const Company
*c
= Company::Get(company
);
1856 LiveryScheme scheme
= LS_DEFAULT
;
1858 /* The default livery is always available for use, but its in_use flag determines
1859 * whether any _other_ liveries are in use. */
1860 if (c
->livery
[LS_DEFAULT
].in_use
&& (livery_setting
== LIT_ALL
|| (livery_setting
== LIT_COMPANY
&& company
== _local_company
))) {
1861 /* Determine the livery scheme to use */
1862 scheme
= GetEngineLiveryScheme(engine_type
, parent_engine_type
, v
);
1864 /* Switch back to the default scheme if the resolved scheme is not in use */
1865 if (!c
->livery
[scheme
].in_use
) scheme
= LS_DEFAULT
;
1868 return &c
->livery
[scheme
];
1872 static PaletteID
GetEngineColourMap(EngineID engine_type
, CompanyID company
, EngineID parent_engine_type
, const Vehicle
*v
)
1874 PaletteID map
= (v
!= NULL
) ? v
->colourmap
: PAL_NONE
;
1876 /* Return cached value if any */
1877 if (map
!= PAL_NONE
) return map
;
1879 const Engine
*e
= Engine::Get(engine_type
);
1881 /* Check if we should use the colour map callback */
1882 if (HasBit(e
->info
.callback_mask
, CBM_VEHICLE_COLOUR_REMAP
)) {
1883 uint16 callback
= GetVehicleCallback(CBID_VEHICLE_COLOUR_MAPPING
, 0, 0, engine_type
, v
);
1884 /* Failure means "use the default two-colour" */
1885 if (callback
!= CALLBACK_FAILED
) {
1886 assert_compile(PAL_NONE
== 0); // Returning 0x4000 (resp. 0xC000) coincidences with default value (PAL_NONE)
1887 map
= GB(callback
, 0, 14);
1888 /* If bit 14 is set, then the company colours are applied to the
1889 * map else it's returned as-is. */
1890 if (!HasBit(callback
, 14)) {
1892 if (v
!= NULL
) const_cast<Vehicle
*>(v
)->colourmap
= map
;
1898 bool twocc
= HasBit(e
->info
.misc_flags
, EF_USES_2CC
);
1900 if (map
== PAL_NONE
) map
= twocc
? (PaletteID
)SPR_2CCMAP_BASE
: (PaletteID
)PALETTE_RECOLOUR_START
;
1902 /* Spectator has news shown too, but has invalid company ID - as well as dedicated server */
1903 if (!Company::IsValidID(company
)) return map
;
1905 const Livery
*livery
= GetEngineLivery(engine_type
, company
, parent_engine_type
, v
, _settings_client
.gui
.liveries
);
1907 map
+= livery
->colour1
;
1908 if (twocc
) map
+= livery
->colour2
* 16;
1911 if (v
!= NULL
) const_cast<Vehicle
*>(v
)->colourmap
= map
;
1916 * Get the colour map for an engine. This used for unbuilt engines in the user interface.
1917 * @param engine_type ID of engine
1918 * @param company ID of company
1919 * @return A ready-to-use palette modifier
1921 PaletteID
GetEnginePalette(EngineID engine_type
, CompanyID company
)
1923 return GetEngineColourMap(engine_type
, company
, INVALID_ENGINE
, NULL
);
1927 * Get the colour map for a vehicle.
1928 * @param v Vehicle to get colour map for
1929 * @return A ready-to-use palette modifier
1931 PaletteID
GetVehiclePalette(const Vehicle
*v
)
1933 if (v
->IsGroundVehicle()) {
1934 return GetEngineColourMap(v
->engine_type
, v
->owner
, v
->GetGroundVehicleCache()->first_engine
, v
);
1937 return GetEngineColourMap(v
->engine_type
, v
->owner
, INVALID_ENGINE
, v
);
1941 * Delete all implicit orders which were not reached.
1943 void Vehicle::DeleteUnreachedImplicitOrders()
1945 if (this->IsGroundVehicle()) {
1946 uint16
&gv_flags
= this->GetGroundVehicleFlags();
1947 if (HasBit(gv_flags
, GVF_SUPPRESS_IMPLICIT_ORDERS
)) {
1948 /* Do not delete orders, only skip them */
1949 ClrBit(gv_flags
, GVF_SUPPRESS_IMPLICIT_ORDERS
);
1950 this->cur_implicit_order_index
= this->cur_real_order_index
;
1951 InvalidateVehicleOrder(this, 0);
1956 const Order
*order
= this->GetOrder(this->cur_implicit_order_index
);
1957 while (order
!= NULL
) {
1958 if (this->cur_implicit_order_index
== this->cur_real_order_index
) break;
1960 if (order
->IsType(OT_IMPLICIT
)) {
1961 DeleteOrder(this, this->cur_implicit_order_index
);
1962 /* DeleteOrder does various magic with order_indices, so resync 'order' with 'cur_implicit_order_index' */
1963 order
= this->GetOrder(this->cur_implicit_order_index
);
1965 /* Skip non-implicit orders, e.g. service-orders */
1966 order
= order
->next
;
1967 this->cur_implicit_order_index
++;
1971 if (order
== NULL
) {
1972 order
= this->GetOrder(0);
1973 this->cur_implicit_order_index
= 0;
1979 * Prepare everything to begin the loading when arriving at a station.
1980 * @pre IsTileType(this->tile, MP_STATION) || this->type == VEH_SHIP.
1982 void Vehicle::BeginLoading()
1984 assert(IsTileType(this->tile
, MP_STATION
) || this->type
== VEH_SHIP
);
1986 if (this->current_order
.IsType(OT_GOTO_STATION
) &&
1987 this->current_order
.GetDestination() == this->last_station_visited
) {
1988 this->DeleteUnreachedImplicitOrders();
1990 /* Now both order indices point to the destination station, and we can start loading */
1991 this->current_order
.MakeLoading(true);
1992 UpdateVehicleTimetable(this, true);
1994 /* Furthermore add the Non Stop flag to mark that this station
1995 * is the actual destination of the vehicle, which is (for example)
1996 * necessary to be known for HandleTrainLoading to determine
1997 * whether the train is lost or not; not marking a train lost
1998 * that arrives at random stations is bad. */
1999 this->current_order
.SetNonStopType(ONSF_NO_STOP_AT_ANY_STATION
);
2002 /* We weren't scheduled to stop here. Insert an implicit order
2003 * to show that we are stopping here.
2004 * While only groundvehicles have implicit orders, e.g. aircraft might still enter
2005 * the 'wrong' terminal when skipping orders etc. */
2006 Order
*in_list
= this->GetOrder(this->cur_implicit_order_index
);
2007 if (this->IsGroundVehicle() &&
2008 (in_list
== NULL
|| !in_list
->IsType(OT_IMPLICIT
) ||
2009 in_list
->GetDestination() != this->last_station_visited
)) {
2010 bool suppress_implicit_orders
= HasBit(this->GetGroundVehicleFlags(), GVF_SUPPRESS_IMPLICIT_ORDERS
);
2011 /* Do not create consecutive duplicates of implicit orders */
2012 Order
*prev_order
= this->cur_implicit_order_index
> 0 ? this->GetOrder(this->cur_implicit_order_index
- 1) : (this->GetNumOrders() > 1 ? this->GetLastOrder() : NULL
);
2013 if (prev_order
== NULL
||
2014 (!prev_order
->IsType(OT_IMPLICIT
) && !prev_order
->IsType(OT_GOTO_STATION
)) ||
2015 prev_order
->GetDestination() != this->last_station_visited
) {
2017 /* Prefer deleting implicit orders instead of inserting new ones,
2018 * so test whether the right order follows later. In case of only
2019 * implicit orders treat the last order in the list like an
2020 * explicit one, except if the overall number of orders surpasses
2021 * IMPLICIT_ORDER_ONLY_CAP. */
2022 int target_index
= this->cur_implicit_order_index
;
2024 while (target_index
!= this->cur_real_order_index
|| this->GetNumManualOrders() == 0) {
2025 const Order
*order
= this->GetOrder(target_index
);
2026 if (order
== NULL
) break; // No orders.
2027 if (order
->IsType(OT_IMPLICIT
) && order
->GetDestination() == this->last_station_visited
) {
2032 if (target_index
>= this->orders
.list
->GetNumOrders()) {
2033 if (this->GetNumManualOrders() == 0 &&
2034 this->GetNumOrders() < IMPLICIT_ORDER_ONLY_CAP
) {
2039 if (target_index
== this->cur_implicit_order_index
) break; // Avoid infinite loop.
2043 if (suppress_implicit_orders
) {
2044 /* Skip to the found order */
2045 this->cur_implicit_order_index
= target_index
;
2046 InvalidateVehicleOrder(this, 0);
2048 /* Delete all implicit orders up to the station we just reached */
2049 const Order
*order
= this->GetOrder(this->cur_implicit_order_index
);
2050 while (!order
->IsType(OT_IMPLICIT
) || order
->GetDestination() != this->last_station_visited
) {
2051 if (order
->IsType(OT_IMPLICIT
)) {
2052 DeleteOrder(this, this->cur_implicit_order_index
);
2053 /* DeleteOrder does various magic with order_indices, so resync 'order' with 'cur_implicit_order_index' */
2054 order
= this->GetOrder(this->cur_implicit_order_index
);
2056 /* Skip non-implicit orders, e.g. service-orders */
2057 order
= order
->next
;
2058 this->cur_implicit_order_index
++;
2062 if (order
== NULL
) {
2063 order
= this->GetOrder(0);
2064 this->cur_implicit_order_index
= 0;
2066 assert(order
!= NULL
);
2069 } else if (!suppress_implicit_orders
&&
2070 ((this->orders
.list
== NULL
? OrderList::CanAllocateItem() : this->orders
.list
->GetNumOrders() < MAX_VEH_ORDER_ID
)) &&
2071 Order::CanAllocateItem()) {
2072 /* Insert new implicit order */
2073 Order
*implicit_order
= new Order();
2074 implicit_order
->MakeImplicit(this->last_station_visited
);
2075 InsertOrder(this, implicit_order
, this->cur_implicit_order_index
);
2076 if (this->cur_implicit_order_index
> 0) --this->cur_implicit_order_index
;
2078 /* InsertOrder disabled creation of implicit orders for all vehicles with the same implicit order.
2079 * Reenable it for this vehicle */
2080 uint16
&gv_flags
= this->GetGroundVehicleFlags();
2081 ClrBit(gv_flags
, GVF_SUPPRESS_IMPLICIT_ORDERS
);
2085 this->current_order
.MakeLoading(false);
2088 if (this->last_loading_station
!= INVALID_STATION
&&
2089 this->last_loading_station
!= this->last_station_visited
&&
2090 ((this->current_order
.GetLoadType() & OLFB_NO_LOAD
) == 0 ||
2091 (this->current_order
.GetUnloadType() & OUFB_NO_UNLOAD
) == 0)) {
2092 IncreaseStats(Station::Get(this->last_loading_station
), this, this->last_station_visited
);
2095 PrepareUnload(this);
2097 SetWindowDirty(GetWindowClassForVehicleType(this->type
), this->owner
);
2098 SetWindowWidgetDirty(WC_VEHICLE_VIEW
, this->index
, WID_VV_START_STOP
);
2099 SetWindowDirty(WC_VEHICLE_DETAILS
, this->index
);
2100 SetWindowDirty(WC_STATION_VIEW
, this->last_station_visited
);
2102 Station::Get(this->last_station_visited
)->MarkTilesDirty(true);
2103 this->cur_speed
= 0;
2108 * Return all reserved cargo packets to the station and reset all packets
2109 * staged for transfer.
2110 * @param st the station where the reserved packets should go.
2112 void Vehicle::CancelReservation(StationID next
, Station
*st
)
2114 for (Vehicle
*v
= this; v
!= NULL
; v
= v
->next
) {
2115 VehicleCargoList
&cargo
= v
->cargo
;
2116 if (cargo
.ActionCount(VehicleCargoList::MTA_LOAD
) > 0) {
2117 DEBUG(misc
, 1, "cancelling cargo reservation");
2118 cargo
.Return(UINT_MAX
, &st
->goods
[v
->cargo_type
].cargo
, next
);
2119 cargo
.SetTransferLoadPlace(st
->xy
);
2126 * Perform all actions when leaving a station.
2127 * @pre this->current_order.IsType(OT_LOADING)
2129 void Vehicle::LeaveStation()
2131 assert(this->current_order
.IsType(OT_LOADING
));
2133 delete this->cargo_payment
;
2134 assert(this->cargo_payment
== NULL
); // cleared by ~CargoPayment
2136 /* Only update the timetable if the vehicle was supposed to stop here. */
2137 if (this->current_order
.GetNonStopType() != ONSF_STOP_EVERYWHERE
) UpdateVehicleTimetable(this, false);
2139 if ((this->current_order
.GetLoadType() & OLFB_NO_LOAD
) == 0 ||
2140 (this->current_order
.GetUnloadType() & OUFB_NO_UNLOAD
) == 0) {
2141 if (this->current_order
.CanLeaveWithCargo(this->last_loading_station
!= INVALID_STATION
)) {
2142 /* Refresh next hop stats to make sure we've done that at least once
2143 * during the stop and that refit_cap == cargo_cap for each vehicle in
2145 this->ResetRefitCaps();
2146 LinkRefresher::Run(this);
2148 /* if the vehicle could load here or could stop with cargo loaded set the last loading station */
2149 this->last_loading_station
= this->last_station_visited
;
2151 /* if the vehicle couldn't load and had to unload or transfer everything
2152 * set the last loading station to invalid as it will leave empty. */
2153 this->last_loading_station
= INVALID_STATION
;
2157 this->current_order
.MakeLeaveStation();
2158 Station
*st
= Station::Get(this->last_station_visited
);
2159 this->CancelReservation(INVALID_STATION
, st
);
2160 st
->loading_vehicles
.remove(this);
2162 HideFillingPercent(&this->fill_percent_te_id
);
2164 if (this->type
== VEH_TRAIN
&& !(this->vehstatus
& VS_CRASHED
)) {
2165 /* Trigger station animation (trains only) */
2166 if (IsTileType(this->tile
, MP_STATION
)) {
2167 TriggerStationRandomisation(st
, this->tile
, SRT_TRAIN_DEPARTS
);
2168 TriggerStationAnimation(st
, this->tile
, SAT_TRAIN_DEPARTS
);
2171 SetBit(Train::From(this)->flags
, VRF_LEAVING_STATION
);
2178 * Reset all refit_cap in the consist to cargo_cap.
2180 void Vehicle::ResetRefitCaps()
2182 for (Vehicle
*v
= this; v
!= NULL
; v
= v
->Next()) v
->refit_cap
= v
->cargo_cap
;
2186 * Handle the loading of the vehicle; when not it skips through dummy
2187 * orders and does nothing in all other cases.
2188 * @param mode is the non-first call for this vehicle in this tick?
2190 void Vehicle::HandleLoading(bool mode
)
2192 switch (this->current_order
.GetType()) {
2194 uint wait_time
= max(this->current_order
.GetTimetabledWait() - this->lateness_counter
, 0);
2196 /* Not the first call for this tick, or still loading */
2197 if (mode
|| !HasBit(this->vehicle_flags
, VF_LOADING_FINISHED
) || this->current_order_time
< wait_time
) return;
2199 this->PlayLeaveStationSound();
2201 this->LeaveStation();
2203 /* Only advance to next order if we just loaded at the current one */
2204 const Order
*order
= this->GetOrder(this->cur_implicit_order_index
);
2205 if (order
== NULL
||
2206 (!order
->IsType(OT_IMPLICIT
) && !order
->IsType(OT_GOTO_STATION
)) ||
2207 order
->GetDestination() != this->last_station_visited
) {
2213 case OT_DUMMY
: break;
2218 this->IncrementImplicitOrderIndex();
2222 * Get a map of cargoes and free capacities in the consist.
2223 * @param capacities Map to be filled with cargoes and capacities.
2225 void Vehicle::GetConsistFreeCapacities(SmallMap
<CargoID
, uint
> &capacities
) const
2227 for (const Vehicle
*v
= this; v
!= NULL
; v
= v
->Next()) {
2228 if (v
->cargo_cap
== 0) continue;
2229 SmallPair
<CargoID
, uint
> *pair
= capacities
.Find(v
->cargo_type
);
2230 if (pair
== capacities
.End()) {
2231 pair
= capacities
.Append();
2232 pair
->first
= v
->cargo_type
;
2233 pair
->second
= v
->cargo_cap
- v
->cargo
.StoredCount();
2235 pair
->second
+= v
->cargo_cap
- v
->cargo
.StoredCount();
2240 uint
Vehicle::GetConsistTotalCapacity() const
2243 for (const Vehicle
*v
= this; v
!= NULL
; v
= v
->Next()) {
2244 result
+= v
->cargo_cap
;
2250 * Send this vehicle to the depot using the given command(s).
2251 * @param flags the command flags (like execute and such).
2252 * @param command the command to execute.
2253 * @return the cost of the depot action.
2255 CommandCost
Vehicle::SendToDepot(DoCommandFlag flags
, DepotCommand command
)
2257 CommandCost ret
= CheckOwnership(this->owner
);
2258 if (ret
.Failed()) return ret
;
2260 if (this->vehstatus
& VS_CRASHED
) return CMD_ERROR
;
2261 if (this->IsStoppedInDepot()) return CMD_ERROR
;
2263 if (this->current_order
.IsType(OT_GOTO_DEPOT
)) {
2264 bool halt_in_depot
= (this->current_order
.GetDepotActionType() & ODATFB_HALT
) != 0;
2265 if (!!(command
& DEPOT_SERVICE
) == halt_in_depot
) {
2266 /* We called with a different DEPOT_SERVICE setting.
2267 * Now we change the setting to apply the new one and let the vehicle head for the same depot.
2268 * Note: the if is (true for requesting service == true for ordered to stop in depot) */
2269 if (flags
& DC_EXEC
) {
2270 this->current_order
.SetDepotOrderType(ODTF_MANUAL
);
2271 this->current_order
.SetDepotActionType(halt_in_depot
? ODATF_SERVICE_ONLY
: ODATFB_HALT
);
2272 SetWindowWidgetDirty(WC_VEHICLE_VIEW
, this->index
, WID_VV_START_STOP
);
2274 return CommandCost();
2277 if (command
& DEPOT_DONT_CANCEL
) return CMD_ERROR
; // Requested no cancelation of depot orders
2278 if (flags
& DC_EXEC
) {
2279 /* If the orders to 'goto depot' are in the orders list (forced servicing),
2280 * then skip to the next order; effectively cancelling this forced service */
2281 if (this->current_order
.GetDepotOrderType() & ODTFB_PART_OF_ORDERS
) this->IncrementRealOrderIndex();
2283 if (this->IsGroundVehicle()) {
2284 uint16
&gv_flags
= this->GetGroundVehicleFlags();
2285 SetBit(gv_flags
, GVF_SUPPRESS_IMPLICIT_ORDERS
);
2288 this->current_order
.MakeDummy();
2289 SetWindowWidgetDirty(WC_VEHICLE_VIEW
, this->index
, WID_VV_START_STOP
);
2291 return CommandCost();
2295 DestinationID destination
;
2297 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
};
2298 if (!this->FindClosestDepot(&location
, &destination
, &reverse
)) return_cmd_error(no_depot
[this->type
]);
2300 if (flags
& DC_EXEC
) {
2301 if (this->current_order
.IsType(OT_LOADING
)) this->LeaveStation();
2303 if (this->IsGroundVehicle() && this->GetNumManualOrders() > 0) {
2304 uint16
&gv_flags
= this->GetGroundVehicleFlags();
2305 SetBit(gv_flags
, GVF_SUPPRESS_IMPLICIT_ORDERS
);
2308 this->dest_tile
= location
;
2309 this->current_order
.MakeGoToDepot(destination
, ODTF_MANUAL
);
2310 if (!(command
& DEPOT_SERVICE
)) this->current_order
.SetDepotActionType(ODATFB_HALT
);
2311 SetWindowWidgetDirty(WC_VEHICLE_VIEW
, this->index
, WID_VV_START_STOP
);
2313 /* If there is no depot in front, reverse automatically (trains only) */
2314 if (this->type
== VEH_TRAIN
&& reverse
) DoCommand(this->tile
, this->index
, 0, DC_EXEC
, CMD_REVERSE_TRAIN_DIRECTION
);
2316 if (this->type
== VEH_AIRCRAFT
) {
2317 Aircraft
*a
= Aircraft::From(this);
2318 if (a
->state
== FLYING
&& a
->targetairport
!= destination
) {
2319 /* The aircraft is now heading for a different hangar than the next in the orders */
2320 extern void AircraftNextAirportPos_and_Order(Aircraft
*a
);
2321 AircraftNextAirportPos_and_Order(a
);
2326 return CommandCost();
2331 * Update the cached visual effect.
2332 * @param allow_power_change true if the wagon-is-powered-state may change.
2334 void Vehicle::UpdateVisualEffect(bool allow_power_change
)
2336 bool powered_before
= HasBit(this->vcache
.cached_vis_effect
, VE_DISABLE_WAGON_POWER
);
2337 const Engine
*e
= this->GetEngine();
2339 /* Evaluate properties */
2342 case VEH_TRAIN
: visual_effect
= e
->u
.rail
.visual_effect
; break;
2343 case VEH_ROAD
: visual_effect
= e
->u
.road
.visual_effect
; break;
2344 case VEH_SHIP
: visual_effect
= e
->u
.ship
.visual_effect
; break;
2345 default: visual_effect
= 1 << VE_DISABLE_EFFECT
; break;
2348 /* Check powered wagon / visual effect callback */
2349 if (HasBit(e
->info
.callback_mask
, CBM_VEHICLE_VISUAL_EFFECT
)) {
2350 uint16 callback
= GetVehicleCallback(CBID_VEHICLE_VISUAL_EFFECT
, 0, 0, this->engine_type
, this);
2352 if (callback
!= CALLBACK_FAILED
) {
2353 if (callback
>= 0x100 && e
->GetGRF()->grf_version
>= 8) ErrorUnknownCallbackResult(e
->GetGRFID(), CBID_VEHICLE_VISUAL_EFFECT
, callback
);
2355 callback
= GB(callback
, 0, 8);
2356 /* Avoid accidentally setting 'visual_effect' to the default value
2357 * Since bit 6 (disable effects) is set anyways, we can safely erase some bits. */
2358 if (callback
== VE_DEFAULT
) {
2359 assert(HasBit(callback
, VE_DISABLE_EFFECT
));
2360 SB(callback
, VE_TYPE_START
, VE_TYPE_COUNT
, 0);
2362 visual_effect
= callback
;
2366 /* Apply default values */
2367 if (visual_effect
== VE_DEFAULT
||
2368 (!HasBit(visual_effect
, VE_DISABLE_EFFECT
) && GB(visual_effect
, VE_TYPE_START
, VE_TYPE_COUNT
) == VE_TYPE_DEFAULT
)) {
2369 /* Only train engines have default effects.
2370 * Note: This is independent of whether the engine is a front engine or articulated part or whatever. */
2371 if (e
->type
!= VEH_TRAIN
|| e
->u
.rail
.railveh_type
== RAILVEH_WAGON
|| !IsInsideMM(e
->u
.rail
.engclass
, EC_STEAM
, EC_MONORAIL
)) {
2372 if (visual_effect
== VE_DEFAULT
) {
2373 visual_effect
= 1 << VE_DISABLE_EFFECT
;
2375 SetBit(visual_effect
, VE_DISABLE_EFFECT
);
2378 if (visual_effect
== VE_DEFAULT
) {
2379 /* Also set the offset */
2380 visual_effect
= (VE_OFFSET_CENTRE
- (e
->u
.rail
.engclass
== EC_STEAM
? 4 : 0)) << VE_OFFSET_START
;
2382 SB(visual_effect
, VE_TYPE_START
, VE_TYPE_COUNT
, e
->u
.rail
.engclass
- EC_STEAM
+ VE_TYPE_STEAM
);
2386 this->vcache
.cached_vis_effect
= visual_effect
;
2388 if (!allow_power_change
&& powered_before
!= HasBit(this->vcache
.cached_vis_effect
, VE_DISABLE_WAGON_POWER
)) {
2389 ToggleBit(this->vcache
.cached_vis_effect
, VE_DISABLE_WAGON_POWER
);
2390 ShowNewGrfVehicleError(this->engine_type
, STR_NEWGRF_BROKEN
, STR_NEWGRF_BROKEN_POWERED_WAGON
, GBUG_VEH_POWERED_WAGON
, false);
2394 static const int8 _vehicle_smoke_pos
[8] = {
2395 1, 1, 1, 0, -1, -1, -1, 0
2399 * Call CBID_VEHICLE_SPAWN_VISUAL_EFFECT and spawn requested effects.
2400 * @param v Vehicle to create effects for.
2402 static void SpawnAdvancedVisualEffect(const Vehicle
*v
)
2404 uint16 callback
= GetVehicleCallback(CBID_VEHICLE_SPAWN_VISUAL_EFFECT
, 0, Random(), v
->engine_type
, v
);
2405 if (callback
== CALLBACK_FAILED
) return;
2407 uint count
= GB(callback
, 0, 2);
2408 bool auto_center
= HasBit(callback
, 13);
2409 bool auto_rotate
= !HasBit(callback
, 14);
2413 /* For road vehicles: Compute offset from vehicle position to vehicle center */
2414 if (v
->type
== VEH_ROAD
) l_center
= -(int)(VEHICLE_LENGTH
- RoadVehicle::From(v
)->gcache
.cached_veh_length
) / 2;
2416 /* For trains: Compute offset from vehicle position to sprite position */
2417 if (v
->type
== VEH_TRAIN
) l_center
= (VEHICLE_LENGTH
- Train::From(v
)->gcache
.cached_veh_length
) / 2;
2420 Direction l_dir
= v
->direction
;
2421 if (v
->type
== VEH_TRAIN
&& HasBit(Train::From(v
)->flags
, VRF_REVERSE_DIRECTION
)) l_dir
= ReverseDir(l_dir
);
2422 Direction t_dir
= ChangeDir(l_dir
, DIRDIFF_90RIGHT
);
2424 int8 x_center
= _vehicle_smoke_pos
[l_dir
] * l_center
;
2425 int8 y_center
= _vehicle_smoke_pos
[t_dir
] * l_center
;
2427 for (uint i
= 0; i
< count
; i
++) {
2428 uint32 reg
= GetRegister(0x100 + i
);
2429 uint type
= GB(reg
, 0, 8);
2430 int8 x
= GB(reg
, 8, 8);
2431 int8 y
= GB(reg
, 16, 8);
2432 int8 z
= GB(reg
, 24, 8);
2437 x
= _vehicle_smoke_pos
[l_dir
] * l
+ _vehicle_smoke_pos
[t_dir
] * t
;
2438 y
= _vehicle_smoke_pos
[t_dir
] * l
- _vehicle_smoke_pos
[l_dir
] * t
;
2443 case 0xF1: CreateEffectVehicleRel(v
, x_center
+ x
, y_center
+ y
, z
, EV_STEAM_SMOKE
); break;
2444 case 0xF2: CreateEffectVehicleRel(v
, x_center
+ x
, y_center
+ y
, z
, EV_DIESEL_SMOKE
); break;
2445 case 0xF3: CreateEffectVehicleRel(v
, x_center
+ x
, y_center
+ y
, z
, EV_ELECTRIC_SPARK
); break;
2446 case 0xFA: CreateEffectVehicleRel(v
, x_center
+ x
, y_center
+ y
, z
, EV_BREAKDOWN_SMOKE_AIRCRAFT
); break;
2454 * Draw visual effects (smoke and/or sparks) for a vehicle chain.
2455 * @pre this->IsPrimaryVehicle()
2457 void Vehicle::ShowVisualEffect() const
2459 assert(this->IsPrimaryVehicle());
2462 /* Do not show any smoke when:
2463 * - vehicle smoke is disabled by the player
2464 * - the vehicle is slowing down or stopped (by the player)
2465 * - the vehicle is moving very slowly
2467 if (_settings_game
.vehicle
.smoke_amount
== 0 ||
2468 this->vehstatus
& (VS_TRAIN_SLOWING
| VS_STOPPED
) ||
2469 this->cur_speed
< 2) {
2473 /* Use the speed as limited by underground and orders. */
2474 uint max_speed
= this->GetCurrentMaxSpeed();
2476 if (this->type
== VEH_TRAIN
) {
2477 const Train
*t
= Train::From(this);
2478 /* For trains, do not show any smoke when:
2479 * - the train is reversing
2480 * - is entering a station with an order to stop there and its speed is equal to maximum station entering speed
2482 if (HasBit(t
->flags
, VRF_REVERSING
) ||
2483 (IsRailStationTile(t
->tile
) && t
->IsFrontEngine() && t
->current_order
.ShouldStopAtStation(t
, GetStationIndex(t
->tile
)) &&
2484 t
->cur_speed
>= max_speed
)) {
2489 const Vehicle
*v
= this;
2492 bool advanced
= HasBit(v
->vcache
.cached_vis_effect
, VE_ADVANCED_EFFECT
);
2493 int effect_offset
= GB(v
->vcache
.cached_vis_effect
, VE_OFFSET_START
, VE_OFFSET_COUNT
) - VE_OFFSET_CENTRE
;
2494 VisualEffectSpawnModel effect_model
= VESM_NONE
;
2496 effect_offset
= VE_OFFSET_CENTRE
;
2497 effect_model
= (VisualEffectSpawnModel
)GB(v
->vcache
.cached_vis_effect
, 0, VE_ADVANCED_EFFECT
);
2498 if (effect_model
>= VESM_END
) effect_model
= VESM_NONE
; // unknown spawning model
2500 effect_model
= (VisualEffectSpawnModel
)GB(v
->vcache
.cached_vis_effect
, VE_TYPE_START
, VE_TYPE_COUNT
);
2501 assert(effect_model
!= (VisualEffectSpawnModel
)VE_TYPE_DEFAULT
); // should have been resolved by UpdateVisualEffect
2502 assert_compile((uint
)VESM_STEAM
== (uint
)VE_TYPE_STEAM
);
2503 assert_compile((uint
)VESM_DIESEL
== (uint
)VE_TYPE_DIESEL
);
2504 assert_compile((uint
)VESM_ELECTRIC
== (uint
)VE_TYPE_ELECTRIC
);
2507 /* Show no smoke when:
2508 * - Smoke has been disabled for this vehicle
2509 * - The vehicle is not visible
2510 * - The vehicle is under a bridge
2511 * - The vehicle is on a depot tile
2512 * - The vehicle is on a tunnel tile
2513 * - The vehicle is a train engine that is currently unpowered */
2514 if (effect_model
== VESM_NONE
||
2515 v
->vehstatus
& VS_HIDDEN
||
2516 IsBridgeAbove(v
->tile
) ||
2517 IsDepotTile(v
->tile
) ||
2518 IsTunnelTile(v
->tile
) ||
2519 (v
->type
== VEH_TRAIN
&&
2520 !HasPowerOnRail(Train::From(v
)->railtype
, GetTileRailType(v
->tile
)))) {
2524 EffectVehicleType evt
= EV_END
;
2525 switch (effect_model
) {
2527 /* Steam smoke - amount is gradually falling until vehicle reaches its maximum speed, after that it's normal.
2528 * Details: while vehicle's current speed is gradually increasing, steam plumes' density decreases by one third each
2529 * third of its maximum speed spectrum. Steam emission finally normalises at very close to vehicle's maximum speed.
2531 * - instead of 1, 4 / 2^smoke_amount (max. 2) is used to provide sufficient regulation to steam puffs' amount. */
2532 if (GB(v
->tick_counter
, 0, ((4 >> _settings_game
.vehicle
.smoke_amount
) + ((this->cur_speed
* 3) / max_speed
))) == 0) {
2533 evt
= EV_STEAM_SMOKE
;
2538 /* Diesel smoke - thicker when vehicle is starting, gradually subsiding till it reaches its maximum speed
2539 * when smoke emission stops.
2540 * Details: Vehicle's (max.) speed spectrum is divided into 32 parts. When max. speed is reached, chance for smoke
2541 * emission erodes by 32 (1/4). For trains, power and weight come in handy too to either increase smoke emission in
2542 * 6 steps (1000HP each) if the power is low or decrease smoke emission in 6 steps (512 tonnes each) if the train
2543 * isn't overweight. Power and weight contributions are expressed in a way that neither extreme power, nor
2544 * extreme weight can ruin the balance (e.g. FreightWagonMultiplier) in the formula. When the vehicle reaches
2545 * maximum speed no diesel_smoke is emitted.
2547 * - up to which speed a diesel vehicle is emitting smoke (with reduced/small setting only until 1/2 of max_speed),
2548 * - in Chance16 - the last value is 512 / 2^smoke_amount (max. smoke when 128 = smoke_amount of 2). */
2549 int power_weight_effect
= 0;
2550 if (v
->type
== VEH_TRAIN
) {
2551 power_weight_effect
= (32 >> (Train::From(this)->gcache
.cached_power
>> 10)) - (32 >> (Train::From(this)->gcache
.cached_weight
>> 9));
2553 if (this->cur_speed
< (max_speed
>> (2 >> _settings_game
.vehicle
.smoke_amount
)) &&
2554 Chance16((64 - ((this->cur_speed
<< 5) / max_speed
) + power_weight_effect
), (512 >> _settings_game
.vehicle
.smoke_amount
))) {
2555 evt
= EV_DIESEL_SMOKE
;
2561 /* Electric train's spark - more often occurs when train is departing (more load)
2562 * Details: Electric locomotives are usually at least twice as powerful as their diesel counterparts, so spark
2563 * emissions are kept simple. Only when starting, creating huge force are sparks more likely to happen, but when
2564 * reaching its max. speed, quarter by quarter of it, chance decreases until the usual 2,22% at train's top speed.
2566 * - in Chance16 the last value is 360 / 2^smoke_amount (max. sparks when 90 = smoke_amount of 2). */
2567 if (GB(v
->tick_counter
, 0, 2) == 0 &&
2568 Chance16((6 - ((this->cur_speed
<< 2) / max_speed
)), (360 >> _settings_game
.vehicle
.smoke_amount
))) {
2569 evt
= EV_ELECTRIC_SPARK
;
2577 if (evt
!= EV_END
&& advanced
) {
2579 SpawnAdvancedVisualEffect(v
);
2580 } else if (evt
!= EV_END
) {
2583 /* The effect offset is relative to a point 4 units behind the vehicle's
2584 * front (which is the center of an 8/8 vehicle). Shorter vehicles need a
2585 * correction factor. */
2586 if (v
->type
== VEH_TRAIN
) effect_offset
+= (VEHICLE_LENGTH
- Train::From(v
)->gcache
.cached_veh_length
) / 2;
2588 int x
= _vehicle_smoke_pos
[v
->direction
] * effect_offset
;
2589 int y
= _vehicle_smoke_pos
[(v
->direction
+ 2) % 8] * effect_offset
;
2591 if (v
->type
== VEH_TRAIN
&& HasBit(Train::From(v
)->flags
, VRF_REVERSE_DIRECTION
)) {
2596 CreateEffectVehicleRel(v
, x
, y
, 10, evt
);
2598 } while ((v
= v
->Next()) != NULL
);
2600 if (sound
) PlayVehicleSound(this, VSE_VISUAL_EFFECT
);
2604 * Set the next vehicle of this vehicle.
2605 * @param next the next vehicle. NULL removes the next vehicle.
2607 void Vehicle::SetNext(Vehicle
*next
)
2609 assert(this != next
);
2611 if (this->next
!= NULL
) {
2612 /* We had an old next vehicle. Update the first and previous pointers */
2613 for (Vehicle
*v
= this->next
; v
!= NULL
; v
= v
->Next()) {
2614 v
->first
= this->next
;
2616 this->next
->previous
= NULL
;
2621 if (this->next
!= NULL
) {
2622 /* A new next vehicle. Update the first and previous pointers */
2623 if (this->next
->previous
!= NULL
) this->next
->previous
->next
= NULL
;
2624 this->next
->previous
= this;
2625 for (Vehicle
*v
= this->next
; v
!= NULL
; v
= v
->Next()) {
2626 v
->first
= this->first
;
2632 * Adds this vehicle to a shared vehicle chain.
2633 * @param shared_chain a vehicle of the chain with shared vehicles.
2634 * @pre !this->IsOrderListShared()
2636 void Vehicle::AddToShared(Vehicle
*shared_chain
)
2638 assert(this->previous_shared
== NULL
&& this->next_shared
== NULL
);
2640 if (shared_chain
->orders
.list
== NULL
) {
2641 assert(shared_chain
->previous_shared
== NULL
);
2642 assert(shared_chain
->next_shared
== NULL
);
2643 this->orders
.list
= shared_chain
->orders
.list
= new OrderList(NULL
, shared_chain
);
2646 this->next_shared
= shared_chain
->next_shared
;
2647 this->previous_shared
= shared_chain
;
2649 shared_chain
->next_shared
= this;
2651 if (this->next_shared
!= NULL
) this->next_shared
->previous_shared
= this;
2653 shared_chain
->orders
.list
->AddVehicle(this);
2657 * Removes the vehicle from the shared order list.
2659 void Vehicle::RemoveFromShared()
2661 /* Remember if we were first and the old window number before RemoveVehicle()
2662 * as this changes first if needed. */
2663 bool were_first
= (this->FirstShared() == this);
2664 VehicleListIdentifier
vli(VL_SHARED_ORDERS
, this->type
, this->owner
, this->FirstShared()->index
);
2666 this->orders
.list
->RemoveVehicle(this);
2669 /* We are not the first shared one, so only relink our previous one. */
2670 this->previous_shared
->next_shared
= this->NextShared();
2673 if (this->next_shared
!= NULL
) this->next_shared
->previous_shared
= this->previous_shared
;
2676 if (this->orders
.list
->GetNumVehicles() == 1) {
2677 /* When there is only one vehicle, remove the shared order list window. */
2678 DeleteWindowById(GetWindowClassForVehicleType(this->type
), vli
.Pack());
2679 InvalidateVehicleOrder(this->FirstShared(), 0);
2680 } else if (were_first
) {
2681 /* If we were the first one, update to the new first one.
2682 * Note: FirstShared() is already the new first */
2683 InvalidateWindowData(GetWindowClassForVehicleType(this->type
), vli
.Pack(), this->FirstShared()->index
| (1U << 31));
2686 this->next_shared
= NULL
;
2687 this->previous_shared
= NULL
;
2690 void VehiclesYearlyLoop()
2693 FOR_ALL_VEHICLES(v
) {
2694 if (v
->IsPrimaryVehicle()) {
2695 /* show warning if vehicle is not generating enough income last 2 years (corresponds to a red icon in the vehicle list) */
2696 Money profit
= v
->GetDisplayProfitThisYear();
2697 if (v
->age
>= 730 && profit
< 0) {
2698 if (_settings_client
.gui
.vehicle_income_warn
&& v
->owner
== _local_company
) {
2699 SetDParam(0, v
->index
);
2700 SetDParam(1, profit
);
2701 AddVehicleAdviceNewsItem(STR_NEWS_VEHICLE_IS_UNPROFITABLE
, v
->index
);
2703 AI::NewEvent(v
->owner
, new ScriptEventVehicleUnprofitable(v
->index
));
2706 v
->profit_last_year
= v
->profit_this_year
;
2707 v
->profit_this_year
= 0;
2708 SetWindowDirty(WC_VEHICLE_DETAILS
, v
->index
);
2711 GroupStatistics::UpdateProfits();
2712 SetWindowClassesDirty(WC_TRAINS_LIST
);
2713 SetWindowClassesDirty(WC_SHIPS_LIST
);
2714 SetWindowClassesDirty(WC_ROADVEH_LIST
);
2715 SetWindowClassesDirty(WC_AIRCRAFT_LIST
);
2720 * Can this station be used by the given engine type?
2721 * @param engine_type the type of vehicles to test
2722 * @param st the station to test for
2723 * @return true if and only if the vehicle of the type can use this station.
2724 * @note For road vehicles the Vehicle is needed to determine whether it can
2725 * use the station. This function will return true for road vehicles
2726 * when at least one of the facilities is available.
2728 bool CanVehicleUseStation(EngineID engine_type
, const Station
*st
)
2730 const Engine
*e
= Engine::GetIfValid(engine_type
);
2735 return (st
->facilities
& FACIL_TRAIN
) != 0;
2738 /* For road vehicles we need the vehicle to know whether it can actually
2739 * use the station, but if it doesn't have facilities for RVs it is
2740 * certainly not possible that the station can be used. */
2741 return (st
->facilities
& (FACIL_BUS_STOP
| FACIL_TRUCK_STOP
)) != 0;
2744 return (st
->facilities
& FACIL_DOCK
) != 0;
2747 return (st
->facilities
& FACIL_AIRPORT
) != 0 &&
2748 (st
->airport
.GetFTA()->flags
& (e
->u
.air
.subtype
& AIR_CTOL
? AirportFTAClass::AIRPLANES
: AirportFTAClass::HELICOPTERS
)) != 0;
2756 * Can this station be used by the given vehicle?
2757 * @param v the vehicle to test
2758 * @param st the station to test for
2759 * @return true if and only if the vehicle can use this station.
2761 bool CanVehicleUseStation(const Vehicle
*v
, const Station
*st
)
2763 if (v
->type
== VEH_ROAD
) return st
->GetPrimaryRoadStop(RoadVehicle::From(v
)) != NULL
;
2765 return CanVehicleUseStation(v
->engine_type
, st
);
2769 * Access the ground vehicle cache of the vehicle.
2770 * @pre The vehicle is a #GroundVehicle.
2771 * @return #GroundVehicleCache of the vehicle.
2773 GroundVehicleCache
*Vehicle::GetGroundVehicleCache()
2775 assert(this->IsGroundVehicle());
2776 if (this->type
== VEH_TRAIN
) {
2777 return &Train::From(this)->gcache
;
2779 return &RoadVehicle::From(this)->gcache
;
2784 * Access the ground vehicle cache of the vehicle.
2785 * @pre The vehicle is a #GroundVehicle.
2786 * @return #GroundVehicleCache of the vehicle.
2788 const GroundVehicleCache
*Vehicle::GetGroundVehicleCache() const
2790 assert(this->IsGroundVehicle());
2791 if (this->type
== VEH_TRAIN
) {
2792 return &Train::From(this)->gcache
;
2794 return &RoadVehicle::From(this)->gcache
;
2799 * Access the ground vehicle flags of the vehicle.
2800 * @pre The vehicle is a #GroundVehicle.
2801 * @return #GroundVehicleFlags of the vehicle.
2803 uint16
&Vehicle::GetGroundVehicleFlags()
2805 assert(this->IsGroundVehicle());
2806 if (this->type
== VEH_TRAIN
) {
2807 return Train::From(this)->gv_flags
;
2809 return RoadVehicle::From(this)->gv_flags
;
2814 * Access the ground vehicle flags of the vehicle.
2815 * @pre The vehicle is a #GroundVehicle.
2816 * @return #GroundVehicleFlags of the vehicle.
2818 const uint16
&Vehicle::GetGroundVehicleFlags() const
2820 assert(this->IsGroundVehicle());
2821 if (this->type
== VEH_TRAIN
) {
2822 return Train::From(this)->gv_flags
;
2824 return RoadVehicle::From(this)->gv_flags
;
2829 * Calculates the set of vehicles that will be affected by a given selection.
2830 * @param set [inout] Set of affected vehicles.
2831 * @param v First vehicle of the selection.
2832 * @param num_vehicles Number of vehicles in the selection (not counting articulated parts).
2833 * @pre \a set must be empty.
2834 * @post \a set will contain the vehicles that will be refitted.
2836 void GetVehicleSet(VehicleSet
&set
, Vehicle
*v
, uint8 num_vehicles
)
2838 if (v
->type
== VEH_TRAIN
) {
2839 Train
*u
= Train::From(v
);
2840 /* Only include whole vehicles, so start with the first articulated part */
2841 u
= u
->GetFirstEnginePart();
2843 /* Include num_vehicles vehicles, not counting articulated parts */
2844 for (; u
!= NULL
&& num_vehicles
> 0; num_vehicles
--) {
2846 /* Include current vehicle in the selection. */
2847 set
.Include(u
->index
);
2849 /* If the vehicle is multiheaded, add the other part too. */
2850 if (u
->IsMultiheaded()) set
.Include(u
->other_multiheaded_part
->index
);
2853 } while (u
!= NULL
&& u
->IsArticulatedPart());