# HG changeset patch # User rubidium # Date 2014-09-20 15:46:44 # Node ID 67b34383fd38560b3c676f8a52bf47c4a655dfda # Parent 231348c2865550631fcea3dce0f720587c46f05e (svn r26864) -Codechange: bring a bit more OO into the disaster vehicles diff --git a/projects/openttd_vs100.vcxproj b/projects/openttd_vs100.vcxproj --- a/projects/openttd_vs100.vcxproj +++ b/projects/openttd_vs100.vcxproj @@ -311,6 +311,7 @@ + @@ -437,6 +438,7 @@ + @@ -805,7 +807,6 @@ - diff --git a/projects/openttd_vs100.vcxproj.filters b/projects/openttd_vs100.vcxproj.filters --- a/projects/openttd_vs100.vcxproj.filters +++ b/projects/openttd_vs100.vcxproj.filters @@ -162,6 +162,9 @@ Source Files + + Source Files + Source Files @@ -540,6 +543,9 @@ Header Files + + Header Files + Header Files @@ -1644,9 +1650,6 @@ Command handlers - - Command handlers - Command handlers diff --git a/projects/openttd_vs80.vcproj b/projects/openttd_vs80.vcproj --- a/projects/openttd_vs80.vcproj +++ b/projects/openttd_vs80.vcproj @@ -515,6 +515,10 @@ > + + @@ -1023,6 +1027,10 @@ > + + @@ -2511,10 +2519,6 @@ > - - diff --git a/projects/openttd_vs90.vcproj b/projects/openttd_vs90.vcproj --- a/projects/openttd_vs90.vcproj +++ b/projects/openttd_vs90.vcproj @@ -512,6 +512,10 @@ > + + @@ -1020,6 +1024,10 @@ > + + @@ -2508,10 +2516,6 @@ > - - diff --git a/source.list b/source.list --- a/source.list +++ b/source.list @@ -19,6 +19,7 @@ date.cpp debug.cpp dedicated.cpp depot.cpp +disaster_vehicle.cpp driver.cpp economy.cpp effectvehicle.cpp @@ -172,6 +173,7 @@ depot_map.h depot_type.h direction_func.h direction_type.h +disaster_vehicle.h music/dmusic.h driver.h economy_base.h @@ -563,7 +565,6 @@ autoreplace_cmd.cpp clear_cmd.cpp company_cmd.cpp depot_cmd.cpp -disaster_cmd.cpp group_cmd.cpp industry_cmd.cpp misc_cmd.cpp diff --git a/src/aircraft.h b/src/aircraft.h --- a/src/aircraft.h +++ b/src/aircraft.h @@ -30,6 +30,7 @@ enum VehicleAirFlags { VAF_DEST_TOO_FAR = 0, ///< Next destination is too far away. }; +static const int ROTOR_Z_OFFSET = 5; ///< Z Offset between helicopter- and rotorsprite. void HandleAircraftEnterHangar(Aircraft *v); void GetAircraftSpriteSize(EngineID engine, uint &width, uint &height, int &xoffs, int &yoffs, EngineImageType image_type); diff --git a/src/aircraft_cmd.cpp b/src/aircraft_cmd.cpp --- a/src/aircraft_cmd.cpp +++ b/src/aircraft_cmd.cpp @@ -41,8 +41,6 @@ #include "safeguards.h" -static const int ROTOR_Z_OFFSET = 5; ///< Z Offset between helicopter- and rotorsprite. - static const int PLANE_HOLDING_ALTITUDE = 150; ///< Altitude of planes in holding pattern (= lowest flight altitude). static const int HELI_FLIGHT_ALTITUDE = 184; ///< Normal flight altitude of helicopters. diff --git a/src/disaster_cmd.cpp b/src/disaster_cmd.cpp deleted file mode 100644 --- a/src/disaster_cmd.cpp +++ /dev/null @@ -1,990 +0,0 @@ -/* $Id$ */ - -/* - * This file is part of OpenTTD. - * 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. - * 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. - * 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 . - */ - -/** - * @file disaster_cmd.cpp - * All disaster/easter egg vehicles are handled here. - * The general flow of control for the disaster vehicles is as follows: - *
    - *
  1. Initialize the disaster in a disaster specific way (eg start position, - * possible target, etc.) Disaster_XXX_Init() function - *
  2. Add a subtype to a disaster, which is an index into the function array - * that handles the vehicle's ticks. - *
  3. Run the disaster vehicles each tick until their target has been reached, - * this happens in the DisasterTick_XXX() functions. In here, a vehicle's - * state is kept by v->current_order.dest variable. Each achieved sub-target - * will increase this value, and the last one will remove the disaster itself - *
