diff --git a/src/aircraft.h b/src/aircraft.h index a093f3b7ff..0805ae8ec6 100644 --- a/src/aircraft.h +++ b/src/aircraft.h @@ -15,6 +15,17 @@ #include "station_map.h" #include "vehicle_base.h" +/** + * Base values for flight levels above ground level for 'normal' flight and holding patterns. + * Due to speed and direction, the actual flight level may be higher. + */ +enum AircraftFlyingAltitude { + AIRCRAFT_MIN_FLYING_ALTITUDE = 120, ///< Minimum flying altitude above tile. + AIRCRAFT_MAX_FLYING_ALTITUDE = 360, ///< Maximum flying altitude above tile. + PLANE_HOLD_MAX_FLYING_ALTITUDE = 150, ///< holding flying altitude above tile of planes. + HELICOPTER_HOLD_MAX_FLYING_ALTITUDE = 184 ///< holding flying altitude above tile of helicopters. +}; + struct Aircraft; /** An aircraft can be one of those types. */ @@ -25,9 +36,15 @@ enum AircraftSubType { AIR_ROTOR = 6, ///< rotor of an helicopter }; -/** Aircraft flags. */ -enum VehicleAirFlags { - VAF_DEST_TOO_FAR = 0, ///< Next destination is too far away. +/** Flags for air vehicles; shared with disaster vehicles. */ +enum AirVehicleFlags { + VAF_DEST_TOO_FAR = 0, ///< Next destination is too far away. + + /* The next two flags are to prevent stair climbing of the aircraft. The idea is that the aircraft + * will ascend or descend multiple flight levels at a time instead of following the contours of the + * landscape at a fixed altitude. This only has effect when there are more than 15 height levels. */ + VAF_IN_MAX_HEIGHT_CORRECTION = 1, ///< The vehicle is currently lowering its altitude because it hit the upper bound. + VAF_IN_MIN_HEIGHT_CORRECTION = 2, ///< The vehicle is currently raising its altitude because it hit the lower bound. }; static const int ROTOR_Z_OFFSET = 5; ///< Z Offset between helicopter- and rotorsprite. @@ -40,7 +57,10 @@ void UpdateAircraftCache(Aircraft *v, bool update_range = false); void AircraftLeaveHangar(Aircraft *v, Direction exit_dir); void AircraftNextAirportPos_and_Order(Aircraft *v); void SetAircraftPosition(Aircraft *v, int x, int y, int z); -int GetAircraftFlyingAltitude(const Aircraft *v); + +void GetAircraftFlightLevelBounds(const Vehicle *v, int *min, int *max); +template +int GetAircraftFlightLevel(T *v, bool takeoff = false); /** Variables that are cached to improve performance and such. */ struct AircraftCache { @@ -60,7 +80,7 @@ struct Aircraft FINAL : public SpecializedVehicle { DirectionByte last_direction; byte number_consecutive_turns; ///< Protection to prevent the aircraft of making a lot of turns in order to reach a specific point. byte turn_counter; ///< Ticks between each turn to prevent > 45 degree turns. - byte flags; ///< Aircraft flags. @see VehicleAirFlags + byte flags; ///< Aircraft flags. @see AirVehicleFlags AircraftCache acache; diff --git a/src/aircraft_cmd.cpp b/src/aircraft_cmd.cpp index 74a5235c5e..3f66f07c07 100644 --- a/src/aircraft_cmd.cpp +++ b/src/aircraft_cmd.cpp @@ -41,10 +41,6 @@ #include "safeguards.h" -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. - - void Aircraft::UpdateDeltaXY(Direction direction) { this->x_offs = -1; @@ -663,20 +659,35 @@ static int UpdateAircraftSpeed(Aircraft *v, uint speed_limit = SPEED_LIMIT_NONE, } /** - * Gets the cruise altitude of an aircraft. - * The cruise altitude is determined by the velocity of the vehicle - * and the direction it is moving - * @param v The vehicle. Should be an aircraft - * @returns Altitude in pixel units + * Get the tile height below the aircraft. + * This function is needed because aircraft can leave the mapborders. + * + * @param v The vehicle to get the height for. + * @return The height in pixels from 'z_pos' 0. */ -int GetAircraftFlyingAltitude(const Aircraft *v) +int GetTileHeightBelowAircraft(const Vehicle *v) { - if (v->subtype == AIR_HELICOPTER) return HELI_FLIGHT_ALTITUDE; + int safe_x = Clamp(v->x_pos, 0, MapMaxX() * TILE_SIZE); + int safe_y = Clamp(v->y_pos, 0, MapMaxY() * TILE_SIZE); + return TileHeight(TileVirtXY(safe_x, safe_y)) * TILE_HEIGHT; +} - /* Make sure Aircraft fly no lower so that they don't conduct - * CFITs (controlled flight into terrain) - */ - int base_altitude = PLANE_HOLDING_ALTITUDE; +/** + * Get the 'flight level' bounds, in pixels from 'z_pos' 0 for a particular + * vehicle for normal flight situation. + * When the maximum is reached the vehicle should consider descending. + * When the minimum is reached the vehicle should consider ascending. + * + * @param v The vehicle to get the flight levels for. + * @param [out] min_level The minimum bounds for flight level. + * @param [out] max_level The maximum bounds for flight level. + */ +void GetAircraftFlightLevelBounds(const Vehicle *v, int *min_level, int *max_level) +{ + int base_altitude = GetTileHeightBelowAircraft(v); + if (v->type == VEH_AIRCRAFT && Aircraft::From(v)->subtype == AIR_HELICOPTER) { + base_altitude += HELICOPTER_HOLD_MAX_FLYING_ALTITUDE - PLANE_HOLD_MAX_FLYING_ALTITUDE; + } /* Make sure eastbound and westbound planes do not "crash" into each * other by providing them with vertical separation @@ -693,9 +704,63 @@ int GetAircraftFlyingAltitude(const Aircraft *v) } /* Make faster planes fly higher so that they can overtake slower ones */ - base_altitude += min(20 * (v->vcache.cached_max_speed / 200), 90); + base_altitude += min(20 * (v->vcache.cached_max_speed / 200) - 90, 0); - return base_altitude; + if (min_level != NULL) *min_level = base_altitude + AIRCRAFT_MIN_FLYING_ALTITUDE; + if (max_level != NULL) *max_level = base_altitude + AIRCRAFT_MAX_FLYING_ALTITUDE; +} + +/** + * Gets the maximum 'flight level' for the holding pattern of the aircraft, + * in pixels 'z_pos' 0, depending on terrain below.. + * + * @param v The aircraft that may or may not need to decrease its altitude. + * @return Maximal aircraft holding altitude, while in normal flight, in pixels. + */ +int GetAircraftHoldMaxAltitude(const Aircraft *v) +{ + int tile_height = GetTileHeightBelowAircraft(v); + + return tile_height + ((v->subtype == AIR_HELICOPTER) ? HELICOPTER_HOLD_MAX_FLYING_ALTITUDE : PLANE_HOLD_MAX_FLYING_ALTITUDE); +} + +template +int GetAircraftFlightLevel(T *v, bool takeoff = false) +{ + /* Aircraft is in flight. We want to enforce it being somewhere + * between the minimum and the maximum allowed altitude. */ + int aircraft_min_altitude; + int aircraft_max_altitude; + GetAircraftFlightLevelBounds(v, &aircraft_min_altitude, &aircraft_max_altitude); + int aircraft_middle_altitude = (aircraft_min_altitude + aircraft_max_altitude) / 2; + + /* If those assumptions would be violated, aircrafts would behave fairly strange. */ + assert(aircraft_min_altitude < aircraft_middle_altitude); + assert(aircraft_middle_altitude < aircraft_max_altitude); + + int z = v->z_pos; + if (z < aircraft_min_altitude || + (HasBit(v->flags, VAF_IN_MIN_HEIGHT_CORRECTION) && z < aircraft_middle_altitude)) { + /* Ascend. And don't fly into that mountain right ahead. + * And avoid our aircraft become a stairclimber, so if we start + * correcting altitude, then we stop correction not too early. */ + SetBit(v->flags, VAF_IN_MIN_HEIGHT_CORRECTION); + z += takeoff ? 2 : 1; + } else if (!takeoff && (z > aircraft_max_altitude || + (HasBit(v->flags, VAF_IN_MAX_HEIGHT_CORRECTION) && z > aircraft_middle_altitude))) { + /* Descend lower. You are an aircraft, not an space ship. + * And again, don't stop correcting altitude too early. */ + SetBit(v->flags, VAF_IN_MAX_HEIGHT_CORRECTION); + z--; + } else if (HasBit(v->flags, VAF_IN_MIN_HEIGHT_CORRECTION) && z >= aircraft_middle_altitude) { + /* Now, we have corrected altitude enough. */ + ClrBit(v->flags, VAF_IN_MIN_HEIGHT_CORRECTION); + } else if (HasBit(v->flags, VAF_IN_MAX_HEIGHT_CORRECTION) && z <= aircraft_middle_altitude) { + /* Now, we have corrected altitude enough. */ + ClrBit(v->flags, VAF_IN_MAX_HEIGHT_CORRECTION); + } + + return z; } /** @@ -710,7 +775,7 @@ int GetAircraftFlyingAltitude(const Aircraft *v) * @param v The vehicle that is approaching the airport * @param apc The Airport Class being approached. * @param rotation The rotation of the airport. - * @returns The index of the entry point + * @return The index of the entry point */ static byte AircraftGetEntryPoint(const Aircraft *v, const AirportFTAClass *apc, Direction rotation) { @@ -787,7 +852,7 @@ static bool AircraftController(Aircraft *v) UpdateAircraftCache(v); AircraftNextAirportPos_and_Order(v); /* get aircraft back on running altitude */ - SetAircraftPosition(v, v->x_pos, v->y_pos, GetAircraftFlyingAltitude(v)); + SetAircraftPosition(v, v->x_pos, v->y_pos, GetAircraftFlightLevel(v)); return false; } } @@ -815,7 +880,9 @@ static bool AircraftController(Aircraft *v) count = UpdateAircraftSpeed(v); if (count > 0) { v->tile = 0; - int z_dest = GetAircraftFlyingAltitude(v); + + int z_dest; + GetAircraftFlightLevelBounds(v, &z_dest, NULL); /* Reached altitude? */ if (v->z_pos >= z_dest) { @@ -972,12 +1039,14 @@ static bool AircraftController(Aircraft *v) int z = v->z_pos; if (amd.flag & AMED_TAKEOFF) { - z = min(z + 2, GetAircraftFlyingAltitude(v)); + z = GetAircraftFlightLevel(v, true); + } else if (amd.flag & AMED_HOLD) { + /* Let the plane drop from normal flight altitude to holding pattern altitude */ + if (z > GetAircraftHoldMaxAltitude(v)) z--; + } else if ((amd.flag & AMED_SLOWTURN) && (amd.flag & AMED_NOSPDCLAMP)) { + z = GetAircraftFlightLevel(v); } - /* Let the plane drop from normal flight altitude to holding pattern altitude */ - if ((amd.flag & AMED_HOLD) && (z > PLANE_HOLDING_ALTITUDE)) z--; - if (amd.flag & AMED_LAND) { if (st->airport.tile == INVALID_TILE) { /* Airport has been removed, abort the landing procedure */ @@ -985,7 +1054,7 @@ static bool AircraftController(Aircraft *v) UpdateAircraftCache(v); AircraftNextAirportPos_and_Order(v); /* get aircraft back on running altitude */ - SetAircraftPosition(v, gp.x, gp.y, GetAircraftFlyingAltitude(v)); + SetAircraftPosition(v, gp.x, gp.y, GetAircraftFlightLevel(v)); continue; } diff --git a/src/newgrf_engine.cpp b/src/newgrf_engine.cpp index cdd62b7aef..0b23aad909 100644 --- a/src/newgrf_engine.cpp +++ b/src/newgrf_engine.cpp @@ -567,7 +567,7 @@ static uint32 VehicleGetVariable(Vehicle *v, const VehicleScopeResolver *object, { const Vehicle *w = v->Next(); - uint16 altitude = v->z_pos - w->z_pos; // Aircraft height - shadow height + uint16 altitude = ClampToU16(v->z_pos - w->z_pos); // Aircraft height - shadow height byte airporttype = ATP_TTDP_LARGE; const Station *st = GetTargetAirportIfValid(Aircraft::From(v)); diff --git a/src/saveload/afterload.cpp b/src/saveload/afterload.cpp index e5a1ecfd25..986803b23d 100644 --- a/src/saveload/afterload.cpp +++ b/src/saveload/afterload.cpp @@ -2374,7 +2374,10 @@ bool AfterLoadGame() UpdateAircraftCache(v); AircraftNextAirportPos_and_Order(v); /* get aircraft back on running altitude */ - if ((v->vehstatus & VS_CRASHED) == 0) SetAircraftPosition(v, v->x_pos, v->y_pos, GetAircraftFlyingAltitude(v)); + if ((v->vehstatus & VS_CRASHED) == 0) { + GetAircraftFlightLevelBounds(v, &v->z_pos, NULL); + SetAircraftPosition(v, v->x_pos, v->y_pos, GetAircraftFlightLevel(v)); + } } } } diff --git a/src/saveload/vehicle_sl.cpp b/src/saveload/vehicle_sl.cpp index af14d2d67a..2d5ff7d510 100644 --- a/src/saveload/vehicle_sl.cpp +++ b/src/saveload/vehicle_sl.cpp @@ -200,7 +200,8 @@ void UpdateOldAircraft() if (a->subtype == AIR_HELICOPTER) a->Next()->Next()->cur_speed = 32; /* set new position x,y,z */ - SetAircraftPosition(a, gp.x, gp.y, GetAircraftFlyingAltitude(a)); + GetAircraftFlightLevelBounds(a, &a->z_pos, NULL); + SetAircraftPosition(a, gp.x, gp.y, GetAircraftFlightLevel(a)); } } }