2 * This file is part of OpenTTD.
3 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
4 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
5 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
9 * @file disaster_vehicle.cpp
11 * All disaster/easter egg vehicles are handled here.
12 * The general flow of control for the disaster vehicles is as follows:
14 * <li>Initialize the disaster in a disaster specific way (eg start position,
15 * possible target, etc.) Disaster_XXX_Init() function
16 * <li>Add a subtype to a disaster, which is an index into the function array
17 * that handles the vehicle's ticks.
18 * <li>Run the disaster vehicles each tick until their target has been reached,
19 * this happens in the DisasterTick_XXX() functions. In here, a vehicle's
20 * state is kept by v->state variable. Each achieved sub-target
21 * will increase this value, and the last one will remove the disaster itself
29 #include "disaster_vehicle.h"
31 #include "station_base.h"
32 #include "command_func.h"
33 #include "news_func.h"
35 #include "company_func.h"
36 #include "strings_func.h"
37 #include "viewport_func.h"
38 #include "vehicle_func.h"
39 #include "sound_func.h"
40 #include "effectvehicle_func.h"
44 #include "game/game.hpp"
45 #include "company_base.h"
46 #include "core/random_func.hpp"
47 #include "core/backup_type.hpp"
48 #include "landscape_cmd.h"
49 #include "timer/timer.h"
50 #include "timer/timer_game_economy.h"
52 #include "table/strings.h"
54 #include "safeguards.h"
56 /** Delay counter for considering the next disaster. */
57 uint16_t _disaster_delay
;
59 static void DisasterClearSquare(TileIndex tile
)
61 if (EnsureNoVehicleOnGround(tile
).Failed()) return;
63 switch (GetTileType(tile
)) {
65 if (Company::IsHumanID(GetTileOwner(tile
)) && !IsRailDepot(tile
)) {
66 Backup
<CompanyID
> cur_company(_current_company
, OWNER_WATER
, FILE_LINE
);
67 Command
<CMD_LANDSCAPE_CLEAR
>::Do(DC_EXEC
, tile
);
68 cur_company
.Restore();
70 /* update signals in buffer */
71 UpdateSignalsInBuffer();
76 Backup
<CompanyID
> cur_company(_current_company
, OWNER_NONE
, FILE_LINE
);
77 Command
<CMD_LANDSCAPE_CLEAR
>::Do(DC_EXEC
, tile
);
78 cur_company
.Restore();
92 static const SpriteID _disaster_images_1
[] = {SPR_BLIMP
, SPR_BLIMP
, SPR_BLIMP
, SPR_BLIMP
, SPR_BLIMP
, SPR_BLIMP
, SPR_BLIMP
, SPR_BLIMP
};
93 static const SpriteID _disaster_images_2
[] = {SPR_UFO_SMALL_SCOUT
, SPR_UFO_SMALL_SCOUT
, SPR_UFO_SMALL_SCOUT
, SPR_UFO_SMALL_SCOUT
, SPR_UFO_SMALL_SCOUT
, SPR_UFO_SMALL_SCOUT
, SPR_UFO_SMALL_SCOUT
, SPR_UFO_SMALL_SCOUT
};
94 static const SpriteID _disaster_images_3
[] = {SPR_F_15
, SPR_F_15
, SPR_F_15
, SPR_F_15
, SPR_F_15
, SPR_F_15
, SPR_F_15
, SPR_F_15
};
95 static const SpriteID _disaster_images_4
[] = {SPR_SUB_SMALL_NE
, SPR_SUB_SMALL_NE
, SPR_SUB_SMALL_SE
, SPR_SUB_SMALL_SE
, SPR_SUB_SMALL_SW
, SPR_SUB_SMALL_SW
, SPR_SUB_SMALL_NW
, SPR_SUB_SMALL_NW
};
96 static const SpriteID _disaster_images_5
[] = {SPR_SUB_LARGE_NE
, SPR_SUB_LARGE_NE
, SPR_SUB_LARGE_SE
, SPR_SUB_LARGE_SE
, SPR_SUB_LARGE_SW
, SPR_SUB_LARGE_SW
, SPR_SUB_LARGE_NW
, SPR_SUB_LARGE_NW
};
97 static const SpriteID _disaster_images_6
[] = {SPR_UFO_HARVESTER
, SPR_UFO_HARVESTER
, SPR_UFO_HARVESTER
, SPR_UFO_HARVESTER
, SPR_UFO_HARVESTER
, SPR_UFO_HARVESTER
, SPR_UFO_HARVESTER
, SPR_UFO_HARVESTER
};
98 static const SpriteID _disaster_images_7
[] = {SPR_XCOM_SKYRANGER
, SPR_XCOM_SKYRANGER
, SPR_XCOM_SKYRANGER
, SPR_XCOM_SKYRANGER
, SPR_XCOM_SKYRANGER
, SPR_XCOM_SKYRANGER
, SPR_XCOM_SKYRANGER
, SPR_XCOM_SKYRANGER
};
99 static const SpriteID _disaster_images_8
[] = {SPR_AH_64A
, SPR_AH_64A
, SPR_AH_64A
, SPR_AH_64A
, SPR_AH_64A
, SPR_AH_64A
, SPR_AH_64A
, SPR_AH_64A
};
100 static const SpriteID _disaster_images_9
[] = {SPR_ROTOR_MOVING_1
, SPR_ROTOR_MOVING_1
, SPR_ROTOR_MOVING_1
, SPR_ROTOR_MOVING_1
, SPR_ROTOR_MOVING_1
, SPR_ROTOR_MOVING_1
, SPR_ROTOR_MOVING_1
, SPR_ROTOR_MOVING_1
};
102 static const SpriteID
* const _disaster_images
[] = {
103 _disaster_images_1
, _disaster_images_1
, ///< zeppeliner and zeppeliner shadow
104 _disaster_images_2
, _disaster_images_2
, ///< small ufo and small ufo shadow
105 _disaster_images_3
, _disaster_images_3
, ///< combat aircraft and shadow
106 _disaster_images_8
, _disaster_images_8
, _disaster_images_9
, ///< combat helicopter, shadow and rotor
107 _disaster_images_6
, _disaster_images_6
, ///< big ufo and shadow
108 _disaster_images_7
, _disaster_images_7
, ///< skyranger and shadow
109 _disaster_images_4
, _disaster_images_5
, ///< small and big submarine sprites
112 void DisasterVehicle::UpdateImage()
114 SpriteID img
= this->image_override
;
115 if (img
== 0) img
= _disaster_images
[this->subtype
][this->direction
];
116 this->sprite_cache
.sprite_seq
.Set(img
);
120 * Construct the disaster vehicle.
121 * @param x The X coordinate.
122 * @param y The Y coordinate.
123 * @param direction The direction the vehicle is facing.
124 * @param subtype The sub type of vehicle.
125 * @param big_ufo_destroyer_target The target for the UFO destroyer.
127 DisasterVehicle::DisasterVehicle(int x
, int y
, Direction direction
, DisasterSubType subtype
, VehicleID big_ufo_destroyer_target
) :
128 SpecializedVehicleBase(), big_ufo_destroyer_target(big_ufo_destroyer_target
)
130 this->vehstatus
= VS_UNCLICKABLE
;
140 case ST_BIG_UFO_DESTROYER
:
141 GetAircraftFlightLevelBounds(this, &this->z_pos
, nullptr);
144 case ST_HELICOPTER_ROTORS
:
145 GetAircraftFlightLevelBounds(this, &this->z_pos
, nullptr);
146 this->z_pos
+= ROTOR_Z_OFFSET
;
149 case ST_SMALL_SUBMARINE
:
150 case ST_BIG_SUBMARINE
:
154 case ST_ZEPPELINER_SHADOW
:
155 case ST_SMALL_UFO_SHADOW
:
156 case ST_AIRPLANE_SHADOW
:
157 case ST_HELICOPTER_SHADOW
:
158 case ST_BIG_UFO_SHADOW
:
159 case ST_BIG_UFO_DESTROYER_SHADOW
:
161 this->vehstatus
|= VS_SHADOW
;
165 this->direction
= direction
;
166 this->tile
= TileVirtXY(x
, y
);
167 this->subtype
= subtype
;
168 this->UpdateDeltaXY();
169 this->owner
= OWNER_NONE
;
170 this->image_override
= 0;
174 this->UpdatePositionAndViewport();
178 * Update the position of the vehicle.
179 * @param x The new X-coordinate.
180 * @param y The new Y-coordinate.
181 * @param z The new Z-coordinate.
183 void DisasterVehicle::UpdatePosition(int x
, int y
, int z
)
188 this->tile
= TileVirtXY(x
, y
);
191 this->UpdatePositionAndViewport();
193 DisasterVehicle
*u
= this->Next();
195 int safe_x
= Clamp(x
, 0, Map::MaxX() * TILE_SIZE
);
196 int safe_y
= Clamp(y
- 1, 0, Map::MaxY() * TILE_SIZE
);
199 u
->y_pos
= y
- 1 - (std::max(z
- GetSlopePixelZ(safe_x
, safe_y
), 0) >> 3);
200 safe_y
= Clamp(u
->y_pos
, 0, Map::MaxY() * TILE_SIZE
);
201 u
->z_pos
= GetSlopePixelZ(safe_x
, safe_y
);
202 u
->direction
= this->direction
;
205 u
->UpdatePositionAndViewport();
207 if ((u
= u
->Next()) != nullptr) {
210 u
->z_pos
= z
+ ROTOR_Z_OFFSET
;
211 u
->UpdatePositionAndViewport();
217 * Zeppeliner handling, v->state states:
218 * 0: Zeppeliner initialization has found a small airport, go there and crash
219 * 1: Create crash and animate falling down for extra dramatic effect
220 * 2: Create more smoke and leave debris on ground
221 * 2: Clear the runway after some time and remove crashed zeppeliner
222 * If not airport was found, only state 0 is reached until zeppeliner leaves map
224 static bool DisasterTick_Zeppeliner(DisasterVehicle
*v
)
229 if (HasBit(v
->tick_counter
, 0)) return true;
231 GetNewVehiclePosResult gp
= GetNewVehiclePos(v
);
233 v
->UpdatePosition(gp
.x
, gp
.y
, GetAircraftFlightLevel(v
));
236 if (++v
->age
== 38) {
241 if (GB(v
->tick_counter
, 0, 3) == 0) CreateEffectVehicleRel(v
, 0, -17, 2, EV_CRASH_SMOKE
);
243 } else if (v
->state
== 0) {
244 if (IsValidTile(v
->tile
) && IsAirportTile(v
->tile
)) {
248 SetDParam(0, GetStationIndex(v
->tile
));
249 AddTileNewsItem(STR_NEWS_DISASTER_ZEPPELIN
, NT_ACCIDENT
, v
->tile
);
250 AI::NewEvent(GetTileOwner(v
->tile
), new ScriptEventDisasterZeppelinerCrashed(GetStationIndex(v
->tile
)));
254 if (v
->y_pos
>= (int)((Map::SizeY() + 9) * TILE_SIZE
- 1)) {
263 if (++v
->age
<= 13320) return true;
265 if (IsValidTile(v
->tile
) && IsAirportTile(v
->tile
)) {
266 Station
*st
= Station::GetByTile(v
->tile
);
267 CLRBITS(st
->airport
.flags
, RUNWAY_IN_block
);
268 AI::NewEvent(GetTileOwner(v
->tile
), new ScriptEventDisasterZeppelinerCleared(st
->index
));
271 v
->UpdatePosition(v
->x_pos
, v
->y_pos
, GetAircraftFlightLevel(v
));
278 int z
= GetSlopePixelZ(x
, y
);
279 if (z
< v
->z_pos
) z
= v
->z_pos
- 1;
280 v
->UpdatePosition(x
, y
, z
);
283 CreateEffectVehicleRel(v
, 0, 7, 8, EV_EXPLOSION_LARGE
);
284 if (_settings_client
.sound
.disaster
) SndPlayVehicleFx(SND_12_EXPLOSION
, v
);
285 v
->image_override
= SPR_BLIMP_CRASHING
;
286 } else if (v
->age
== 70) {
287 v
->image_override
= SPR_BLIMP_CRASHED
;
288 } else if (v
->age
<= 300) {
289 if (GB(v
->tick_counter
, 0, 3) == 0) {
290 uint32_t r
= Random();
292 CreateEffectVehicleRel(v
,
298 } else if (v
->age
== 350) {
303 if (IsValidTile(v
->tile
) && IsAirportTile(v
->tile
)) {
304 SETBITS(Station::GetByTile(v
->tile
)->airport
.flags
, RUNWAY_IN_block
);
311 * (Small) Ufo handling, v->state states:
312 * 0: Fly around to the middle of the map, then randomly, after a while target a road vehicle
313 * 1: Home in on a road vehicle and crash it >:)
314 * If not road vehicle was found, only state 0 is used and Ufo disappears after a while
316 static bool DisasterTick_Ufo(DisasterVehicle
*v
)
318 v
->image_override
= (HasBit(++v
->tick_counter
, 3)) ? SPR_UFO_SMALL_SCOUT_DARKER
: SPR_UFO_SMALL_SCOUT
;
321 /* Fly around randomly */
322 int x
= TileX(v
->dest_tile
) * TILE_SIZE
;
323 int y
= TileY(v
->dest_tile
) * TILE_SIZE
;
324 if (Delta(x
, v
->x_pos
) + Delta(y
, v
->y_pos
) >= (int)TILE_SIZE
) {
325 v
->direction
= GetDirectionTowards(v
, x
, y
);
326 GetNewVehiclePosResult gp
= GetNewVehiclePos(v
);
327 v
->UpdatePosition(gp
.x
, gp
.y
, GetAircraftFlightLevel(v
));
331 v
->dest_tile
= RandomTile();
336 uint n
= 0; // Total number of targetable road vehicles.
337 for (const Company
*c
: Company::Iterate()) {
338 n
+= c
->group_all
[VEH_ROAD
].num_vehicle
;
342 /* If there are no targetable road vehicles, destroy the UFO. */
347 n
= RandomRange(n
); // Choose one of them.
348 for (RoadVehicle
*u
: RoadVehicle::Iterate()) {
349 /* Find (n+1)-th road vehicle. */
350 if (u
->IsFrontEngine() && (n
-- == 0)) {
351 if (u
->crashed_ctr
!= 0 || u
->disaster_vehicle
!= INVALID_VEHICLE
) {
352 /* Targetted vehicle is crashed or already a target, destroy the UFO. */
357 v
->dest_tile
= u
->index
;
359 u
->disaster_vehicle
= v
->index
;
366 /* Target a vehicle */
367 RoadVehicle
*u
= RoadVehicle::Get(v
->dest_tile
.base());
368 assert(u
!= nullptr && u
->type
== VEH_ROAD
&& u
->IsFrontEngine());
370 uint dist
= Delta(v
->x_pos
, u
->x_pos
) + Delta(v
->y_pos
, u
->y_pos
);
372 if (dist
< TILE_SIZE
&& !(u
->vehstatus
& VS_HIDDEN
) && u
->breakdown_ctr
== 0) {
373 u
->breakdown_ctr
= 3;
374 u
->breakdown_delay
= 140;
377 v
->direction
= GetDirectionTowards(v
, u
->x_pos
, u
->y_pos
);
378 GetNewVehiclePosResult gp
= GetNewVehiclePos(v
);
381 if (dist
<= TILE_SIZE
&& z
> u
->z_pos
) z
--;
382 v
->UpdatePosition(gp
.x
, gp
.y
, z
);
384 if (z
<= u
->z_pos
&& (u
->vehstatus
& VS_HIDDEN
) == 0) {
386 if (u
->crashed_ctr
== 0) {
388 u
->disaster_vehicle
= INVALID_VEHICLE
;
390 AddTileNewsItem(STR_NEWS_DISASTER_SMALL_UFO
, NT_ACCIDENT
, u
->tile
);
392 AI::NewEvent(u
->owner
, new ScriptEventVehicleCrashed(u
->index
, u
->tile
, ScriptEventVehicleCrashed::CRASH_RV_UFO
));
393 Game::NewEvent(new ScriptEventVehicleCrashed(u
->index
, u
->tile
, ScriptEventVehicleCrashed::CRASH_RV_UFO
));
399 CreateEffectVehicleRel(v
, 0, 7, 8, EV_EXPLOSION_LARGE
);
400 if (_settings_client
.sound
.disaster
) SndPlayVehicleFx(SND_12_EXPLOSION
, v
);
409 static void DestructIndustry(Industry
*i
)
411 for (TileIndex tile
= 0; tile
!= Map::Size(); tile
++) {
412 if (i
->TileBelongsToIndustry(tile
)) {
413 ResetIndustryConstructionStage(tile
);
414 MarkTileDirtyByTile(tile
);
420 * Aircraft handling, v->state states:
421 * 0: Fly towards the targeted industry
422 * 1: If within 15 tiles, fire away rockets and destroy industry
423 * 2: Industry explosions
424 * 3: Fly out of the map
425 * If the industry was removed in the meantime just fly to the end of the map.
426 * @param v The disaster vehicle.
427 * @param image_override The image at the time the aircraft is firing.
428 * @param leave_at_top True iff the vehicle leaves the map at the north side.
429 * @param news_message The string that's used as news message.
430 * @param industry_flag Only attack industries that have this flag set.
432 static bool DisasterTick_Aircraft(DisasterVehicle
*v
, uint16_t image_override
, bool leave_at_top
, StringID news_message
, IndustryBehaviour industry_flag
)
435 v
->image_override
= (v
->state
== 1 && HasBit(v
->tick_counter
, 2)) ? image_override
: 0;
437 GetNewVehiclePosResult gp
= GetNewVehiclePos(v
);
438 v
->UpdatePosition(gp
.x
, gp
.y
, GetAircraftFlightLevel(v
));
440 if ((leave_at_top
&& gp
.x
< (-10 * (int)TILE_SIZE
)) || (!leave_at_top
&& gp
.x
> (int)(Map::SizeX() * TILE_SIZE
+ 9 * TILE_SIZE
) - 1)) {
446 if (GB(v
->tick_counter
, 0, 2) == 0) {
447 Industry
*i
= Industry::Get(v
->dest_tile
.base()); // Industry destructor calls ReleaseDisastersTargetingIndustry, so this is valid
448 int x
= TileX(i
->location
.tile
) * TILE_SIZE
;
449 int y
= TileY(i
->location
.tile
) * TILE_SIZE
;
450 uint32_t r
= Random();
452 CreateEffectVehicleAbove(
458 if (++v
->age
>= 55) v
->state
= 3;
460 } else if (v
->state
== 1) {
461 if (++v
->age
== 112) {
465 Industry
*i
= Industry::Get(v
->dest_tile
.base()); // Industry destructor calls ReleaseDisastersTargetingIndustry, so this is valid
468 SetDParam(0, i
->town
->index
);
469 AddIndustryNewsItem(news_message
, NT_ACCIDENT
, i
->index
);
470 if (_settings_client
.sound
.disaster
) SndPlayTileFx(SND_12_EXPLOSION
, i
->location
.tile
);
472 } else if (v
->state
== 0) {
473 int x
= v
->x_pos
+ ((leave_at_top
? -15 : 15) * TILE_SIZE
);
476 if ((uint
)x
> Map::MaxX() * TILE_SIZE
- 1) return true;
478 TileIndex tile
= TileVirtXY(x
, y
);
479 if (!IsTileType(tile
, MP_INDUSTRY
)) return true;
481 IndustryID ind
= GetIndustryIndex(tile
);
484 if (GetIndustrySpec(Industry::Get(ind
)->type
)->behaviour
& industry_flag
) {
493 /** Airplane handling. */
494 static bool DisasterTick_Airplane(DisasterVehicle
*v
)
496 return DisasterTick_Aircraft(v
, SPR_F_15_FIRING
, true, STR_NEWS_DISASTER_AIRPLANE_OIL_REFINERY
, INDUSTRYBEH_AIRPLANE_ATTACKS
);
499 /** Helicopter handling. */
500 static bool DisasterTick_Helicopter(DisasterVehicle
*v
)
502 return DisasterTick_Aircraft(v
, SPR_AH_64A_FIRING
, false, STR_NEWS_DISASTER_HELICOPTER_FACTORY
, INDUSTRYBEH_CHOPPER_ATTACKS
);
505 /** Helicopter rotor blades; keep these spinning */
506 static bool DisasterTick_Helicopter_Rotors(DisasterVehicle
*v
)
509 if (HasBit(v
->tick_counter
, 0)) return true;
511 SpriteID
&cur_image
= v
->sprite_cache
.sprite_seq
.seq
[0].sprite
;
512 if (++cur_image
> SPR_ROTOR_MOVING_3
) cur_image
= SPR_ROTOR_MOVING_1
;
514 v
->UpdatePositionAndViewport();
520 * (Big) Ufo handling, v->state states:
521 * 0: Fly around to the middle of the map, then randomly for a while and home in on a piece of rail
522 * 1: Land there and breakdown all trains in a radius of 12 tiles; and now we wait...
523 * because as soon as the Ufo lands, a fighter jet, a Skyranger, is called to clear up the mess
525 static bool DisasterTick_Big_Ufo(DisasterVehicle
*v
)
530 int x
= TileX(v
->dest_tile
) * TILE_SIZE
+ TILE_SIZE
/ 2;
531 int y
= TileY(v
->dest_tile
) * TILE_SIZE
+ TILE_SIZE
/ 2;
532 if (Delta(v
->x_pos
, x
) + Delta(v
->y_pos
, y
) >= 8) {
533 v
->direction
= GetDirectionTowards(v
, x
, y
);
535 GetNewVehiclePosResult gp
= GetNewVehiclePos(v
);
536 v
->UpdatePosition(gp
.x
, gp
.y
, GetAircraftFlightLevel(v
));
540 if (!IsValidTile(v
->dest_tile
)) {
541 /* Make sure we don't land outside the map. */
546 int z
= GetSlopePixelZ(v
->x_pos
, v
->y_pos
);
548 v
->UpdatePosition(v
->x_pos
, v
->y_pos
, v
->z_pos
- 1);
554 for (Vehicle
*target
: Vehicle::Iterate()) {
555 if (target
->IsGroundVehicle()) {
556 if (Delta(target
->x_pos
, v
->x_pos
) + Delta(target
->y_pos
, v
->y_pos
) <= 12 * (int)TILE_SIZE
) {
557 target
->breakdown_ctr
= 5;
558 target
->breakdown_delay
= 0xF0;
563 Town
*t
= ClosestTownFromTile(v
->dest_tile
, UINT_MAX
);
564 SetDParam(0, t
->index
);
565 AddTileNewsItem(STR_NEWS_DISASTER_BIG_UFO
, NT_ACCIDENT
, v
->tile
);
567 if (!Vehicle::CanAllocateItem(2)) {
571 DisasterVehicle
*u
= new DisasterVehicle(-6 * (int)TILE_SIZE
, v
->y_pos
, DIR_SW
, ST_BIG_UFO_DESTROYER
, v
->index
);
572 DisasterVehicle
*w
= new DisasterVehicle(-6 * (int)TILE_SIZE
, v
->y_pos
, DIR_SW
, ST_BIG_UFO_DESTROYER_SHADOW
);
574 } else if (v
->state
== 0) {
575 int x
= TileX(v
->dest_tile
) * TILE_SIZE
;
576 int y
= TileY(v
->dest_tile
) * TILE_SIZE
;
577 if (Delta(x
, v
->x_pos
) + Delta(y
, v
->y_pos
) >= (int)TILE_SIZE
) {
578 v
->direction
= GetDirectionTowards(v
, x
, y
);
579 GetNewVehiclePosResult gp
= GetNewVehiclePos(v
);
580 v
->UpdatePosition(gp
.x
, gp
.y
, GetAircraftFlightLevel(v
));
585 v
->dest_tile
= RandomTile();
590 const auto is_valid_target
= [](const Train
*t
) {
591 return t
->IsFrontEngine() // Only the engines
592 && Company::IsHumanID(t
->owner
) // Don't break AIs
593 && IsPlainRailTile(t
->tile
) // No tunnels
594 && (t
->vehstatus
& VS_CRASHED
) == 0; // Not crashed
597 uint n
= 0; // Total number of targetable trains.
598 for (const Train
*t
: Train::Iterate()) {
599 if (is_valid_target(t
)) n
++;
603 /* If there are no targetable trains, destroy the UFO. */
608 n
= RandomRange(n
); // Choose one of them.
609 for (const Train
*t
: Train::Iterate()) {
610 /* Find (n+1)-th train. */
611 if (is_valid_target(t
) && (n
-- == 0)) {
613 v
->dest_tile
= t
->tile
;
624 * Skyranger destroying (Big) Ufo handling, v->state states:
625 * 0: Home in on landed Ufo and shoot it down
627 static bool DisasterTick_Big_Ufo_Destroyer(DisasterVehicle
*v
)
631 GetNewVehiclePosResult gp
= GetNewVehiclePos(v
);
632 v
->UpdatePosition(gp
.x
, gp
.y
, GetAircraftFlightLevel(v
));
634 if (gp
.x
> (int)(Map::SizeX() * TILE_SIZE
+ 9 * TILE_SIZE
) - 1) {
640 Vehicle
*u
= Vehicle::Get(v
->big_ufo_destroyer_target
);
641 if (Delta(v
->x_pos
, u
->x_pos
) > (int)TILE_SIZE
) return true;
644 CreateEffectVehicleRel(u
, 0, 7, 8, EV_EXPLOSION_LARGE
);
645 if (_settings_client
.sound
.disaster
) SndPlayVehicleFx(SND_12_EXPLOSION
, u
);
649 for (int i
= 0; i
!= 80; i
++) {
650 uint32_t r
= Random();
651 CreateEffectVehicleAbove(
652 GB(r
, 0, 6) + v
->x_pos
- 32,
653 GB(r
, 5, 6) + v
->y_pos
- 32,
658 for (int dy
= -3; dy
< 3; dy
++) {
659 for (int dx
= -3; dx
< 3; dx
++) {
660 TileIndex tile
= TileAddWrap(v
->tile
, dx
, dy
);
661 if (tile
!= INVALID_TILE
) DisasterClearSquare(tile
);
670 * Submarine, v->state states:
671 * Unused, just float around aimlessly and pop up at different places, turning around
673 static bool DisasterTick_Submarine(DisasterVehicle
*v
)
677 if (++v
->age
> 8880) {
682 if (!HasBit(v
->tick_counter
, 0)) return true;
684 TileIndex tile
= v
->tile
+ TileOffsByDiagDir(DirToDiagDir(v
->direction
));
685 if (IsValidTile(tile
)) {
686 TrackBits trackbits
= TrackStatusToTrackBits(GetTileTrackStatus(tile
, TRANSPORT_WATER
, 0));
687 if (trackbits
== TRACK_BIT_ALL
&& !Chance16(1, 90)) {
688 GetNewVehiclePosResult gp
= GetNewVehiclePos(v
);
689 v
->UpdatePosition(gp
.x
, gp
.y
, v
->z_pos
);
694 v
->direction
= ChangeDir(v
->direction
, GB(Random(), 0, 1) ? DIRDIFF_90RIGHT
: DIRDIFF_90LEFT
);
700 static bool DisasterTick_NULL(DisasterVehicle
*)
705 typedef bool DisasterVehicleTickProc(DisasterVehicle
*v
);
707 static DisasterVehicleTickProc
* const _disastervehicle_tick_procs
[] = {
708 DisasterTick_Zeppeliner
, DisasterTick_NULL
,
709 DisasterTick_Ufo
, DisasterTick_NULL
,
710 DisasterTick_Airplane
, DisasterTick_NULL
,
711 DisasterTick_Helicopter
, DisasterTick_NULL
, DisasterTick_Helicopter_Rotors
,
712 DisasterTick_Big_Ufo
, DisasterTick_NULL
, DisasterTick_Big_Ufo_Destroyer
,
714 DisasterTick_Submarine
,
715 DisasterTick_Submarine
,
719 bool DisasterVehicle::Tick()
721 return _disastervehicle_tick_procs
[this->subtype
](this);
724 typedef void DisasterInitProc();
728 * Zeppeliner which crashes on a small airport if one found,
729 * otherwise crashes on a random tile
731 static void Disaster_Zeppeliner_Init()
733 if (!Vehicle::CanAllocateItem(2)) return;
735 /* Pick a random place, unless we find a small airport */
736 int x
= TileX(Random()) * TILE_SIZE
+ TILE_SIZE
/ 2;
738 for (const Station
*st
: Station::Iterate()) {
739 if (st
->airport
.tile
!= INVALID_TILE
&& (st
->airport
.type
== AT_SMALL
|| st
->airport
.type
== AT_LARGE
)) {
740 x
= (TileX(st
->airport
.tile
) + 2) * TILE_SIZE
;
745 DisasterVehicle
*v
= new DisasterVehicle(x
, 0, DIR_SE
, ST_ZEPPELINER
);
746 /* Allocate shadow */
747 DisasterVehicle
*u
= new DisasterVehicle(x
, 0, DIR_SE
, ST_ZEPPELINER_SHADOW
);
753 * Ufo which flies around aimlessly from the middle of the map a bit
754 * until it locates a road vehicle which it targets and then destroys
756 static void Disaster_Small_Ufo_Init()
758 if (!Vehicle::CanAllocateItem(2)) return;
760 int x
= TileX(Random()) * TILE_SIZE
+ TILE_SIZE
/ 2;
761 DisasterVehicle
*v
= new DisasterVehicle(x
, 0, DIR_SE
, ST_SMALL_UFO
);
762 v
->dest_tile
= TileXY(Map::SizeX() / 2, Map::SizeY() / 2);
764 /* Allocate shadow */
765 DisasterVehicle
*u
= new DisasterVehicle(x
, 0, DIR_SE
, ST_SMALL_UFO_SHADOW
);
770 /* Combat airplane which destroys an oil refinery */
771 static void Disaster_Airplane_Init()
773 if (!Vehicle::CanAllocateItem(2)) return;
775 Industry
*found
= nullptr;
777 for (Industry
*i
: Industry::Iterate()) {
778 if ((GetIndustrySpec(i
->type
)->behaviour
& INDUSTRYBEH_AIRPLANE_ATTACKS
) &&
779 (found
== nullptr || Chance16(1, 2))) {
784 if (found
== nullptr) return;
786 /* Start from the bottom (south side) of the map */
787 int x
= (Map::SizeX() + 9) * TILE_SIZE
- 1;
788 int y
= TileY(found
->location
.tile
) * TILE_SIZE
+ 37;
790 DisasterVehicle
*v
= new DisasterVehicle(x
, y
, DIR_NE
, ST_AIRPLANE
);
791 DisasterVehicle
*u
= new DisasterVehicle(x
, y
, DIR_NE
, ST_AIRPLANE_SHADOW
);
796 /** Combat helicopter that destroys a factory */
797 static void Disaster_Helicopter_Init()
799 if (!Vehicle::CanAllocateItem(3)) return;
801 Industry
*found
= nullptr;
803 for (Industry
*i
: Industry::Iterate()) {
804 if ((GetIndustrySpec(i
->type
)->behaviour
& INDUSTRYBEH_CHOPPER_ATTACKS
) &&
805 (found
== nullptr || Chance16(1, 2))) {
810 if (found
== nullptr) return;
812 int x
= -16 * (int)TILE_SIZE
;
813 int y
= TileY(found
->location
.tile
) * TILE_SIZE
+ 37;
815 DisasterVehicle
*v
= new DisasterVehicle(x
, y
, DIR_SW
, ST_HELICOPTER
);
816 DisasterVehicle
*u
= new DisasterVehicle(x
, y
, DIR_SW
, ST_HELICOPTER_SHADOW
);
819 DisasterVehicle
*w
= new DisasterVehicle(x
, y
, DIR_SW
, ST_HELICOPTER_ROTORS
);
824 /* Big Ufo which lands on a piece of rail and will consequently be shot
825 * down by a combat airplane, destroying the surroundings */
826 static void Disaster_Big_Ufo_Init()
828 if (!Vehicle::CanAllocateItem(2)) return;
830 int x
= TileX(Random()) * TILE_SIZE
+ TILE_SIZE
/ 2;
831 int y
= Map::MaxX() * TILE_SIZE
- 1;
833 DisasterVehicle
*v
= new DisasterVehicle(x
, y
, DIR_NW
, ST_BIG_UFO
);
834 v
->dest_tile
= TileXY(Map::SizeX() / 2, Map::SizeY() / 2);
836 /* Allocate shadow */
837 DisasterVehicle
*u
= new DisasterVehicle(x
, y
, DIR_NW
, ST_BIG_UFO_SHADOW
);
842 static void Disaster_Submarine_Init(DisasterSubType subtype
)
844 if (!Vehicle::CanAllocateItem()) return;
848 uint32_t r
= Random();
849 int x
= TileX(r
) * TILE_SIZE
+ TILE_SIZE
/ 2;
852 y
= Map::MaxY() * TILE_SIZE
- TILE_SIZE
/ 2 - 1;
856 if (_settings_game
.construction
.freeform_edges
) y
+= TILE_SIZE
;
859 if (!IsWaterTile(TileVirtXY(x
, y
))) return;
861 new DisasterVehicle(x
, y
, dir
, subtype
);
864 /* Curious submarine #1, just floats around */
865 static void Disaster_Small_Submarine_Init()
867 Disaster_Submarine_Init(ST_SMALL_SUBMARINE
);
871 /* Curious submarine #2, just floats around */
872 static void Disaster_Big_Submarine_Init()
874 Disaster_Submarine_Init(ST_BIG_SUBMARINE
);
879 * Coal mine catastrophe, destroys a stretch of 30 tiles of
880 * land in a certain direction
882 static void Disaster_CoalMine_Init()
884 int index
= GB(Random(), 0, 4);
887 for (m
= 0; m
< 15; m
++) {
888 for (const Industry
*i
: Industry::Iterate()) {
889 if ((GetIndustrySpec(i
->type
)->behaviour
& INDUSTRYBEH_CAN_SUBSIDENCE
) && --index
< 0) {
890 SetDParam(0, i
->town
->index
);
891 AddTileNewsItem(STR_NEWS_DISASTER_COAL_MINE_SUBSIDENCE
, NT_ACCIDENT
, i
->location
.tile
+ TileDiffXY(1, 1)); // keep the news, even when the mine closes
894 TileIndex tile
= i
->location
.tile
;
895 TileIndexDiff step
= TileOffsByDiagDir((DiagDirection
)GB(Random(), 0, 2));
897 for (uint n
= 0; n
< 30; n
++) {
898 DisasterClearSquare(tile
);
900 if (!IsValidTile(tile
)) break;
910 DisasterInitProc
*init_proc
; ///< The init function for this disaster.
911 TimerGameCalendar::Year min_year
; ///< The first year this disaster will occur.
912 TimerGameCalendar::Year max_year
; ///< The last year this disaster will occur.
915 static const Disaster _disasters
[] = {
916 {Disaster_Zeppeliner_Init
, 1930, 1955}, // zeppeliner
917 {Disaster_Small_Ufo_Init
, 1940, 1970}, // ufo (small)
918 {Disaster_Airplane_Init
, 1960, 1990}, // airplane
919 {Disaster_Helicopter_Init
, 1970, 2000}, // helicopter
920 {Disaster_Big_Ufo_Init
, 2000, 2100}, // ufo (big)
921 {Disaster_Small_Submarine_Init
, 1940, 1965}, // submarine (small)
922 {Disaster_Big_Submarine_Init
, 1975, 2010}, // submarine (big)
923 {Disaster_CoalMine_Init
, 1950, 1985}, // coalmine
926 static void DoDisaster()
928 byte buf
[lengthof(_disasters
)];
931 for (size_t i
= 0; i
!= lengthof(_disasters
); i
++) {
932 if (TimerGameCalendar::year
>= _disasters
[i
].min_year
&& TimerGameCalendar::year
< _disasters
[i
].max_year
) buf
[j
++] = (byte
)i
;
937 _disasters
[buf
[RandomRange(j
)]].init_proc();
941 static void ResetDisasterDelay()
943 _disaster_delay
= GB(Random(), 0, 9) + 730;
946 static IntervalTimer
<TimerGameEconomy
> _economy_disaster_daily({TimerGameEconomy::DAY
, TimerGameEconomy::Priority::DISASTER
}, [](auto)
948 if (--_disaster_delay
!= 0) return;
950 ResetDisasterDelay();
952 if (_settings_game
.difficulty
.disasters
!= 0) DoDisaster();
955 void StartupDisasters()
957 ResetDisasterDelay();
961 * Marks all disasters targeting this industry in such a way
962 * they won't call Industry::Get(v->dest_tile) on invalid industry anymore.
963 * @param i deleted industry
965 void ReleaseDisastersTargetingIndustry(IndustryID i
)
967 for (DisasterVehicle
*v
: DisasterVehicle::Iterate()) {
968 /* primary disaster vehicles that have chosen target */
969 if (v
->subtype
== ST_AIRPLANE
|| v
->subtype
== ST_HELICOPTER
) {
970 /* if it has chosen target, and it is this industry (yes, dest_tile is IndustryID here), set order to "leaving map peacefully" */
971 if (v
->state
> 0 && v
->dest_tile
== (uint32_t)i
) v
->state
= 3;
977 * Notify disasters that we are about to delete a vehicle. So make them head elsewhere.
978 * @param vehicle deleted vehicle
980 void ReleaseDisasterVehicle(VehicleID vehicle
)
982 DisasterVehicle
*v
= DisasterVehicle::GetIfValid(vehicle
);
983 if (v
== nullptr) return;
985 /* primary disaster vehicles that have chosen target */
986 assert(v
->subtype
== ST_SMALL_UFO
);
987 assert(v
->state
!= 0);
989 /* Revert to target-searching */
991 v
->dest_tile
= RandomTile();
992 GetAircraftFlightLevelBounds(v
, &v
->z_pos
, nullptr);
996 void DisasterVehicle::UpdateDeltaXY()