- */ - - -#include "stdafx.h" - -#include "industry.h" -#include "station_base.h" -#include "command_func.h" -#include "news_func.h" -#include "town.h" -#include "company_func.h" -#include "strings_func.h" -#include "date_func.h" -#include "viewport_func.h" -#include "vehicle_func.h" -#include "sound_func.h" -#include "effectvehicle_func.h" -#include "roadveh.h" -#include "ai/ai.hpp" -#include "game/game.hpp" -#include "company_base.h" -#include "core/random_func.hpp" -#include "core/backup_type.hpp" - -#include "table/strings.h" - -#include "safeguards.h" - -/** Delay counter for considering the next disaster. */ -uint16 _disaster_delay; - -enum DisasterSubType { - ST_ZEPPELINER, - ST_ZEPPELINER_SHADOW, - ST_SMALL_UFO, - ST_SMALL_UFO_SHADOW, - ST_AIRPLANE, - ST_AIRPLANE_SHADOW, - ST_HELICOPTER, - ST_HELICOPTER_SHADOW, - ST_HELICOPTER_ROTORS, - ST_BIG_UFO, - ST_BIG_UFO_SHADOW, - ST_BIG_UFO_DESTROYER, - ST_BIG_UFO_DESTROYER_SHADOW, - ST_SMALL_SUBMARINE, - ST_BIG_SUBMARINE, -}; - -static const uint INITIAL_DISASTER_VEHICLE_ZPOS = 135; ///< Initial Z position of flying disaster vehicles. - -static void DisasterClearSquare(TileIndex tile) -{ - if (EnsureNoVehicleOnGround(tile).Failed()) return; - - switch (GetTileType(tile)) { - case MP_RAILWAY: - if (Company::IsHumanID(GetTileOwner(tile)) && !IsRailDepot(tile)) { - Backup cur_company(_current_company, OWNER_WATER, FILE_LINE); - DoCommand(tile, 0, 0, DC_EXEC, CMD_LANDSCAPE_CLEAR); - cur_company.Restore(); - - /* update signals in buffer */ - UpdateSignalsInBuffer(); - } - break; - - case MP_HOUSE: { - Backup cur_company(_current_company, OWNER_NONE, FILE_LINE); - DoCommand(tile, 0, 0, DC_EXEC, CMD_LANDSCAPE_CLEAR); - cur_company.Restore(); - break; - } - - case MP_TREES: - case MP_CLEAR: - DoClearSquare(tile); - break; - - default: - break; - } -} - -static const SpriteID _disaster_images_1[] = {SPR_BLIMP, SPR_BLIMP, SPR_BLIMP, SPR_BLIMP, SPR_BLIMP, SPR_BLIMP, SPR_BLIMP, SPR_BLIMP}; -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}; -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}; -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}; -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}; -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}; -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}; -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}; -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}; - -static const SpriteID * const _disaster_images[] = { - _disaster_images_1, _disaster_images_1, ///< zeppeliner and zeppeliner shadow - _disaster_images_2, _disaster_images_2, ///< small ufo and small ufo shadow - _disaster_images_3, _disaster_images_3, ///< combat aircraft and shadow - _disaster_images_8, _disaster_images_8, _disaster_images_9, ///< combat helicopter, shadow and rotor - _disaster_images_6, _disaster_images_6, ///< big ufo and shadow - _disaster_images_7, _disaster_images_7, ///< skyranger and shadow - _disaster_images_4, _disaster_images_5, ///< small and big submarine sprites -}; - -static void DisasterVehicleUpdateImage(DisasterVehicle *v) -{ - SpriteID img = v->image_override; - if (img == 0) img = _disaster_images[v->subtype][v->direction]; - v->cur_image = img; -} - -/** - * Initialize a disaster vehicle. These vehicles are of type VEH_DISASTER, are unclickable - * and owned by nobody - */ -static void InitializeDisasterVehicle(DisasterVehicle *v, int x, int y, int z, Direction direction, byte subtype) -{ - v->x_pos = x; - v->y_pos = y; - v->z_pos = z; - v->tile = TileVirtXY(x, y); - v->direction = direction; - v->subtype = subtype; - v->UpdateDeltaXY(INVALID_DIR); - v->owner = OWNER_NONE; - v->vehstatus = VS_UNCLICKABLE; - v->image_override = 0; - v->current_order.Free(); - - DisasterVehicleUpdateImage(v); - v->UpdatePositionAndViewport(); -} - -static void SetDisasterVehiclePos(DisasterVehicle *v, int x, int y, int z) -{ - v->x_pos = x; - v->y_pos = y; - v->z_pos = z; - v->tile = TileVirtXY(x, y); - - DisasterVehicleUpdateImage(v); - v->UpdatePositionAndViewport(); - - DisasterVehicle *u = v->Next(); - if (u != NULL) { - int safe_x = Clamp(x, 0, MapMaxX() * TILE_SIZE); - int safe_y = Clamp(y - 1, 0, MapMaxY() * TILE_SIZE); - - u->x_pos = x; - u->y_pos = y - 1 - (max(z - GetSlopePixelZ(safe_x, safe_y), 0) >> 3); - safe_y = Clamp(u->y_pos, 0, MapMaxY() * TILE_SIZE); - u->z_pos = GetSlopePixelZ(safe_x, safe_y); - u->direction = v->direction; - - DisasterVehicleUpdateImage(u); - u->UpdatePositionAndViewport(); - - if ((u = u->Next()) != NULL) { - u->x_pos = x; - u->y_pos = y; - u->z_pos = z + 5; - u->UpdatePositionAndViewport(); - } - } -} - -/** - * Zeppeliner handling, v->current_order.dest states: - * 0: Zeppeliner initialization has found a small airport, go there and crash - * 1: Create crash and animate falling down for extra dramatic effect - * 2: Create more smoke and leave debris on ground - * 2: Clear the runway after some time and remove crashed zeppeliner - * If not airport was found, only state 0 is reached until zeppeliner leaves map - */ -static bool DisasterTick_Zeppeliner(DisasterVehicle *v) -{ - v->tick_counter++; - - if (v->current_order.GetDestination() < 2) { - if (HasBit(v->tick_counter, 0)) return true; - - GetNewVehiclePosResult gp = GetNewVehiclePos(v); - - SetDisasterVehiclePos(v, gp.x, gp.y, v->z_pos); - - if (v->current_order.GetDestination() == 1) { - if (++v->age == 38) { - v->current_order.SetDestination(2); - v->age = 0; - } - - if (GB(v->tick_counter, 0, 3) == 0) CreateEffectVehicleRel(v, 0, -17, 2, EV_CRASH_SMOKE); - - } else if (v->current_order.GetDestination() == 0) { - if (IsValidTile(v->tile) && IsAirportTile(v->tile)) { - v->current_order.SetDestination(1); - v->age = 0; - - SetDParam(0, GetStationIndex(v->tile)); - AddVehicleNewsItem(STR_NEWS_DISASTER_ZEPPELIN, NT_ACCIDENT, v->index); // Delete the news, when the zeppelin is gone - AI::NewEvent(GetTileOwner(v->tile), new ScriptEventDisasterZeppelinerCrashed(GetStationIndex(v->tile))); - } - } - - if (v->y_pos >= (int)((MapSizeY() + 9) * TILE_SIZE - 1)) { - delete v; - return false; - } - - return true; - } - - if (v->current_order.GetDestination() > 2) { - if (++v->age <= 13320) return true; - - if (IsValidTile(v->tile) && IsAirportTile(v->tile)) { - Station *st = Station::GetByTile(v->tile); - CLRBITS(st->airport.flags, RUNWAY_IN_block); - AI::NewEvent(GetTileOwner(v->tile), new ScriptEventDisasterZeppelinerCleared(st->index)); - } - - SetDisasterVehiclePos(v, v->x_pos, v->y_pos, v->z_pos); - delete v; - return false; - } - - int x = v->x_pos; - int y = v->y_pos; - int z = GetSlopePixelZ(x, y); - if (z < v->z_pos) z = v->z_pos - 1; - SetDisasterVehiclePos(v, x, y, z); - - if (++v->age == 1) { - CreateEffectVehicleRel(v, 0, 7, 8, EV_EXPLOSION_LARGE); - if (_settings_client.sound.disaster) SndPlayVehicleFx(SND_12_EXPLOSION, v); - v->image_override = SPR_BLIMP_CRASHING; - } else if (v->age == 70) { - v->image_override = SPR_BLIMP_CRASHED; - } else if (v->age <= 300) { - if (GB(v->tick_counter, 0, 3) == 0) { - uint32 r = Random(); - - CreateEffectVehicleRel(v, - GB(r, 0, 4) - 7, - GB(r, 4, 4) - 7, - GB(r, 8, 3) + 5, - EV_EXPLOSION_SMALL); - } - } else if (v->age == 350) { - v->current_order.SetDestination(3); - v->age = 0; - } - - if (IsValidTile(v->tile) && IsAirportTile(v->tile)) { - SETBITS(Station::GetByTile(v->tile)->airport.flags, RUNWAY_IN_block); - } - - return true; -} - -/** - * (Small) Ufo handling, v->current_order.dest states: - * 0: Fly around to the middle of the map, then randomly, after a while target a road vehicle - * 1: Home in on a road vehicle and crash it >:) - * If not road vehicle was found, only state 0 is used and Ufo disappears after a while - */ -static bool DisasterTick_Ufo(DisasterVehicle *v) -{ - v->image_override = (HasBit(++v->tick_counter, 3)) ? SPR_UFO_SMALL_SCOUT_DARKER : SPR_UFO_SMALL_SCOUT; - - if (v->current_order.GetDestination() == 0) { - /* Fly around randomly */ - int x = TileX(v->dest_tile) * TILE_SIZE; - int y = TileY(v->dest_tile) * TILE_SIZE; - if (Delta(x, v->x_pos) + Delta(y, v->y_pos) >= (int)TILE_SIZE) { - v->direction = GetDirectionTowards(v, x, y); - GetNewVehiclePosResult gp = GetNewVehiclePos(v); - SetDisasterVehiclePos(v, gp.x, gp.y, v->z_pos); - return true; - } - if (++v->age < 6) { - v->dest_tile = RandomTile(); - return true; - } - v->current_order.SetDestination(1); - - uint n = 0; // Total number of targetable road vehicles. - RoadVehicle *u; - FOR_ALL_ROADVEHICLES(u) { - if (u->IsFrontEngine()) n++; - } - - if (n == 0) { - /* If there are no targetable road vehicles, destroy the UFO. */ - delete v; - return false; - } - - n = RandomRange(n); // Choose one of them. - FOR_ALL_ROADVEHICLES(u) { - /* Find (n+1)-th road vehicle. */ - if (u->IsFrontEngine() && (n-- == 0)) break; - } - - /* Target it. */ - v->dest_tile = u->index; - v->age = 0; - return true; - } else { - /* Target a vehicle */ - RoadVehicle *u = RoadVehicle::Get(v->dest_tile); - assert(u != NULL && u->type == VEH_ROAD && u->IsFrontEngine()); - - uint dist = Delta(v->x_pos, u->x_pos) + Delta(v->y_pos, u->y_pos); - - if (dist < TILE_SIZE && !(u->vehstatus & VS_HIDDEN) && u->breakdown_ctr == 0) { - u->breakdown_ctr = 3; - u->breakdown_delay = 140; - } - - v->direction = GetDirectionTowards(v, u->x_pos, u->y_pos); - GetNewVehiclePosResult gp = GetNewVehiclePos(v); - - int z = v->z_pos; - if (dist <= TILE_SIZE && z > u->z_pos) z--; - SetDisasterVehiclePos(v, gp.x, gp.y, z); - - if (z <= u->z_pos && (u->vehstatus & VS_HIDDEN) == 0) { - v->age++; - if (u->crashed_ctr == 0) { - u->Crash(); - - AddVehicleNewsItem(STR_NEWS_DISASTER_SMALL_UFO, NT_ACCIDENT, u->index); // delete the news, when the roadvehicle is gone - - AI::NewEvent(u->owner, new ScriptEventVehicleCrashed(u->index, u->tile, ScriptEventVehicleCrashed::CRASH_RV_UFO)); - Game::NewEvent(new ScriptEventVehicleCrashed(u->index, u->tile, ScriptEventVehicleCrashed::CRASH_RV_UFO)); - } - } - - /* Destroy? */ - if (v->age > 50) { - CreateEffectVehicleRel(v, 0, 7, 8, EV_EXPLOSION_LARGE); - if (_settings_client.sound.disaster) SndPlayVehicleFx(SND_12_EXPLOSION, v); - delete v; - return false; - } - } - - return true; -} - -static void DestructIndustry(Industry *i) -{ - for (TileIndex tile = 0; tile != MapSize(); tile++) { - if (i->TileBelongsToIndustry(tile)) { - ResetIndustryConstructionStage(tile); - MarkTileDirtyByTile(tile); - } - } -} - -/** - * Aircraft handling, v->current_order.dest states: - * 0: Fly towards the targeted industry - * 1: If within 15 tiles, fire away rockets and destroy industry - * 2: Industry explosions - * 3: Fly out of the map - * If the industry was removed in the meantime just fly to the end of the map. - * @param v The disaster vehicle. - * @param image_override The image at the time the aircraft is firing. - * @param leave_at_top True iff the vehicle leaves the map at the north side. - * @param news_message The string that's used as news message. - * @param industry_flag Only attack industries that have this flag set. - */ -static bool DisasterTick_Aircraft(DisasterVehicle *v, uint16 image_override, bool leave_at_top, StringID news_message, IndustryBehaviour industry_flag) -{ - v->tick_counter++; - v->image_override = (v->current_order.GetDestination() == 1 && HasBit(v->tick_counter, 2)) ? image_override : 0; - - GetNewVehiclePosResult gp = GetNewVehiclePos(v); - SetDisasterVehiclePos(v, gp.x, gp.y, v->z_pos); - - if ((leave_at_top && gp.x < (-10 * (int)TILE_SIZE)) || (!leave_at_top && gp.x > (int)(MapSizeX() * TILE_SIZE + 9 * TILE_SIZE) - 1)) { - delete v; - return false; - } - - if (v->current_order.GetDestination() == 2) { - if (GB(v->tick_counter, 0, 2) == 0) { - Industry *i = Industry::Get(v->dest_tile); // Industry destructor calls ReleaseDisastersTargetingIndustry, so this is valid - int x = TileX(i->location.tile) * TILE_SIZE; - int y = TileY(i->location.tile) * TILE_SIZE; - uint32 r = Random(); - - CreateEffectVehicleAbove( - GB(r, 0, 6) + x, - GB(r, 6, 6) + y, - GB(r, 12, 4), - EV_EXPLOSION_SMALL); - - if (++v->age >= 55) v->current_order.SetDestination(3); - } - } else if (v->current_order.GetDestination() == 1) { - if (++v->age == 112) { - v->current_order.SetDestination(2); - v->age = 0; - - Industry *i = Industry::Get(v->dest_tile); // Industry destructor calls ReleaseDisastersTargetingIndustry, so this is valid - DestructIndustry(i); - - SetDParam(0, i->town->index); - AddIndustryNewsItem(news_message, NT_ACCIDENT, i->index); // delete the news, when the industry closes - if (_settings_client.sound.disaster) SndPlayTileFx(SND_12_EXPLOSION, i->location.tile); - } - } else if (v->current_order.GetDestination() == 0) { - int x = v->x_pos + ((leave_at_top ? -15 : 15) * TILE_SIZE); - int y = v->y_pos; - - if ((uint)x > MapMaxX() * TILE_SIZE - 1) return true; - - TileIndex tile = TileVirtXY(x, y); - if (!IsTileType(tile, MP_INDUSTRY)) return true; - - IndustryID ind = GetIndustryIndex(tile); - v->dest_tile = ind; - - if (GetIndustrySpec(Industry::Get(ind)->type)->behaviour & industry_flag) { - v->current_order.SetDestination(1); - v->age = 0; - } - } - - return true; -} - -/** Airplane handling. */ -static bool DisasterTick_Airplane(DisasterVehicle *v) -{ - return DisasterTick_Aircraft(v, SPR_F_15_FIRING, true, STR_NEWS_DISASTER_AIRPLANE_OIL_REFINERY, INDUSTRYBEH_AIRPLANE_ATTACKS); -} - -/** Helicopter handling. */ -static bool DisasterTick_Helicopter(DisasterVehicle *v) -{ - return DisasterTick_Aircraft(v, SPR_AH_64A_FIRING, false, STR_NEWS_DISASTER_HELICOPTER_FACTORY, INDUSTRYBEH_CHOPPER_ATTACKS); -} - -/** Helicopter rotor blades; keep these spinning */ -static bool DisasterTick_Helicopter_Rotors(DisasterVehicle *v) -{ - v->tick_counter++; - if (HasBit(v->tick_counter, 0)) return true; - - if (++v->cur_image > SPR_ROTOR_MOVING_3) v->cur_image = SPR_ROTOR_MOVING_1; - - v->UpdatePositionAndViewport(); - - return true; -} - -/** - * (Big) Ufo handling, v->current_order.dest states: - * 0: Fly around to the middle of the map, then randomly for a while and home in on a piece of rail - * 1: Land there and breakdown all trains in a radius of 12 tiles; and now we wait... - * because as soon as the Ufo lands, a fighter jet, a Skyranger, is called to clear up the mess - */ -static bool DisasterTick_Big_Ufo(DisasterVehicle *v) -{ - v->tick_counter++; - - if (v->current_order.GetDestination() == 1) { - int x = TileX(v->dest_tile) * TILE_SIZE + TILE_SIZE / 2; - int y = TileY(v->dest_tile) * TILE_SIZE + TILE_SIZE / 2; - if (Delta(v->x_pos, x) + Delta(v->y_pos, y) >= 8) { - v->direction = GetDirectionTowards(v, x, y); - - GetNewVehiclePosResult gp = GetNewVehiclePos(v); - SetDisasterVehiclePos(v, gp.x, gp.y, v->z_pos); - return true; - } - - if (!IsValidTile(v->dest_tile)) { - /* Make sure we don't land outside the map. */ - delete v; - return false; - } - - int z = GetSlopePixelZ(v->x_pos, v->y_pos); - if (z < v->z_pos) { - SetDisasterVehiclePos(v, v->x_pos, v->y_pos, v->z_pos - 1); - return true; - } - - v->current_order.SetDestination(2); - - Vehicle *target; - FOR_ALL_VEHICLES(target) { - if (target->IsGroundVehicle()) { - if (Delta(target->x_pos, v->x_pos) + Delta(target->y_pos, v->y_pos) <= 12 * (int)TILE_SIZE) { - target->breakdown_ctr = 5; - target->breakdown_delay = 0xF0; - } - } - } - - Town *t = ClosestTownFromTile(v->dest_tile, UINT_MAX); - SetDParam(0, t->index); - AddTileNewsItem(STR_NEWS_DISASTER_BIG_UFO, NT_ACCIDENT, v->tile); - - if (!Vehicle::CanAllocateItem(2)) { - delete v; - return false; - } - DisasterVehicle *u = new DisasterVehicle(); - - InitializeDisasterVehicle(u, -6 * (int)TILE_SIZE, v->y_pos, INITIAL_DISASTER_VEHICLE_ZPOS, DIR_SW, ST_BIG_UFO_DESTROYER); - u->big_ufo_destroyer_target = v->index; - - DisasterVehicle *w = new DisasterVehicle(); - - u->SetNext(w); - InitializeDisasterVehicle(w, -6 * (int)TILE_SIZE, v->y_pos, 0, DIR_SW, ST_BIG_UFO_DESTROYER_SHADOW); - w->vehstatus |= VS_SHADOW; - } else if (v->current_order.GetDestination() == 0) { - int x = TileX(v->dest_tile) * TILE_SIZE; - int y = TileY(v->dest_tile) * TILE_SIZE; - if (Delta(x, v->x_pos) + Delta(y, v->y_pos) >= (int)TILE_SIZE) { - v->direction = GetDirectionTowards(v, x, y); - GetNewVehiclePosResult gp = GetNewVehiclePos(v); - SetDisasterVehiclePos(v, gp.x, gp.y, v->z_pos); - return true; - } - - if (++v->age < 6) { - v->dest_tile = RandomTile(); - return true; - } - v->current_order.SetDestination(1); - - TileIndex tile_org = RandomTile(); - TileIndex tile = tile_org; - do { - if (IsPlainRailTile(tile) && - Company::IsHumanID(GetTileOwner(tile))) { - break; - } - tile = TILE_MASK(tile + 1); - } while (tile != tile_org); - v->dest_tile = tile; - v->age = 0; - } - - return true; -} - -/** - * Skyranger destroying (Big) Ufo handling, v->current_order.dest states: - * 0: Home in on landed Ufo and shoot it down - */ -static bool DisasterTick_Big_Ufo_Destroyer(DisasterVehicle *v) -{ - v->tick_counter++; - - GetNewVehiclePosResult gp = GetNewVehiclePos(v); - SetDisasterVehiclePos(v, gp.x, gp.y, v->z_pos); - - if (gp.x > (int)(MapSizeX() * TILE_SIZE + 9 * TILE_SIZE) - 1) { - delete v; - return false; - } - - if (v->current_order.GetDestination() == 0) { - Vehicle *u = Vehicle::Get(v->big_ufo_destroyer_target); - if (Delta(v->x_pos, u->x_pos) > (int)TILE_SIZE) return true; - v->current_order.SetDestination(1); - - CreateEffectVehicleRel(u, 0, 7, 8, EV_EXPLOSION_LARGE); - if (_settings_client.sound.disaster) SndPlayVehicleFx(SND_12_EXPLOSION, u); - - delete u; - - for (int i = 0; i != 80; i++) { - uint32 r = Random(); - CreateEffectVehicleAbove( - GB(r, 0, 6) + v->x_pos - 32, - GB(r, 5, 6) + v->y_pos - 32, - 0, - EV_EXPLOSION_SMALL); - } - - for (int dy = -3; dy < 3; dy++) { - for (int dx = -3; dx < 3; dx++) { - TileIndex tile = TileAddWrap(v->tile, dx, dy); - if (tile != INVALID_TILE) DisasterClearSquare(tile); - } - } - } - - return true; -} - -/** - * Submarine, v->current_order.dest states: - * Unused, just float around aimlessly and pop up at different places, turning around - */ -static bool DisasterTick_Submarine(DisasterVehicle *v) -{ - v->tick_counter++; - - if (++v->age > 8880) { - delete v; - return false; - } - - if (!HasBit(v->tick_counter, 0)) return true; - - TileIndex tile = v->tile + TileOffsByDiagDir(DirToDiagDir(v->direction)); - if (IsValidTile(tile)) { - TrackBits trackbits = TrackStatusToTrackBits(GetTileTrackStatus(tile, TRANSPORT_WATER, 0)); - if (trackbits == TRACK_BIT_ALL && !Chance16(1, 90)) { - GetNewVehiclePosResult gp = GetNewVehiclePos(v); - SetDisasterVehiclePos(v, gp.x, gp.y, v->z_pos); - return true; - } - } - - v->direction = ChangeDir(v->direction, GB(Random(), 0, 1) ? DIRDIFF_90RIGHT : DIRDIFF_90LEFT); - - return true; -} - - -static bool DisasterTick_NULL(DisasterVehicle *v) -{ - return true; -} - -typedef bool DisasterVehicleTickProc(DisasterVehicle *v); - -static DisasterVehicleTickProc * const _disastervehicle_tick_procs[] = { - DisasterTick_Zeppeliner, DisasterTick_NULL, - DisasterTick_Ufo, DisasterTick_NULL, - DisasterTick_Airplane, DisasterTick_NULL, - DisasterTick_Helicopter, DisasterTick_NULL, DisasterTick_Helicopter_Rotors, - DisasterTick_Big_Ufo, DisasterTick_NULL, DisasterTick_Big_Ufo_Destroyer, - DisasterTick_NULL, - DisasterTick_Submarine, - DisasterTick_Submarine, -}; - - -bool DisasterVehicle::Tick() -{ - return _disastervehicle_tick_procs[this->subtype](this); -} - -typedef void DisasterInitProc(); - - -/** - * Zeppeliner which crashes on a small airport if one found, - * otherwise crashes on a random tile - */ -static void Disaster_Zeppeliner_Init() -{ - if (!Vehicle::CanAllocateItem(2)) return; - - /* Pick a random place, unless we find a small airport */ - int x = TileX(Random()) * TILE_SIZE + TILE_SIZE / 2; - - Station *st; - FOR_ALL_STATIONS(st) { - if (st->airport.tile != INVALID_TILE && (st->airport.type == AT_SMALL || st->airport.type == AT_LARGE)) { - x = (TileX(st->airport.tile) + 2) * TILE_SIZE; - break; - } - } - - DisasterVehicle *v = new DisasterVehicle(); - InitializeDisasterVehicle(v, x, 0, INITIAL_DISASTER_VEHICLE_ZPOS, DIR_SE, ST_ZEPPELINER); - - /* Allocate shadow */ - DisasterVehicle *u = new DisasterVehicle(); - v->SetNext(u); - InitializeDisasterVehicle(u, x, 0, 0, DIR_SE, ST_ZEPPELINER_SHADOW); - u->vehstatus |= VS_SHADOW; -} - - -/** - * Ufo which flies around aimlessly from the middle of the map a bit - * until it locates a road vehicle which it targets and then destroys - */ -static void Disaster_Small_Ufo_Init() -{ - if (!Vehicle::CanAllocateItem(2)) return; - - DisasterVehicle *v = new DisasterVehicle(); - int x = TileX(Random()) * TILE_SIZE + TILE_SIZE / 2; - - InitializeDisasterVehicle(v, x, 0, INITIAL_DISASTER_VEHICLE_ZPOS, DIR_SE, ST_SMALL_UFO); - v->dest_tile = TileXY(MapSizeX() / 2, MapSizeY() / 2); - v->age = 0; - - /* Allocate shadow */ - DisasterVehicle *u = new DisasterVehicle(); - v->SetNext(u); - InitializeDisasterVehicle(u, x, 0, 0, DIR_SE, ST_SMALL_UFO_SHADOW); - u->vehstatus |= VS_SHADOW; -} - - -/* Combat airplane which destroys an oil refinery */ -static void Disaster_Airplane_Init() -{ - if (!Vehicle::CanAllocateItem(2)) return; - - Industry *i, *found = NULL; - - FOR_ALL_INDUSTRIES(i) { - if ((GetIndustrySpec(i->type)->behaviour & INDUSTRYBEH_AIRPLANE_ATTACKS) && - (found == NULL || Chance16(1, 2))) { - found = i; - } - } - - if (found == NULL) return; - - DisasterVehicle *v = new DisasterVehicle(); - - /* Start from the bottom (south side) of the map */ - int x = (MapSizeX() + 9) * TILE_SIZE - 1; - int y = TileY(found->location.tile) * TILE_SIZE + 37; - - InitializeDisasterVehicle(v, x, y, INITIAL_DISASTER_VEHICLE_ZPOS, DIR_NE, ST_AIRPLANE); - - DisasterVehicle *u = new DisasterVehicle(); - v->SetNext(u); - InitializeDisasterVehicle(u, x, y, 0, DIR_SE, ST_AIRPLANE_SHADOW); - u->vehstatus |= VS_SHADOW; -} - - -/** Combat helicopter that destroys a factory */ -static void Disaster_Helicopter_Init() -{ - if (!Vehicle::CanAllocateItem(3)) return; - - Industry *i, *found = NULL; - - FOR_ALL_INDUSTRIES(i) { - if ((GetIndustrySpec(i->type)->behaviour & INDUSTRYBEH_CHOPPER_ATTACKS) && - (found == NULL || Chance16(1, 2))) { - found = i; - } - } - - if (found == NULL) return; - - DisasterVehicle *v = new DisasterVehicle(); - - int x = -16 * (int)TILE_SIZE; - int y = TileY(found->location.tile) * TILE_SIZE + 37; - - InitializeDisasterVehicle(v, x, y, INITIAL_DISASTER_VEHICLE_ZPOS, DIR_SW, ST_HELICOPTER); - - DisasterVehicle *u = new DisasterVehicle(); - v->SetNext(u); - InitializeDisasterVehicle(u, x, y, 0, DIR_SW, ST_HELICOPTER_SHADOW); - u->vehstatus |= VS_SHADOW; - - DisasterVehicle *w = new DisasterVehicle(); - u->SetNext(w); - InitializeDisasterVehicle(w, x, y, 140, DIR_SW, ST_HELICOPTER_ROTORS); -} - - -/* Big Ufo which lands on a piece of rail and will consequently be shot - * down by a combat airplane, destroying the surroundings */ -static void Disaster_Big_Ufo_Init() -{ - if (!Vehicle::CanAllocateItem(2)) return; - - DisasterVehicle *v = new DisasterVehicle(); - int x = TileX(Random()) * TILE_SIZE + TILE_SIZE / 2; - int y = MapMaxX() * TILE_SIZE - 1; - - InitializeDisasterVehicle(v, x, y, INITIAL_DISASTER_VEHICLE_ZPOS, DIR_NW, ST_BIG_UFO); - v->dest_tile = TileXY(MapSizeX() / 2, MapSizeY() / 2); - v->age = 0; - - /* Allocate shadow */ - DisasterVehicle *u = new DisasterVehicle(); - v->SetNext(u); - InitializeDisasterVehicle(u, x, y, 0, DIR_NW, ST_BIG_UFO_SHADOW); - u->vehstatus |= VS_SHADOW; -} - - -static void Disaster_Submarine_Init(DisasterSubType subtype) -{ - if (!Vehicle::CanAllocateItem()) return; - - int y; - Direction dir; - uint32 r = Random(); - int x = TileX(r) * TILE_SIZE + TILE_SIZE / 2; - - if (HasBit(r, 31)) { - y = MapMaxY() * TILE_SIZE - TILE_SIZE / 2 - 1; - dir = DIR_NW; - } else { - y = TILE_SIZE / 2; - if (_settings_game.construction.freeform_edges) y += TILE_SIZE; - dir = DIR_SE; - } - if (!IsWaterTile(TileVirtXY(x, y))) return; - - DisasterVehicle *v = new DisasterVehicle(); - InitializeDisasterVehicle(v, x, y, 0, dir, subtype); - v->age = 0; -} - -/* Curious submarine #1, just floats around */ -static void Disaster_Small_Submarine_Init() -{ - Disaster_Submarine_Init(ST_SMALL_SUBMARINE); -} - - -/* Curious submarine #2, just floats around */ -static void Disaster_Big_Submarine_Init() -{ - Disaster_Submarine_Init(ST_BIG_SUBMARINE); -} - - -/** - * Coal mine catastrophe, destroys a stretch of 30 tiles of - * land in a certain direction - */ -static void Disaster_CoalMine_Init() -{ - int index = GB(Random(), 0, 4); - uint m; - - for (m = 0; m < 15; m++) { - const Industry *i; - - FOR_ALL_INDUSTRIES(i) { - if ((GetIndustrySpec(i->type)->behaviour & INDUSTRYBEH_CAN_SUBSIDENCE) && --index < 0) { - SetDParam(0, i->town->index); - AddTileNewsItem(STR_NEWS_DISASTER_COAL_MINE_SUBSIDENCE, NT_ACCIDENT, i->location.tile + TileDiffXY(1, 1)); // keep the news, even when the mine closes - - { - TileIndex tile = i->location.tile; - TileIndexDiff step = TileOffsByDiagDir((DiagDirection)GB(Random(), 0, 2)); - - for (uint n = 0; n < 30; n++) { - DisasterClearSquare(tile); - tile += step; - if (!IsValidTile(tile)) break; - } - } - return; - } - } - } -} - -struct Disaster { - DisasterInitProc *init_proc; ///< The init function for this disaster. - Year min_year; ///< The first year this disaster will occur. - Year max_year; ///< The last year this disaster will occur. -}; - -static const Disaster _disasters[] = { - {Disaster_Zeppeliner_Init, 1930, 1955}, // zeppeliner - {Disaster_Small_Ufo_Init, 1940, 1970}, // ufo (small) - {Disaster_Airplane_Init, 1960, 1990}, // airplane - {Disaster_Helicopter_Init, 1970, 2000}, // helicopter - {Disaster_Big_Ufo_Init, 2000, 2100}, // ufo (big) - {Disaster_Small_Submarine_Init, 1940, 1965}, // submarine (small) - {Disaster_Big_Submarine_Init, 1975, 2010}, // submarine (big) - {Disaster_CoalMine_Init, 1950, 1985}, // coalmine -}; - -static void DoDisaster() -{ - byte buf[lengthof(_disasters)]; - - byte j = 0; - for (size_t i = 0; i != lengthof(_disasters); i++) { - if (_cur_year >= _disasters[i].min_year && _cur_year < _disasters[i].max_year) buf[j++] = (byte)i; - } - - if (j == 0) return; - - _disasters[buf[RandomRange(j)]].init_proc(); -} - - -static void ResetDisasterDelay() -{ - _disaster_delay = GB(Random(), 0, 9) + 730; -} - -void DisasterDailyLoop() -{ - if (--_disaster_delay != 0) return; - - ResetDisasterDelay(); - - if (_settings_game.difficulty.disasters != 0) DoDisaster(); -} - -void StartupDisasters() -{ - ResetDisasterDelay(); -} - -/** - * Marks all disasters targeting this industry in such a way - * they won't call Industry::Get(v->dest_tile) on invalid industry anymore. - * @param i deleted industry - */ -void ReleaseDisastersTargetingIndustry(IndustryID i) -{ - DisasterVehicle *v; - FOR_ALL_DISASTERVEHICLES(v) { - /* primary disaster vehicles that have chosen target */ - if (v->subtype == ST_AIRPLANE || v->subtype == ST_HELICOPTER) { - /* if it has chosen target, and it is this industry (yes, dest_tile is IndustryID here), set order to "leaving map peacefully" */ - if (v->current_order.GetDestination() > 0 && v->dest_tile == i) v->current_order.SetDestination(3); - } - } -} - -/** - * Notify disasters that we are about to delete a vehicle. So make them head elsewhere. - * @param vehicle deleted vehicle - */ -void ReleaseDisastersTargetingVehicle(VehicleID vehicle) -{ - DisasterVehicle *v; - FOR_ALL_DISASTERVEHICLES(v) { - /* primary disaster vehicles that have chosen target */ - if (v->subtype == ST_SMALL_UFO) { - if (v->current_order.GetDestination() != 0 && v->dest_tile == vehicle) { - /* Revert to target-searching */ - v->current_order.SetDestination(0); - v->dest_tile = RandomTile(); - v->z_pos = INITIAL_DISASTER_VEHICLE_ZPOS; - v->age = 0; - } - } - } -} - -void DisasterVehicle::UpdateDeltaXY(Direction direction) -{ - this->x_offs = -1; - this->y_offs = -1; - this->x_extent = 2; - this->y_extent = 2; - this->z_extent = 5; -} diff --git a/src/disaster_vehicle.cpp b/src/disaster_vehicle.cpp new file mode 100644 --- /dev/null +++ b/src/disaster_vehicle.cpp @@ -0,0 +1,982 @@ +/* $Id$ */ + +/* + * This file is part of OpenTTD. + * 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. + * 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. + * 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 . + */ + +/** + * @file disaster_vehicle.cpp + * + * All disaster/easter egg vehicles are handled here. + * The general flow of control for the disaster vehicles is as follows: + *
    + *
  1. Initialize the disaster in a disaster specific way (eg start position, + * possible target, etc.) Disaster_XXX_Init() function + *
  2. Add a subtype to a disaster, which is an index into the function array + * that handles the vehicle's ticks. + *
  3. Run the disaster vehicles each tick until their target has been reached, + * this happens in the DisasterTick_XXX() functions. In here, a vehicle's + * state is kept by v->current_order.dest variable. Each achieved sub-target + * will increase this value, and the last one will remove the disaster itself + *
+ */ + + +#include "stdafx.h" + +#include "aircraft.h" +#include "disaster_vehicle.h" +#include "industry.h" +#include "station_base.h" +#include "command_func.h" +#include "news_func.h" +#include "town.h" +#include "company_func.h" +#include "strings_func.h" +#include "date_func.h" +#include "viewport_func.h" +#include "vehicle_func.h" +#include "sound_func.h" +#include "effectvehicle_func.h" +#include "roadveh.h" +#include "ai/ai.hpp" +#include "game/game.hpp" +#include "company_base.h" +#include "core/random_func.hpp" +#include "core/backup_type.hpp" + +#include "table/strings.h" + +#include "safeguards.h" + +/** Delay counter for considering the next disaster. */ +uint16 _disaster_delay; + +static const uint INITIAL_DISASTER_VEHICLE_ZPOS = 135; ///< Initial Z position of flying disaster vehicles. + +static void DisasterClearSquare(TileIndex tile) +{ + if (EnsureNoVehicleOnGround(tile).Failed()) return; + + switch (GetTileType(tile)) { + case MP_RAILWAY: + if (Company::IsHumanID(GetTileOwner(tile)) && !IsRailDepot(tile)) { + Backup cur_company(_current_company, OWNER_WATER, FILE_LINE); + DoCommand(tile, 0, 0, DC_EXEC, CMD_LANDSCAPE_CLEAR); + cur_company.Restore(); + + /* update signals in buffer */ + UpdateSignalsInBuffer(); + } + break; + + case MP_HOUSE: { + Backup cur_company(_current_company, OWNER_NONE, FILE_LINE); + DoCommand(tile, 0, 0, DC_EXEC, CMD_LANDSCAPE_CLEAR); + cur_company.Restore(); + break; + } + + case MP_TREES: + case MP_CLEAR: + DoClearSquare(tile); + break; + + default: + break; + } +} + +static const SpriteID _disaster_images_1[] = {SPR_BLIMP, SPR_BLIMP, SPR_BLIMP, SPR_BLIMP, SPR_BLIMP, SPR_BLIMP, SPR_BLIMP, SPR_BLIMP}; +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}; +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}; +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}; +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}; +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}; +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}; +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}; +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}; + +static const SpriteID * const _disaster_images[] = { + _disaster_images_1, _disaster_images_1, ///< zeppeliner and zeppeliner shadow + _disaster_images_2, _disaster_images_2, ///< small ufo and small ufo shadow + _disaster_images_3, _disaster_images_3, ///< combat aircraft and shadow + _disaster_images_8, _disaster_images_8, _disaster_images_9, ///< combat helicopter, shadow and rotor + _disaster_images_6, _disaster_images_6, ///< big ufo and shadow + _disaster_images_7, _disaster_images_7, ///< skyranger and shadow + _disaster_images_4, _disaster_images_5, ///< small and big submarine sprites +}; + +void DisasterVehicle::UpdateImage() +{ + SpriteID img = this->image_override; + if (img == 0) img = _disaster_images[this->subtype][this->direction]; + this->cur_image = img; +} + +/** + * Construct the disaster vehicle. + * @param x The X coordinate. + * @param y The Y coordinate. + * @param direction The direction the vehicle is facing. + * @param subtype The sub type of vehicle. + * @param big_ufo_destroyer_target The target for the UFO destroyer. + */ +DisasterVehicle::DisasterVehicle(int x, int y, Direction direction, DisasterSubType subtype, VehicleID big_ufo_destroyer_target) : + SpecializedVehicleBase(), big_ufo_destroyer_target(big_ufo_destroyer_target) +{ + this->x_pos = x; + this->y_pos = y; + switch (subtype) { + case ST_ZEPPELINER: + case ST_SMALL_UFO: + case ST_AIRPLANE: + case ST_HELICOPTER: + case ST_BIG_UFO: + case ST_BIG_UFO_DESTROYER: + this->z_pos = INITIAL_DISASTER_VEHICLE_ZPOS; + break; + + case ST_HELICOPTER_ROTORS: + this->z_pos = INITIAL_DISASTER_VEHICLE_ZPOS + ROTOR_Z_OFFSET; + break; + + case ST_SMALL_SUBMARINE: + case ST_BIG_SUBMARINE: + this->z_pos = 0; + break; + + case ST_ZEPPELINER_SHADOW: + case ST_SMALL_UFO_SHADOW: + case ST_AIRPLANE_SHADOW: + case ST_HELICOPTER_SHADOW: + case ST_BIG_UFO_SHADOW: + case ST_BIG_UFO_DESTROYER_SHADOW: + this->z_pos = 0; + this->vehstatus |= VS_SHADOW; + break; + } + + this->direction = direction; + this->tile = TileVirtXY(x, y); + this->subtype = subtype; + this->UpdateDeltaXY(INVALID_DIR); + this->owner = OWNER_NONE; + this->vehstatus = VS_UNCLICKABLE; + this->image_override = 0; + this->current_order.Free(); + + this->UpdateImage(); + this->UpdatePositionAndViewport(); +} + +/** + * Update the position of the vehicle. + * @param x The new X-coordinate. + * @param y The new Y-coordinate. + * @param z The new Z-coordinate. + */ +void DisasterVehicle::UpdatePosition(int x, int y, int z) +{ + this->x_pos = x; + this->y_pos = y; + this->z_pos = z; + this->tile = TileVirtXY(x, y); + + this->UpdateImage(); + this->UpdatePositionAndViewport(); + + DisasterVehicle *u = this->Next(); + if (u != NULL) { + int safe_x = Clamp(x, 0, MapMaxX() * TILE_SIZE); + int safe_y = Clamp(y - 1, 0, MapMaxY() * TILE_SIZE); + + u->x_pos = x; + u->y_pos = y - 1 - (max(z - GetSlopePixelZ(safe_x, safe_y), 0) >> 3); + safe_y = Clamp(u->y_pos, 0, MapMaxY() * TILE_SIZE); + u->z_pos = GetSlopePixelZ(safe_x, safe_y); + u->direction = this->direction; + + u->UpdateImage(); + u->UpdatePositionAndViewport(); + + if ((u = u->Next()) != NULL) { + u->x_pos = x; + u->y_pos = y; + u->z_pos = z + ROTOR_Z_OFFSET; + u->UpdatePositionAndViewport(); + } + } +} + +/** + * Zeppeliner handling, v->current_order.dest states: + * 0: Zeppeliner initialization has found a small airport, go there and crash + * 1: Create crash and animate falling down for extra dramatic effect + * 2: Create more smoke and leave debris on ground + * 2: Clear the runway after some time and remove crashed zeppeliner + * If not airport was found, only state 0 is reached until zeppeliner leaves map + */ +static bool DisasterTick_Zeppeliner(DisasterVehicle *v) +{ + v->tick_counter++; + + if (v->current_order.GetDestination() < 2) { + if (HasBit(v->tick_counter, 0)) return true; + + GetNewVehiclePosResult gp = GetNewVehiclePos(v); + + v->UpdatePosition(gp.x, gp.y, v->z_pos); + + if (v->current_order.GetDestination() == 1) { + if (++v->age == 38) { + v->current_order.SetDestination(2); + v->age = 0; + } + + if (GB(v->tick_counter, 0, 3) == 0) CreateEffectVehicleRel(v, 0, -17, 2, EV_CRASH_SMOKE); + + } else if (v->current_order.GetDestination() == 0) { + if (IsValidTile(v->tile) && IsAirportTile(v->tile)) { + v->current_order.SetDestination(1); + v->age = 0; + + SetDParam(0, GetStationIndex(v->tile)); + AddVehicleNewsItem(STR_NEWS_DISASTER_ZEPPELIN, NT_ACCIDENT, v->index); // Delete the news, when the zeppelin is gone + AI::NewEvent(GetTileOwner(v->tile), new ScriptEventDisasterZeppelinerCrashed(GetStationIndex(v->tile))); + } + } + + if (v->y_pos >= (int)((MapSizeY() + 9) * TILE_SIZE - 1)) { + delete v; + return false; + } + + return true; + } + + if (v->current_order.GetDestination() > 2) { + if (++v->age <= 13320) return true; + + if (IsValidTile(v->tile) && IsAirportTile(v->tile)) { + Station *st = Station::GetByTile(v->tile); + CLRBITS(st->airport.flags, RUNWAY_IN_block); + AI::NewEvent(GetTileOwner(v->tile), new ScriptEventDisasterZeppelinerCleared(st->index)); + } + + v->UpdatePosition(v->x_pos, v->y_pos, v->z_pos); + delete v; + return false; + } + + int x = v->x_pos; + int y = v->y_pos; + int z = GetSlopePixelZ(x, y); + if (z < v->z_pos) z = v->z_pos - 1; + v->UpdatePosition(x, y, z); + + if (++v->age == 1) { + CreateEffectVehicleRel(v, 0, 7, 8, EV_EXPLOSION_LARGE); + if (_settings_client.sound.disaster) SndPlayVehicleFx(SND_12_EXPLOSION, v); + v->image_override = SPR_BLIMP_CRASHING; + } else if (v->age == 70) { + v->image_override = SPR_BLIMP_CRASHED; + } else if (v->age <= 300) { + if (GB(v->tick_counter, 0, 3) == 0) { + uint32 r = Random(); + + CreateEffectVehicleRel(v, + GB(r, 0, 4) - 7, + GB(r, 4, 4) - 7, + GB(r, 8, 3) + 5, + EV_EXPLOSION_SMALL); + } + } else if (v->age == 350) { + v->current_order.SetDestination(3); + v->age = 0; + } + + if (IsValidTile(v->tile) && IsAirportTile(v->tile)) { + SETBITS(Station::GetByTile(v->tile)->airport.flags, RUNWAY_IN_block); + } + + return true; +} + +/** + * (Small) Ufo handling, v->current_order.dest states: + * 0: Fly around to the middle of the map, then randomly, after a while target a road vehicle + * 1: Home in on a road vehicle and crash it >:) + * If not road vehicle was found, only state 0 is used and Ufo disappears after a while + */ +static bool DisasterTick_Ufo(DisasterVehicle *v) +{ + v->image_override = (HasBit(++v->tick_counter, 3)) ? SPR_UFO_SMALL_SCOUT_DARKER : SPR_UFO_SMALL_SCOUT; + + if (v->current_order.GetDestination() == 0) { + /* Fly around randomly */ + int x = TileX(v->dest_tile) * TILE_SIZE; + int y = TileY(v->dest_tile) * TILE_SIZE; + if (Delta(x, v->x_pos) + Delta(y, v->y_pos) >= (int)TILE_SIZE) { + v->direction = GetDirectionTowards(v, x, y); + GetNewVehiclePosResult gp = GetNewVehiclePos(v); + v->UpdatePosition(gp.x, gp.y, v->z_pos); + return true; + } + if (++v->age < 6) { + v->dest_tile = RandomTile(); + return true; + } + v->current_order.SetDestination(1); + + uint n = 0; // Total number of targetable road vehicles. + RoadVehicle *u; + FOR_ALL_ROADVEHICLES(u) { + if (u->IsFrontEngine()) n++; + } + + if (n == 0) { + /* If there are no targetable road vehicles, destroy the UFO. */ + delete v; + return false; + } + + n = RandomRange(n); // Choose one of them. + FOR_ALL_ROADVEHICLES(u) { + /* Find (n+1)-th road vehicle. */ + if (u->IsFrontEngine() && (n-- == 0)) break; + } + + /* Target it. */ + v->dest_tile = u->index; + v->age = 0; + return true; + } else { + /* Target a vehicle */ + RoadVehicle *u = RoadVehicle::Get(v->dest_tile); + assert(u != NULL && u->type == VEH_ROAD && u->IsFrontEngine()); + + uint dist = Delta(v->x_pos, u->x_pos) + Delta(v->y_pos, u->y_pos); + + if (dist < TILE_SIZE && !(u->vehstatus & VS_HIDDEN) && u->breakdown_ctr == 0) { + u->breakdown_ctr = 3; + u->breakdown_delay = 140; + } + + v->direction = GetDirectionTowards(v, u->x_pos, u->y_pos); + GetNewVehiclePosResult gp = GetNewVehiclePos(v); + + int z = v->z_pos; + if (dist <= TILE_SIZE && z > u->z_pos) z--; + v->UpdatePosition(gp.x, gp.y, z); + + if (z <= u->z_pos && (u->vehstatus & VS_HIDDEN) == 0) { + v->age++; + if (u->crashed_ctr == 0) { + u->Crash(); + + AddVehicleNewsItem(STR_NEWS_DISASTER_SMALL_UFO, NT_ACCIDENT, u->index); // delete the news, when the roadvehicle is gone + + AI::NewEvent(u->owner, new ScriptEventVehicleCrashed(u->index, u->tile, ScriptEventVehicleCrashed::CRASH_RV_UFO)); + Game::NewEvent(new ScriptEventVehicleCrashed(u->index, u->tile, ScriptEventVehicleCrashed::CRASH_RV_UFO)); + } + } + + /* Destroy? */ + if (v->age > 50) { + CreateEffectVehicleRel(v, 0, 7, 8, EV_EXPLOSION_LARGE); + if (_settings_client.sound.disaster) SndPlayVehicleFx(SND_12_EXPLOSION, v); + delete v; + return false; + } + } + + return true; +} + +static void DestructIndustry(Industry *i) +{ + for (TileIndex tile = 0; tile != MapSize(); tile++) { + if (i->TileBelongsToIndustry(tile)) { + ResetIndustryConstructionStage(tile); + MarkTileDirtyByTile(tile); + } + } +} + +/** + * Aircraft handling, v->current_order.dest states: + * 0: Fly towards the targeted industry + * 1: If within 15 tiles, fire away rockets and destroy industry + * 2: Industry explosions + * 3: Fly out of the map + * If the industry was removed in the meantime just fly to the end of the map. + * @param v The disaster vehicle. + * @param image_override The image at the time the aircraft is firing. + * @param leave_at_top True iff the vehicle leaves the map at the north side. + * @param news_message The string that's used as news message. + * @param industry_flag Only attack industries that have this flag set. + */ +static bool DisasterTick_Aircraft(DisasterVehicle *v, uint16 image_override, bool leave_at_top, StringID news_message, IndustryBehaviour industry_flag) +{ + v->tick_counter++; + v->image_override = (v->current_order.GetDestination() == 1 && HasBit(v->tick_counter, 2)) ? image_override : 0; + + GetNewVehiclePosResult gp = GetNewVehiclePos(v); + v->UpdatePosition(gp.x, gp.y, v->z_pos); + + if ((leave_at_top && gp.x < (-10 * (int)TILE_SIZE)) || (!leave_at_top && gp.x > (int)(MapSizeX() * TILE_SIZE + 9 * TILE_SIZE) - 1)) { + delete v; + return false; + } + + if (v->current_order.GetDestination() == 2) { + if (GB(v->tick_counter, 0, 2) == 0) { + Industry *i = Industry::Get(v->dest_tile); // Industry destructor calls ReleaseDisastersTargetingIndustry, so this is valid + int x = TileX(i->location.tile) * TILE_SIZE; + int y = TileY(i->location.tile) * TILE_SIZE; + uint32 r = Random(); + + CreateEffectVehicleAbove( + GB(r, 0, 6) + x, + GB(r, 6, 6) + y, + GB(r, 12, 4), + EV_EXPLOSION_SMALL); + + if (++v->age >= 55) v->current_order.SetDestination(3); + } + } else if (v->current_order.GetDestination() == 1) { + if (++v->age == 112) { + v->current_order.SetDestination(2); + v->age = 0; + + Industry *i = Industry::Get(v->dest_tile); // Industry destructor calls ReleaseDisastersTargetingIndustry, so this is valid + DestructIndustry(i); + + SetDParam(0, i->town->index); + AddIndustryNewsItem(news_message, NT_ACCIDENT, i->index); // delete the news, when the industry closes + if (_settings_client.sound.disaster) SndPlayTileFx(SND_12_EXPLOSION, i->location.tile); + } + } else if (v->current_order.GetDestination() == 0) { + int x = v->x_pos + ((leave_at_top ? -15 : 15) * TILE_SIZE); + int y = v->y_pos; + + if ((uint)x > MapMaxX() * TILE_SIZE - 1) return true; + + TileIndex tile = TileVirtXY(x, y); + if (!IsTileType(tile, MP_INDUSTRY)) return true; + + IndustryID ind = GetIndustryIndex(tile); + v->dest_tile = ind; + + if (GetIndustrySpec(Industry::Get(ind)->type)->behaviour & industry_flag) { + v->current_order.SetDestination(1); + v->age = 0; + } + } + + return true; +} + +/** Airplane handling. */ +static bool DisasterTick_Airplane(DisasterVehicle *v) +{ + return DisasterTick_Aircraft(v, SPR_F_15_FIRING, true, STR_NEWS_DISASTER_AIRPLANE_OIL_REFINERY, INDUSTRYBEH_AIRPLANE_ATTACKS); +} + +/** Helicopter handling. */ +static bool DisasterTick_Helicopter(DisasterVehicle *v) +{ + return DisasterTick_Aircraft(v, SPR_AH_64A_FIRING, false, STR_NEWS_DISASTER_HELICOPTER_FACTORY, INDUSTRYBEH_CHOPPER_ATTACKS); +} + +/** Helicopter rotor blades; keep these spinning */ +static bool DisasterTick_Helicopter_Rotors(DisasterVehicle *v) +{ + v->tick_counter++; + if (HasBit(v->tick_counter, 0)) return true; + + if (++v->cur_image > SPR_ROTOR_MOVING_3) v->cur_image = SPR_ROTOR_MOVING_1; + + v->UpdatePositionAndViewport(); + + return true; +} + +/** + * (Big) Ufo handling, v->current_order.dest states: + * 0: Fly around to the middle of the map, then randomly for a while and home in on a piece of rail + * 1: Land there and breakdown all trains in a radius of 12 tiles; and now we wait... + * because as soon as the Ufo lands, a fighter jet, a Skyranger, is called to clear up the mess + */ +static bool DisasterTick_Big_Ufo(DisasterVehicle *v) +{ + v->tick_counter++; + + if (v->current_order.GetDestination() == 1) { + int x = TileX(v->dest_tile) * TILE_SIZE + TILE_SIZE / 2; + int y = TileY(v->dest_tile) * TILE_SIZE + TILE_SIZE / 2; + if (Delta(v->x_pos, x) + Delta(v->y_pos, y) >= 8) { + v->direction = GetDirectionTowards(v, x, y); + + GetNewVehiclePosResult gp = GetNewVehiclePos(v); + v->UpdatePosition(gp.x, gp.y, v->z_pos); + return true; + } + + if (!IsValidTile(v->dest_tile)) { + /* Make sure we don't land outside the map. */ + delete v; + return false; + } + + int z = GetSlopePixelZ(v->x_pos, v->y_pos); + if (z < v->z_pos) { + v->UpdatePosition(v->x_pos, v->y_pos, v->z_pos - 1); + return true; + } + + v->current_order.SetDestination(2); + + Vehicle *target; + FOR_ALL_VEHICLES(target) { + if (target->IsGroundVehicle()) { + if (Delta(target->x_pos, v->x_pos) + Delta(target->y_pos, v->y_pos) <= 12 * (int)TILE_SIZE) { + target->breakdown_ctr = 5; + target->breakdown_delay = 0xF0; + } + } + } + + Town *t = ClosestTownFromTile(v->dest_tile, UINT_MAX); + SetDParam(0, t->index); + AddTileNewsItem(STR_NEWS_DISASTER_BIG_UFO, NT_ACCIDENT, v->tile); + + if (!Vehicle::CanAllocateItem(2)) { + delete v; + return false; + } + DisasterVehicle *u = new DisasterVehicle(-6 * (int)TILE_SIZE, v->y_pos, DIR_SW, ST_BIG_UFO_DESTROYER, v->index); + DisasterVehicle *w = new DisasterVehicle(-6 * (int)TILE_SIZE, v->y_pos, DIR_SW, ST_BIG_UFO_DESTROYER_SHADOW); + u->SetNext(w); + } else if (v->current_order.GetDestination() == 0) { + int x = TileX(v->dest_tile) * TILE_SIZE; + int y = TileY(v->dest_tile) * TILE_SIZE; + if (Delta(x, v->x_pos) + Delta(y, v->y_pos) >= (int)TILE_SIZE) { + v->direction = GetDirectionTowards(v, x, y); + GetNewVehiclePosResult gp = GetNewVehiclePos(v); + v->UpdatePosition(gp.x, gp.y, v->z_pos); + return true; + } + + if (++v->age < 6) { + v->dest_tile = RandomTile(); + return true; + } + v->current_order.SetDestination(1); + + TileIndex tile_org = RandomTile(); + TileIndex tile = tile_org; + do { + if (IsPlainRailTile(tile) && + Company::IsHumanID(GetTileOwner(tile))) { + break; + } + tile = TILE_MASK(tile + 1); + } while (tile != tile_org); + v->dest_tile = tile; + v->age = 0; + } + + return true; +} + +/** + * Skyranger destroying (Big) Ufo handling, v->current_order.dest states: + * 0: Home in on landed Ufo and shoot it down + */ +static bool DisasterTick_Big_Ufo_Destroyer(DisasterVehicle *v) +{ + v->tick_counter++; + + GetNewVehiclePosResult gp = GetNewVehiclePos(v); + v->UpdatePosition(gp.x, gp.y, v->z_pos); + + if (gp.x > (int)(MapSizeX() * TILE_SIZE + 9 * TILE_SIZE) - 1) { + delete v; + return false; + } + + if (v->current_order.GetDestination() == 0) { + Vehicle *u = Vehicle::Get(v->big_ufo_destroyer_target); + if (Delta(v->x_pos, u->x_pos) > (int)TILE_SIZE) return true; + v->current_order.SetDestination(1); + + CreateEffectVehicleRel(u, 0, 7, 8, EV_EXPLOSION_LARGE); + if (_settings_client.sound.disaster) SndPlayVehicleFx(SND_12_EXPLOSION, u); + + delete u; + + for (int i = 0; i != 80; i++) { + uint32 r = Random(); + CreateEffectVehicleAbove( + GB(r, 0, 6) + v->x_pos - 32, + GB(r, 5, 6) + v->y_pos - 32, + 0, + EV_EXPLOSION_SMALL); + } + + for (int dy = -3; dy < 3; dy++) { + for (int dx = -3; dx < 3; dx++) { + TileIndex tile = TileAddWrap(v->tile, dx, dy); + if (tile != INVALID_TILE) DisasterClearSquare(tile); + } + } + } + + return true; +} + +/** + * Submarine, v->current_order.dest states: + * Unused, just float around aimlessly and pop up at different places, turning around + */ +static bool DisasterTick_Submarine(DisasterVehicle *v) +{ + v->tick_counter++; + + if (++v->age > 8880) { + delete v; + return false; + } + + if (!HasBit(v->tick_counter, 0)) return true; + + TileIndex tile = v->tile + TileOffsByDiagDir(DirToDiagDir(v->direction)); + if (IsValidTile(tile)) { + TrackBits trackbits = TrackStatusToTrackBits(GetTileTrackStatus(tile, TRANSPORT_WATER, 0)); + if (trackbits == TRACK_BIT_ALL && !Chance16(1, 90)) { + GetNewVehiclePosResult gp = GetNewVehiclePos(v); + v->UpdatePosition(gp.x, gp.y, v->z_pos); + return true; + } + } + + v->direction = ChangeDir(v->direction, GB(Random(), 0, 1) ? DIRDIFF_90RIGHT : DIRDIFF_90LEFT); + + return true; +} + + +static bool DisasterTick_NULL(DisasterVehicle *v) +{ + return true; +} + +typedef bool DisasterVehicleTickProc(DisasterVehicle *v); + +static DisasterVehicleTickProc * const _disastervehicle_tick_procs[] = { + DisasterTick_Zeppeliner, DisasterTick_NULL, + DisasterTick_Ufo, DisasterTick_NULL, + DisasterTick_Airplane, DisasterTick_NULL, + DisasterTick_Helicopter, DisasterTick_NULL, DisasterTick_Helicopter_Rotors, + DisasterTick_Big_Ufo, DisasterTick_NULL, DisasterTick_Big_Ufo_Destroyer, + DisasterTick_NULL, + DisasterTick_Submarine, + DisasterTick_Submarine, +}; + + +bool DisasterVehicle::Tick() +{ + return _disastervehicle_tick_procs[this->subtype](this); +} + +typedef void DisasterInitProc(); + + +/** + * Zeppeliner which crashes on a small airport if one found, + * otherwise crashes on a random tile + */ +static void Disaster_Zeppeliner_Init() +{ + if (!Vehicle::CanAllocateItem(2)) return; + + /* Pick a random place, unless we find a small airport */ + int x = TileX(Random()) * TILE_SIZE + TILE_SIZE / 2; + + Station *st; + FOR_ALL_STATIONS(st) { + if (st->airport.tile != INVALID_TILE && (st->airport.type == AT_SMALL || st->airport.type == AT_LARGE)) { + x = (TileX(st->airport.tile) + 2) * TILE_SIZE; + break; + } + } + + DisasterVehicle *v = new DisasterVehicle(x, 0, DIR_SE, ST_ZEPPELINER); + /* Allocate shadow */ + DisasterVehicle *u = new DisasterVehicle(x, 0, DIR_SE, ST_ZEPPELINER_SHADOW); + v->SetNext(u); +} + + +/** + * Ufo which flies around aimlessly from the middle of the map a bit + * until it locates a road vehicle which it targets and then destroys + */ +static void Disaster_Small_Ufo_Init() +{ + if (!Vehicle::CanAllocateItem(2)) return; + + int x = TileX(Random()) * TILE_SIZE + TILE_SIZE / 2; + DisasterVehicle *v = new DisasterVehicle(x, 0, DIR_SE, ST_SMALL_UFO); + v->dest_tile = TileXY(MapSizeX() / 2, MapSizeY() / 2); + + /* Allocate shadow */ + DisasterVehicle *u = new DisasterVehicle(x, 0, DIR_SE, ST_SMALL_UFO_SHADOW); + v->SetNext(u); +} + + +/* Combat airplane which destroys an oil refinery */ +static void Disaster_Airplane_Init() +{ + if (!Vehicle::CanAllocateItem(2)) return; + + Industry *i, *found = NULL; + + FOR_ALL_INDUSTRIES(i) { + if ((GetIndustrySpec(i->type)->behaviour & INDUSTRYBEH_AIRPLANE_ATTACKS) && + (found == NULL || Chance16(1, 2))) { + found = i; + } + } + + if (found == NULL) return; + + /* Start from the bottom (south side) of the map */ + int x = (MapSizeX() + 9) * TILE_SIZE - 1; + int y = TileY(found->location.tile) * TILE_SIZE + 37; + + DisasterVehicle *v = new DisasterVehicle(x, y, DIR_NE, ST_AIRPLANE); + DisasterVehicle *u = new DisasterVehicle(x, y, DIR_NE, ST_AIRPLANE_SHADOW); + v->SetNext(u); +} + + +/** Combat helicopter that destroys a factory */ +static void Disaster_Helicopter_Init() +{ + if (!Vehicle::CanAllocateItem(3)) return; + + Industry *i, *found = NULL; + + FOR_ALL_INDUSTRIES(i) { + if ((GetIndustrySpec(i->type)->behaviour & INDUSTRYBEH_CHOPPER_ATTACKS) && + (found == NULL || Chance16(1, 2))) { + found = i; + } + } + + if (found == NULL) return; + + int x = -16 * (int)TILE_SIZE; + int y = TileY(found->location.tile) * TILE_SIZE + 37; + + DisasterVehicle *v = new DisasterVehicle(x, y, DIR_SW, ST_HELICOPTER); + DisasterVehicle *u = new DisasterVehicle(x, y, DIR_SW, ST_HELICOPTER_SHADOW); + v->SetNext(u); + + DisasterVehicle *w = new DisasterVehicle(x, y, DIR_SW, ST_HELICOPTER_ROTORS); + u->SetNext(w); +} + + +/* Big Ufo which lands on a piece of rail and will consequently be shot + * down by a combat airplane, destroying the surroundings */ +static void Disaster_Big_Ufo_Init() +{ + if (!Vehicle::CanAllocateItem(2)) return; + + int x = TileX(Random()) * TILE_SIZE + TILE_SIZE / 2; + int y = MapMaxX() * TILE_SIZE - 1; + + DisasterVehicle *v = new DisasterVehicle(x, y, DIR_NW, ST_BIG_UFO); + v->dest_tile = TileXY(MapSizeX() / 2, MapSizeY() / 2); + + /* Allocate shadow */ + DisasterVehicle *u = new DisasterVehicle(x, y, DIR_NW, ST_BIG_UFO_SHADOW); + v->SetNext(u); +} + + +static void Disaster_Submarine_Init(DisasterSubType subtype) +{ + if (!Vehicle::CanAllocateItem()) return; + + int y; + Direction dir; + uint32 r = Random(); + int x = TileX(r) * TILE_SIZE + TILE_SIZE / 2; + + if (HasBit(r, 31)) { + y = MapMaxY() * TILE_SIZE - TILE_SIZE / 2 - 1; + dir = DIR_NW; + } else { + y = TILE_SIZE / 2; + if (_settings_game.construction.freeform_edges) y += TILE_SIZE; + dir = DIR_SE; + } + if (!IsWaterTile(TileVirtXY(x, y))) return; + + new DisasterVehicle(x, y, dir, subtype); +} + +/* Curious submarine #1, just floats around */ +static void Disaster_Small_Submarine_Init() +{ + Disaster_Submarine_Init(ST_SMALL_SUBMARINE); +} + + +/* Curious submarine #2, just floats around */ +static void Disaster_Big_Submarine_Init() +{ + Disaster_Submarine_Init(ST_BIG_SUBMARINE); +} + + +/** + * Coal mine catastrophe, destroys a stretch of 30 tiles of + * land in a certain direction + */ +static void Disaster_CoalMine_Init() +{ + int index = GB(Random(), 0, 4); + uint m; + + for (m = 0; m < 15; m++) { + const Industry *i; + + FOR_ALL_INDUSTRIES(i) { + if ((GetIndustrySpec(i->type)->behaviour & INDUSTRYBEH_CAN_SUBSIDENCE) && --index < 0) { + SetDParam(0, i->town->index); + AddTileNewsItem(STR_NEWS_DISASTER_COAL_MINE_SUBSIDENCE, NT_ACCIDENT, i->location.tile + TileDiffXY(1, 1)); // keep the news, even when the mine closes + + { + TileIndex tile = i->location.tile; + TileIndexDiff step = TileOffsByDiagDir((DiagDirection)GB(Random(), 0, 2)); + + for (uint n = 0; n < 30; n++) { + DisasterClearSquare(tile); + tile += step; + if (!IsValidTile(tile)) break; + } + } + return; + } + } + } +} + +struct Disaster { + DisasterInitProc *init_proc; ///< The init function for this disaster. + Year min_year; ///< The first year this disaster will occur. + Year max_year; ///< The last year this disaster will occur. +}; + +static const Disaster _disasters[] = { + {Disaster_Zeppeliner_Init, 1930, 1955}, // zeppeliner + {Disaster_Small_Ufo_Init, 1940, 1970}, // ufo (small) + {Disaster_Airplane_Init, 1960, 1990}, // airplane + {Disaster_Helicopter_Init, 1970, 2000}, // helicopter + {Disaster_Big_Ufo_Init, 2000, 2100}, // ufo (big) + {Disaster_Small_Submarine_Init, 1940, 1965}, // submarine (small) + {Disaster_Big_Submarine_Init, 1975, 2010}, // submarine (big) + {Disaster_CoalMine_Init, 1950, 1985}, // coalmine +}; + +static void DoDisaster() +{ + byte buf[lengthof(_disasters)]; + + byte j = 0; + for (size_t i = 0; i != lengthof(_disasters); i++) { + if (_cur_year >= _disasters[i].min_year && _cur_year < _disasters[i].max_year) buf[j++] = (byte)i; + } + + if (j == 0) return; + + _disasters[buf[RandomRange(j)]].init_proc(); +} + + +static void ResetDisasterDelay() +{ + _disaster_delay = GB(Random(), 0, 9) + 730; +} + +void DisasterDailyLoop() +{ + if (--_disaster_delay != 0) return; + + ResetDisasterDelay(); + + if (_settings_game.difficulty.disasters != 0) DoDisaster(); +} + +void StartupDisasters() +{ + ResetDisasterDelay(); +} + +/** + * Marks all disasters targeting this industry in such a way + * they won't call Industry::Get(v->dest_tile) on invalid industry anymore. + * @param i deleted industry + */ +void ReleaseDisastersTargetingIndustry(IndustryID i) +{ + DisasterVehicle *v; + FOR_ALL_DISASTERVEHICLES(v) { + /* primary disaster vehicles that have chosen target */ + if (v->subtype == ST_AIRPLANE || v->subtype == ST_HELICOPTER) { + /* if it has chosen target, and it is this industry (yes, dest_tile is IndustryID here), set order to "leaving map peacefully" */ + if (v->current_order.GetDestination() > 0 && v->dest_tile == i) v->current_order.SetDestination(3); + } + } +} + +/** + * Notify disasters that we are about to delete a vehicle. So make them head elsewhere. + * @param vehicle deleted vehicle + */ +void ReleaseDisastersTargetingVehicle(VehicleID vehicle) +{ + DisasterVehicle *v; + FOR_ALL_DISASTERVEHICLES(v) { + /* primary disaster vehicles that have chosen target */ + if (v->subtype == ST_SMALL_UFO) { + if (v->current_order.GetDestination() != 0 && v->dest_tile == vehicle) { + /* Revert to target-searching */ + v->current_order.SetDestination(0); + v->dest_tile = RandomTile(); + v->z_pos = INITIAL_DISASTER_VEHICLE_ZPOS; + v->age = 0; + } + } + } +} + +void DisasterVehicle::UpdateDeltaXY(Direction direction) +{ + this->x_offs = -1; + this->y_offs = -1; + this->x_extent = 2; + this->y_extent = 2; + this->z_extent = 5; +} diff --git a/src/disaster_vehicle.h b/src/disaster_vehicle.h new file mode 100644 --- /dev/null +++ b/src/disaster_vehicle.h @@ -0,0 +1,61 @@ +/* $Id$ */ + +/* + * This file is part of OpenTTD. + * 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. + * 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. + * 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 . + */ + +/** @file disaster_vehicle.h All disaster vehicles. */ + +#ifndef DISASTER_VEHICLE_H +#define DISASTER_VEHICLE_H + +#include "vehicle_base.h" + +/** Different sub types of disaster vehicles. */ +enum DisasterSubType { + ST_ZEPPELINER, ///< Zeppelin, crashes at airports. + ST_ZEPPELINER_SHADOW, ///< Shadow of the zeppelin. + ST_SMALL_UFO, ///< Small UFO, tries to find a road vehicle to destroy. + ST_SMALL_UFO_SHADOW, ///< Shadow of small UFO + ST_AIRPLANE, ///< Airplane destroying an oil refinery + ST_AIRPLANE_SHADOW, ///< Shadow of airplane + ST_HELICOPTER, ///< Helicopter destroying a factory. + ST_HELICOPTER_SHADOW, ///< Shadow of helicopter. + ST_HELICOPTER_ROTORS, ///< Rotors of helicopter. + ST_BIG_UFO, ///< Big UFO, finds a piece of railroad to "park" on + ST_BIG_UFO_SHADOW, ///< Shadow of the big UFO + ST_BIG_UFO_DESTROYER, ///< Aircraft the will bomb the big UFO + ST_BIG_UFO_DESTROYER_SHADOW, ///< Shadow of the aircraft. + ST_SMALL_SUBMARINE, ///< Small submarine, pops up in the oceans but doesn't do anything + ST_BIG_SUBMARINE, ///< Big submarine, pops up in the oceans but doesn't do anything +}; + +/** + * Disasters, like submarines, skyrangers and their shadows, belong to this class. + */ +struct DisasterVehicle FINAL : public SpecializedVehicle { + SpriteID image_override; ///< Override for the default disaster vehicle sprite. + VehicleID big_ufo_destroyer_target; ///< The big UFO that this destroyer is supposed to bomb. + + /** For use by saveload. */ + DisasterVehicle() : SpecializedVehicleBase() {} + DisasterVehicle(int x, int y, Direction direction, DisasterSubType subtype, VehicleID big_ufo_destroyer_target = VEH_INVALID); + /** We want to 'destruct' the right class. */ + virtual ~DisasterVehicle() {} + + void UpdatePosition(int x, int y, int z); + void UpdateDeltaXY(Direction direction); + void UpdateImage(); + bool Tick(); +}; + +/** + * Iterate over disaster vehicles. + * @param var The variable used to iterate over. + */ +#define FOR_ALL_DISASTERVEHICLES(var) FOR_ALL_VEHICLES_OF_TYPE(DisasterVehicle, var) + +#endif /* DISASTER_VEHICLE_H */ diff --git a/src/saveload/afterload.cpp b/src/saveload/afterload.cpp --- a/src/saveload/afterload.cpp +++ b/src/saveload/afterload.cpp @@ -53,6 +53,7 @@ #include "../news_func.h" #include "../order_backup.h" #include "../error.h" +#include "../disaster_vehicle.h" #include "saveload_internal.h" diff --git a/src/saveload/oldloader_sl.cpp b/src/saveload/oldloader_sl.cpp --- a/src/saveload/oldloader_sl.cpp +++ b/src/saveload/oldloader_sl.cpp @@ -27,6 +27,7 @@ #include "../effectvehicle_base.h" #include "../engine_func.h" #include "../company_base.h" +#include "../disaster_vehicle.h" #include "saveload_internal.h" #include "oldloader.h" diff --git a/src/saveload/vehicle_sl.cpp b/src/saveload/vehicle_sl.cpp --- a/src/saveload/vehicle_sl.cpp +++ b/src/saveload/vehicle_sl.cpp @@ -19,6 +19,7 @@ #include "../effectvehicle_base.h" #include "../company_base.h" #include "../company_func.h" +#include "../disaster_vehicle.h" #include "saveload.h" diff --git a/src/vehicle_base.h b/src/vehicle_base.h --- a/src/vehicle_base.h +++ b/src/vehicle_base.h @@ -1088,28 +1088,6 @@ struct SpecializedVehicle : public Vehic */ #define FOR_ALL_VEHICLES_OF_TYPE(name, var) FOR_ALL_ITEMS_FROM(name, vehicle_index, var, 0) if (var->type == name::EXPECTED_TYPE) -/** - * Disasters, like submarines, skyrangers and their shadows, belong to this class. - */ -struct DisasterVehicle FINAL : public SpecializedVehicle { - SpriteID image_override; ///< Override for the default disaster vehicle sprite. - VehicleID big_ufo_destroyer_target; ///< The big UFO that this destroyer is supposed to bomb. - - /** We don't want GCC to zero our struct! It already is zeroed and has an index! */ - DisasterVehicle() : SpecializedVehicleBase() {} - /** We want to 'destruct' the right class. */ - virtual ~DisasterVehicle() {} - - void UpdateDeltaXY(Direction direction); - bool Tick(); -}; - -/** - * Iterate over disaster vehicles. - * @param var The variable used to iterate over. - */ -#define FOR_ALL_DISASTERVEHICLES(var) FOR_ALL_VEHICLES_OF_TYPE(DisasterVehicle, var) - /** Generates sequence of free UnitID numbers */ struct FreeUnitIDGenerator { bool *cache; ///< array of occupied unit id numbers