AirTypeLabelList;
+
+/**
+ * This struct contains all the info that is needed to draw and construct tracks.
+ */
+class AirTypeInfo {
+public:
+ /**
+ * Struct containing the main sprites. @note not all sprites are listed, but only
+ * the ones used directly in the code
+ */
+ struct {
+ SpriteID ground[20]; ///< ground sprite
+ SpriteID infra_catch[2][5][4]; ///< non-snowed/snowed + building number + rotation
+ SpriteID wind[4][4];
+ SpriteID radar[12];
+ SpriteID infra_no_catch[4][4]; // transmitter, snowed transmitter, tower, snowed tower
+ SpriteID runways[24]; // 2 normal + 1 cross + 4 ends
+ SpriteID aprons[10];
+ SpriteID hangars[12];
+ } base_sprites;
+
+ /**
+ * struct containing the sprites for the airport GUI. @note only sprites referred to
+ * directly in the code are listed
+ */
+ struct {
+ SpriteID add_airport_tiles;
+ SpriteID build_track_tile;
+ SpriteID change_airtype;
+ SpriteID build_catchment_infra;
+ SpriteID build_noncatchment_infra;
+ SpriteID define_landing_runway;
+ SpriteID define_nonlanding_runway;
+ SpriteID build_apron;
+ SpriteID build_helipad;
+ SpriteID build_heliport;
+ SpriteID build_hangar;
+ } gui_sprites;
+
+ /**
+ * struct containing the sprites for the airport GUI. @note only sprites referred to
+ * directly in the code are listed
+ */
+ struct {
+ SpriteID add_airport_tiles;
+ SpriteID build_track_tile;
+ SpriteID change_airtype;
+ SpriteID build_catchment_infra;
+ SpriteID build_noncatchment_infra;
+ SpriteID define_landing_runway;
+ SpriteID define_nonlanding_runway;
+ SpriteID build_apron;
+ SpriteID build_helipad;
+ SpriteID build_heliport;
+ SpriteID build_hangar;
+ } cursor;
+
+ struct {
+ StringID name;
+ StringID toolbar_caption;
+ StringID menu_text;
+ StringID replace_text;
+ } strings;
+
+ /** sprite number difference between a piece of track on a snowy ground and the corresponding one on normal ground */
+ SpriteID snow_offset;
+
+ /** bitmask to the OTHER airtypes on which an engine of THIS airtype can physically travel */
+ AirTypes compatible_airtypes;
+
+ /**
+ * Original airtype number to use when drawing non-newgrf airtypes, or when drawing stations.
+ */
+ uint8_t fallback_airtype;
+
+ /**
+ * Cost multiplier for building this air type
+ */
+ uint16_t cost_multiplier;
+
+ /**
+ * Cost multiplier for maintenance of this air type
+ */
+ uint16_t maintenance_multiplier;
+
+ /**
+ * Maximum speed for vehicles travelling on this air type
+ */
+ uint16_t max_speed;
+
+ /**
+ * Unique 32 bit air type identifier
+ */
+ AirTypeLabel label;
+
+ /**
+ * Air type labels this type provides in addition to the main label.
+ */
+ AirTypeLabelList alternate_labels;
+
+ /**
+ * Colour on mini-map
+ */
+ uint8_t map_colour;
+
+ /**
+ * Introduction date.
+ * When #INVALID_DATE or a vehicle using this airtype gets introduced earlier,
+ * the vehicle's introduction date will be used instead for this airtype.
+ * The introduction at this date is furthermore limited by the
+ * #introduction_required_types.
+ */
+ TimerGameCalendar::Date introduction_date;
+
+ /**
+ * Bitmask of airtypes that are required for this airtype to be introduced
+ * at a given #introduction_date.
+ */
+ AirTypes introduction_required_airtypes;
+
+ /**
+ * Bitmask of which other airtypes are introduced when this airtype is introduced.
+ */
+ AirTypes introduces_airtypes;
+
+ /**
+ * The sorting order of this airtype for the toolbar dropdown.
+ */
+ uint8_t sorting_order;
+
+ /**
+ * NewGRF providing the Action3 for the airtype. nullptr if not available.
+ */
+ const GRFFile *grffile[ATSG_END];
+
+ /**
+ * Sprite groups for resolving sprites
+ */
+ const SpriteGroup *group[ATSG_END];
+
+ /**
+ * Catchment area radius.
+ */
+ uint8_t catchment_radius;
+
+ /**
+ * Max number of runways.
+ */
+ uint8_t max_num_runways;
+
+ /**
+ * Minimum runway length in tiles.
+ */
+ uint8_t min_runway_length;
+
+ /**
+ * Base noise level. Each station has this noise level plus the noise created by each runway.
+ * Example: if base noise is 5 and there are 4 runways and runway level is 6,
+ * total noise level of the airport is 5 + 4 * 6 = 29
+ */
+ uint8_t base_noise_level;
+
+ /**
+ * Runway noise level.
+ */
+ uint8_t runway_noise_level;
+
+ /**
+ * Heliport availability.
+ */
+ bool heliport_availability;
+
+ /**
+ * Build airports on water.
+ */
+ bool build_on_water;
+
+
+ inline bool UsesOverlay() const
+ {
+ return this->group[ATSG_GROUND] != nullptr;
+ }
+
+ /**
+ * Offset between the current airtype and normal air. This means that:
+ * 1) All the sprites in an airset MUST be in the same order. This order
+ * is determined by normal air. Check sprites xxxx and following for this order
+ * 2) The position where the airtype is loaded must always be the same, otherwise
+ * the offset will fail.
+ */
+ inline uint GetAirTypeSpriteOffset() const
+ {
+ return 82 * this->fallback_airtype;
+ }
+};
+
+
+/**
+ * Returns a pointer to the AirType information for a given airtype
+ * @param airtype the air type which the information is requested for
+ * @return The pointer to the AirTypeInfo
+ */
+static inline const AirTypeInfo *GetAirTypeInfo(const AirType airtype)
+{
+ extern AirTypeInfo _airtypes[AIRTYPE_END];
+ assert(airtype < AIRTYPE_END);
+ return &_airtypes[airtype];
+}
+
+/**
+ * Checks if an engine of the given \a enginetype can drive
+ * on a tile with a given AirType \a tiletype.
+ * @return Whether the engine can drive on this tile.
+ * @param enginetype The AirType of the engine we are considering.
+ * @param tiletype The AirType of the tile we are considering.
+ */
+static inline bool IsCompatibleAirType(const AirType enginetype, const AirType tiletype)
+{
+ return HasBit(GetAirTypeInfo(enginetype)->compatible_airtypes, tiletype);
+}
+
+/**
+ * Checks if an engine of the given AirType can drive
+ * on a tile with a given AirType.
+ * @return Whether the engine can drive on this tile.
+ * @param enginetype The AirType of the engine we are considering.
+ * @param tiletype The AirType of the tile we are considering.
+ */
+static inline AirTypes GetCompatibleAirTypes(const AirType airtype)
+{
+ return GetAirTypeInfo(airtype)->compatible_airtypes;
+}
+
+/**
+ * Returns the cost of building the specified airtype.
+ * @param airtype The airtype being built.
+ * @return The cost multiplier.
+ */
+static inline Money AirBuildCost(AirType airtype)
+{
+ assert(airtype < AIRTYPE_END);
+ return (_price[PR_BUILD_STATION_AIRPORT] * GetAirTypeInfo(airtype)->cost_multiplier) >> 3;
+}
+
+/**
+ * Returns the 'cost' of clearing the specified airtype.
+ * @param airtype The airtype being removed.
+ * @return The cost.
+ */
+static inline Money AirClearCost(AirType airtype)
+{
+ /* Clearing airport tiles in fact earns money, but if the build cost is set
+ * very low then a loophole exists where money can be made.
+ * In this case we limit the removal earnings to 3/4s of the build
+ * cost.
+ */
+ assert(airtype < AIRTYPE_END);
+ return std::max(_price[PR_CLEAR_STATION_AIRPORT], -AirBuildCost(airtype) * 3 / 4);
+}
+
+/**
+ * Calculates the cost of air conversion
+ * @param from The airtype we are converting from
+ * @param to The airtype we are converting to
+ * @return Cost per TrackBit
+ */
+static inline Money AirConvertCost(AirType from, AirType to)
+{
+ return AirBuildCost(to) + AirClearCost(from);
+}
+
+/**
+ * Calculates the maintenance cost of a number of track bits.
+ * @param airtype The airtype to get the cost of.
+ * @param num Number of track bits of this airtype.
+ * @param total_num Total number of track bits of all airtypes.
+ * @return Total cost.
+ */
+static inline Money AirMaintenanceCost(AirType airtype, uint32_t num, uint32_t total_num)
+{
+ assert(airtype < AIRTYPE_END);
+ return (_price[PR_INFRASTRUCTURE_AIRPORT] * GetAirTypeInfo(airtype)->maintenance_multiplier * num * (1 + IntSqrt(total_num))) >> 11; // 4 bits fraction for the multiplier and 7 bits scaling.
+}
+
+static inline bool DoesHaveWaterCompatibleAirTypes(AirTypes airtypes)
+{
+ return (airtypes & AIRTYPES_WATER) != 0;
+}
+
+static inline bool AreHeliportsAvailable(AirType airtype)
+{
+ return GetAirTypeInfo(airtype)->heliport_availability;
+}
+
+Foundation GetAirFoundation(Slope tileh, TrackBits bits);
+
+
+bool HasAirTypeAvail(const CompanyID company, const AirType AirType);
+bool HasAnyAirTypesAvail(const CompanyID company);
+bool ValParamAirType(const AirType Air);
+
+AirTypes AddDateIntroducedAirTypes(AirTypes current, TimerGameCalendar::Date date);
+
+AirTypes GetCompanyAirTypes(CompanyID company, bool introduces = true);
+AirTypes GetAirTypes(bool introduces);
+
+AirType GetAirTypeByLabel(AirTypeLabel label, bool allow_alternate_labels = true);
+
+void ResetAirTypes();
+void InitAirTypes();
+AirType AllocateAirType(AirTypeLabel label);
+
+extern std::vector _sorted_airtypes;
+extern AirTypes _airtypes_hidden_mask;
+
+void AfterLoadSetAirportTileTypes();
+
+#endif /* AIR_H */
diff --git a/src/air_map.h b/src/air_map.h
new file mode 100644
index 0000000000000..139aaa966f429
--- /dev/null
+++ b/src/air_map.h
@@ -0,0 +1,1109 @@
+/*
+ * 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 air_map.h Hides the direct accesses to the map array with map accessors. */
+
+#ifndef AIR_MAP_H
+#define AIR_MAP_H
+
+#include "air_type.h"
+#include "depot_type.h"
+#include "track_func.h"
+#include "tile_map.h"
+#include "station_map.h"
+#include "viewport_func.h"
+#include "table/airporttile_ids.h"
+
+extern bool _show_airport_tracks;
+
+/**
+ * Set the airport type of an airport tile.
+ * @param t Tile to modify.
+ * @param type New type for the tile: gravel, asphalt, ...
+ * @pre IsAirportTile
+ */
+static inline void SetAirType(Tile t, AirType type)
+{
+ assert(IsValidTile(t));
+ assert(IsTileType(t, MP_STATION));
+ assert(IsAirport(t));
+ assert(type < AIRTYPE_END);
+ SB(t.m3(), 0, 4, type);
+}
+
+/**
+ * Get the airport type of an airport tile.
+ * @param t Tile to get the type of.
+ * @return The type of the tile: gravel, asphalt, ...
+ * @pre IsAirportTile
+ */
+static inline AirType GetAirType(Tile t)
+{
+ assert(IsValidTile(t));
+ assert(IsTileType(t, MP_STATION));
+ assert(IsAirport(t));
+ AirType type = (AirType)GB(t.m3(), 0, 4);
+ assert(type < AIRTYPE_END);
+ return type;
+}
+
+/**
+ * Set the airport tile type of an airport tile.
+ * @param t Tile to modify.
+ * @param type Type for the tile: hangar, runway, ...
+ * @pre IsAirportTile
+ */
+static inline void SetAirportTileType(Tile t, AirportTileType type)
+{
+ assert(IsValidTile(t));
+ assert(IsTileType(t, MP_STATION));
+ assert(IsAirport(t));
+ assert(type < ATT_END);
+ SB(t.m5(), 4, ATT_NUM_BITS, type);
+}
+
+/**
+ * Get the airport tile type of an airport tile.
+ * @param t Tile to get the type of.
+ * @return The type of the tile.
+ * @pre IsAirportTile
+ */
+static inline AirportTileType GetAirportTileType(Tile t)
+{
+ assert(IsValidTile(t));
+ assert(IsTileType(t, MP_STATION));
+ assert(IsAirport(t));
+ AirportTileType type = (AirportTileType)(GB(t.m5(), 4, ATT_NUM_BITS));
+ assert(type < ATT_END);
+ return type;
+}
+
+/**
+ * Check if a tile is a plain airport tile.
+ * @param t Tile to check.
+ * @return The type of the tile.
+ * @pre IsAirportTile
+ */
+static inline bool IsSimpleTrack(TileIndex t)
+{
+ assert(IsValidTile(t));
+ assert(IsTileType(t, MP_STATION));
+ assert(IsAirport(t));
+
+ return GetAirportTileType(t) == ATT_SIMPLE_TRACK;
+}
+
+/**
+ * Check if a tile is infrastructure of an airport.
+ * @param t Tile to check.
+ * @return The type of the tile.
+ * @pre IsAirportTile
+ */
+static inline bool IsInfrastructure(Tile t)
+{
+ assert(IsValidTile(t));
+ assert(IsTileType(t, MP_STATION));
+ assert(IsAirport(t));
+
+ return GB(t.m5(), 8 - ATT_INFRA_LAYOUT_NUM_BITS, ATT_INFRA_LAYOUT_NUM_BITS) == ATT_INFRA_LAYOUT_BITS;
+}
+
+/**
+ * Check if a tile can contain tracks for aircraft.
+ * @param t Tile to check.
+ * @return Whether the tile may contain airport tracks.
+ * @pre IsAirportTile
+ */
+static inline bool MayHaveAirTracks(TileIndex t)
+{
+ assert(IsValidTile(t));
+ assert(IsTileType(t, MP_STATION));
+ assert(IsAirport(t));
+
+ return !IsInfrastructure(t);
+}
+
+/**
+ * Infrastructure may be part of the catchment tiles of the station or not (buildings/radars).
+ * @param t Tile to modify.
+ * @param catchment Whether the tile should be marked as getting/delivering cargo.
+ * @pre IsInfrastructure
+ */
+static inline void SetCatchmentAirportType(Tile t, bool catchment)
+{
+ assert(IsValidTile(t));
+ assert(IsTileType(t, MP_STATION));
+ assert(IsAirport(t));
+ assert(IsInfrastructure(t));
+
+ SB(t.m5(), 4, 1, catchment);
+}
+
+/**
+ * Get whether the tile has catchment or not.
+ * @param t Tile to get the accessibility of.
+ * @return Whether the tile is marked as getting/delivering cargo.
+ * @pre IsInfrastructure
+ */
+static inline bool GetCatchmentAirportType(Tile t)
+{
+ assert(IsValidTile(t));
+ assert(IsTileType(t, MP_STATION));
+ assert(IsAirport(t));
+ assert(IsInfrastructure(t));
+
+ return GB(t.m5(), 4, 1);
+}
+
+/**
+ * Set the apron type of an airport tile.
+ * @param t Tile to modify.
+ * @param type Type of apron.
+ * @pre IsApron
+ */
+static inline void SetApronType(Tile t, ApronType type)
+{
+ assert(IsValidTile(t));
+ assert(IsTileType(t, MP_STATION));
+ assert(IsAirport(t));
+
+ assert(IsApron(t));
+
+ assert(type < APRON_END);
+
+ SB(t.m5(), 4, 2, type);
+}
+
+/**
+ * Is a given tile a plane apron?
+ * @param t Tile to get the type of.
+ * @return True if it is a plane apron.
+ * @pre IsApron
+ */
+static inline bool IsPlaneApron(TileIndex t)
+{
+ assert(IsValidTile(t));
+ assert(IsTileType(t, MP_STATION));
+ assert(IsAirport(t));
+ assert(IsApron(t));
+
+ return GetApronType(t) == APRON_APRON;
+}
+
+/**
+ * Is this tile a basic plane apron?
+ * @param t the tile to get the information from.
+ * @return true if and only if the tile is a plane apron.
+ */
+static inline bool IsPlaneApronTile(TileIndex t)
+{
+ return IsTileType(t, MP_STATION) &&
+ IsAirport(t) &&
+ IsApron(t) &&
+ IsPlaneApron(t);
+}
+
+/**
+ * Is a given tile a heliport or a built-in heliport?
+ * @param t Tile to get the type of.
+ * @return True if it is a heliport.
+ * @pre IsApron
+ */
+static inline bool IsHeliport(TileIndex t)
+{
+ assert(IsValidTile(t));
+ assert(IsTileType(t, MP_STATION));
+ assert(IsAirport(t));
+ assert(IsApron(t));
+
+ ApronType type = GetApronType(t);
+
+ return type == APRON_HELIPORT || type == APRON_BUILTIN_HELIPORT;
+}
+
+/**
+ * Is a given tile a heliport or a built-in heliport?
+ * @param t Tile to get the type of.
+ * @return True if it is a heliport.
+ */
+static inline bool IsHeliportTile(TileIndex t)
+{
+ assert(IsValidTile(t));
+
+ return IsTileType(t, MP_STATION) &&
+ IsAirport(t) &&
+ IsApron(t) &&
+ IsHeliport(t);
+}
+
+/**
+ * Is a given tile a helipad?
+ * @param t Tile to get the type of.
+ * @return True if it is a helipad.
+ * @pre IsApron
+ */
+static inline bool IsHelipad(TileIndex t)
+{
+ assert(IsValidTile(t));
+ assert(IsTileType(t, MP_STATION));
+ assert(IsAirport(t));
+ assert(IsApron(t));
+
+ return GetApronType(t) == APRON_HELIPAD;
+}
+
+/**
+ * Is this tile a helipad?
+ * @param t the tile to get the information from.
+ * @return true if and only if the tile is a helipad.
+ */
+static inline bool IsHelipadTile(TileIndex t)
+{
+ return IsTileType(t, MP_STATION) &&
+ IsAirport(t) &&
+ IsApron(t) &&
+ IsHelipad(t);
+}
+
+/**
+ * Get landing height for aircraft.
+ * @param t the tile to get the information from.
+ * @return landing height for aircraft.
+ */
+static inline int GetLandingHeight(TileIndex t)
+{
+ assert(IsTileType(t, MP_STATION) && IsAirport(t));
+
+ if (!IsApron(t)) return 0;
+
+ switch (GetApronType(t)) {
+ case APRON_HELIPORT:
+ return 60;
+ case APRON_BUILTIN_HELIPORT:
+ return 54;
+ default:
+ return 0;
+ }
+}
+
+/**
+ * Has this tile airport catchment?
+ * @param t the tile to get the information from.
+ * @return true if and only if the tile adds for airport catchment.
+ * @pre IsAirportTile
+ */
+static inline bool HasAirportCatchment(TileIndex t)
+{
+ assert(IsAirportTile(t));
+
+ return ((IsInfrastructure(t) && GetCatchmentAirportType(t)) || IsHeliportTile(t));
+}
+
+/**
+ * Set the rotation of an airport tile (see SetHangarDirection).
+ * @param t Tile to modify.
+ * @param dir Rotation.
+ * @pre IsAirportTile && (IsInfrastructure || IsApron)
+ */
+static inline void SetAirportTileRotation(Tile t, DiagDirection dir)
+{
+ assert(IsAirportTile(t));
+ assert(IsApron(t) || IsInfrastructure(t));
+ SB(t.m8(), 14, 2, dir);
+}
+
+/**
+ * Get the hangar direction.
+ * @param t Tile to check.
+ * @return The exit direction of the hangar.
+ * @pre IsAirportTile && (IsInfrastructure || IsApron)
+ */
+static inline DiagDirection GetAirportTileRotation(Tile t)
+{
+ assert(IsAirportTile(t));
+ assert(IsApron(t) || IsInfrastructure(t));
+ return (DiagDirection)GB(t.m8(), 14, 2);
+
+}
+
+/**
+ * Is a given tile a runway?
+ * @param t Tile to check.
+ * @return True if it is a runway.
+ * @pre IsAirportTile
+ */
+static inline bool IsRunway(Tile t)
+{
+ assert(IsValidTile(t));
+ assert(IsTileType(t, MP_STATION));
+ assert(IsAirport(t));
+
+ return GB(t.m5(), 8 - ATT_RUNWAY_LAYOUT_NUM_BITS, ATT_RUNWAY_LAYOUT_NUM_BITS) == ATT_RUNWAY_LAYOUT_BITS;
+}
+
+/**
+ * Is a given tile a runway extreme?
+ * @param t Tile to check.
+ * @return True if it is a runway extreme.
+ * @pre IsAirportTile
+ */
+static inline bool IsRunwayExtreme(TileIndex t)
+{
+ assert(IsValidTile(t));
+ assert(IsTileType(t, MP_STATION));
+ assert(IsAirport(t));
+
+ return IsRunway(t) && GetAirportTileType(t) != ATT_RUNWAY_MIDDLE;
+}
+
+/**
+ * Is a given tile a starting runway?
+ * @param t Tile to check.
+ * @return True if it is the start of a runway.
+ * @pre IsAirportTile
+ */
+static inline bool IsRunwayStart(Tile t)
+{
+ assert(IsValidTile(t));
+ assert(IsTileType(t, MP_STATION));
+ assert(IsAirport(t));
+
+ return GB(t.m5(), 8 - ATT_RUNWAY_START_LAYOUT_NUM_BITS, ATT_RUNWAY_START_LAYOUT_NUM_BITS) == ATT_RUNWAY_START_LAYOUT_BITS;
+}
+
+/**
+ * Is a given tile an ending runway?
+ * @param t Tile to check.
+ * @return True if it is the ending of a runway.
+ * @pre IsAirportTile
+ */
+static inline bool IsRunwayEnd(TileIndex t)
+{
+ assert(IsValidTile(t));
+ assert(IsTileType(t, MP_STATION));
+ assert(IsAirport(t));
+
+ return GetAirportTileType(t) == ATT_RUNWAY_END;
+}
+
+/**
+ * Is a given tile the middle section of a runway?
+ * @param t Tile to check.
+ * @return True if it is a runway middle tile.
+ * @pre IsAirportTile
+ */
+static inline bool IsPlainRunway(TileIndex t)
+{
+ assert(IsValidTile(t));
+ assert(IsTileType(t, MP_STATION));
+ assert(IsAirport(t));
+
+ return IsRunway(t) && GetAirportTileType(t) == ATT_RUNWAY_MIDDLE;
+}
+
+/**
+ * Set the runway reservation bit.
+ * @param t Tile to set.
+ * @param reserve new state for the runway reservation.
+ * @pre IsRunway
+ */
+static inline void SetReservationAsRunway(Tile t, bool reserve)
+{
+ assert(IsValidTile(t));
+ assert(IsTileType(t, MP_STATION));
+ assert(IsAirport(t));
+ assert(IsRunway(t));
+
+ SB(t.m8(), 15, 1, reserve);
+}
+
+/**
+ * Check if a runway is reserved (as a runway).
+ * @param t Tile to check.
+ * @return True iff it is reserved.
+ * @pre IsRunway
+ */
+static inline bool GetReservationAsRunway(Tile t)
+{
+ assert(IsValidTile(t));
+ assert(IsTileType(t, MP_STATION));
+ assert(IsAirport(t));
+ assert(IsRunway(t));
+
+ return HasBit(t.m8(), 15);
+}
+
+/**
+ * Set the allow landing bit on a runway start/end.
+ * @param t Tile to check.
+ * @param landing True iff runway should allow landing planes.
+ * @pre IsRunwayExtreme
+ */
+static inline void SetLandingType(Tile t, bool landing)
+{
+ assert(IsValidTile(t));
+ assert(IsTileType(t, MP_STATION));
+ assert(IsAirport(t));
+ assert(IsRunwayExtreme(t));
+
+ SB(t.m5(), 4, 1, landing);
+}
+
+/**
+ * Is a given tile a starting runway where landing is allowed?
+ * @param t Tile to check.
+ * @return True if landing is allowed.
+ * @pre IsRunwayExtreme
+ */
+static inline bool IsLandingTypeTile(Tile t)
+{
+ assert(IsValidTile(t));
+ assert(IsTileType(t, MP_STATION));
+ assert(IsAirport(t));
+ assert(IsRunwayExtreme(t));
+
+ return HasBit(t.m5(), 4);
+}
+
+
+/**
+ * Get the direction of a runway.
+ * @param t Tile to check.
+ * @return Direction of the runway.
+ * @pre IsRunwayExtreme
+ */
+static inline DiagDirection GetRunwayExtremeDirection(Tile t)
+{
+ assert(IsValidTile(t));
+ assert(IsTileType(t, MP_STATION));
+ assert(IsAirport(t));
+ assert(IsRunwayExtreme(t));
+
+ return (DiagDirection)GB(t.m8(), 12, 2);
+}
+
+/**
+ * Get the two bits for a runway middle section.
+ * @param t Tile to inspect.
+ * @return the directions of the runway.
+ * @pre IsPlainRunway
+ */
+static inline Direction GetPlainRunwayDirections(Tile t)
+{
+ assert(IsValidTile(t));
+ assert(IsTileType(t, MP_STATION));
+ assert(IsAirport(t));
+ assert(IsPlainRunway(t));
+
+ return (Direction)GB(t.m8(), 12, 3);
+}
+
+/**
+ * Get the runway tracks of a tile.
+ * @param t Tile to get the type of.
+ * @return Runway tracks of this tile.
+ * @pre IsRunway
+ */
+static inline TrackdirBits GetRunwayTrackdirs(TileIndex t)
+{
+ assert(IsValidTile(t));
+ assert(IsTileType(t, MP_STATION));
+ assert(IsAirport(t));
+ assert(IsRunway(t));
+
+ Direction dir;
+ if (IsPlainRunway(t)) {
+ dir = GetPlainRunwayDirections(t);
+ } else {
+ dir = DiagDirToDir(GetRunwayExtremeDirection(t));
+ }
+
+ static TrackdirBits dir_to_trackdirbits[] = {
+ TRACKDIR_BIT_Y_NW | TRACKDIR_BIT_X_NE,
+ TRACKDIR_BIT_X_NE,
+ TRACKDIR_BIT_X_NE | TRACKDIR_BIT_Y_SE,
+ TRACKDIR_BIT_Y_SE,
+ TRACKDIR_BIT_Y_SE | TRACKDIR_BIT_X_SW,
+ TRACKDIR_BIT_X_SW,
+ TRACKDIR_BIT_X_SW | TRACKDIR_BIT_Y_NW,
+ TRACKDIR_BIT_Y_NW,
+ };
+ return dir_to_trackdirbits[dir];
+}
+
+static inline TrackBits GetRunwayTracks(TileIndex t)
+{
+ assert(IsValidTile(t));
+ assert(IsTileType(t, MP_STATION));
+ assert(IsAirport(t));
+ assert(IsRunway(t));
+
+ TrackdirBits trackdirs = GetRunwayTrackdirs(t);
+
+ return TrackdirBitsToTrackBits(trackdirs);
+}
+
+/**
+ * Set the direction of a runway.
+ * @param t Tile to check.
+ * @param dir Direction of the runway.
+ * @pre IsRunwayExtreme
+ */
+static inline void SetRunwayExtremeDirection(Tile t, DiagDirection dir)
+{
+ assert(IsValidTile(t));
+ assert(IsTileType(t, MP_STATION));
+ assert(IsAirport(t));
+ assert(IsRunwayExtreme(t));
+
+ SB(t.m8(), 12, 2, dir);
+}
+
+/**
+ * Set the directions for a runway middle section.
+ * @param t Tile to set.
+ * @param dir the directions for the runway tile.
+ * @pre IsPlainRunway
+ */
+static inline void AddPlainRunwayDirections(Tile t, DiagDirection dir, bool first)
+{
+ assert(IsValidTile(t));
+ assert(IsTileType(t, MP_STATION));
+ assert(IsAirport(t));
+ assert(IsPlainRunway(t));
+
+ if (first) {
+ SB(t.m8(), 12, 3, DiagDirToDir(dir));
+ } else {
+ Direction pre_dir = GetPlainRunwayDirections(t);
+ Direction add_dir = DiagDirToDir(dir);
+ assert(IsDiagonalDirection(pre_dir));
+ if (pre_dir < add_dir) Swap(add_dir, pre_dir);
+ assert(((uint)DirToDiagDir(pre_dir) + (uint)DirToDiagDir(add_dir)) % 2 == 1);
+ if (add_dir + 2 == pre_dir) {
+ SB(t.m8(), 12, 3, add_dir + 1);
+ } else if (pre_dir == DIR_NW && add_dir == DIR_NE) {
+ SB(t.m8(), 12, 3, DIR_N);
+ } else {
+ NOT_REACHED();
+ }
+ }
+}
+
+/**
+ * Set the directions for a runway middle section.
+ * @param t Tile to set.
+ * @param dir the directions for the runway tile.
+ * @return true if tile is no more a runway.
+ * @pre IsPlainRunway
+ */
+static inline bool RemovePlainRunwayDirections(Tile t, DiagDirection dir)
+{
+ assert(IsValidTile(t));
+ assert(IsTileType(t, MP_STATION));
+ assert(IsAirport(t));
+ assert(IsPlainRunway(t));
+
+ Direction cur_dir = GetPlainRunwayDirections(t);
+ Direction remove_dir = DiagDirToDir(dir);
+
+ if (remove_dir == cur_dir) {
+ SB(t.m8(), 12, 4, 0);
+ SetAirportTileType(t, ATT_SIMPLE_TRACK);
+ return true;
+ } else if ((cur_dir + 1) % DIR_END == remove_dir) {
+ SB(t.m8(), 12, 3, (cur_dir - 1) & (DIR_END - 1));
+ return false;
+ } else if (cur_dir == (remove_dir + 1) % DIR_END) {
+ SB(t.m8(), 12, 3, (cur_dir + 1) % DIR_END);
+ return false;
+ }
+
+ NOT_REACHED();
+}
+
+/**
+ * Set the airport tracks a given tile has
+ * (runway tracks are stored in another place).
+ * @param t Tile to modify.
+ * @param tracks Tracks this tile has.
+ * @pre MayHaveAirTracks and !IsHangar
+ */
+static inline void SetAirportTileTracks(Tile t, TrackBits tracks)
+{
+ assert(IsAirportTile(t));
+ assert(MayHaveAirTracks(t));
+
+ SB(t.m8(), 0, 6, tracks);
+}
+
+/**
+ * Set the hangar direction.
+ * @param t Tile to modify.
+ * @param dir Exit direction of the hangar.
+ * @pre IsHangar
+ */
+static inline void SetHangarDirection(Tile t, DiagDirection dir)
+{
+ assert(IsHangar(t));
+ SB(t.m8(), 14, 2, dir);
+}
+
+/**
+ * Get the hangar direction.
+ * @param t Tile to check.
+ * @return the exit direction of the hangar.
+ * @pre IsHangar
+ */
+static inline DiagDirection GetHangarDirection(Tile t)
+{
+ assert(IsHangar(t));
+ return (DiagDirection)GB(t.m8(), 14, 2);
+
+}
+
+/**
+ * Set whether the hangar is an extended one.
+ * @param t Tile to modify.
+ * @param is_extended Whether the hangar is an extended hangar.
+ * @pre IsHangar
+ */
+static inline void SetExtendedHangar(Tile t, bool is_extended)
+{
+ assert(IsHangar(t));
+ SB(t.m5(), 5, 1, is_extended);
+}
+
+/**
+ * Check if it a tile is an extended hangar.
+ * @param t Tile.
+ * @return true if it is an extended hangar.
+ * @pre IsHangar
+ */
+static inline bool IsExtendedHangar(Tile t)
+{
+ assert(IsHangar(t));
+ return GB(t.m5(), 5, 1);
+}
+
+/**
+ * Check if it a tile is a standard hangar.
+ * @param t Tile.
+ * @return true if extended hangar.
+ * @pre IsHangar
+ */
+static inline bool IsStandardHangar(TileIndex t)
+{
+ assert(IsHangar(t));
+ return !IsExtendedHangar(t);
+}
+
+/**
+ * Return true if tile is an extended hangar.
+ * @param t Tile.
+ * @return true if extended hangar.
+ * @pre IsHangar
+ */
+static inline bool IsExtendedHangarTile(TileIndex t)
+{
+ assert(IsAirport(t));
+ return IsHangar(t) && IsExtendedHangar(t);
+}
+
+/**
+ * Return true if tile is a standard hangar.
+ * @param t Tile.
+ * @return true if standard hangar.
+ * @pre IsHangar
+ */
+static inline bool IsStandardHangarTile(TileIndex t)
+{
+ assert(IsAirport(t));
+ return IsHangar(t) && !IsExtendedHangar(t);
+}
+
+/**
+ * Get the tracks a given tile has.
+ * @param t Tile to get the tracks of.
+ * @pre MayHaveAirTracks
+ */
+static inline TrackBits GetAirportTileTracks(Tile t)
+{
+ assert(MayHaveAirTracks(t));
+
+ return (TrackBits)GB(t.m8(), 0, 6);
+}
+
+/**
+ * Get the tracks a given tile has.
+ * @param t Tile to get the tracks of.
+ * @pre MayHaveAirTracks
+ */
+static inline bool HasAirportTileSomeTrack(TileIndex t)
+{
+ assert(MayHaveAirTracks(t));
+
+ return GetAirportTileTracks(t) != TRACK_BIT_NONE;
+}
+
+/**
+ * Check if a tile has a given airport track.
+ * @param t Tile to check.
+ * @param track Track to check.
+ * @return True iff tile has given track.
+ * @pre MayHaveAirTracks
+ */
+static inline bool HasAirportTileTrack(TileIndex t, Track track)
+{
+ assert(MayHaveAirTracks(t));
+ return HasTrack(GetAirportTileTracks(t), track);
+}
+
+/**
+ * Return the reserved airport track bits of the tile.
+ * @param t Tile to query.
+ * @return Reserved trackbits.
+ * @pre MayHaveAirTracks
+ */
+static inline TrackBits GetReservedAirportTracks(Tile t)
+{
+ assert(IsTileType(t, MP_STATION));
+ assert(IsAirport(t));
+ assert(MayHaveAirTracks(t));
+
+ return (TrackBits)GB(t.m8(), 6, 6);
+}
+
+/**
+ * Check if a given track is reserved.
+ * @param t Tile to query.
+ * @param track Track to check.
+ * @return True iff the track is reserved on tile t.
+ * @pre MayHaveAirTracks
+ */
+static inline bool HasAirportTrackReserved(TileIndex t, Track track)
+{
+ assert(IsTileType(t, MP_STATION));
+ assert(IsAirport(t));
+ assert(MayHaveAirTracks(t));
+
+ return HasTrack(GetReservedAirportTracks(t), track);
+}
+
+/**
+ * Check if an airport tile has any reserved track.
+ * @param t Tile to query.
+ * @return True iff the track is reserved on tile t.
+ * @pre MayHaveAirTracks
+ */
+static inline bool HasAirportTrackReserved(TileIndex t)
+{
+ assert(IsTileType(t, MP_STATION));
+ assert(IsAirport(t));
+ assert(MayHaveAirTracks(t));
+
+ return GetReservedAirportTracks(t) != 0;
+}
+
+/**
+ * Are some of this tracks reserved?
+ * @param t Tile to check.
+ * @param tracks Tracks to check.
+ * @return True if any of the given tracks \a tracks is reserved on tile \a t.
+ */
+static inline bool HasAirportTracksReserved(TileIndex t, TrackBits tracks)
+{
+ assert(IsAirportTile(t));
+ assert(MayHaveAirTracks(t));
+ return (GetReservedAirportTracks(t) & tracks) != TRACK_BIT_NONE;
+}
+
+/**
+ * Set the reserved tracks of an airport tile.
+ * @param t Tile where to reserve.
+ * @param trackbits The tracks that will be reserved on tile \a tile
+ * @pre MayHaveAirTracks
+ */
+static inline bool SetAirportTracksReservation(Tile t, TrackBits tracks)
+{
+ assert(IsTileType(t, MP_STATION));
+ assert(IsAirport(t));
+ assert(MayHaveAirTracks(t));
+
+ TrackBits already_set = GetReservedAirportTracks(t);
+ if ((tracks & ~already_set) == 0) return false;
+
+ tracks |= already_set;
+
+ SB(t.m8(), 6, 6, tracks);
+
+ if (_show_airport_tracks) MarkTileDirtyByTile(t);
+ return true;
+}
+
+/**
+ * Reserve an airport track on a tile.
+ * @param t Tile where to reserve.
+ * @param track The track that will be reserved on tile \a tile
+ * @pre MayHaveAirTracks
+ */
+static inline void SetAirportTrackReservation(TileIndex t, Track track)
+{
+ assert(IsTileType(t, MP_STATION));
+ assert(IsAirport(t));
+ assert(MayHaveAirTracks(t));
+
+ SetAirportTracksReservation(t, TrackToTrackBits(track));
+}
+
+/**
+ * Remove an airport track on a tile.
+ * @param t Tile where to free the reserved track.
+ * @param track The track that will be freed on tile \a tile
+ * return whether the track has been removed
+ * @pre MayHaveAirTracks
+ */
+static inline bool RemoveAirportTrackReservation(Tile t, Track track)
+{
+ assert(IsTileType(t, MP_STATION));
+ assert(IsAirport(t));
+ assert(MayHaveAirTracks(t));
+
+ TrackBits reserved = GetReservedAirportTracks(t);
+ TrackBits tracks = TrackToTrackBits(track);
+ if ((tracks & reserved) == TRACK_BIT_NONE) return false;
+ reserved &= ~tracks;
+
+ SB(t.m8(), 6, 6, reserved);
+ if (_show_airport_tracks) MarkTileDirtyByTile(t);
+
+ return true;
+}
+
+/**
+ * Are some of this tracks reserved?
+ * @param t Tile to check.
+ * @param tracks Tracks to check.
+ * @return True if any of the given tracks \a tracks is reserved on tile \a t.
+ */
+static inline bool HasAirportTileAnyReservation(TileIndex t)
+{
+ assert(IsAirportTile(t));
+ assert(MayHaveAirTracks(t));
+ return (GetReservedAirportTracks(t) != TRACK_BIT_NONE) ||
+ (IsRunway(t) && GetReservationAsRunway(t));
+}
+
+/**
+ * Whether the gfx of the tile are controlled through its airtype.
+ * @param t Tile.
+ * @return whether it is an airtype controlled airtype.
+ */
+static inline bool HasAirtypeGfx(Tile t)
+{
+ assert(IsAirportTile(t));
+ return (bool)GB(t.m6(), 7, 1);
+}
+
+/**
+ * Set the gfx type of the tile.
+ * @param t Tile to set the gfx type of.
+ * @param airtype_controlled whether the gfx of the tile are handled via its airtype.
+ */
+static inline void SetAirGfxType(Tile t, bool airtype_controlled)
+{
+ assert(IsAirportTile(t));
+ SB(t.m6(), 7, 1, airtype_controlled);
+}
+
+/**
+ * Get the gfx_id a given tile has.
+ * @param t Tile to get the gfx of.
+ */
+static inline AirportTiles GetTileAirportGfx(Tile t)
+{
+ assert(IsAirportTile(t));
+ return (AirportTiles)GB(t.m4(), 0, 8);
+}
+
+/**
+ * Set the gfx_id a given tile has.
+ * @param t Tile to set the gfx of.
+ */
+static inline void SetTileAirportGfx(Tile t, AirportTiles at)
+{
+ assert(IsAirportTile(t));
+ SB(t.m4(), 0, 8, at);
+}
+
+/**
+ * Get the sprite for an airport tile.
+ * @param t Tile to get the sprite of.
+ * @return AirportTile ID.
+ */
+StationGfx GetAirportGfx(TileIndex t);
+
+StationGfx GetTranslatedAirportTileID(StationGfx gfx);
+
+/**
+ * Set the gfx_id a given tile has for an airtype.
+ * @param t Tile to set the gfx of.
+ * @pre IsInfrastructure
+ */
+static inline void SetAirportGfxForAirtype(Tile t, AirportTiles at)
+{
+ assert(IsAirportTile(t));
+ assert(IsInfrastructure(t));
+ SB(t.m8(), 0, 8, at);
+}
+
+/**
+ * Get the gfx_id a given tile has for an airtype.
+ * @param t Tile to get the gfx of.
+ */
+static inline AirportTiles GetAirportGfxForAirtype(Tile t)
+{
+ assert(IsAirportTile(t));
+ return (AirportTiles)GB(t.m8(), 0, 8);
+}
+
+/**
+ * Return whether is an airport tile of a given station.
+ * @param t tile.
+ * @param st_id ID of the station.
+ */
+static inline bool IsAirportTileOfStation(TileIndex t, StationID st_id)
+{
+ assert(IsValidTile(t));
+
+ return IsAirportTile(t) && st_id == GetStationIndex(t);
+}
+
+/**
+ * Airport ground types. Valid densities in comments after the enum.
+ */
+enum AirportGround {
+ AG_GRASS = 0, ///< 0-3
+ AG_SNOW = 1, ///< 0-3
+ AG_DESERT = 2, ///< 1,3
+ AG_AIRTYPE = 3, ///< 0-3 snow density
+};
+
+/**
+ * Get the type of airport ground to draw on a tile.
+ * @param t the tile to get the airport ground type of
+ * @pre IsAirportTile(t)
+ * @return the ground type
+ */
+inline AirportGround GetAirportGround(Tile t)
+{
+ assert(IsAirportTile(t));
+ return (AirportGround)GB(t.m5(), 0, 2);
+}
+
+/**
+ * Get the ground density of an airport tile.
+ * @param t the tile to get the density of
+ * @pre IsAirportTile(t)
+ * @return the density
+ */
+inline uint GetAirportGroundDensity(Tile t)
+{
+ assert(IsAirportTile(t));
+ return GB(t.m5(), 2, 2);
+}
+
+/**
+ * Get whether an airport tile has snow.
+ * @param t the tile to check
+ * @pre IsAirportTile(t)
+ * @return whether the tile has snow.
+ */
+inline bool HasAirportGroundSnow(Tile t)
+{
+ assert(IsAirportTile(t));
+ AirportGround airport_ground = GetAirportGround(t);
+ return (airport_ground == AG_SNOW || airport_ground == AG_AIRTYPE) && GetAirportGroundDensity(t) != 0;
+}
+
+/**
+ * Increment the ground density of an airport tile.
+ * @param t the tile to increment the density of
+ * @param d the amount to increment the density with
+ * @pre IsAirportTile(t)
+ */
+inline void AddAirportGroundDensity(Tile t, int d)
+{
+ assert(IsAirportTile(t));
+ SB(t.m5(), 2, 2, GetAirportGroundDensity(t) + d);
+}
+
+/**
+ * Set the ground density of an airport tile.
+ * @param t the tile to set the density of
+ * @param d the new density
+ * @pre IsAirportTile(t)
+ */
+inline void SetAirportGroundDensity(Tile t, uint d)
+{
+ assert(IsAirportTile(t));
+ SB(t.m5(), 2, 2, d);
+}
+
+/**
+ * Get the counter used to advance to the next ground density.
+ * @param t the tile to get the counter of
+ * @return the value of the counter
+ * @pre IsAirportTile(t)
+ */
+inline uint GetAirportGroundCounter(Tile t)
+{
+ assert(IsAirportTile(t));
+ return GB(t.m6(), 0, 3);
+}
+
+/**
+ * Increments the counter used to advance to the next ground density.
+ * @param t the tile to increment the counter of
+ * @param c the amount to increment the counter with
+ * @pre IsAirportTile(t)
+ */
+inline void AddAirportGroundCounter(Tile t, int c)
+{
+ assert(IsAirportTile(t));
+ SB(t.m6(), 0, 3, GetAirportGroundCounter(t) + c);
+}
+
+/**
+ * Sets the counter used to advance to the next ground density.
+ * @param t the tile to set the counter of
+ * @param c the amount to set the counter to
+ * @pre IsAirportTile(t)
+ */
+inline void SetAirportGroundCounter(Tile t, uint c)
+{
+ assert(IsAirportTile(t));
+ SB(t.m6(), 0, 3, c);
+}
+
+
+/**
+ * Sets ground type and density in one go. Also sets the counter to 0.
+ * @param t the tile to set the ground type and density for
+ * @param type the new ground type of the tile
+ * @param density the density of the ground tile
+ * @pre IsAirportTile(t)
+ */
+inline void SetAirportGroundAndDensity(Tile t, AirportGround type, uint density)
+{
+ assert(IsAirportTile(t));
+ SB(t.m5(), 0, 2, type);
+ SB(t.m5(), 2, 2, density);
+ SetAirportGroundCounter(t, 0);
+}
+
+#endif /* AIR_MAP_H */
diff --git a/src/air_type.h b/src/air_type.h
new file mode 100644
index 0000000000000..a16a53a6ef0ce
--- /dev/null
+++ b/src/air_type.h
@@ -0,0 +1,100 @@
+/*
+ * 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 air_type.h The different types of air tracks. */
+
+#ifndef AIR_TYPE_H
+#define AIR_TYPE_H
+
+#include "core/enum_type.hpp"
+
+typedef uint32_t AirTypeLabel;
+
+static const AirTypeLabel AIRTYPE_LABEL_GRAVEL = 'GRVL';
+static const AirTypeLabel AIRTYPE_LABEL_ASPHALT = 'ASPH';
+static const AirTypeLabel AIRTYPE_LABEL_WATER = 'WATR';
+static const AirTypeLabel AIRTYPE_LABEL_ASPHALT_DARK = 'ASPD';
+static const AirTypeLabel AIRTYPE_LABEL_ASPHALT_YELLOW = 'ASPY';
+
+
+/** Enumeration for all possible airtypes. */
+enum AirType : uint8_t {
+ AIRTYPE_BEGIN = 0, ///< Used for iterations
+ AIRTYPE_GRAVEL = 0, ///< Gravel surface
+ AIRTYPE_ASPHALT = 1, ///< Asphalt surface
+ AIRTYPE_WATER = 2, ///< Water surface
+ AIRTYPE_DARK = 3,
+ AIRTYPE_YELLOW = 4,
+ AIRTYPE_END = 16, ///< Used for iterations
+ INVALID_AIRTYPE = 0xFF, ///< Flag for invalid airtype
+
+ DEF_AIRTYPE_FIRST = AIRTYPE_END, ///< Default airtype: first available
+ DEF_AIRTYPE_LAST, ///< Default airtype: last available
+ DEF_AIRTYPE_MOST_USED, ///< Default airtype: most used
+};
+
+/** Allow incrementing of airtype variables */
+DECLARE_POSTFIX_INCREMENT(AirType)
+
+/** The different airtypes we support, but then a bitmask of them. */
+enum AirTypes : uint64_t {
+ AIRTYPES_NONE = 0, ///< No rail types
+ AIRTYPES_GRAVEL = 1 << AIRTYPE_GRAVEL, ///< Gravel surface
+ AIRTYPES_ASPHALT = 1 << AIRTYPE_ASPHALT, ///< Asphalt surface
+ AIRTYPES_WATER = 1 << AIRTYPE_WATER, ///< Water surface
+ AIRTYPES_DARK = 1 << AIRTYPE_DARK, ///< Dark surface
+ AIRTYPES_YELLOW = 1 << AIRTYPE_YELLOW, ///< Yellow surface
+ AIRTYPES_ALL = AIRTYPES_GRAVEL | AIRTYPES_ASPHALT | AIRTYPES_WATER | AIRTYPES_DARK | AIRTYPES_YELLOW,
+ INVALID_AIRTYPES = UINT16_MAX, ///< Invalid airtypes
+};
+DECLARE_ENUM_AS_BIT_SET(AirTypes)
+
+/** Types of tiles an airport can have. */
+enum AirportTileType : uint8_t {
+ ATT_BEGIN = 0,
+ ATT_INFRASTRUCTURE_NO_CATCH = ATT_BEGIN, // 0000
+ ATT_INFRASTRUCTURE_WITH_CATCH = 1, // 0001
+ ATT_SIMPLE_TRACK = 2, // 0010
+ ATT_WAITING_POINT = 3, // 0011
+ ATT_APRON_NORMAL = 4, // 0100
+ ATT_APRON_HELIPAD = 5, // 0101
+ ATT_APRON_HELIPORT = 6, // 0110
+ ATT_APRON_BUILTIN_HELIPORT = 7, // 0111
+ ATT_HANGAR_STANDARD = 8, // 1000
+ ATT_HANGAR_EXTENDED = 10, // 1010
+ ATT_RUNWAY_MIDDLE = 12, // 1100
+ ATT_RUNWAY_END = 13, // 1101
+ ATT_RUNWAY_START_NO_LANDING = 14, // 1110
+ ATT_RUNWAY_START_ALLOW_LANDING = 15, // 1111
+ ATT_END,
+
+ ATT_INVALID,
+
+ ATT_NUM_BITS = 4,
+ ATT_INFRA_LAYOUT_NUM_BITS = 3,
+ ATT_INFRA_LAYOUT_BITS = 0,
+ ATT_APRON_LAYOUT_NUM_BITS = 2,
+ ATT_APRON_LAYOUT_BITS = 1,
+ ATT_HANGAR_LAYOUT_NUM_BITS = 2,
+ ATT_HANGAR_LAYOUT_BITS = 2,
+ ATT_RUNWAY_LAYOUT_NUM_BITS = 2,
+ ATT_RUNWAY_LAYOUT_BITS = 3,
+ ATT_RUNWAY_START_LAYOUT_NUM_BITS = 3,
+ ATT_RUNWAY_START_LAYOUT_BITS = 7,
+};
+
+enum ApronType : uint8_t {
+ APRON_BEGIN = 0,
+ APRON_APRON = APRON_BEGIN,
+ APRON_HELIPAD,
+ APRON_HELIPORT,
+ APRON_BUILTIN_HELIPORT,
+ APRON_END,
+ APRON_INVALID = APRON_END,
+};
+
+#endif /* AIR_TYPE_H */
diff --git a/src/aircraft.h b/src/aircraft.h
index 6c47d6df56037..a0044c1dd1978 100644
--- a/src/aircraft.h
+++ b/src/aircraft.h
@@ -31,11 +31,11 @@ enum AircraftSubType {
AIR_HELICOPTER = 0, ///< an helicopter
AIR_AIRCRAFT = 2, ///< an airplane
AIR_SHADOW = 4, ///< shadow of the aircraft
- AIR_ROTOR = 6, ///< rotor of an helicopter
+ AIR_ROTOR = 6, ///< rotor of a helicopter
};
/** Flags for air vehicles; shared with disaster vehicles. */
-enum AirVehicleFlags {
+enum AirVehicleFlags : uint8_t {
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
@@ -44,20 +44,20 @@ enum AirVehicleFlags {
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.
- VAF_HELI_DIRECT_DESCENT = 3, ///< The helicopter is descending directly at its destination (helipad or in front of hangar)
+ VAF_CAN_T_LAND = 3, ///< The vehicle cannot land on destination airport.
};
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);
-void UpdateAirplanesOnNewStation(const Station *st);
void UpdateAircraftCache(Aircraft *v, bool update_range = false);
-void AircraftLeaveHangar(Aircraft *v, Direction exit_dir);
-void AircraftNextAirportPos_and_Order(Aircraft *v);
+void AircraftUpdateNextPos(Aircraft *v);
void SetAircraftPosition(Aircraft *v, int x, int y, int z);
+void AssignLandingTile(Aircraft *v, TileIndex tile);
+TileIndex FindClosestLandingTile(Aircraft *v);
+
void GetAircraftFlightLevelBounds(const Vehicle *v, int *min, int *max);
template
int GetAircraftFlightLevel(T *v, bool takeoff = false);
@@ -68,15 +68,153 @@ struct AircraftCache {
uint16_t cached_max_range; ///< Cached maximum range.
};
+enum AircraftStateBits : uint8_t {
+ ASB_FLYING_CRASHING = 3,
+ ASB_FLYING_ON_AIRPORT = 4,
+ ASB_FREE_FLIGHT = 5,
+ ASB_ON_HOLD = 6,
+ ASB_NO_HARD_LIMIT_SPEED = 7,
+};
+
+/** States of aircraft. */
+enum AircraftState : uint8_t {
+ AS_FLYING_CRASHING = 1 << ASB_FLYING_CRASHING,
+ AS_FLYING_FREE_FLIGHT = 1 << ASB_FREE_FLIGHT,
+ AS_FLYING_ON_AIRPORT = 1 << ASB_FLYING_ON_AIRPORT,
+ AS_ON_HOLD = 1 << ASB_ON_HOLD,
+ AS_NO_HARD_LIMIT_SPEED = 1 << ASB_NO_HARD_LIMIT_SPEED,
+
+ AS_BEGIN = 0,
+ AS_HANGAR = AS_BEGIN,
+ AS_IDLE = 1,
+ AS_TERMINAL_BEGIN,
+ AS_APRON = AS_TERMINAL_BEGIN,
+ AS_HELIPAD = 3,
+ AS_HELIPORT = 4,
+ AS_BUILTIN_HELIPORT = 5,
+ AS_TERMINAL_END = AS_BUILTIN_HELIPORT,
+ AS_MOVING = 6,
+ AS_RUNNING = AS_MOVING,
+
+ AS_START_TAKEOFF = 7,
+ AS_TAKEOFF_BEFORE_FLYING = 8,
+ AS_LANDED = 9 | AS_NO_HARD_LIMIT_SPEED,
+
+ /* Flying while keeping some reserved track on the airport. */
+ AS_FLYING_TAKEOFF = AS_FLYING_ON_AIRPORT,
+ AS_FLYING_HELICOPTER_TAKEOFF,
+ AS_DESCENDING = AS_FLYING_ON_AIRPORT | AS_NO_HARD_LIMIT_SPEED,
+ AS_FLYING_LANDING,
+ AS_FLYING_HELICOPTER_LANDING,
+ AS_ON_HOLD_APPROACHING = AS_FLYING_ON_AIRPORT | AS_NO_HARD_LIMIT_SPEED | AS_ON_HOLD,
+
+ /* Flying free with no reservation on any airport tile. */
+ AS_FLYING = AS_FLYING_FREE_FLIGHT | AS_NO_HARD_LIMIT_SPEED,
+ AS_FLYING_FALLING,
+ AS_FLYING_NO_DEST,
+ AS_FLYING_LEAVING_AIRPORT,
+ AS_ON_HOLD_WAITING = AS_FLYING_FREE_FLIGHT | AS_FLYING_ON_AIRPORT | AS_NO_HARD_LIMIT_SPEED | AS_ON_HOLD,
+
+ AS_FLYING_MASK = AS_FLYING_FREE_FLIGHT | AS_FLYING_ON_AIRPORT,
+
+ INVALID_AS = 0xFF,
+
+ /* Helicopter rotor animation states. */
+ HRS_ROTOR_STOPPED = 0,
+ HRS_ROTOR_MOVING_1 = 1,
+ HRS_ROTOR_MOVING_2 = 2,
+ HRS_ROTOR_MOVING_3 = 3,
+ HRS_ROTOR_NUM_STATES = 3,
+};
+DECLARE_ENUM_AS_ADDABLE(AircraftState)
+
+inline bool IsTerminalState(AircraftState as)
+{
+ return as >= AS_TERMINAL_BEGIN && as <= AS_TERMINAL_END;
+}
+
+enum AircraftPos : uint8_t {
+ AP_BEGIN = 0,
+ AP_DEFAULT = AP_BEGIN,
+ AP_HELICOPTER_HOLD_START,
+ AP_HELICOPTER_HOLD_2,
+ AP_HELICOPTER_HOLD_3,
+ AP_HELICOPTER_HOLD_4,
+ AP_HELICOPTER_HOLD_5,
+ AP_HELICOPTER_HOLD_6,
+ AP_HELICOPTER_HOLD_7,
+ AP_HELICOPTER_HOLD_END,
+ AP_HELIPORT_DEST,
+ AP_BUILTIN_HELIPORT_DEST,
+ AP_START_TAKE_OFF,
+ AP_PLANE_BEFORE_FLYING,
+ AP_PLANE_START_FLYING,
+ AP_PLANE_LEAVE_AIRPORT,
+ AP_PLANE_HOLD_START,
+ AP_PLANE_HOLD_2,
+ AP_PLANE_HOLD_3,
+ AP_PLANE_HOLD_4,
+ AP_PLANE_HOLD_5,
+ AP_PLANE_HOLD_6,
+ AP_PLANE_HOLD_7,
+ AP_PLANE_HOLD_8,
+ AP_PLANE_HOLD_END,
+ AP_PLANE_DESCENDING,
+ AP_PLANE_LANDING,
+
+ AP_END
+};
+DECLARE_ENUM_AS_ADDABLE(AircraftPos)
+
+/**
+ * Struct that contains the offsets in x and y of a position
+ * that an aircraft must reach calculated from its destination tile.
+ */
+struct AircraftPosition {
+ AircraftPos pos;
+ int x;
+ int y;
+};
+
+struct AircraftPathChoice {
+ std::deque td;
+ std::deque tile; ///< Kept for debugging purposes. Should be removed in the future.
+
+ inline bool empty() const { return this->td.empty(); }
+
+ inline size_t size() const
+ {
+ assert(this->td.size() == this->tile.size());
+ return this->td.size();
+ }
+
+ inline void clear()
+ {
+ this->td.clear();
+ this->tile.clear();
+ }
+
+ inline void pop_front()
+ {
+ assert(!this->empty());
+ this->td.pop_front();
+ this->tile.pop_front();
+ }
+};
+
/**
* Aircraft, helicopters, rotors and their shadows belong to this class.
*/
struct Aircraft final : public SpecializedVehicle {
- uint16_t crashed_counter; ///< Timer for handling crash animations.
- uint8_t pos; ///< Next desired position of the aircraft.
- uint8_t previous_pos; ///< Previous desired position of the aircraft.
+ AircraftPathChoice path; ///< Cached path choices
+ uint16_t crashed_counter; ///< Timer for handling crash animations.
+ Trackdir trackdir; ///< Current trackdir while aircraft is on land.
+ AircraftState state; ///< Current aircraft state. @see AircraftState
StationID targetairport; ///< Airport to go to next.
- uint8_t state; ///< State of the airport. @see AirportMovementStates
+
+ Trackdir next_trackdir; ///< Desired trackdir when rotating at airport, or entry trackdir to an airport while flying.
+ AircraftPosition next_pos; ///< next x_pos and y_pos coordinate.
+
Direction last_direction;
uint8_t number_consecutive_turns; ///< Protection to prevent the aircraft of making a lot of turns in order to reach a specific point.
uint8_t turn_counter; ///< Ticks between each turn to prevent > 45 degree turns.
@@ -103,9 +241,26 @@ struct Aircraft final : public SpecializedVehicle {
bool IsInDepot() const override
{
assert(this->IsPrimaryVehicle());
- return (this->vehstatus & VS_HIDDEN) != 0 && IsHangarTile(this->tile);
+ return this->state == AS_HANGAR;
+ }
+
+ Trackdir GetVehicleTrackdir() const override
+ {
+ assert(this->IsPrimaryVehicle());
+ return this->trackdir;
+ }
+
+ TileIndex GetNextTile() const
+ {
+ assert(this->IsPrimaryVehicle());
+ return this->Next()->dest_tile;
}
+ StationID GetCurrentAirportID() const;
+ Station *GetCurrentAirport() const;
+ void UpdateNextTile(TileIndex tile);
+ void SetDestTile(TileIndex tile) override;
+
bool Tick() override;
void OnNewCalendarDay() override;
void OnNewEconomyDay() override;
@@ -113,6 +268,7 @@ struct Aircraft final : public SpecializedVehicle {
TileIndex GetOrderStationLocation(StationID station) override;
TileIndex GetCargoTile() const override { return this->First()->tile; }
ClosestDepot FindClosestDepot() override;
+ TileIndex GetOrderHangarLocation(DepotID depot);
/**
* Check if the aircraft type is a normal flying device; eg
@@ -128,6 +284,11 @@ struct Aircraft final : public SpecializedVehicle {
return this->subtype <= AIR_AIRCRAFT;
}
+ inline bool IsHelicopter() const
+ {
+ return this->subtype == AIR_HELICOPTER;
+ }
+
/**
* Get the range of this aircraft.
* @return Range in tiles or 0 if unlimited range.
@@ -136,6 +297,67 @@ struct Aircraft final : public SpecializedVehicle {
{
return this->acache.cached_max_range;
}
+
+ /**
+ * Check whether the vehicle is flying.
+ * @return True if the vehicle is currently flying: from taking off until landing.
+ */
+ bool IsAircraftFlying() const
+ {
+ assert(this->IsNormalAircraft());
+ return (this->state & AS_FLYING_MASK) != 0;
+ }
+
+ /**
+ * Check whether the vehicle is flying and has no reserved tile on any airport.
+ * @return True if the vehicle is currently freely flying.
+ */
+ bool IsAircraftFreelyFlying() const
+ {
+ assert(this->IsNormalAircraft());
+ return HasBit(this->state, ASB_FREE_FLIGHT);
+ }
+
+ /**
+ * Check whether the vehicle is flying and falling, about to crash.
+ * @return True if the vehicle is currently flying and falling.
+ */
+ bool IsAircraftFalling() const
+ {
+ assert(this->IsNormalAircraft());
+ return (this->state == AS_FLYING_FALLING);
+ }
+
+ /**
+ * Check whether the vehicle is flying rotating around its destination.
+ * @return True if the vehicle is currently flying around its destination.
+ */
+ bool IsAircraftOnHold() const
+ {
+ assert(this->IsNormalAircraft());
+ return HasBit(this->state, ASB_ON_HOLD);
+ }
+
+ void SetWaitTime(uint16_t wait_counter)
+ {
+ this->wait_counter = wait_counter;
+ }
+
+ void ClearWaitTime()
+ {
+ this->SetWaitTime(0);
+ }
+
+ bool IsWaiting() const
+ {
+ return this->wait_counter > 0;
+ }
+
+ void AdvanceWaitTime()
+ {
+ assert (this->IsWaiting());
+ this->wait_counter--;
+ }
};
void GetRotorImage(const Aircraft *v, EngineImageType image_type, VehicleSpriteSeq *result);
diff --git a/src/aircraft_cmd.cpp b/src/aircraft_cmd.cpp
index 44d76650a171b..d81a6afa37d3c 100644
--- a/src/aircraft_cmd.cpp
+++ b/src/aircraft_cmd.cpp
@@ -11,10 +11,9 @@
*/
#include "stdafx.h"
+#include "air.h"
#include "aircraft.h"
-#include "landscape.h"
#include "news_func.h"
-#include "newgrf_engine.h"
#include "newgrf_sound.h"
#include "spritecache.h"
#include "error_func.h"
@@ -23,24 +22,23 @@
#include "window_func.h"
#include "timer/timer_game_calendar.h"
#include "timer/timer_game_economy.h"
+#include "strings_func.h"
#include "vehicle_func.h"
#include "sound_func.h"
-#include "cheat_type.h"
-#include "company_base.h"
#include "ai/ai.hpp"
#include "game/game.hpp"
-#include "company_func.h"
#include "effectvehicle_func.h"
-#include "station_base.h"
-#include "engine_base.h"
-#include "core/random_func.hpp"
-#include "core/backup_type.hpp"
#include "zoom_func.h"
#include "disaster_vehicle.h"
#include "newgrf_airporttiles.h"
#include "framerate_type.h"
#include "aircraft_cmd.h"
#include "vehicle_cmd.h"
+#include "air_map.h"
+#include "pbs_air.h"
+
+#include "pathfinder/yapf/yapf.h"
+#include "pathfinder/follow_track.hpp"
#include "table/strings.h"
@@ -58,15 +56,9 @@ void Aircraft::UpdateDeltaXY()
case AIR_AIRCRAFT:
case AIR_HELICOPTER:
- switch (this->state) {
- default: break;
- case ENDTAKEOFF:
- case LANDING:
- case HELILANDING:
- case FLYING:
- this->x_extent = 24;
- this->y_extent = 24;
- break;
+ if (this->IsAircraftFlying()) {
+ this->x_extent = 24;
+ this->y_extent = 24;
}
this->z_extent = 5;
break;
@@ -83,12 +75,41 @@ void Aircraft::UpdateDeltaXY()
}
}
-static bool AirportMove(Aircraft *v, const AirportFTAClass *apc);
-static bool AirportSetBlocks(Aircraft *v, const AirportFTA *current_pos, const AirportFTAClass *apc);
-static bool AirportHasBlock(Aircraft *v, const AirportFTA *current_pos, const AirportFTAClass *apc);
-static bool AirportFindFreeTerminal(Aircraft *v, const AirportFTAClass *apc);
-static bool AirportFindFreeHelipad(Aircraft *v, const AirportFTAClass *apc);
-static void CrashAirplane(Aircraft *v);
+void Aircraft::MarkDirty()
+{
+ this->colourmap = PAL_NONE;
+ this->UpdateViewport(true, false);
+ if (this->subtype == AIR_HELICOPTER) {
+ GetRotorImage(this, EIT_ON_MAP, &this->Next()->Next()->sprite_cache.sprite_seq);
+ }
+}
+
+/**
+ * Sets the visibility of an aircraft when it enters or leaves a hangar.
+ * @param v Aircraft
+ * @param visible Whether it should be visible or not.
+ */
+void SetVisibility(Aircraft *v, bool visible)
+{
+ assert(IsHangarTile(v->tile));
+
+ if (visible) {
+ v->vehstatus &= ~VS_HIDDEN;
+ v->Next()->vehstatus &= ~VS_HIDDEN;
+ if (v->IsHelicopter()) v->Next()->Next()->vehstatus &= ~VS_HIDDEN;
+ } else {
+ v->vehstatus |= VS_HIDDEN;
+ v->Next()->vehstatus |= VS_HIDDEN;
+ /* Hide and stop rotor for helicopters. */
+ if (v->IsHelicopter()) {
+ v->Next()->Next()->vehstatus |= VS_HIDDEN;
+ v->Next()->Next()->cur_speed = 0;
+ }
+ }
+
+ v->UpdateViewport(true, true);
+ v->UpdatePosition();
+}
static const SpriteID _aircraft_sprite[] = {
0x0EB5, 0x0EBD, 0x0EC5, 0x0ECD,
@@ -106,71 +127,6 @@ bool IsValidImageIndex(uint8_t image_index)
return image_index < lengthof(_aircraft_sprite);
}
-/** Helicopter rotor animation states */
-enum HelicopterRotorStates {
- HRS_ROTOR_STOPPED,
- HRS_ROTOR_MOVING_1,
- HRS_ROTOR_MOVING_2,
- HRS_ROTOR_MOVING_3,
-};
-
-/**
- * Find the nearest hangar to v
- * INVALID_STATION is returned, if the company does not have any suitable
- * airports (like helipads only)
- * @param v vehicle looking for a hangar
- * @return the StationID if one is found, otherwise, INVALID_STATION
- */
-static StationID FindNearestHangar(const Aircraft *v)
-{
- uint best = 0;
- StationID index = INVALID_STATION;
- TileIndex vtile = TileVirtXY(v->x_pos, v->y_pos);
- const AircraftVehicleInfo *avi = AircraftVehInfo(v->engine_type);
- uint max_range = v->acache.cached_max_range_sqr;
-
- /* Determine destinations where it's coming from and where it's heading to */
- const Station *last_dest = nullptr;
- const Station *next_dest = nullptr;
- if (max_range != 0) {
- if (v->current_order.IsType(OT_GOTO_STATION) ||
- (v->current_order.IsType(OT_GOTO_DEPOT) && (v->current_order.GetDepotActionType() & ODATFB_NEAREST_DEPOT) == 0)) {
- last_dest = Station::GetIfValid(v->last_station_visited);
- next_dest = Station::GetIfValid(v->current_order.GetDestination());
- } else {
- last_dest = GetTargetAirportIfValid(v);
- next_dest = Station::GetIfValid(v->GetNextStoppingStation().value);
- }
- }
-
- for (const Station *st : Station::Iterate()) {
- if (st->owner != v->owner || !(st->facilities & FACIL_AIRPORT) || !st->airport.HasHangar()) continue;
-
- const AirportFTAClass *afc = st->airport.GetFTA();
-
- /* don't crash the plane if we know it can't land at the airport */
- if ((afc->flags & AirportFTAClass::SHORT_STRIP) && (avi->subtype & AIR_FAST) && !_cheats.no_jetcrash.value) continue;
-
- /* the plane won't land at any helicopter station */
- if (!(afc->flags & AirportFTAClass::AIRPLANES) && (avi->subtype & AIR_CTOL)) continue;
-
- /* Check if our last and next destinations can be reached from the depot airport. */
- if (max_range != 0) {
- uint last_dist = (last_dest != nullptr && last_dest->airport.tile != INVALID_TILE) ? DistanceSquare(st->airport.tile, last_dest->airport.tile) : 0;
- uint next_dist = (next_dest != nullptr && next_dest->airport.tile != INVALID_TILE) ? DistanceSquare(st->airport.tile, next_dest->airport.tile) : 0;
- if (last_dist > max_range || next_dist > max_range) continue;
- }
-
- /* v->tile can't be used here, when aircraft is flying v->tile is set to 0 */
- uint distance = DistanceSquare(vtile, st->airport.tile);
- if (distance < best || index == INVALID_STATION) {
- best = distance;
- index = st->index;
- }
- }
- return index;
-}
-
void Aircraft::GetImage(Direction direction, EngineImageType image_type, VehicleSpriteSeq *result) const
{
uint8_t spritenum = this->spritenum;
@@ -260,6 +216,145 @@ void GetAircraftSpriteSize(EngineID engine, uint &width, uint &height, int &xoff
yoffs = UnScaleGUI(rect.top);
}
+/**
+ * Get the station ID of the airport where the aircraft is in.
+ * @return the current aiport id if the aircraft is in, or INVALID_STATION if the aircraft is flying.
+ */
+StationID Aircraft::GetCurrentAirportID() const
+{
+ assert(this->IsPrimaryVehicle());
+ if (this->state > AS_MOVING) return INVALID_STATION;
+
+ assert(IsAirportTile(this->tile));
+ return GetStationIndex(this->tile);
+}
+
+/**
+ * Returns aircraft's target station if its target
+ * is a valid station with an airport.
+ * @param v Aircraft to get target airport for
+ * @return pointer to target station, nullptr if invalid
+ */
+Station *GetTargetAirportIfValid(const Aircraft *v)
+{
+ Station *st = Station::GetIfValid(v->targetairport);
+ if (st == nullptr) return nullptr;
+
+ return st->airport.tile == INVALID_TILE ? nullptr : st;
+}
+
+TileIndex Aircraft::GetOrderStationLocation(StationID station)
+{
+ if (station == this->last_station_visited) this->last_station_visited = INVALID_STATION;
+
+ assert(Station::IsValidID(station));
+ Station *st = Station::Get(station);
+
+ if (!CanVehicleUseStation(this, st)) {
+ this->IncrementRealOrderIndex();
+ this->Next()->dest_tile = INVALID_TILE;
+ return 0;
+ }
+
+ if (!st->airport.aprons.empty()) return st->airport.aprons[0];
+
+ assert(this->IsHelicopter());
+
+ if (!st->airport.helipads.empty()) return st->airport.helipads[0];
+
+ assert(!st->airport.heliports.empty());
+ return st->airport.heliports[0];
+}
+
+TileIndex Aircraft::GetOrderHangarLocation(DepotID depot)
+{
+ assert(Depot::IsValidID(depot));
+ Depot *dep = Depot::Get(depot);
+ assert(dep->veh_type == VEH_AIRCRAFT);
+ if (!dep->depot_tiles.empty()) {
+ TileIndex tile = dep->depot_tiles[0];
+ assert(IsAirportTile(tile) && IsHangar(tile));
+ Station *st = Station::GetByTile(tile);
+ if (CanVehicleUseStation(this, st)) return tile;
+ }
+
+ this->IncrementRealOrderIndex();
+ return 0;
+}
+
+/**
+ * Find the nearest hangar for an aircraft.
+ * @param v vehicle looking for a hangar
+ * @return the StationID of the closest airport with a hangar; otherwise, INVALID_STATION.
+ */
+static StationID FindClosestHangar(const Aircraft *v)
+{
+ uint best = 0;
+ StationID index = INVALID_STATION;
+ /* revise: they are not clamped */
+ TileIndex vtile = TileVirtXY(v->x_pos, v->y_pos);
+ uint max_range = v->acache.cached_max_range_sqr;
+
+ /* Determine destinations where it's coming from and where it's heading to */
+ const Station *last_dest = nullptr;
+ const Station *next_dest = nullptr;
+ if (max_range != 0) {
+ if (v->current_order.IsType(OT_GOTO_STATION) ||
+ (v->current_order.IsType(OT_GOTO_DEPOT) && (v->current_order.GetDepotActionType() & ODATFB_NEAREST_DEPOT) == 0)) {
+ last_dest = Station::GetIfValid(v->last_station_visited);
+ next_dest = Station::GetIfValid(GetTargetDestination(v->current_order, true));
+ } else {
+ last_dest = GetTargetAirportIfValid(v);
+ next_dest = Station::GetIfValid(v->GetNextStoppingStation().value); // revise getnextstoppingstation could ignore depot orders
+ }
+ }
+
+ for (const Station *st : Station::Iterate()) {
+ if (st->owner != v->owner || !CanVehicleUseStation(v, st) || !st->airport.HasHangar()) continue;
+
+ /* Check if our last and next destinations can be reached from the depot airport. */
+ if (max_range != 0) {
+ if (last_dest != nullptr &&
+ (last_dest->facilities & FACIL_AIRPORT) &&
+ DistanceSquare(st->airport.tile, last_dest->airport.tile) > max_range) continue;
+ if (next_dest != nullptr &&
+ (next_dest->facilities & FACIL_AIRPORT) &&
+ DistanceSquare(st->airport.tile, next_dest->airport.tile) > max_range) continue;
+ }
+
+ uint distance = DistanceSquare(vtile, st->airport.tile);
+ if (distance < best || index == INVALID_STATION) {
+ best = distance;
+ index = st->index;
+ }
+ }
+ return index;
+}
+
+/**
+ * Return a tile for placing a newly bought aircraft.
+ * @param depot a depot.
+ * @return a hangar tile where the new aircraft can be placed, or INVALID_TILE if no hangar available.
+ */
+TileIndex GetHangarTileForNewAircraft(const Depot *depot)
+{
+ assert(depot->veh_type == VEH_AIRCRAFT);
+
+ for (const auto &tile : depot->depot_tiles) {
+ switch (GetAirportTileType(tile)) {
+ case ATT_HANGAR_STANDARD:
+ return tile;
+ case ATT_HANGAR_EXTENDED:
+ if (!HasAirportTileAnyReservation(tile)) return tile;
+ break;
+ default:
+ NOT_REACHED();
+ }
+ }
+
+ return INVALID_TILE;
+}
+
/**
* Build an aircraft.
* @param flags type of operation.
@@ -276,22 +371,34 @@ CommandCost CmdBuildAircraft(DoCommandFlag flags, TileIndex tile, const Engine *
/* Prevent building aircraft types at places which can't handle them */
if (!CanVehicleUseStation(e->index, st)) return CMD_ERROR;
- /* Make sure all aircraft end up in the first tile of the hangar. */
- tile = st->airport.GetHangarTile(st->airport.GetHangarNum(tile));
+ if (!st->airport.HasHangar()) return CMD_ERROR;
+
+ /* Make sure all aircraft ends up in an appropriate hangar. */
+ if ((flags & DC_AUTOREPLACE) == 0) {
+ tile = GetHangarTileForNewAircraft(st->airport.hangar);
+ if (tile == INVALID_TILE) return_cmd_error(STR_ERROR_NO_FREE_HANGAR);
+ }
+
+ bool extended_hangar = IsExtendedHangar(tile);
if (flags & DC_EXEC) {
Aircraft *v = new Aircraft(); // aircraft
Aircraft *u = new Aircraft(); // shadow
*ret = v;
- v->direction = DIR_SE;
+ v->tile = tile;
+ v->dest_tile = 0;
+ v->next_trackdir = INVALID_TRACKDIR;
+ v->direction = u->direction = DiagDirToDir(GetHangarDirection(tile));
+ v->trackdir = DiagDirToDiagTrackdir(GetHangarDirection(tile));
+ v->wait_counter = 0;
v->owner = u->owner = _current_company;
+ v->SetNext(u);
+ v->UpdateNextTile(tile);
- v->tile = tile;
-
- uint x = TileX(tile) * TILE_SIZE + 5;
- uint y = TileY(tile) * TILE_SIZE + 3;
+ uint x = TileX(tile) * TILE_SIZE + 8;
+ uint y = TileY(tile) * TILE_SIZE + 8;
v->x_pos = u->x_pos = x;
v->y_pos = u->y_pos = y;
@@ -299,8 +406,16 @@ CommandCost CmdBuildAircraft(DoCommandFlag flags, TileIndex tile, const Engine *
u->z_pos = GetSlopePixelZ(x, y);
v->z_pos = u->z_pos + 1;
- v->vehstatus = VS_HIDDEN | VS_STOPPED | VS_DEFPAL;
- u->vehstatus = VS_HIDDEN | VS_UNCLICKABLE | VS_SHADOW;
+ v->vehstatus = VS_STOPPED | VS_DEFPAL;
+ u->vehstatus = VS_UNCLICKABLE | VS_SHADOW;
+
+ if (!extended_hangar) {
+ v->vehstatus |= VS_HIDDEN;
+ u->vehstatus |= VS_HIDDEN;
+ } else {
+ assert(IsValidTrackdir(v->trackdir));
+ SetAirportTrackReservation(tile, TrackdirToTrack(v->trackdir));
+ }
v->spritenum = avi->image_index;
@@ -335,13 +450,9 @@ CommandCost CmdBuildAircraft(DoCommandFlag flags, TileIndex tile, const Engine *
v->reliability_spd_dec = e->reliability_spd_dec;
v->max_age = e->GetLifeLengthInDays();
- v->pos = GetVehiclePosOnBuild(tile);
+ v->state = AS_HANGAR;
- v->state = HANGAR;
- v->previous_pos = v->pos;
v->targetairport = GetStationIndex(tile);
- v->SetNext(u);
-
v->SetServiceInterval(Company::Get(_current_company)->settings.vehicle.servint_aircraft);
v->date_of_last_service = TimerGameEconomy::date;
@@ -378,7 +489,8 @@ CommandCost CmdBuildAircraft(DoCommandFlag flags, TileIndex tile, const Engine *
w->x_pos = v->x_pos;
w->y_pos = v->y_pos;
w->z_pos = v->z_pos + ROTOR_Z_OFFSET;
- w->vehstatus = VS_HIDDEN | VS_UNCLICKABLE;
+ w->vehstatus = VS_UNCLICKABLE;
+ if (!extended_hangar) w->vehstatus |= VS_HIDDEN;
w->spritenum = 0xFF;
w->subtype = AIR_ROTOR;
w->sprite_cache.sprite_seq.Set(SPR_ROTOR_STOPPED);
@@ -390,30 +502,23 @@ CommandCost CmdBuildAircraft(DoCommandFlag flags, TileIndex tile, const Engine *
u->SetNext(w);
w->UpdatePosition();
}
- }
-
- return CommandCost();
-}
-
-ClosestDepot Aircraft::FindClosestDepot()
-{
- const Station *st = GetTargetAirportIfValid(this);
- /* If the station is not a valid airport or if it has no hangars */
- if (st == nullptr || !CanVehicleUseStation(this, st) || !st->airport.HasHangar()) {
- /* the aircraft has to search for a hangar on its own */
- StationID station = FindNearestHangar(this);
-
- if (station == INVALID_STATION) return ClosestDepot();
-
- st = Station::Get(station);
+ if (extended_hangar) {
+ SetAircraftPosition(v, v->x_pos, v->y_pos, v->z_pos);
+ v->MarkDirty();
+ }
}
- return ClosestDepot(st->xy, st->index);
+ return CommandCost();
}
+/** Check whether the aircrafts needs to visit a hangar.
+ * @param v Aircraft
+ */
static void CheckIfAircraftNeedsService(Aircraft *v)
{
+ if (v->IsAircraftFlying() && !v->IsAircraftFreelyFlying()) return;
+
if (Company::Get(v->owner)->settings.vehicle.servint_aircraft == 0 || !v->NeedsAutomaticServicing()) return;
if (v->IsChainInDepot()) {
VehicleServiceInDepot(v);
@@ -424,17 +529,29 @@ static void CheckIfAircraftNeedsService(Aircraft *v)
* we don't want to consider going to a depot too. */
if (!v->current_order.IsType(OT_GOTO_DEPOT) && !v->current_order.IsType(OT_GOTO_STATION)) return;
- const Station *st = Station::Get(v->current_order.GetDestination());
-
+ const Station *st;
+ if (v->state <= AS_RUNNING) {
+ st = Station::Get(v->GetCurrentAirportID());
+ } else {
+ st = Station::Get(GetTargetDestination(v->current_order, true));
+ }
assert(st != nullptr);
- /* only goto depot if the target airport has a depot */
if (st->airport.HasHangar() && CanVehicleUseStation(v, st)) {
- v->current_order.MakeGoToDepot(st->index, ODTFB_SERVICE);
+ v->current_order.MakeGoToDepot(st->airport.hangar->index, ODTFB_SERVICE);
+ v->SetDestTile(v->GetOrderHangarLocation(st->airport.hangar->index));
SetWindowWidgetDirty(WC_VEHICLE_VIEW, v->index, WID_VV_START_STOP);
} else if (v->current_order.IsType(OT_GOTO_DEPOT)) {
v->current_order.MakeDummy();
SetWindowWidgetDirty(WC_VEHICLE_VIEW, v->index, WID_VV_START_STOP);
+ } else {
+ /* Try going to another hangar. */
+ ClosestDepot closest_hangar = v->FindClosestDepot();
+ if (closest_hangar.location != INVALID_TILE) {
+ v->current_order.MakeGoToDepot(closest_hangar.destination, ODTFB_SERVICE);
+ v->SetDestTile(closest_hangar.location);
+ SetWindowWidgetDirty(WC_VEHICLE_VIEW, v->index, WID_VV_START_STOP);
+ }
}
}
@@ -478,52 +595,6 @@ void Aircraft::OnNewEconomyDay()
SetWindowClassesDirty(WC_AIRCRAFT_LIST);
}
-static void HelicopterTickHandler(Aircraft *v)
-{
- Aircraft *u = v->Next()->Next();
-
- if (u->vehstatus & VS_HIDDEN) return;
-
- /* if true, helicopter rotors do not rotate. This should only be the case if a helicopter is
- * loading/unloading at a terminal or stopped */
- if (v->current_order.IsType(OT_LOADING) || (v->vehstatus & VS_STOPPED)) {
- if (u->cur_speed != 0) {
- u->cur_speed++;
- if (u->cur_speed >= 0x80 && u->state == HRS_ROTOR_MOVING_3) {
- u->cur_speed = 0;
- }
- }
- } else {
- if (u->cur_speed == 0) {
- u->cur_speed = 0x70;
- }
- if (u->cur_speed >= 0x50) {
- u->cur_speed--;
- }
- }
-
- int tick = ++u->tick_counter;
- int spd = u->cur_speed >> 4;
-
- VehicleSpriteSeq seq;
- if (spd == 0) {
- u->state = HRS_ROTOR_STOPPED;
- GetRotorImage(v, EIT_ON_MAP, &seq);
- if (u->sprite_cache.sprite_seq == seq) return;
- } else if (tick >= spd) {
- u->tick_counter = 0;
- u->state++;
- if (u->state > HRS_ROTOR_MOVING_3) u->state = HRS_ROTOR_MOVING_1;
- GetRotorImage(v, EIT_ON_MAP, &seq);
- } else {
- return;
- }
-
- u->sprite_cache.sprite_seq = seq;
-
- u->UpdatePositionAndViewport();
-}
-
/**
* Set aircraft position.
* @param v Aircraft to position.
@@ -566,34 +637,6 @@ void SetAircraftPosition(Aircraft *v, int x, int y, int z)
}
}
-/**
- * Handle Aircraft specific tasks when an Aircraft enters a hangar
- * @param *v Vehicle that enters the hangar
- */
-void HandleAircraftEnterHangar(Aircraft *v)
-{
- v->subspeed = 0;
- v->progress = 0;
-
- Aircraft *u = v->Next();
- u->vehstatus |= VS_HIDDEN;
- u = u->Next();
- if (u != nullptr) {
- u->vehstatus |= VS_HIDDEN;
- u->cur_speed = 0;
- }
-
- SetAircraftPosition(v, v->x_pos, v->y_pos, v->z_pos);
-}
-
-static void PlayAircraftSound(const Vehicle *v)
-{
- if (!PlayVehicleSound(v, VSE_START)) {
- SndPlayVehicleFx(AircraftVehInfo(v->engine_type)->sfx, v);
- }
-}
-
-
/**
* Update cached values of an aircraft.
* Currently caches callback 36 max speed.
@@ -626,12 +669,10 @@ void UpdateAircraftCache(Aircraft *v, bool update_range)
}
}
-
/**
* Special velocities for aircraft
*/
enum AircraftSpeedLimits {
- SPEED_LIMIT_TAXI = 50, ///< Maximum speed of an aircraft while taxiing
SPEED_LIMIT_APPROACH = 230, ///< Maximum speed of an aircraft on finals
SPEED_LIMIT_BROKEN = 320, ///< Maximum speed of an aircraft that is broken
SPEED_LIMIT_HOLD = 425, ///< Maximum speed of an aircraft that flies the holding pattern
@@ -641,12 +682,30 @@ enum AircraftSpeedLimits {
/**
* Sets the new speed for an aircraft
* @param v The vehicle for which the speed should be obtained
- * @param speed_limit The maximum speed the vehicle may have.
- * @param hard_limit If true, the limit is directly enforced, otherwise the plane is slowed down gradually
* @return The number of position updates needed within the tick
*/
-static int UpdateAircraftSpeed(Aircraft *v, uint speed_limit = SPEED_LIMIT_NONE, bool hard_limit = true)
+static int UpdateAircraftSpeed(Aircraft *v)
{
+ assert(v->state >= AS_MOVING);
+
+ /* If true, the limit is directly enforced, otherwise the plane is slowed down gradually. */
+ bool hard_limit = true;
+ /* The maximum speed the vehicle may have. */
+ uint speed_limit = SPEED_LIMIT_NONE;
+
+ hard_limit = !HasBit(v->state, ASB_NO_HARD_LIMIT_SPEED);
+
+ if (!hard_limit) {
+ if (HasBit(v->state, ASB_FLYING_ON_AIRPORT)) {
+ speed_limit = v->IsAircraftOnHold() ? SPEED_LIMIT_HOLD : SPEED_LIMIT_APPROACH;
+ } else if (!v->IsAircraftFlying()){
+ speed_limit = GetAirTypeInfo(GetAirType(v->GetNextTile()))->max_speed;
+ }
+ } else if (v->state == AS_RUNNING) {
+ assert(IsAirportTile(v->tile));
+ speed_limit = GetAirTypeInfo(GetAirType(v->tile))->max_speed;
+ }
+
/**
* 'acceleration' has the unit 3/8 mph/tick. This function is called twice per tick.
* So the speed amount we need to accelerate is:
@@ -714,7 +773,7 @@ int GetTileHeightBelowAircraft(const Vehicle *v)
{
int safe_x = Clamp(v->x_pos, 0, Map::MaxX() * TILE_SIZE);
int safe_y = Clamp(v->y_pos, 0, Map::MaxY() * TILE_SIZE);
- return TileHeight(TileVirtXY(safe_x, safe_y)) * TILE_HEIGHT;
+ return TilePixelHeight(TileVirtXY(safe_x, safe_y));
}
/**
@@ -730,7 +789,7 @@ int GetTileHeightBelowAircraft(const Vehicle *v)
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) {
+ if (v->type == VEH_AIRCRAFT && Aircraft::From(v)->IsHelicopter()) {
base_altitude += HELICOPTER_HOLD_MAX_FLYING_ALTITUDE - PLANE_HOLD_MAX_FLYING_ALTITUDE;
}
@@ -766,7 +825,7 @@ 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);
+ return tile_height + (v->IsHelicopter() ? HELICOPTER_HOLD_MAX_FLYING_ALTITUDE : PLANE_HOLD_MAX_FLYING_ALTITUDE);
}
template
@@ -811,423 +870,51 @@ int GetAircraftFlightLevel(T *v, bool takeoff)
template int GetAircraftFlightLevel(DisasterVehicle *v, bool takeoff);
template int GetAircraftFlightLevel(Aircraft *v, bool takeoff);
-/**
- * Find the entry point to an airport depending on direction which
- * the airport is being approached from. Each airport can have up to
- * four entry points for its approach system so that approaching
- * aircraft do not fly through each other or are forced to do 180
- * degree turns during the approach. The arrivals are grouped into
- * four sectors dependent on the DiagDirection from which the airport
- * is approached.
- *
- * @param v The vehicle that is approaching the airport
- * @param apc The Airport Class being approached.
- * @param rotation The rotation of the airport.
- * @return The index of the entry point
- */
-static uint8_t AircraftGetEntryPoint(const Aircraft *v, const AirportFTAClass *apc, Direction rotation)
+static void HandleHelicopterRotor(Aircraft *v)
{
- assert(v != nullptr);
- assert(apc != nullptr);
+ Aircraft *u = v->Next()->Next();
- /* In the case the station doesn't exit anymore, set target tile 0.
- * It doesn't hurt much, aircraft will go to next order, nearest hangar
- * or it will simply crash in next tick */
- TileIndex tile = 0;
+ if (u->vehstatus & VS_HIDDEN) return;
- const Station *st = Station::GetIfValid(v->targetairport);
- if (st != nullptr) {
- /* Make sure we don't go to INVALID_TILE if the airport has been removed. */
- tile = (st->airport.tile != INVALID_TILE) ? st->airport.tile : st->xy;
- }
-
- int delta_x = v->x_pos - TileX(tile) * TILE_SIZE;
- int delta_y = v->y_pos - TileY(tile) * TILE_SIZE;
-
- DiagDirection dir;
- if (abs(delta_y) < abs(delta_x)) {
- /* We are northeast or southwest of the airport */
- dir = delta_x < 0 ? DIAGDIR_NE : DIAGDIR_SW;
- } else {
- /* We are northwest or southeast of the airport */
- dir = delta_y < 0 ? DIAGDIR_NW : DIAGDIR_SE;
- }
- dir = ChangeDiagDir(dir, DiagDirDifference(DIAGDIR_NE, DirToDiagDir(rotation)));
- return apc->entry_points[dir];
-}
-
-
-static void MaybeCrashAirplane(Aircraft *v);
-
-/**
- * Controls the movement of an aircraft. This function actually moves the vehicle
- * on the map and takes care of minor things like sound playback.
- * @todo De-mystify the cur_speed values for helicopter rotors.
- * @param v The vehicle that is moved. Must be the first vehicle of the chain
- * @return Whether the position requested by the State Machine has been reached
- */
-static bool AircraftController(Aircraft *v)
-{
- /* nullptr if station is invalid */
- const Station *st = Station::GetIfValid(v->targetairport);
- /* INVALID_TILE if there is no station */
- TileIndex tile = INVALID_TILE;
- Direction rotation = DIR_N;
- uint size_x = 1, size_y = 1;
- if (st != nullptr) {
- if (st->airport.tile != INVALID_TILE) {
- tile = st->airport.tile;
- rotation = st->airport.rotation;
- size_x = st->airport.w;
- size_y = st->airport.h;
- } else {
- tile = st->xy;
- }
- }
- /* DUMMY if there is no station or no airport */
- const AirportFTAClass *afc = tile == INVALID_TILE ? GetAirport(AT_DUMMY) : st->airport.GetFTA();
-
- /* prevent going to INVALID_TILE if airport is deleted. */
- if (st == nullptr || st->airport.tile == INVALID_TILE) {
- /* Jump into our "holding pattern" state machine if possible */
- if (v->pos >= afc->nofelements) {
- v->pos = v->previous_pos = AircraftGetEntryPoint(v, afc, DIR_N);
- } else if (v->targetairport != v->current_order.GetDestination()) {
- /* If not possible, just get out of here fast */
- v->state = FLYING;
- UpdateAircraftCache(v);
- AircraftNextAirportPos_and_Order(v);
- /* get aircraft back on running altitude */
- SetAircraftPosition(v, v->x_pos, v->y_pos, GetAircraftFlightLevel(v));
- return false;
- }
- }
-
- /* get airport moving data */
- const AirportMovingData amd = RotateAirportMovingData(afc->MovingData(v->pos), rotation, size_x, size_y);
-
- int x = TileX(tile) * TILE_SIZE;
- int y = TileY(tile) * TILE_SIZE;
-
- /* Helicopter raise */
- if (amd.flag & AMED_HELI_RAISE) {
- Aircraft *u = v->Next()->Next();
-
- /* Make sure the rotors don't rotate too fast */
- if (u->cur_speed > 32) {
- v->cur_speed = 0;
- if (--u->cur_speed == 32) {
- if (!PlayVehicleSound(v, VSE_START)) {
- SoundID sfx = AircraftVehInfo(v->engine_type)->sfx;
- /* For compatibility with old NewGRF we ignore the sfx property, unless a NewGRF-defined sound is used.
- * The baseset has only one helicopter sound, so this only limits using plane or cow sounds. */
- if (sfx < ORIGINAL_SAMPLE_COUNT) sfx = SND_18_TAKEOFF_HELICOPTER;
- SndPlayVehicleFx(sfx, v);
- }
- }
- } else {
- u->cur_speed = 32;
- int count = UpdateAircraftSpeed(v);
- if (count > 0) {
- v->tile = 0;
-
- int z_dest;
- GetAircraftFlightLevelBounds(v, &z_dest, nullptr);
-
- /* Reached altitude? */
- if (v->z_pos >= z_dest) {
- v->cur_speed = 0;
- return true;
- }
- SetAircraftPosition(v, v->x_pos, v->y_pos, std::min(v->z_pos + count, z_dest));
+ /* if true, helicopter rotors do not rotate. This should only be the case if a helicopter is
+ * loading/unloading at a terminal or stopped */
+ if (v->current_order.IsType(OT_LOADING) || (v->vehstatus & VS_STOPPED)) {
+ if (u->cur_speed != 0) {
+ u->cur_speed++;
+ if (u->cur_speed >= 0x80 && u->state == HRS_ROTOR_MOVING_3) {
+ u->cur_speed = 0;
}
}
- return false;
- }
-
- /* Helicopter landing. */
- if (amd.flag & AMED_HELI_LOWER) {
- SetBit(v->flags, VAF_HELI_DIRECT_DESCENT);
-
- if (st == nullptr) {
- /* FIXME - AircraftController -> if station no longer exists, do not land
- * helicopter will circle until sign disappears, then go to next order
- * what to do when it is the only order left, right now it just stays in 1 place */
- v->state = FLYING;
- UpdateAircraftCache(v);
- AircraftNextAirportPos_and_Order(v);
- return false;
- }
-
- /* Vehicle is now at the airport.
- * Helicopter has arrived at the target landing pad, so the current position is also where it should land.
- * Except for Oilrigs which are special due to being a 1x1 station, and helicopters land outside it. */
- if (st->airport.type != AT_OILRIG) {
- x = v->x_pos;
- y = v->y_pos;
- tile = TileVirtXY(x, y);
- }
- v->tile = tile;
-
- /* Find altitude of landing position. */
- int z = GetSlopePixelZ(x, y) + 1 + afc->delta_z;
-
- if (z == v->z_pos) {
- Vehicle *u = v->Next()->Next();
-
- /* Increase speed of rotors. When speed is 80, we've landed. */
- if (u->cur_speed >= 80) {
- ClrBit(v->flags, VAF_HELI_DIRECT_DESCENT);
- return true;
- }
- u->cur_speed += 4;
- } else {
- int count = UpdateAircraftSpeed(v);
- if (count > 0) {
- if (v->z_pos > z) {
- SetAircraftPosition(v, v->x_pos, v->y_pos, std::max(v->z_pos - count, z));
- } else {
- SetAircraftPosition(v, v->x_pos, v->y_pos, std::min(v->z_pos + count, z));
- }
- }
+ } else {
+ if (u->cur_speed == 0) {
+ u->cur_speed = 0x70;
}
- return false;
- }
-
- /* Get distance from destination pos to current pos. */
- uint dist = abs(x + amd.x - v->x_pos) + abs(y + amd.y - v->y_pos);
-
- /* Need exact position? */
- if (!(amd.flag & AMED_EXACTPOS) && dist <= (amd.flag & AMED_SLOWTURN ? 8U : 4U)) return true;
-
- /* At final pos? */
- if (dist == 0) {
- /* Change direction smoothly to final direction. */
- DirDiff dirdiff = DirDifference(amd.direction, v->direction);
- /* if distance is 0, and plane points in right direction, no point in calling
- * UpdateAircraftSpeed(). So do it only afterwards */
- if (dirdiff == DIRDIFF_SAME) {
- v->cur_speed = 0;
- return true;
+ if (u->cur_speed >= 0x50) {
+ u->cur_speed--;
}
-
- if (!UpdateAircraftSpeed(v, SPEED_LIMIT_TAXI)) return false;
-
- v->direction = ChangeDir(v->direction, dirdiff > DIRDIFF_REVERSE ? DIRDIFF_45LEFT : DIRDIFF_45RIGHT);
- v->cur_speed >>= 1;
-
- SetAircraftPosition(v, v->x_pos, v->y_pos, v->z_pos);
- return false;
- }
-
- if (amd.flag & AMED_BRAKE && v->cur_speed > SPEED_LIMIT_TAXI * _settings_game.vehicle.plane_speed) {
- MaybeCrashAirplane(v);
- if ((v->vehstatus & VS_CRASHED) != 0) return false;
}
- uint speed_limit = SPEED_LIMIT_TAXI;
- bool hard_limit = true;
-
- if (amd.flag & AMED_NOSPDCLAMP) speed_limit = SPEED_LIMIT_NONE;
- if (amd.flag & AMED_HOLD) { speed_limit = SPEED_LIMIT_HOLD; hard_limit = false; }
- if (amd.flag & AMED_LAND) { speed_limit = SPEED_LIMIT_APPROACH; hard_limit = false; }
- if (amd.flag & AMED_BRAKE) { speed_limit = SPEED_LIMIT_TAXI; hard_limit = false; }
-
- int count = UpdateAircraftSpeed(v, speed_limit, hard_limit);
- if (count == 0) return false;
-
- /* If the plane will be a few subpixels away from the destination after
- * this movement loop, start nudging it towards the exact position for
- * the whole loop. Otherwise, heavily depending on the speed of the plane,
- * it is possible we totally overshoot the target, causing the plane to
- * make a loop, and trying again, and again, and again .. */
- bool nudge_towards_target = static_cast(count) + 3 > dist;
-
- if (v->turn_counter != 0) v->turn_counter--;
-
- do {
-
- GetNewVehiclePosResult gp;
-
- if (nudge_towards_target || (amd.flag & AMED_LAND)) {
- /* move vehicle one pixel towards target */
- gp.x = (v->x_pos != (x + amd.x)) ?
- v->x_pos + ((x + amd.x > v->x_pos) ? 1 : -1) :
- v->x_pos;
- gp.y = (v->y_pos != (y + amd.y)) ?
- v->y_pos + ((y + amd.y > v->y_pos) ? 1 : -1) :
- v->y_pos;
-
- /* Oilrigs must keep v->tile as st->airport.tile, since the landing pad is in a non-airport tile */
- gp.new_tile = (st->airport.type == AT_OILRIG) ? st->airport.tile : TileVirtXY(gp.x, gp.y);
-
- } else {
-
- /* Turn. Do it slowly if in the air. */
- Direction newdir = GetDirectionTowards(v, x + amd.x, y + amd.y);
- if (newdir != v->direction) {
- if (amd.flag & AMED_SLOWTURN && v->number_consecutive_turns < 8 && v->subtype == AIR_AIRCRAFT) {
- if (v->turn_counter == 0 || newdir == v->last_direction) {
- if (newdir == v->last_direction) {
- v->number_consecutive_turns = 0;
- } else {
- v->number_consecutive_turns++;
- }
- v->turn_counter = 2 * _settings_game.vehicle.plane_speed;
- v->last_direction = v->direction;
- v->direction = newdir;
- }
-
- /* Move vehicle. */
- gp = GetNewVehiclePos(v);
- } else {
- v->cur_speed >>= 1;
- v->direction = newdir;
-
- /* When leaving a terminal an aircraft often goes to a position
- * directly in front of it. If it would move while turning it
- * would need an two extra turns to end up at the correct position.
- * To make it easier just disallow all moving while turning as
- * long as an aircraft is on the ground. */
- gp.x = v->x_pos;
- gp.y = v->y_pos;
- gp.new_tile = gp.old_tile = v->tile;
- }
- } else {
- v->number_consecutive_turns = 0;
- /* Move vehicle. */
- gp = GetNewVehiclePos(v);
- }
- }
-
- v->tile = gp.new_tile;
- /* If vehicle is in the air, use tile coordinate 0. */
- if (amd.flag & (AMED_TAKEOFF | AMED_SLOWTURN | AMED_LAND)) v->tile = 0;
-
- /* Adjust Z for land or takeoff? */
- int z = v->z_pos;
-
- if (amd.flag & AMED_TAKEOFF) {
- 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);
- }
-
- /* NewGRF airports (like a rotated intercontinental from OpenGFX+Airports) can be non-rectangular
- * and their primary (north-most) tile does not have to be part of the airport.
- * As such, the height of the primary tile can be different from the rest of the airport.
- * Given we are landing/breaking, and as such are not a helicopter, we know that there has to be a hangar.
- * We also know that the airport itself has to be completely flat (otherwise it is not a valid airport).
- * Therefore, use the height of this hangar to calculate our z-value. */
- int airport_z = v->z_pos;
- if ((amd.flag & (AMED_LAND | AMED_BRAKE)) && st != nullptr) {
- assert(st->airport.HasHangar());
- TileIndex hangar_tile = st->airport.GetHangarTile(0);
- airport_z = GetTileMaxPixelZ(hangar_tile) + 1; // To avoid clashing with the shadow
- }
-
- if (amd.flag & AMED_LAND) {
- if (st->airport.tile == INVALID_TILE) {
- /* Airport has been removed, abort the landing procedure */
- v->state = FLYING;
- UpdateAircraftCache(v);
- AircraftNextAirportPos_and_Order(v);
- /* get aircraft back on running altitude */
- SetAircraftPosition(v, gp.x, gp.y, GetAircraftFlightLevel(v));
- continue;
- }
-
- /* We're not flying below our destination, right? */
- assert(airport_z <= z);
- int t = std::max(1U, dist - 4);
- int delta = z - airport_z;
-
- /* Only start lowering when we're sufficiently close for a 1:1 glide */
- if (delta >= t) {
- z -= CeilDiv(z - airport_z, t);
- }
- if (z < airport_z) z = airport_z;
- }
-
- /* We've landed. Decrease speed when we're reaching end of runway. */
- if (amd.flag & AMED_BRAKE) {
-
- if (z > airport_z) {
- z--;
- } else if (z < airport_z) {
- z++;
- }
-
- }
-
- SetAircraftPosition(v, gp.x, gp.y, z);
- } while (--count != 0);
- return false;
-}
-
-/**
- * Handle crashed aircraft \a v.
- * @param v Crashed aircraft.
- */
-static bool HandleCrashedAircraft(Aircraft *v)
-{
- v->crashed_counter += 3;
-
- Station *st = GetTargetAirportIfValid(v);
+ int tick = ++u->tick_counter;
+ int spd = u->cur_speed >> 4;
- /* make aircraft crash down to the ground */
- if (v->crashed_counter < 500 && st == nullptr && ((v->crashed_counter % 3) == 0) ) {
- int z = GetSlopePixelZ(Clamp(v->x_pos, 0, Map::MaxX() * TILE_SIZE), Clamp(v->y_pos, 0, Map::MaxY() * TILE_SIZE));
- v->z_pos -= 1;
- if (v->z_pos <= z) {
- v->crashed_counter = 500;
- v->z_pos = z + 1;
- } else {
- v->crashed_counter = 0;
- }
- SetAircraftPosition(v, v->x_pos, v->y_pos, v->z_pos);
+ VehicleSpriteSeq seq;
+ if (spd == 0) {
+ u->state = HRS_ROTOR_STOPPED;
+ GetRotorImage(v, EIT_ON_MAP, &seq);
+ if (u->sprite_cache.sprite_seq == seq) return;
+ } else if (tick >= spd) {
+ u->tick_counter = 0;
+ u->state = (AircraftState)((u->state % HRS_ROTOR_NUM_STATES) + 1);
+ GetRotorImage(v, EIT_ON_MAP, &seq);
+ } else {
+ return;
}
- if (v->crashed_counter < 650) {
- uint32_t r;
- if (Chance16R(1, 32, r)) {
- static const DirDiff delta[] = {
- DIRDIFF_45LEFT, DIRDIFF_SAME, DIRDIFF_SAME, DIRDIFF_45RIGHT
- };
-
- v->direction = ChangeDir(v->direction, delta[GB(r, 16, 2)]);
- SetAircraftPosition(v, v->x_pos, v->y_pos, v->z_pos);
- r = Random();
- CreateEffectVehicleRel(v,
- GB(r, 0, 4) - 4,
- GB(r, 4, 4) - 4,
- GB(r, 8, 4),
- EV_EXPLOSION_SMALL);
- }
- } else if (v->crashed_counter >= 10000) {
- /* remove rubble of crashed airplane */
-
- /* clear runway-in on all airports, set by crashing plane
- * small airports use AIRPORT_BUSY, city airports use RUNWAY_IN_OUT_block, etc.
- * but they all share the same number */
- if (st != nullptr) {
- CLRBITS(st->airport.flags, RUNWAY_IN_block);
- CLRBITS(st->airport.flags, RUNWAY_IN_OUT_block); // commuter airport
- CLRBITS(st->airport.flags, RUNWAY_IN2_block); // intercontinental
- }
-
- delete v;
-
- return false;
- }
+ u->sprite_cache.sprite_seq = seq;
- return true;
+ u->UpdatePositionAndViewport();
}
-
/**
* Handle smoke of broken aircraft.
* @param v Aircraft
@@ -1269,61 +956,45 @@ static void HandleAircraftSmoke(Aircraft *v, bool mode)
}
}
-void HandleMissingAircraftOrders(Aircraft *v)
+// REVISE
+/**
+ * Mark an aircraft as falling.
+ * @param v aircraft
+ */
+void AircraftStartsFalling(Aircraft *v)
{
- /*
- * We do not have an order. This can be divided into two cases:
- * 1) we are heading to an invalid station. In this case we must
- * find another airport to go to. If there is nowhere to go,
- * we will destroy the aircraft as it otherwise will enter
- * the holding pattern for the first airport, which can cause
- * the plane to go into an undefined state when building an
- * airport with the same StationID.
- * 2) we are (still) heading to a (still) valid airport, then we
- * can continue going there. This can happen when you are
- * changing the aircraft's orders while in-flight or in for
- * example a depot. However, when we have a current order to
- * go to a depot, we have to keep that order so the aircraft
- * actually stops.
- */
- const Station *st = GetTargetAirportIfValid(v);
- if (st == nullptr) {
- Backup cur_company(_current_company, v->owner);
- CommandCost ret = Command::Do(DC_EXEC, v->index, DepotCommand::None, {});
- cur_company.Restore();
+ assert(v->IsAircraftFreelyFlying());
- if (ret.Failed()) CrashAirplane(v);
- } else if (!v->current_order.IsType(OT_GOTO_DEPOT)) {
- v->current_order.Free();
- }
+ v->state = AS_FLYING_FALLING;
+ v->vehstatus |= VS_AIRCRAFT_BROKEN;
+ v->acceleration = 0;
+ v->dest_tile = 0;
+ v->current_order.MakeDummy();
+ // revise: next pos? tile? desttile?
+ SetWindowWidgetDirty(WC_VEHICLE_VIEW, v->index, WID_VV_START_STOP);
}
-
-TileIndex Aircraft::GetOrderStationLocation(StationID)
+uint Aircraft::Crash(bool flooded)
{
- /* Orders are changed in flight, ensure going to the right station. */
- if (this->state == FLYING) {
- AircraftNextAirportPos_and_Order(this);
+ if (this->IsAircraftFalling() &&
+ HasTileWaterClass(this->tile) &&
+ IsTileOnWater(this->tile)) {
+ flooded = true;
}
- /* Aircraft do not use dest-tile */
- return 0;
-}
+ uint victims = Vehicle::Crash(flooded) + 2; // pilots
+ this->crashed_counter = flooded ? 9000 : 0; // max 10000, disappear pretty fast when flooded
-void Aircraft::MarkDirty()
-{
- this->colourmap = PAL_NONE;
- this->UpdateViewport(true, false);
- if (this->subtype == AIR_HELICOPTER) {
- GetRotorImage(this, EIT_ON_MAP, &this->Next()->Next()->sprite_cache.sprite_seq);
- }
-}
+ /* Remove the loading indicators (if any) */
+ HideFillingPercent(&this->fill_percent_te_id);
+ // revise: what happens if a falling aircraft falls in an airport?
+ if (!this->IsAircraftFalling() && !(IsRunway(this->tile) && GetReservationAsRunway(this->tile))) {
+ /* Lift reserved path except the first tile. Skip reserved runways. */
+ LiftAirportPathReservation(this, true);
+ }
-uint Aircraft::Crash(bool flooded)
-{
- uint victims = Vehicle::Crash(flooded) + 2; // pilots
- this->crashed_counter = flooded ? 9000 : 0; // max 10000, disappear pretty fast when flooded
+ this->dest_tile = 0;
return victims;
}
@@ -1332,7 +1003,7 @@ uint Aircraft::Crash(bool flooded)
* Bring the aircraft in a crashed state, create the explosion animation, and create a news item about the crash.
* @param v Aircraft that crashed.
*/
-static void CrashAirplane(Aircraft *v)
+void CrashAircraft(Aircraft *v)
{
CreateEffectVehicleRel(v, 4, 4, 8, EV_EXPLOSION_LARGE);
@@ -1342,23 +1013,18 @@ static void CrashAirplane(Aircraft *v)
v->cargo.Truncate();
v->Next()->cargo.Truncate();
const Station *st = GetTargetAirportIfValid(v);
- StringID newsitem;
- TileIndex vt = TileVirtXY(v->x_pos, v->y_pos);
- if (st == nullptr) {
- newsitem = STR_NEWS_PLANE_CRASH_OUT_OF_FUEL;
- } else {
+
+ StringID newsitem = STR_NEWS_AIRCRAFT_CRASH_NO_AIRPORT;
+ if (st != nullptr && !v->IsAircraftFalling()) {
SetDParam(1, st->index);
newsitem = STR_NEWS_AIRCRAFT_CRASH;
}
+ TileIndex vt = TileVirtXY(v->x_pos, v->y_pos);
AI::NewEvent(v->owner, new ScriptEventVehicleCrashed(v->index, vt, st == nullptr ? ScriptEventVehicleCrashed::CRASH_AIRCRAFT_NO_AIRPORT : ScriptEventVehicleCrashed::CRASH_PLANE_LANDING, victims));
Game::NewEvent(new ScriptEventVehicleCrashed(v->index, vt, st == nullptr ? ScriptEventVehicleCrashed::CRASH_AIRCRAFT_NO_AIRPORT : ScriptEventVehicleCrashed::CRASH_PLANE_LANDING, victims));
- NewsType newstype = NT_ACCIDENT;
- if (v->owner != _local_company) {
- newstype = NT_ACCIDENT_OTHER;
- }
-
+ NewsType newstype = v->owner == _local_company ? NT_ACCIDENT : NT_ACCIDENT_OTHER;
AddTileNewsItem(newsitem, newstype, vt, nullptr, st != nullptr ? st->index : INVALID_STATION);
ModifyStationRatingAround(vt, v->owner, -160, 30);
@@ -1368,754 +1034,1966 @@ static void CrashAirplane(Aircraft *v)
/**
* Decide whether aircraft \a v should crash.
* @param v Aircraft to test.
+ * @return Whether the plane has crashed.
*/
-static void MaybeCrashAirplane(Aircraft *v)
+static bool MaybeCrashAirplane(Aircraft *v)
{
+ if (_settings_game.vehicle.plane_crashes == 0) return false;
- Station *st = Station::Get(v->targetairport);
-
- uint32_t prob;
- if ((st->airport.GetFTA()->flags & AirportFTAClass::SHORT_STRIP) &&
- (AircraftVehInfo(v->engine_type)->subtype & AIR_FAST) &&
- !_cheats.no_jetcrash.value) {
- prob = 3276;
- } else {
- if (_settings_game.vehicle.plane_crashes == 0) return;
- prob = (0x4000 << _settings_game.vehicle.plane_crashes) / 1500;
- }
-
- if (GB(Random(), 0, 22) > prob) return;
+ uint32_t prob = (0x4000 << _settings_game.vehicle.plane_crashes) / 1500;
+ uint32_t rand = GB(Random(), 0, 18);
+ if (rand > prob) return false;
/* Crash the airplane. Remove all goods stored at the station. */
+ Station *st = Station::Get(v->targetairport);
for (GoodsEntry &ge : st->goods) {
ge.rating = 1;
ge.cargo.Truncate();
}
- CrashAirplane(v);
+ CrashAircraft(v);
+ return true;
}
/**
- * Aircraft arrives at a terminal. If it is the first aircraft, throw a party.
- * Start loading cargo.
- * @param v Aircraft that arrived.
+ * Handle crashed aircraft \a v.
+ * @param v Crashed aircraft.
*/
-static void AircraftEntersTerminal(Aircraft *v)
+static bool HandleCrashedAircraft(Aircraft *v)
{
- if (v->current_order.IsType(OT_GOTO_DEPOT)) return;
+ v->crashed_counter += 3;
- Station *st = Station::Get(v->targetairport);
- v->last_station_visited = v->targetairport;
+ if (v->crashed_counter < 650) {
+ uint32_t r;
+ if (Chance16R(1, 32, r)) {
+ static const DirDiff delta[] = {
+ DIRDIFF_45LEFT, DIRDIFF_SAME, DIRDIFF_SAME, DIRDIFF_45RIGHT
+ };
- /* Check if station was ever visited before */
- if (!(st->had_vehicle_of_type & HVOT_AIRCRAFT)) {
- st->had_vehicle_of_type |= HVOT_AIRCRAFT;
- SetDParam(0, st->index);
- /* show newsitem of celebrating citizens */
- AddVehicleNewsItem(
- STR_NEWS_FIRST_AIRCRAFT_ARRIVAL,
- (v->owner == _local_company) ? NT_ARRIVAL_COMPANY : NT_ARRIVAL_OTHER,
- v->index,
- st->index
- );
- AI::NewEvent(v->owner, new ScriptEventStationFirstVehicle(st->index, v->index));
- Game::NewEvent(new ScriptEventStationFirstVehicle(st->index, v->index));
+ v->direction = v->Next()->direction = ChangeDir(v->direction, delta[GB(r, 16, 2)]);
+ SetAircraftPosition(v, v->x_pos, v->y_pos, v->z_pos);
+ r = Random();
+ CreateEffectVehicleRel(v,
+ GB(r, 0, 4) - 4,
+ GB(r, 4, 4) - 4,
+ GB(r, 8, 4),
+ EV_EXPLOSION_SMALL);
+ }
+ } else if (v->crashed_counter >= 10000) {
+ if ((v->vehstatus & VS_HIDDEN) != 0 || v->IsAircraftFalling()) {
+ /* Deleting a vehicle in a hangar or crashed outside the airport. */
+ delete v;
+ return false;
+ }
+
+ /* remove rubble of crashed airplane */
+ if (HasAirportTrackReserved(v->tile)) {
+ assert(!v->IsAircraftFlying());
+ assert(HasAirportTrackReserved(v->tile, TrackdirToTrack(v->trackdir)));
+ RemoveAirportTrackReservation(v->tile, TrackdirToTrack(v->trackdir));
+ } else {
+ assert(IsAirportTile(v->tile));
+ assert(IsRunway(v->tile));
+ assert(GetReservationAsRunway(v->tile));
+ assert(IsDiagonalTrackdir(v->trackdir));
+ DiagDirection diagdir = TrackdirToExitdir(v->trackdir);
+ TileIndex start_tile = GetRunwayExtreme(v->tile, ReverseDiagDir(diagdir));
+ SetRunwayReservation(start_tile, false);
+ }
+
+ delete v;
+ return false;
}
- v->BeginLoading();
+ return true;
}
/**
- * Aircraft touched down at the landing strip.
- * @param v Aircraft that landed.
+ * Aircraft \a v cannot find an airport to go to and it will fall until it crashes.
+ * @param v Aircraft falling to the ground.
*/
-static void AircraftLandAirplane(Aircraft *v)
+static void HandleAircraftFalling(Aircraft *v)
{
- Station *st = Station::Get(v->targetairport);
+ assert(v->IsAircraftFalling());
+ int z = GetSlopePixelZ(Clamp(v->x_pos, 0, Map::MaxX() * TILE_SIZE), Clamp(v->y_pos, 0, Map::MaxY() * TILE_SIZE));
+ GetNewVehiclePosResult gp = GetNewVehiclePos(v);
- TileIndex vt = TileVirtXY(v->x_pos, v->y_pos);
+ /* MoveAircraft() is called twice, but handling out of fuel only once. */
+ uint count = UpdateAircraftSpeed(v) + UpdateAircraftSpeed(v);
+ v->x_pos += count * (gp.x - v->x_pos);
+ v->y_pos += count * (gp.y - v->y_pos);
- v->UpdateDeltaXY();
+ if (count > 0) v->z_pos -= 1;
- AirportTileAnimationTrigger(st, vt, AAT_STATION_AIRPLANE_LAND);
+ if (v->z_pos == z) {
+ v->z_pos++;
+ CreateEffectVehicleRel(v, 4, 4, 8, EV_EXPLOSION_LARGE);
+ v->vehstatus &= ~VS_AIRCRAFT_BROKEN;
+ CrashAircraft(v);
+ } else {
+ HandleAircraftSmoke(v, false);
+ SetAircraftPosition(v, v->x_pos, v->y_pos, v->z_pos);
+ }
+}
- if (!PlayVehicleSound(v, VSE_TOUCHDOWN)) {
- SndPlayVehicleFx(SND_17_SKID_PLANE, v);
+/** Structure for aircraft sub-coordinate data for moving into a new tile via a Diagdir onto a Track. */
+struct AircraftSubcoordData {
+ uint8_t x_subcoord; ///< New X sub-coordinate on the new tile
+ uint8_t y_subcoord; ///< New Y sub-coordinate on the new tile
+ Direction dir; ///< New Direction to move in on the new track
+};
+
+/** Aircraft sub-coordinate data for moving into a new tile via a Diagdir onto a Track.
+ * Array indexes are Diagdir, Track.
+ * There will always be three possible tracks going into an adjacent tile via a Diagdir,
+ * so each Diagdir sub-array will have three valid and three invalid structures per Track.
+ */
+static const AircraftSubcoordData _aircraft_subcoord[DIAGDIR_END][TRACK_END] = {
+ // DIAGDIR_NE
+ {
+ {15, 8, DIR_NE}, // TRACK_X
+ { 0, 0, INVALID_DIR}, // TRACK_Y
+ { 0, 0, INVALID_DIR}, // TRACK_UPPER
+ {15, 8, DIR_E}, // TRACK_LOWER
+ {15, 7, DIR_N}, // TRACK_LEFT
+ { 0, 0, INVALID_DIR}, // TRACK_RIGHT
+ },
+ // DIAGDIR_SE
+ {
+ { 0, 0, INVALID_DIR}, // TRACK_X
+ { 8, 0, DIR_SE}, // TRACK_Y
+ { 7, 0, DIR_E}, // TRACK_UPPER
+ { 0, 0, INVALID_DIR}, // TRACK_LOWER
+ { 8, 0, DIR_S}, // TRACK_LEFT
+ { 0, 0, INVALID_DIR}, // TRACK_RIGHT
+ },
+ // DIAGDIR_SW
+ {
+ { 0, 8, DIR_SW}, // TRACK_X
+ { 0, 0, INVALID_DIR}, // TRACK_Y
+ { 0, 7, DIR_W}, // TRACK_UPPER
+ { 0, 0, INVALID_DIR}, // TRACK_LOWER
+ { 0, 0, INVALID_DIR}, // TRACK_LEFT
+ { 0, 8, DIR_S}, // TRACK_RIGHT
+ },
+ // DIAGDIR_NW
+ {
+ { 0, 0, INVALID_DIR}, // TRACK_X
+ { 8, 15, DIR_NW}, // TRACK_Y
+ { 0, 0, INVALID_DIR}, // TRACK_UPPER
+ { 8, 15, DIR_W}, // TRACK_LOWER
+ { 0, 0, INVALID_DIR}, // TRACK_LEFT
+ { 7, 15, DIR_N}, // TRACK_RIGHT
}
+};
+
+/**
+ * Check whether the aircraft needs to rotate its current trackdir.
+ * @param v Aircraft
+ * @return whether the aircraft needs to rotate its current trackdir.
+ */
+bool DoesAircraftNeedRotation(Aircraft *v)
+{
+ assert(v->next_trackdir == INVALID_TRACKDIR || IsValidTrackdir(v->next_trackdir));
+ return v->next_trackdir != INVALID_TRACKDIR;
}
+const uint16_t AIRCRAFT_ROTATION_STEP_TICKS = 30;
+const uint16_t AIRCRAFT_WAIT_FREE_PATH_TICKS = 10;
+const uint16_t AIRCRAFT_WAIT_LEAVE_HANGAR_TICKS = 200;
+const uint16_t AIRCRAFT_CANT_LEAVE_RUNWAY = 200;
-/** set the right pos when heading to other airports after takeoff */
-void AircraftNextAirportPos_and_Order(Aircraft *v)
+/**
+ * Slightly rotate an aircraft towards its desired trackdir.
+ * @param v Aircraft
+ */
+void DoRotationStep(Aircraft *v)
{
- if (v->current_order.IsType(OT_GOTO_STATION) || v->current_order.IsType(OT_GOTO_DEPOT)) {
- v->targetairport = v->current_order.GetDestination();
+ assert(DoesAircraftNeedRotation(v));
+ if (v->trackdir == v->next_trackdir) {
+ v->next_trackdir = INVALID_TRACKDIR;
+ v->ClearWaitTime();
+ return;
+ } else {
+ if (v->cur_speed != 0) {
+ SetWindowWidgetDirty(WC_VEHICLE_VIEW, v->index, WID_VV_START_STOP);
+ v->cur_speed = 0;
+ }
+
+ Direction desired_direction = TrackdirToDir(v->next_trackdir);
+ assert(IsValidDirection(desired_direction));
+ assert(v->direction != desired_direction);
+ DirDiff difference = DirDifference(v->direction, desired_direction);
+ assert(difference != DIRDIFF_SAME);
+ difference = difference <= DIRDIFF_REVERSE ? DIRDIFF_45LEFT : DIRDIFF_45RIGHT;
+ v->direction = v->Next()->direction = ChangeDir(v->direction, difference);
+
+ if (v->direction == desired_direction) {
+ v->trackdir = v->next_trackdir;
+
+ if (IsDiagonalTrackdir(v->trackdir)) {
+ /* Amend position when rotating in the middle of the tile. */
+ if (DiagDirToAxis(DirToDiagDir(v->direction)) == AXIS_X) {
+ v->y_pos = (v->y_pos & ~0xF) | 8;
+ } else {
+ v->x_pos = (v->x_pos & ~0xF) | 8;
+ }
+ } else {
+ /* Amend position when rotating at the edge of a tile. */
+ const AircraftSubcoordData &b = _aircraft_subcoord[TrackdirToEntrydir(v->trackdir)][TrackdirToTrack(v->trackdir)];
+ v->x_pos = (v->x_pos & ~0xF) | b.x_subcoord;
+ v->y_pos = (v->y_pos & ~0xF) | b.y_subcoord;
+ }
+ }
+
+ assert(!v->IsWaiting());
+ v->SetWaitTime(AIRCRAFT_ROTATION_STEP_TICKS);
}
- const Station *st = GetTargetAirportIfValid(v);
- const AirportFTAClass *apc = st == nullptr ? GetAirport(AT_DUMMY) : st->airport.GetFTA();
- Direction rotation = st == nullptr ? DIR_N : st->airport.rotation;
- v->pos = v->previous_pos = AircraftGetEntryPoint(v, apc, rotation);
+ SetAircraftPosition(v, v->x_pos, v->y_pos, v->z_pos);
+}
+
+/**
+ * Check whether a runway can be reserved.
+ * @param tile A start or end tile of the runway.
+ * @param skip_first_tile whether the first tile is already occupied and should be skipped.
+ * @return true if none of the tiles of the runway has a runway or track reservation.
+ */
+bool CanRunwayBeReserved(TileIndex tile, bool skip_first_tile = false)
+{
+ if (tile == 0) return false;
+
+ assert(IsTileType(tile, MP_STATION));
+ assert(IsAirportTile(tile));
+ assert(IsRunwayExtreme(tile));
+ DiagDirection dir = GetRunwayExtremeDirection(tile);
+ if (IsRunwayEnd(tile)) dir = ReverseDiagDir(dir);
+ TileIndexDiff diff = TileOffsByDiagDir(dir);
+
+ TileIndex t = tile;
+ if (skip_first_tile) t = TileAdd(t, diff);
+
+ for (;; t = TileAdd(t, diff)) {
+ assert(IsAirportTile(t));
+ assert(IsRunway(t));
+ if (HasAirportTileAnyReservation(t)) return false;
+ if (t != tile && IsRunwayExtreme(t)) return true;
+ }
+
+ NOT_REACHED();
+}
+
+/**
+ * Checks if an aircraft is at its next position.
+ * @param v aircraft
+ * @return whether it is at its next position.
+ */
+static bool IsAircraftOnNextPosition(const Aircraft *v)
+{
+ return v->x_pos == v->next_pos.x && v->y_pos == v->next_pos.y;
+}
+
+/**
+ * Updates state for an aircraft.
+ * @param v aircraft.
+ */
+void UpdateAircraftState(Aircraft *v)
+{
+ // revise: check conditions
+ // revise: is IsAircraftOnNextPosition always true here?
+ if (v->state == AS_RUNNING && !IsAircraftOnNextPosition(v)) return;
+ if (v->IsAircraftFlying() && !v->IsAircraftFreelyFlying()) return;
+
+ StationID cur_station = v->GetCurrentAirportID();
+ StationID cur_dest_station = v->targetairport = GetTargetDestination(v->current_order, true);
+ AircraftState next_state = AS_IDLE;
+ TileIndex dest_tile = 0;
+
+ switch (v->current_order.GetType()) {
+ case OT_GOTO_STATION:
+ next_state = AS_APRON;
+ dest_tile = v->GetOrderStationLocation(v->current_order.GetDestination());
+ break;
+
+ case OT_GOTO_DEPOT:
+ next_state = AS_HANGAR;
+ dest_tile = v->GetOrderHangarLocation(v->current_order.GetDestination());
+ break;
+
+ case OT_NOTHING:
+ if (cur_station == INVALID_STATION) {
+ /* If flying, find closest airport and go there. */
+ ClosestDepot closestHangar = v->FindClosestDepot();
+ cur_dest_station = closestHangar.st_destination;
+ dest_tile = v->GetOrderHangarLocation(closestHangar.destination);
+ } else {
+ /* If aircraft is in an airport, go to its hangar or aprons. */
+ Station *st = Station::Get(cur_station);
+ if (st->airport.HasHangar()) {
+ next_state = AS_HANGAR;
+ dest_tile = v->GetOrderHangarLocation(st->airport.hangar->index);
+ } else {
+ next_state = AS_APRON;
+ dest_tile = v->GetOrderStationLocation(st->index);
+ }
+ }
+ break;
+
+ default:
+ Debug(misc, 0, "Unhandled order type");
+ break;
+ }
+
+ v->dest_tile = dest_tile;
+
+ if (cur_station == INVALID_STATION) {
+ if (cur_dest_station == INVALID_STATION && v->IsAircraftFreelyFlying()) AircraftStartsFalling(v);
+ return;
+ }
+
+ if (cur_station != cur_dest_station) {
+ /* Aircraft has to leave current airport. */
+ next_state = AS_START_TAKEOFF;
+ }
+
+ if (v->state == next_state) return;
+
+ switch (next_state) {
+ case AS_START_TAKEOFF:
+ if (v->IsHelicopter()) {
+ if (IsApron(v->tile)) {
+ v->state = AS_START_TAKEOFF;
+ }
+ } else {
+ if (IsRunwayStart(v->tile)) {
+ v->state = AS_START_TAKEOFF;
+ v->UpdateNextTile(v->tile);
+ } else {
+ v->UpdateNextTile(INVALID_TILE);
+ }
+ }
+ break;
+ case AS_APRON:
+ if (v->state == AS_HANGAR) break;
+ if (!IsApron(v->tile) || (!v->IsHelicopter() && !IsPlaneApron(v->tile))) {
+ // Current tile is not a valid terminal.
+ v->state = AS_IDLE;
+ v->UpdateNextTile(INVALID_TILE);
+ }
+ break;
+ case AS_HANGAR:
+ if (!IsHangarTile(v->tile)) {
+ if (IsHeliportTile(v->tile)) {
+ /* Take off, as it is not possible to reach the hangar. */
+ v->state = AS_START_TAKEOFF;
+ v->Next()->Next()->cur_speed = 0;
+ break;
+ }
+ } else if (v->current_order.IsType(OT_GOTO_DEPOT) && v->current_order.GetDestination() == GetDepotIndex(v->tile)) {
+ v->UpdateNextTile(v->tile);
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+/**
+ * Handle Aircraft specific tasks when an Aircraft enters a hangar.
+ * @param v Vehicle that enters the hangar.
+ */
+void AircraftEntersHangar(Aircraft *v)
+{
+ v->subspeed = 0;
+ v->progress = 0;
+ v->cur_speed = 0;
+ v->state = AS_HANGAR;
+
+ if (IsExtendedDepot(v->tile)) {
+ v->UpdateViewport(true, true);
+ SetWindowClassesDirty(WC_AIRCRAFT_LIST);
+ SetWindowDirty(WC_VEHICLE_VIEW, v->index);
+
+ InvalidateWindowData(WC_VEHICLE_DEPOT, GetDepotIndex(v->tile));
+ assert(!v->IsServicing());
+ v->StartService();
+ } else {
+ assert(IsValidTrackdir(v->trackdir));
+ assert(TrackdirToTrack(v->trackdir) == DiagDirToDiagTrack(GetHangarDirection(v->tile)));
+ if ((v->vehstatus & VS_HIDDEN) == 0) {
+ v->direction = v->Next()->direction = DiagDirToDir(GetHangarDirection(v->tile));
+ RemoveAirportTrackReservation(v->tile, TrackdirToTrack(v->trackdir));
+
+ /* Hide vehicle. */
+ SetVisibility(v, false);
+ SetAircraftPosition(v, v->x_pos, v->y_pos, v->z_pos);
+ }
+ VehicleEnterDepot(v);
+ }
}
/**
* Aircraft is about to leave the hangar.
* @param v Aircraft leaving.
- * @param exit_dir The direction the vehicle leaves the hangar.
- * @note This function is called in AfterLoadGame for old savegames, so don't rely
- * on any data to be valid, especially don't rely on the fact that the vehicle
- * is actually on the ground inside a depot.
*/
-void AircraftLeaveHangar(Aircraft *v, Direction exit_dir)
+void AircraftLeavesHangar(Aircraft *v)
{
+ assert(IsHangarTile(v->tile));
v->cur_speed = 0;
v->subspeed = 0;
v->progress = 0;
- v->direction = exit_dir;
- v->vehstatus &= ~VS_HIDDEN;
- {
- Vehicle *u = v->Next();
- u->vehstatus &= ~VS_HIDDEN;
- /* Rotor blades */
- u = u->Next();
- if (u != nullptr) {
- u->vehstatus &= ~VS_HIDDEN;
- u->cur_speed = 80;
- }
- }
+ Aircraft *u = v->Next();
+ u->direction = v->direction;
+ u->trackdir = v->trackdir;
+
+ /* Rotor blades */
+ u = u->Next();
+ if (u != nullptr) u->cur_speed = 80;
VehicleServiceInDepot(v);
v->LeaveUnbunchingDepot();
+ if (!IsExtendedHangar(v->tile)) SetVisibility(v, true);
+
SetAircraftPosition(v, v->x_pos, v->y_pos, v->z_pos);
- InvalidateWindowData(WC_VEHICLE_DEPOT, v->tile);
+ InvalidateWindowData(WC_VEHICLE_DEPOT, GetDepotIndex(v->tile));
SetWindowClassesDirty(WC_AIRCRAFT_LIST);
}
-////////////////////////////////////////////////////////////////////////////////
-/////////////////// AIRCRAFT MOVEMENT SCHEME ////////////////////////////////
-////////////////////////////////////////////////////////////////////////////////
-static void AircraftEventHandler_EnterTerminal(Aircraft *v, const AirportFTAClass *apc)
+/**
+ * Aircraft arrives at a terminal. If it is the first aircraft, throw a party.
+ * Start loading cargo.
+ * @param v Aircraft that arrived.
+ */
+static void AircraftEntersTerminal(Aircraft *v)
{
- AircraftEntersTerminal(v);
- v->state = apc->layout[v->pos].heading;
+ assert(HasAirportTrackReserved(v->tile));
+ assert(CountBits(GetReservedAirportTracks(v->tile)) == 1);
+ assert(IsDiagonalTrackdir(v->trackdir));
+ assert(Station::IsValidID(v->targetairport));
+
+ Station *st = Station::Get(v->targetairport);
+ v->last_station_visited = st->index;
+
+ v->state = AS_APRON + (AircraftState)GetApronType(v->tile);
+
+ /* Check if station was ever visited before */
+ if (!(st->had_vehicle_of_type & HVOT_AIRCRAFT)) {
+ st->had_vehicle_of_type |= HVOT_AIRCRAFT;
+ SetDParam(0, st->index);
+ /* show newsitem of celebrating citizens */
+ AddVehicleNewsItem(
+ STR_NEWS_FIRST_AIRCRAFT_ARRIVAL,
+ (v->owner == _local_company) ? NT_ARRIVAL_COMPANY : NT_ARRIVAL_OTHER,
+ v->index,
+ st->index
+ );
+ AI::NewEvent(v->owner, new ScriptEventStationFirstVehicle(st->index, v->index));
+ Game::NewEvent(new ScriptEventStationFirstVehicle(st->index, v->index));
+ }
+
+ if (_settings_game.order.serviceathelipad && v->IsHelicopter() && IsHelipad(v->tile)) {
+ /* an excerpt of ServiceAircraft, without the invisibility stuff */
+ v->date_of_last_service = TimerGameEconomy::date;
+ v->breakdowns_since_last_service = 0;
+ v->reliability = v->GetEngine()->reliability;
+ SetWindowDirty(WC_VEHICLE_DETAILS, v->index);
+ }
+
+ v->BeginLoading();
}
+void HandleAircraftLanding(Aircraft *v);
+
/**
- * Aircraft arrived in an airport hangar.
- * @param v Aircraft in the hangar.
- * @param apc Airport description containing the hangar.
+ * Raises or lowers the helicopter.
+ * @param v The helicopter.
+ * @return Whether the helicopter is taking off or landing.
+ * @pre v->IsHelicopter()
*/
-static void AircraftEventHandler_EnterHangar(Aircraft *v, const AirportFTAClass *apc)
+bool RaiseLowerHelicopter(Aircraft *v)
+{
+ assert(v->IsHelicopter());
+
+ switch (v->state) {
+ case AS_FLYING_HELICOPTER_TAKEOFF:
+ case AS_START_TAKEOFF: {
+ Aircraft *u = v->Next()->Next();
+
+ /* Make sure the rotors don't rotate too fast */
+ if (u->cur_speed > 32) {
+ v->cur_speed = 0;
+ if (--u->cur_speed == 32) {
+ if (!PlayVehicleSound(v, VSE_START)) {
+ SoundID sfx = AircraftVehInfo(v->engine_type)->sfx;
+ /* For compatibility with old NewGRF we ignore the sfx property, unless a NewGRF-defined sound is used.
+ * The baseset has only one helicopter sound, so this only limits using plane or cow sounds. */
+ if (sfx < ORIGINAL_SAMPLE_COUNT) sfx = SND_18_TAKEOFF_HELICOPTER;
+ SndPlayVehicleFx(sfx, v);
+ }
+ v->state = AS_FLYING_HELICOPTER_TAKEOFF;
+ }
+ } else {
+ u->cur_speed = 32;
+ int count = UpdateAircraftSpeed(v);
+ if (count > 0) {
+ int z_dest;
+ GetAircraftFlightLevelBounds(v, &z_dest, nullptr);
+
+ /* Reached altitude? */
+ if (v->z_pos + count >= z_dest) {
+ if (v->cur_speed != 0) SetWindowWidgetDirty(WC_VEHICLE_VIEW, v->index, WID_VV_START_STOP);
+ v->cur_speed = 0;
+ if (v->NeedsAutomaticServicing()) {
+ Backup cur_company(_current_company, v->owner);
+ Command::Do(DC_EXEC, v->index, DepotCommand::Service | DepotCommand::LocateHangar, {});
+ cur_company.Restore();
+ }
+ RemoveAirportTrackReservation(v->tile, TrackdirToTrack(v->trackdir));
+ v->state = AS_FLYING;
+ AircraftUpdateNextPos(v);
+ }
+ v->z_pos = std::min(v->z_pos + count, z_dest);
+ }
+ }
+ SetAircraftPosition(v, v->x_pos, v->y_pos, v->z_pos);
+ return true;
+ }
+
+ case AS_FLYING_HELICOPTER_LANDING: {
+ /* Find altitude of landing position. */
+ int z = GetTileMaxPixelZ(v->tile) + 1;
+ z += GetLandingHeight(v->GetNextTile());
+
+ if (z == v->z_pos) {
+ Vehicle *u = v->Next()->Next();
+
+ /* Increase speed of rotors. When speed is 80, we've landed. */
+ if (u->cur_speed >= 80) {
+ if (v->cur_speed != 0) SetWindowWidgetDirty(WC_VEHICLE_VIEW, v->index, WID_VV_START_STOP);
+ v->cur_speed = 0;
+ SetAircraftPosition(v, v->x_pos, v->y_pos, v->z_pos);
+ v->state = AS_LANDED;
+ HandleAircraftLanding(v);
+ return true;
+ }
+ u->cur_speed += 4;
+ } else {
+ int count = UpdateAircraftSpeed(v);
+ if (count > 0) {
+ SetAircraftPosition(v, v->x_pos, v->y_pos, std::max(v->z_pos - count, z));
+ }
+ }
+ return true;
+ }
+
+ default:
+ return false;
+ }
+}
+
+static void PlayAircraftTakeoffSound(const Vehicle *v)
{
- VehicleEnterDepot(v);
- v->state = apc->layout[v->pos].heading;
+ if (PlayVehicleSound(v, VSE_START)) return;
+ SndPlayVehicleFx(AircraftVehInfo(v->engine_type)->sfx, v);
}
/**
- * Handle aircraft movement/decision making in an airport hangar.
- * @param v Aircraft in the hangar.
- * @param apc Airport description containing the hangar.
+ * Aircraft is at a position where it can start taking off.
+ * Check whether it should start taking off, change its mind or wait till the runway is free.
+ * @param v Vehicle ready to take off.
+ * @return whether it should stop moving this tick.
*/
-static void AircraftEventHandler_InHangar(Aircraft *v, const AirportFTAClass *apc)
+bool HandleAircraftReadyToTakeoff(Aircraft *v)
{
- /* if we just arrived, execute EnterHangar first */
- if (v->previous_pos != v->pos) {
- AircraftEventHandler_EnterHangar(v, apc);
- return;
+ assert(v->state == AS_START_TAKEOFF);
+
+ if (v->IsHelicopter()) {
+ assert(IsAirportTile(v->tile));
+ assert(IsApron(v->tile));
+
+ if (v->targetairport == v->GetCurrentAirportID()) {
+ /* Trying to go to the same airport. */
+ // revise
+ NOT_REACHED();
+ v->state = AS_IDLE;
+ return true;
+ }
+
+ RaiseLowerHelicopter(v);
+ return true;
}
- /* if we were sent to the depot, stay there */
- if (v->current_order.IsType(OT_GOTO_DEPOT) && (v->vehstatus & VS_STOPPED)) {
- v->current_order.Free();
- return;
+ assert(IsRunwayStart(v->tile));
+
+ if (v->targetairport == v->GetCurrentAirportID()) {
+ /* Trying to go to the same airport. */
+ v->state = AS_IDLE;
+ return true;
}
- /* Check if we should wait here for unbunching. */
- if (v->IsWaitingForUnbunching()) return;
+ /* Aircraft tries to take off using a runway. */
+ if (!CanRunwayBeReserved(v->tile, true)) return true;
- if (!v->current_order.IsType(OT_GOTO_STATION) &&
- !v->current_order.IsType(OT_GOTO_DEPOT))
- return;
+ RemoveAirportTrackReservation(v->tile, TrackdirToTrack(v->trackdir));
+ SetRunwayReservation(v->tile, true);
- /* We are leaving a hangar, but have to go to the exact same one; re-enter */
- if (v->current_order.IsType(OT_GOTO_DEPOT) && v->current_order.GetDestination() == v->targetairport) {
- VehicleEnterDepot(v);
- return;
+ v->state = AS_TAKEOFF_BEFORE_FLYING;
+ v->next_trackdir = v->Next()->next_trackdir = DiagDirToDiagTrackdir(GetRunwayExtremeDirection(v->tile));
+ v->UpdateNextTile(GetRunwayExtreme(v->tile, GetRunwayExtremeDirection(v->tile)));
+
+ if (v->trackdir != v->next_trackdir) {
+ /* If plane needs to rotate, rotate first and then play the take off sound. */
+ v->SetWaitTime(AIRCRAFT_ROTATION_STEP_TICKS);
+ } else {
+ /* Plane doesn't need to rotate. Play the take off sound right now. */
+ PlayAircraftTakeoffSound(v);
}
- /* if the block of the next position is busy, stay put */
- if (AirportHasBlock(v, &apc->layout[v->pos], apc)) return;
+ return false;
+}
- /* We are already at the target airport, we need to find a terminal */
- if (v->current_order.GetDestination() == v->targetairport) {
- /* FindFreeTerminal:
- * 1. Find a free terminal, 2. Occupy it, 3. Set the vehicle's state to that terminal */
- if (v->subtype == AIR_HELICOPTER) {
- if (!AirportFindFreeHelipad(v, apc)) return; // helicopter
- } else {
- if (!AirportFindFreeTerminal(v, apc)) return; // airplane
+/**
+ * Aircraft is taking off accelerating on runway, starting its flight or leaving the airport.
+ * @param v Vehicle that is taking off.
+ */
+void HandleAircraftTakingoff(Aircraft *v)
+{
+ switch (v->state) {
+ case AS_TAKEOFF_BEFORE_FLYING:
+ assert(!v->IsHelicopter());
+ v->state = AS_FLYING_TAKEOFF;
+ v->UpdateNextTile(v->GetNextTile());
+ break;
+
+ case AS_FLYING_TAKEOFF: {
+ assert(!v->IsHelicopter());
+ /* Next tile contains the runway end, so it can be unreserved. */
+ TileIndex old_runway_tile = v->GetNextTile();
+ SetRunwayReservation(old_runway_tile, false);
+ v->state = AS_FLYING_LEAVING_AIRPORT;
+ v->UpdateNextTile(old_runway_tile);
+ break;
+ }
+
+ case AS_FLYING_HELICOPTER_TAKEOFF:
+ RaiseLowerHelicopter(v);
+ break;
+
+ case AS_FLYING_LEAVING_AIRPORT: {
+ v->state = AS_FLYING;
+ v->UpdateNextTile(FindClosestLandingTile(v));
+ break;
+ }
+
+ default:
+ Debug(misc, 0, "Shouldnt be reached, state {}", v->state);
+ break;
+ }
+}
+
+TileIndex FindClosestFreeLandingTile(Aircraft *v);
+
+/**
+ * Handle Aircraft flying outside any airport or keeping a holding pattern
+ * on its target airport.
+ * @param v Vehicle that is flying towards its next target station, if any.
+ */
+void HandleAircraftFlying(Aircraft *v)
+{
+ switch (v->state) {
+ case AS_ON_HOLD_WAITING: {
+ bool can_land = !(Station::Get(v->targetairport))->airport.IsClosed();
+ if (v->IsHelicopter()) {
+ // revise
+ //if (!IsAirportTile(v->tile) || !IsApron(v->tile)) return;
+ assert(v->IsAircraftFlying());
+ TileIndex landing_tile;
+ Trackdir trackdir;
+ if (can_land) {
+ landing_tile = FindClosestFreeLandingTile(v);
+ trackdir = GetFreeAirportTrackdir(landing_tile, DiagDirToDiagTrackdir(DirToDiagDir(v->direction)));
+ can_land = trackdir != INVALID_TRACKDIR;
+ }
+
+ if (can_land) {
+ assert(IsValidTrackdir(trackdir));
+ SetAirportTrackReservation(landing_tile, TrackdirToTrack(trackdir));
+ v->state = AS_ON_HOLD_APPROACHING;
+ v->tile = landing_tile;
+ v->UpdateNextTile(landing_tile);
+ v->Next()->next_trackdir = trackdir;
+ } else {
+ v->UpdateNextTile(v->GetNextTile());
+ }
+ } else {
+ assert(IsValidTrackdir(v->trackdir));
+ TileIndex landing_tile = FindClosestLandingTile(v);
+ if (can_land && CanRunwayBeReserved(landing_tile)) {
+ assert(IsRunwayStart(landing_tile));
+ v->trackdir = DiagDirToDiagTrackdir(GetRunwayExtremeDirection(landing_tile));
+ SetRunwayReservation(landing_tile, true);
+ v->state = AS_ON_HOLD_APPROACHING;
+ v->UpdateNextTile(landing_tile);
+ } else {
+ v->UpdateNextTile(v->GetNextTile());
+ }
+ }
+ break;
+ }
+
+ case AS_ON_HOLD_APPROACHING:
+ if (v->IsHelicopter()) {
+ v->state = AS_DESCENDING;
+ assert(HasAirportTrackReserved(v->GetNextTile()));
+ assert(HasAirportTrackReserved(v->GetNextTile(), TrackdirToTrack(v->Next()->next_trackdir)));
+ v->trackdir = DiagDirToDiagTrackdir(DirToDiagDir(v->direction));
+ if (v->trackdir != v->Next()->next_trackdir) {
+ v->next_trackdir = v->Next()->next_trackdir;
+ }
+ } else {
+ if (v->next_pos.pos == AP_PLANE_HOLD_3) {
+ v->state = AS_DESCENDING;
+ }
+ }
+ v->UpdateNextTile(v->GetNextTile());
+ break;
+
+ case AS_FLYING:
+ v->state = AS_ON_HOLD_WAITING;
+ v->UpdateNextTile(v->GetNextTile());
+ break;
+
+ case AS_FLYING_NO_DEST:
+ break;
+
+ default:
+ Debug(misc, 0, "Shouldnt be reached, state {}", v->state);
+ break;
+ }
+}
+
+/**
+ * Handle Aircraft landing on an airport.
+ * @param v Landing aircraft.
+ */
+void HandleAircraftLanding(Aircraft *v)
+{
+ switch (v->state) {
+ case AS_LANDED: {
+ if (v->IsHelicopter()) {
+ assert(IsAirportTile(v->tile));
+ assert(IsApron(v->tile));
+ v->state = (AircraftState)((uint8_t)GetApronType(v->tile) + (uint8_t)AS_APRON);
+ break;
+ }
+
+ assert(IsAirportTile(v->tile));
+ assert(IsRunwayExtreme(v->tile));
+ assert(IsRunwayEnd(v->tile));
+
+ /* Free platform and reserve a track and set it to next trackdir. */
+ Trackdir trackdir = DiagDirToDiagTrackdir(GetRunwayExtremeDirection(v->tile));
+ Trackdir next_trackdir = GetFreeAirportTrackdir(v->tile, trackdir);
+ if (!IsValidTrackdir(next_trackdir)) {
+ v->SetWaitTime(AIRCRAFT_CANT_LEAVE_RUNWAY);
+ break;
+ }
+
+ v->trackdir = trackdir;
+ SetRunwayReservation(v->tile, false);
+ if (next_trackdir != v->trackdir) {
+ v->next_trackdir = next_trackdir;
+ v->SetWaitTime(AIRCRAFT_ROTATION_STEP_TICKS);
+ }
+ SetAirportTrackReservation(v->tile, TrackdirToTrack(next_trackdir));
+ v->state = AS_IDLE;
+ v->cur_speed = 0;
+ SetWindowWidgetDirty(WC_VEHICLE_VIEW, v->index, WID_VV_START_STOP);
+ v->UpdateNextTile(INVALID_TILE);
+ break;
+ }
+
+ case AS_FLYING_LANDING:
+ Debug(misc, 0, "Aircraft reached landed runway end still flying. Error of controller. Crashing aircraft unitnumber {} on air", v->unitnumber);
+ v->state = AS_LANDED;
+ CrashAircraft(v);
+ return;
+
+ case AS_DESCENDING:
+ if (v->IsHelicopter()) {
+ v->state = AS_FLYING_HELICOPTER_LANDING;
+ } else {
+ v->state = AS_FLYING_LANDING;
+ TileIndex tile = GetRunwayExtreme(v->tile, GetRunwayExtremeDirection(v->tile));
+ v->UpdateNextTile(tile);
+ }
+ break;
+
+ default:
+ Debug(misc, 0, "Shouldnt be reached, state {}", v->state);
+ break;
+ }
+}
+
+/**
+ * Plane touched down at the landing strip.
+ * @param v Aircraft that landed.
+ */
+static void HandlePlaneLandsOnRunway(Aircraft *v)
+{
+ assert(!v->IsHelicopter());
+ assert(v->state == AS_FLYING_LANDING);
+ Station *st = Station::Get(v->targetairport);
+
+ TileIndex vt = TileVirtXY(v->x_pos, v->y_pos);
+
+ v->state = AS_LANDED;
+ v->UpdateNextTile(v->GetNextTile());
+
+ /* Check if the aircraft needs to be replaced or renewed and send it to a hangar if needed. */
+ if (v->NeedsAutomaticServicing()) {
+ Backup cur_company(_current_company, v->owner);
+ Command::Do(DC_EXEC, v->index, DepotCommand::Service, {});
+ cur_company.Restore();
+ }
+
+ v->UpdateDeltaXY();
+
+ AirportTileAnimationTrigger(st, vt, AAT_STATION_AIRPLANE_LAND);
+
+ if (!PlayVehicleSound(v, VSE_TOUCHDOWN)) {
+ SndPlayVehicleFx(SND_17_SKID_PLANE, v);
+ }
+}
+
+/**
+ * Given the current state of an aircraft, get which is the next
+ * state to reach its target.
+ * @param a Aircraft
+ * @return the next state \a a should try to reach.
+ */
+AircraftState GetNextAircraftState(const Aircraft &a)
+{
+ assert(!a.IsAircraftFlying());
+
+ if (GetStationIndex(a.tile) != a.targetairport) {
+ /* Aircraft has to leave current airport. */
+ return AS_START_TAKEOFF;
+ }
+
+ if (a.state != AS_RUNNING && IsRunwayEnd(a.tile)) {
+ Airport *airport = &Station::GetByTile(a.tile)->airport;
+ bool free_terminal = false;
+ for (TileIndex tile : airport->aprons) {
+ if (HasAirportTrackReserved(tile)) continue;
+ free_terminal = true;
+ break;
+ }
+
+ if (!free_terminal) {
+ return airport->HasHangar() ? AS_HANGAR : AS_IDLE;
+ }
+ }
+
+ switch (a.current_order.GetType()) {
+ case OT_GOTO_STATION:
+ return a.IsHelicopter() ? AS_HELIPAD : AS_APRON;
+
+ case OT_GOTO_DEPOT:
+ return AS_HANGAR;
+
+ case OT_NOTHING:
+ /* If aircraft is in an airport, go to its hangar or aprons. */
+ if (Station::Get(a.targetairport)->airport.HasHangar()) {
+ return AS_HANGAR;
+ } else {
+ return a.IsHelicopter() ? AS_HELIPAD : AS_APRON;
+ }
+ return AS_IDLE;
+
+ default:
+ return AS_IDLE;
+ }
+}
+
+/**
+ * Aircraft reached a position where it may change to another state.
+ * Decide what to do.
+ * @param v aircraft.
+ * @return whether it should stop moving this tick.
+ */
+bool HandleAircraftState(Aircraft *v)
+{
+ if (!IsAircraftOnNextPosition(v)) return false;
+
+ if (!v->IsAircraftFlying()) {
+ switch (v->current_order.GetType()) {
+ case OT_LEAVESTATION:
+ /* A leave station order only needs one tick to get processed,
+ * so we can always skip ahead. */
+ v->current_order.Free();
+ SetWindowWidgetDirty(WC_VEHICLE_VIEW, v->index, WID_VV_START_STOP);
+ ProcessOrders(v);
+ v->state = AS_IDLE;
+ v->UpdateNextTile(INVALID_TILE);
+ UpdateAircraftState(v);
+ return true;
+
+ case OT_GOTO_STATION:
+ if (IsAirportTile(v->tile) && IsApron(v->tile)) {
+ if (v->targetairport == GetStationIndex(v->tile)) {
+ AircraftEntersTerminal(v);
+ } else {
+ v->state = GetNextAircraftState(*v);
+ v->UpdateNextTile(INVALID_TILE);
+ }
+ return true;
+ }
+ break;
+
+ case OT_NOTHING:
+ case OT_GOTO_DEPOT:
+ if (IsHangarTile(v->tile) && v->state != AS_HANGAR) {
+ AircraftEntersHangar(v);
+ v->UpdateNextTile(INVALID_TILE);
+ return true;
+ } else if (v->state == AS_RUNNING) {
+ v->state = AS_IDLE;
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ UpdateAircraftState(v);
+
+ switch (v->state) {
+ case AS_APRON:
+ case AS_HELIPAD:
+ case AS_HELIPORT:
+ case AS_BUILTIN_HELIPORT:
+ /* Helicopter takeoff. */
+ AircraftEntersTerminal(v);
+ return true;
+
+ case AS_START_TAKEOFF:
+ return HandleAircraftReadyToTakeoff(v);
+
+ case AS_TAKEOFF_BEFORE_FLYING:
+ case AS_FLYING_TAKEOFF:
+ case AS_FLYING_LEAVING_AIRPORT:
+ case AS_FLYING_HELICOPTER_TAKEOFF:
+ HandleAircraftTakingoff(v);
+ return false;
+
+ case AS_FLYING_NO_DEST:
+ case AS_FLYING:
+ case AS_ON_HOLD_WAITING:
+ case AS_ON_HOLD_APPROACHING:
+ HandleAircraftFlying(v);
+ return false;
+
+ case AS_FLYING_LANDING:
+ case AS_DESCENDING:
+ case AS_LANDED:
+ HandleAircraftLanding(v);
+ return v->IsHelicopter() || v->state == AS_IDLE;
+
+ case AS_FLYING_HELICOPTER_LANDING:
+ RaiseLowerHelicopter(v);
+ return true;
+
+ case AS_IDLE:
+ return false;
+
+ case AS_HANGAR:
+ if (IsHangarTile(v->tile)) {
+ AircraftEntersHangar(v);
+ v->UpdateNextTile(INVALID_TILE);
+ UpdateAircraftState(v);
+ return true;
+ }
+ break;
+ case AS_RUNNING:
+ Debug(misc, 0, "Moving aircraft {}, shouldn't reach this point. Probably there will be a crash soon.", v->unitnumber);
+ break;
+ default:
+ Debug(misc, 0, "Unhandled state {}, next {}", v->state, v->Next()->state);
+ NOT_REACHED();
+ break;
+ }
+
+ return false;
+}
+
+/**
+ * Update the aircraft flight level according to aircraft state and position.
+ * @param v Aircraft.
+ * @pre v->IsAircraftFlying()
+ */
+void HandleAircraftFlightLevel(Aircraft *v)
+{
+ assert(v->IsAircraftFlying());
+
+ switch(v->state) {
+ case AS_ON_HOLD_WAITING:
+ case AS_ON_HOLD_APPROACHING:
+ if (v->z_pos > GetAircraftHoldMaxAltitude(v)) v->z_pos--;
+ break;
+ case AS_DESCENDING: {
+ assert(IsValidTile(v->GetNextTile()));
+ int z = GetTileHeightBelowAircraft(v) + 1;
+ /* Runway may be in a higher tile than the current one. */
+ z = std::max(z, GetTileMaxPixelZ(v->GetNextTile()) + 1);
+ z = v->z_pos - z;
+
+ if (z > 32) {
+ v->z_pos = v->z_pos - 2;
+ } else if (z > 16 || (v->tile == v->GetNextTile() && z > 8)) {
+ v->z_pos = v->z_pos - 1;
+ }
+ break;
+ }
+
+ case AS_FLYING_LANDING: {
+ int z = GetTileHeightBelowAircraft(v) + 1;
+ assert(z < v->z_pos);
+ v->z_pos -= 1;
+ if (v->z_pos == z) HandlePlaneLandsOnRunway(v);
+ break;
+ }
+
+ default:
+ v->z_pos = GetAircraftFlightLevel(v, v->state == AS_FLYING_TAKEOFF);
+ break;
+ }
+}
+
+AircraftPosition _aircraft_pos_offsets[AP_END] = {
+ { AP_DEFAULT , 8 , 8 }, // default: middle of the tile
+ { AP_HELICOPTER_HOLD_2 , 8 + 1 * (int)TILE_SIZE, 0 + 0 * (int)TILE_SIZE }, // on helicopter hold start respect the apron tile
+ { AP_HELICOPTER_HOLD_3 , 0 + 0 * (int)TILE_SIZE, 8 + 1 * (int)TILE_SIZE }, // on helicopter hold, pos 2
+ { AP_HELICOPTER_HOLD_4 , 0 - 1 * (int)TILE_SIZE, 8 + 1 * (int)TILE_SIZE }, // on helicopter hold, pos 3
+ { AP_HELICOPTER_HOLD_5 , -8 - 2 * (int)TILE_SIZE, 0 + 0 * (int)TILE_SIZE }, // on helicopter hold, pos 4
+ { AP_HELICOPTER_HOLD_6 , -8 - 2 * (int)TILE_SIZE, 0 - 1 * (int)TILE_SIZE }, // on helicopter hold, pos 5
+ { AP_HELICOPTER_HOLD_7 , 0 - 1 * (int)TILE_SIZE, -8 - 2 * (int)TILE_SIZE }, // on helicopter hold, pos 6
+ { AP_HELICOPTER_HOLD_END , 0 + 0 * (int)TILE_SIZE, -8 - 2 * (int)TILE_SIZE }, // on helicopter hold, pos 7
+ { AP_HELICOPTER_HOLD_START , 8 + 1 * (int)TILE_SIZE, 0 - 1 * (int)TILE_SIZE }, // on helicopter hold, pos 8 and last
+ { AP_HELIPORT_DEST , 6 , 8 }, // heliport landing dest respect the apron tile
+ { AP_BUILTIN_HELIPORT_DEST , - 2 + 2 * (int)TILE_SIZE, 8 }, // builtin heliport dest respect the tile containing the airport
+ { AP_PLANE_BEFORE_FLYING , 8 , 8 }, // default: middle of the tile
+ { AP_PLANE_START_FLYING , 8 + 1 * (int)TILE_SIZE, 8 }, // start flying on runway respect the runway end
+ { AP_PLANE_LEAVE_AIRPORT , 8 + 0 * (int)TILE_SIZE, 8 }, // remove runway reservation respect the runway end
+ { AP_PLANE_HOLD_START , -8 - 5 * (int)TILE_SIZE, 8 }, // leaving airport respect the runway end
+ { AP_PLANE_HOLD_2 , 0 + 8 * (int)TILE_SIZE, 8 }, // on hold start and also descending respect the runway start tile
+ { AP_PLANE_HOLD_3 , 0 + 4 * (int)TILE_SIZE, 8 }, // on hold, pos 2
+ { AP_PLANE_HOLD_4 , 0 - 8 * (int)TILE_SIZE, 8 }, // on hold, pos 3
+ { AP_PLANE_HOLD_5 , -8 - 11 * (int)TILE_SIZE, 0 - 3 * (int)TILE_SIZE }, // on hold, pos 4
+ { AP_PLANE_HOLD_6 , -8 - 11 * (int)TILE_SIZE, 0 - 7 * (int)TILE_SIZE }, // on hold, pos 5
+ { AP_PLANE_HOLD_7 , 0 - 8 * (int)TILE_SIZE, 8 - 11 * (int)TILE_SIZE }, // on hold, pos 6
+ { AP_PLANE_HOLD_8 , 0 + 8 * (int)TILE_SIZE, 8 - 11 * (int)TILE_SIZE }, // on hold, pos 7
+ { AP_PLANE_HOLD_END , -8 + 12 * (int)TILE_SIZE, 0 - 7 * (int)TILE_SIZE }, // on hold, pos 8
+ { AP_PLANE_HOLD_START , -8 + 12 * (int)TILE_SIZE, 0 - 3 * (int)TILE_SIZE }, // on hold, pos 9 and last
+ { AP_PLANE_LANDING , 8 , 8 }, // descending
+ { AP_DEFAULT , 8 , 8 }, // landing
+
+};
+
+/**
+ * Get the position for a given position type and rotation.
+ * @param pos Position type
+ * @param dir DiagDirection indicating the rotation to apply.
+ * @return aircraft position for the position type and rotation.
+ */
+AircraftPosition RotatedAircraftPosition(AircraftPos pos, DiagDirection dir)
+{
+ AircraftPosition rotated_pos;
+ rotated_pos = _aircraft_pos_offsets[pos];
+ switch (dir) {
+ case DIAGDIR_NE:
+ break;
+ case DIAGDIR_SE:
+ rotated_pos.x = _aircraft_pos_offsets[pos].y;
+ rotated_pos.y = TILE_SIZE - _aircraft_pos_offsets[pos].x;
+ break;
+ case DIAGDIR_SW:
+ rotated_pos.x = TILE_SIZE - _aircraft_pos_offsets[pos].x;
+ rotated_pos.y = TILE_SIZE - _aircraft_pos_offsets[pos].y;
+ break;
+ case DIAGDIR_NW:
+ rotated_pos.x = TILE_SIZE - _aircraft_pos_offsets[pos].y;
+ rotated_pos.y = _aircraft_pos_offsets[pos].x;
+ break;
+ default:
+ NOT_REACHED();
+ }
+
+ return rotated_pos;
+}
+
+AircraftPos helicopter_entry_point[8] = {
+ AP_HELICOPTER_HOLD_2,
+ AP_HELICOPTER_HOLD_7,
+ AP_HELICOPTER_HOLD_3,
+ AP_HELICOPTER_HOLD_6,
+ AP_HELICOPTER_HOLD_START,
+ AP_HELICOPTER_HOLD_END,
+ AP_HELICOPTER_HOLD_4,
+ AP_HELICOPTER_HOLD_5,
+};
+
+AircraftPos plane_entry_pos[DIAGDIR_END][4] = {
+ {AP_PLANE_HOLD_START, AP_PLANE_HOLD_7, AP_PLANE_HOLD_5, AP_PLANE_HOLD_3},
+ {AP_PLANE_HOLD_5, AP_PLANE_HOLD_3, AP_PLANE_HOLD_7, AP_PLANE_HOLD_START},
+ {AP_PLANE_HOLD_7, AP_PLANE_HOLD_START, AP_PLANE_HOLD_3, AP_PLANE_HOLD_5},
+ {AP_PLANE_HOLD_3, AP_PLANE_HOLD_5, AP_PLANE_HOLD_START, AP_PLANE_HOLD_7},
+};
+
+/**
+ * Get the offset position an aircraft must get respect a tile or its next position.
+ * @param tile The tile the aircraft tries to reach.
+ * @param next_pos The next position type the aircraft is trying to reach.
+ * @return the destination position of the aircraft.
+ */
+AircraftPosition GetAircraftPositionByTile(TileIndex tile, AircraftPos next_pos) {
+ assert(IsAirportTile(tile));
+
+ switch (GetAirportTileType(tile)) {
+ case ATT_APRON_NORMAL:
+ case ATT_APRON_HELIPAD:
+ return _aircraft_pos_offsets[AP_DEFAULT];
+ case ATT_APRON_HELIPORT: {
+ DiagDirection diagdir = GetAirportTileRotation(tile);
+ return RotatedAircraftPosition(AP_HELIPORT_DEST, diagdir);
}
- } else { // Else prepare for launch.
- /* airplane goto state takeoff, helicopter to helitakeoff */
- v->state = (v->subtype == AIR_HELICOPTER) ? HELITAKEOFF : TAKEOFF;
- }
- const Station *st = Station::GetByTile(v->tile);
- AircraftLeaveHangar(v, st->airport.GetHangarExitDirection(v->tile));
- AirportMove(v, apc);
-}
-
-/** At one of the Airport's Terminals */
-static void AircraftEventHandler_AtTerminal(Aircraft *v, const AirportFTAClass *apc)
-{
- /* if we just arrived, execute EnterTerminal first */
- if (v->previous_pos != v->pos) {
- AircraftEventHandler_EnterTerminal(v, apc);
- /* on an airport with helipads, a helicopter will always land there
- * and get serviced at the same time - setting */
- if (_settings_game.order.serviceathelipad) {
- if (v->subtype == AIR_HELICOPTER && apc->num_helipads > 0) {
- /* an excerpt of ServiceAircraft, without the invisibility stuff */
- v->date_of_last_service = TimerGameEconomy::date;
- v->date_of_last_service_newgrf = TimerGameCalendar::date;
- v->breakdowns_since_last_service = 0;
- v->reliability = v->GetEngine()->reliability;
- SetWindowDirty(WC_VEHICLE_DETAILS, v->index);
+ case ATT_APRON_BUILTIN_HELIPORT:
+ return _aircraft_pos_offsets[AP_BUILTIN_HELIPORT_DEST];
+ case ATT_RUNWAY_START_NO_LANDING:
+ case ATT_RUNWAY_START_ALLOW_LANDING:
+ return _aircraft_pos_offsets[AP_DEFAULT];
+ default:
+ return _aircraft_pos_offsets[next_pos];
+ }
+}
+
+
+/**
+ * @param v Aircraft
+ */
+void SetNextAircraftPosition(Aircraft &v)
+{
+ TileIndex tile = v.GetNextTile();
+ AircraftPos next_pos = v.next_pos.pos;
+ DiagDirection diagdir = DIAGDIR_NE;
+
+ switch (v.state) {
+ case AS_START_TAKEOFF:
+ next_pos = AP_START_TAKE_OFF;
+ [[fallthrough]];
+ case AS_DESCENDING:
+ case AS_FLYING_LEAVING_AIRPORT:
+ case AS_TAKEOFF_BEFORE_FLYING:
+ case AS_FLYING_TAKEOFF:
+ if (!IsValidTile(tile)) break;
+ assert(IsAirportTile(tile));
+ if (v.IsHelicopter()) {
+ assert(IsApron(tile));
+ v.next_pos = GetAircraftPositionByTile(tile, AP_DEFAULT);
+ } else {
+ if (v.state == AS_DESCENDING) next_pos = AP_PLANE_DESCENDING;
+ assert(IsRunwayExtreme(tile));
+ diagdir = GetRunwayExtremeDirection(tile);
+ v.next_pos = RotatedAircraftPosition(next_pos, diagdir);
+ }
+ break;
+
+ case AS_FLYING_NO_DEST:
+ if (next_pos == AP_DEFAULT) {
+ diagdir = DirToDiagDir(v.direction);
+ next_pos = AP_PLANE_HOLD_START;
+ }
+ v.next_pos = RotatedAircraftPosition(next_pos, diagdir);
+ break;
+
+ case AS_FLYING_LANDING:
+ assert(IsRunwayEnd(tile) && IsLandingTypeTile(tile));
+ diagdir = GetRunwayExtremeDirection(tile);
+ diagdir = ReverseDiagDir(diagdir);
+ v.next_pos = RotatedAircraftPosition(AP_PLANE_LANDING, diagdir);
+ break;
+
+ case AS_LANDED:
+ assert(IsValidTile(tile));
+ assert(IsAirportTile(tile));
+ assert(!v.IsHelicopter());
+ assert(IsRunwayEnd(tile) && IsLandingTypeTile(tile));
+ diagdir = GetRunwayExtremeDirection(tile);
+ v.next_pos = RotatedAircraftPosition(AP_PLANE_LANDING, diagdir);
+ break;
+
+ case AS_ON_HOLD_APPROACHING:
+ if (v.IsHelicopter()) {
+ assert(IsAirportTile(tile));
+ v.next_pos = GetAircraftPositionByTile(tile, AP_DEFAULT);
+
+ break;
+ }
+ [[fallthrough]];
+
+ case AS_ON_HOLD_WAITING:
+ if (!v.IsHelicopter()) {
+ assert(IsAirportTile(tile));
+ assert(IsRunwayExtreme(tile));
+ /* If heading to the same runway, but it is occupied, try rotating. */
+ diagdir = GetRunwayExtremeDirection(tile);
+ v.next_pos = RotatedAircraftPosition(next_pos, diagdir);
+ break;
+ }
+ [[fallthrough]];
+
+ case AS_FLYING: {
+ // Decide the entry point
+ AircraftPosition origin_offset;
+ assert(IsAirportTile(tile));
+ if (v.IsHelicopter()) {
+ origin_offset = GetAircraftPositionByTile(tile, AP_DEFAULT);
+ } else {
+ assert(IsAirportTile(tile));
+ assert(IsRunwayStart(tile) && IsLandingTypeTile(tile));
+ diagdir = GetRunwayExtremeDirection(tile);
+ origin_offset = RotatedAircraftPosition(AP_PLANE_HOLD_START, diagdir);
+ }
+
+ int delta_x = v.x_pos - TileX(tile) * TILE_SIZE - origin_offset.x;
+ int delta_y = v.y_pos - TileY(tile) * TILE_SIZE - origin_offset.y;
+
+ uint entry_num;
+ AircraftPos entry_pos;
+ if (v.IsHelicopter()) {
+ entry_num = (delta_y < 0) +
+ ((delta_x < 0) << 1) +
+ ((abs(delta_y) < abs(delta_x)) << 2);
+ entry_pos = helicopter_entry_point[entry_num];
+ } else {
+ if (abs(delta_y) < abs(delta_x)) {
+ /* We are northeast or southwest of the airport */
+ entry_pos = plane_entry_pos[diagdir][delta_x < 0];
+ } else {
+ /* We are northwest or southeast of the airport */
+ entry_pos = plane_entry_pos[diagdir][(delta_y < 0) + 2];
+ }
+ }
+
+ v.next_pos = RotatedAircraftPosition(entry_pos, diagdir);
+ if (v.IsHelicopter()) {
+ v.next_pos.x += origin_offset.x;
+ v.next_pos.y += origin_offset.y;
}
+ break;
}
+
+ default:
+ if (!IsValidTile(tile)) break;
+ v.next_pos = GetAircraftPositionByTile(tile, next_pos);
+ break;
+ }
+}
+
+/**
+ * Update this->next_pos and next path tile (this->Next()->dest_tile).
+ * Use it after updating next_tile or when next desired position changes
+ * (i.e. when flying and approaching a runway).
+ * @param tile next tile
+ */
+void Aircraft::UpdateNextTile(TileIndex tile)
+{
+ if (tile == 0) return;
+
+ /* Update next path tile. */
+ this->Next()->dest_tile = tile;
+
+ SetNextAircraftPosition(*this);
+
+ this->next_pos.x += TileX(tile) * TILE_SIZE;
+ this->next_pos.y += TileY(tile) * TILE_SIZE;
+}
+
+/** Set the right pos when heading to other airports after takeoff.
+ * @param v Aircraft.
+ */
+void AircraftUpdateNextPos(Aircraft *v)
+{
+ assert(v->IsAircraftFreelyFlying());
+
+ TileIndex tile = v->GetNextTile();
+ if (IsValidTile(tile) && IsAirportTile(tile) &&
+ IsRunwayStart(tile) && v->targetairport == GetStationIndex(tile)) {
return;
}
- if (v->current_order.IsType(OT_NOTHING)) return;
+ AssignLandingTile(v, FindClosestLandingTile(v));
+ v->UpdateNextTile(v->GetNextTile());
+}
+
+/**
+ * Get a tile where aircraft can land. For helicopters, it will check helipads, heliports
+ * and aprons, in this ordrer, and finally runways. For normal aircraft, it will check runways.
+ * @param v The aircraft trying to land.
+ * @return a valid tile where to land, or 0 otherwise.
+ */
+TileIndex FindClosestLandingTile(Aircraft *v)
+{
+ v->targetairport = GetTargetDestination(v->current_order, true);
+ assert(Station::IsValidID(v->targetairport));
+ Station *st = Station::GetIfValid(v->targetairport);
+
+ if (!CanVehicleUseStation(v, st)) return 0;
+
+ TileIndex landing_tile = 0;
+ TileIndex free_landing_tile = 0;
+ uint32_t best_dist = UINT32_MAX;
+ uint32_t free_best_dist = UINT32_MAX;
+
+ if (v->IsHelicopter()) {
+ for (auto &it : st->airport.helipads) {
+ if (DistanceSquare(it, v->tile) < best_dist) {
+ landing_tile = it;
+ best_dist = DistanceSquare(it, v->tile);
+ }
+ if (!HasAirportTrackReserved(it) && DistanceSquare(it, v->tile) < free_best_dist) {
+ free_landing_tile = it;
+ free_best_dist = DistanceSquare(it, v->tile);
+ }
+ }
+
+ if (free_landing_tile != 0) return free_landing_tile;
- /* if the block of the next position is busy, stay put */
- if (AirportHasBlock(v, &apc->layout[v->pos], apc)) return;
+ if (v->current_order.GetType() != OT_GOTO_DEPOT) {
+ for (auto &it : st->airport.heliports) {
+ if (DistanceSquare(it, v->tile) < best_dist) {
+ landing_tile = it;
+ best_dist = DistanceSquare(it, v->tile);
+ }
+ if (!HasAirportTrackReserved(it) && DistanceSquare(it, v->tile) < free_best_dist) {
+ free_landing_tile = it;
+ free_best_dist = DistanceSquare(it, v->tile);
+ }
+ }
+ }
- /* airport-road is free. We either have to go to another airport, or to the hangar
- * ---> start moving */
+ if (free_landing_tile != 0) return free_landing_tile;
+
+ for (auto &it : st->airport.aprons) {
+ if (DistanceSquare(it, v->tile) < best_dist) {
+ landing_tile = it;
+ best_dist = DistanceSquare(it, v->tile);
+ }
+ if (!HasAirportTrackReserved(it) && DistanceSquare(it, v->tile) < free_best_dist) {
+ free_landing_tile = it;
+ free_best_dist = DistanceSquare(it, v->tile);
+ }
+ }
+
+ if (free_landing_tile != 0) return free_landing_tile;
+
+ for (auto &it : st->airport.aprons) {
+ if (DistanceSquare(it, v->tile) < best_dist) {
+ landing_tile = it;
+ best_dist = DistanceSquare(it, v->tile);
+ }
+ if (!HasAirportTrackReserved(it) && DistanceSquare(it, v->tile) < free_best_dist) {
+ free_landing_tile = it;
+ free_best_dist = DistanceSquare(it, v->tile);
+ }
+ }
- bool go_to_hangar = false;
- switch (v->current_order.GetType()) {
- case OT_GOTO_STATION: // ready to fly to another airport
- break;
- case OT_GOTO_DEPOT: // visit hangar for servicing, sale, etc.
- go_to_hangar = v->current_order.GetDestination() == v->targetairport;
- break;
- case OT_CONDITIONAL:
- /* In case of a conditional order we just have to wait a tick
- * longer, so the conditional order can actually be processed;
- * we should not clear the order as that makes us go nowhere. */
- return;
- default: // orders have been deleted (no orders), goto depot and don't bother us
- v->current_order.Free();
- go_to_hangar = true;
+ return landing_tile;
}
- if (go_to_hangar && Station::Get(v->targetairport)->airport.HasHangar()) {
- v->state = HANGAR;
- } else {
- /* airplane goto state takeoff, helicopter to helitakeoff */
- v->state = (v->subtype == AIR_HELICOPTER) ? HELITAKEOFF : TAKEOFF;
+ for (auto &it : st->airport.runways) {
+ if (!IsLandingTypeTile(it)) continue;
+ if (DistanceSquare(it, v->tile) < best_dist) {
+ landing_tile = it;
+ best_dist = DistanceSquare(it, v->tile);
+ }
+ if (CanRunwayBeReserved(it) &&
+ DistanceSquare(it, v->tile) < free_best_dist) {
+ free_landing_tile = it;
+ free_best_dist = DistanceSquare(it, v->tile);
+ }
}
- AirportMove(v, apc);
-}
-static void AircraftEventHandler_General(Aircraft *, const AirportFTAClass *)
-{
- FatalError("OK, you shouldn't be here, check your Airport Scheme!");
-}
+ if (free_landing_tile != 0) return free_landing_tile;
-static void AircraftEventHandler_TakeOff(Aircraft *v, const AirportFTAClass *)
-{
- PlayAircraftSound(v); // play takeoffsound for airplanes
- v->state = STARTTAKEOFF;
+ return landing_tile;
}
-static void AircraftEventHandler_StartTakeOff(Aircraft *v, const AirportFTAClass *)
-{
- v->state = ENDTAKEOFF;
- v->UpdateDeltaXY();
+/**
+ * Get a tile where aircraft can land. For helicopters, it will check helipads, heliports
+ * and aprons, in this ordrer, and finally runways. For normal aircraft, it will check runways.
+ * @param v The aircraft trying to land.
+ * @return a valid tile where to land, or INVALID_TILE otherwise.
+ */
+TileIndex FindClosestFreeLandingTile(Aircraft *v) {
+ TileIndex tile = FindClosestLandingTile(v);
+ if (tile == 0) return INVALID_TILE;
+ if (HasAirportTrackReserved(tile)) return INVALID_TILE;
+ return tile;
}
-static void AircraftEventHandler_EndTakeOff(Aircraft *v, const AirportFTAClass *)
-{
- v->state = FLYING;
- /* get the next position to go to, differs per airport */
- AircraftNextAirportPos_and_Order(v);
-}
-static void AircraftEventHandler_HeliTakeOff(Aircraft *v, const AirportFTAClass *)
+ClosestDepot Aircraft::FindClosestDepot()
{
- v->state = FLYING;
- v->UpdateDeltaXY();
+ const Station *st = Station::GetIfValid(this->GetCurrentAirportID());
+ if (st == nullptr || !st->airport.HasHangar()) st = GetTargetAirportIfValid(this);
+ /* If the station is not a valid airport or if it has no hangars */
+ if (st == nullptr || !CanVehicleUseStation(this, st) || !st->airport.HasHangar()) {
+ /* the aircraft has to search for a hangar on its own */
+ StationID station = FindClosestHangar(this);
- /* get the next position to go to, differs per airport */
- AircraftNextAirportPos_and_Order(v);
+ if (station == INVALID_STATION) return ClosestDepot();
- /* Send the helicopter to a hangar if needed for replacement */
- if (v->NeedsAutomaticServicing()) {
- Backup cur_company(_current_company, v->owner);
- Command::Do(DC_EXEC, v->index, DepotCommand::Service | DepotCommand::LocateHangar, {});
- cur_company.Restore();
+ st = Station::Get(station);
}
+
+ return ClosestDepot(st->airport.hangar->depot_tiles[0], st->airport.hangar->index, st->index);
}
-static void AircraftEventHandler_Flying(Aircraft *v, const AirportFTAClass *apc)
+/**
+ * Checks whether an aircraft can land on the next targetairport.
+ * It checks whether it can land (helipads for helicopters, whether there is a landing runway...).
+ * It also checks if the destination is too far.
+ * @param v Aircraft
+ * @return whether it can reach its targetairport
+ */
+bool IsReachableDest(Aircraft *v)
{
+ assert(IsAirportTile(v->tile));
+ assert(!v->IsAircraftFlying());
+ if (v->targetairport == GetStationIndex(v->tile)) return true;
+ if (v->targetairport == INVALID_STATION) return false;
+
+ assert(Station::IsValidID(v->targetairport));
Station *st = Station::Get(v->targetairport);
- /* Runway busy, not allowed to use this airstation or closed, circle. */
- if (CanVehicleUseStation(v, st) && (st->owner == OWNER_NONE || st->owner == v->owner) && !(st->airport.flags & AIRPORT_CLOSED_block)) {
- /* {32,FLYING,NOTHING_block,37}, {32,LANDING,N,33}, {32,HELILANDING,N,41},
- * if it is an airplane, look for LANDING, for helicopter HELILANDING
- * it is possible to choose from multiple landing runways, so loop until a free one is found */
- uint8_t landingtype = (v->subtype == AIR_HELICOPTER) ? HELILANDING : LANDING;
- const AirportFTA *current = apc->layout[v->pos].next;
- while (current != nullptr) {
- if (current->heading == landingtype) {
- /* save speed before, since if AirportHasBlock is false, it resets them to 0
- * we don't want that for plane in air
- * hack for speed thingie */
- uint16_t tcur_speed = v->cur_speed;
- uint16_t tsubspeed = v->subspeed;
- if (!AirportHasBlock(v, current, apc)) {
- v->state = landingtype; // LANDING / HELILANDING
- if (v->state == HELILANDING) SetBit(v->flags, VAF_HELI_DIRECT_DESCENT);
- /* it's a bit dirty, but I need to set position to next position, otherwise
- * if there are multiple runways, plane won't know which one it took (because
- * they all have heading LANDING). And also occupy that block! */
- v->pos = current->next_position;
- SETBITS(st->airport.flags, apc->layout[v->pos].block);
- return;
- }
- v->cur_speed = tcur_speed;
- v->subspeed = tsubspeed;
+ TileIndex closest_landing = FindClosestLandingTile(v);
+ if (closest_landing == 0 || !CanVehicleUseStation(v, st)) {
+ if (!HasBit(v->flags, VAF_CAN_T_LAND)) {
+ SetBit(v->flags, VAF_CAN_T_LAND);
+ v->SetWaitTime(AIRCRAFT_WAIT_FREE_PATH_TICKS);
+ SetWindowWidgetDirty(WC_VEHICLE_VIEW, v->index, WID_VV_START_STOP);
+ AI::NewEvent(v->owner, new ScriptEventAircraftNoLandDest(v->index));
+ if (v->owner == _local_company) {
+ /* Post a news message. */
+ SetDParam(0, v->index);
+ AddVehicleAdviceNewsItem(STR_NEWS_AIRCRAFT_CAN_T_LAND, v->index);
}
- current = current->next;
}
+ if (v->state != AS_HANGAR) {
+ v->state = AS_IDLE;
+ v->UpdateNextTile(v->tile);
+ }
+ return false;
+ } else if (HasBit(v->flags, VAF_CAN_T_LAND)) {
+ /* Aircraft can land now. */
+ ClrBit(v->flags, VAF_CAN_T_LAND);
+ SetWindowWidgetDirty(WC_VEHICLE_VIEW, v->index, WID_VV_START_STOP);
+ DeleteVehicleNews(v->index, STR_NEWS_AIRCRAFT_CAN_T_LAND);
}
- v->state = FLYING;
- v->pos = apc->layout[v->pos].next_position;
-}
-static void AircraftEventHandler_Landing(Aircraft *v, const AirportFTAClass *)
-{
- v->state = ENDLANDING;
- AircraftLandAirplane(v); // maybe crash airplane
+ if (v->acache.cached_max_range_sqr == 0) return true;
+ Station *cur_st = Station::GetIfValid(GetStationIndex(v->tile));
- /* check if the aircraft needs to be replaced or renewed and send it to a hangar if needed */
- if (v->NeedsAutomaticServicing()) {
- Backup cur_company(_current_company, v->owner);
- Command::Do(DC_EXEC, v->index, DepotCommand::Service, {});
- cur_company.Restore();
+ if (DistanceSquare(cur_st->airport.tile, closest_landing) > v->acache.cached_max_range_sqr) {
+ if (!HasBit(v->flags, VAF_DEST_TOO_FAR)) {
+ SetBit(v->flags, VAF_DEST_TOO_FAR);
+ v->SetWaitTime(AIRCRAFT_WAIT_FREE_PATH_TICKS);
+ SetWindowWidgetDirty(WC_VEHICLE_VIEW, v->index, WID_VV_START_STOP);
+ AI::NewEvent(v->owner, new ScriptEventAircraftDestTooFar(v->index));
+ if (v->owner == _local_company) {
+ /* Post a news message. */
+ SetDParam(0, v->index);
+ AddVehicleAdviceNewsItem(STR_NEWS_AIRCRAFT_DEST_TOO_FAR, v->index);
+ }
+ }
+ return false;
}
-}
-static void AircraftEventHandler_HeliLanding(Aircraft *v, const AirportFTAClass *)
-{
- v->state = HELIENDLANDING;
- v->UpdateDeltaXY();
+ if (HasBit(v->flags, VAF_DEST_TOO_FAR)) {
+ /* Not too far anymore, clear flag and message. */
+ ClrBit(v->flags, VAF_DEST_TOO_FAR);
+ SetWindowWidgetDirty(WC_VEHICLE_VIEW, v->index, WID_VV_START_STOP);
+ DeleteVehicleNews(v->index, STR_NEWS_AIRCRAFT_DEST_TOO_FAR);
+ }
+
+ return true;
}
-static void AircraftEventHandler_EndLanding(Aircraft *v, const AirportFTAClass *apc)
+void AssignLandingTile(Aircraft *v, TileIndex tile)
{
- /* next block busy, don't do a thing, just wait */
- if (AirportHasBlock(v, &apc->layout[v->pos], apc)) return;
+ assert(v->IsAircraftFreelyFlying());
- /* if going to terminal (OT_GOTO_STATION) choose one
- * 1. in case all terminals are busy AirportFindFreeTerminal() returns false or
- * 2. not going for terminal (but depot, no order),
- * --> get out of the way to the hangar. */
- if (v->current_order.IsType(OT_GOTO_STATION)) {
- if (AirportFindFreeTerminal(v, apc)) return;
+ if (tile != 0 && IsValidTile(tile)) {
+ assert(IsAirportTile(tile));
+ assert((IsRunwayStart(tile) && IsLandingTypeTile(tile)) || (v->IsHelicopter() && IsApron(tile)));
+ v->state = AS_FLYING;
+ v->UpdateNextTile(tile);
+ } else {
+ v->state = AS_FLYING_NO_DEST;
+ v->next_pos.pos = AP_DEFAULT;
+ v->UpdateNextTile(v->tile);
}
- v->state = HANGAR;
+ v->next_pos.pos = v->IsHelicopter() ? AP_HELICOPTER_HOLD_START : AP_PLANE_HOLD_START;
}
-static void AircraftEventHandler_HeliEndLanding(Aircraft *v, const AirportFTAClass *apc)
+/**
+ * Handle aircraft with missing orders.
+ * @param v An aircraft with missing orders.
+ */
+void HandleMissingAircraftOrders(Aircraft *v)
{
- /* next block busy, don't do a thing, just wait */
- if (AirportHasBlock(v, &apc->layout[v->pos], apc)) return;
+ /*
+ * We do not have an order. This can be divided into two cases:
+ * 1) we are heading to an invalid station. In this case we must
+ * find another airport to go to. If there is nowhere to go,
+ * we will destroy the aircraft as it otherwise will enter
+ * the holding pattern for the first airport, which can cause
+ * the plane to go into an undefined state when building an
+ * airport with the same StationID.
+ * 2) we are (still) heading to a (still) valid airport, then we
+ * can continue going there. This can happen when you are
+ * changing the aircraft's orders while in-flight or in for
+ * example a depot. However, when we have a current order to
+ * go to a depot, we have to keep that order so the aircraft
+ * actually stops.
+ */
+ const Station *st = GetTargetAirportIfValid(v);
+ if (st == nullptr) {
+ Backup cur_company(_current_company, v->owner);
+ CommandCost ret = Command::Do(DC_EXEC, v->index, DepotCommand::None, {});
+ cur_company.Restore();
- /* if going to helipad (OT_GOTO_STATION) choose one. If airport doesn't have helipads, choose terminal
- * 1. in case all terminals/helipads are busy (AirportFindFreeHelipad() returns false) or
- * 2. not going for terminal (but depot, no order),
- * --> get out of the way to the hangar IF there are terminals on the airport.
- * --> else TAKEOFF
- * the reason behind this is that if an airport has a terminal, it also has a hangar. Airplanes
- * must go to a hangar. */
- if (v->current_order.IsType(OT_GOTO_STATION)) {
- if (AirportFindFreeHelipad(v, apc)) return;
+ if (ret.Failed()) HandleAircraftFalling(v);
+ } else if (!v->current_order.IsType(OT_GOTO_DEPOT)) {
+ v->current_order.Free();
}
- v->state = Station::Get(v->targetairport)->airport.HasHangar() ? HANGAR : HELITAKEOFF;
}
/**
- * Signature of the aircraft handler function.
- * @param v Aircraft to handle.
- * @param apc Airport state machine.
+ * Set a destination tile. For aircraft, it won't be assigned directly to this->dest_tile.
+ * @param tile hangar or apron tile of destination airport
+ * (hangar/apron depending on current order type being GOTO_DEPOT/GOTO_STATION).
+ * @pre tile == 0 || (IsAirportTile(tile) && (IsHangar(tile) || IsApron(tile)))
*/
-typedef void AircraftStateHandler(Aircraft *v, const AirportFTAClass *apc);
-/** Array of handler functions for each target of the aircraft. */
-static AircraftStateHandler * const _aircraft_state_handlers[] = {
- AircraftEventHandler_General, // TO_ALL = 0
- AircraftEventHandler_InHangar, // HANGAR = 1
- AircraftEventHandler_AtTerminal, // TERM1 = 2
- AircraftEventHandler_AtTerminal, // TERM2 = 3
- AircraftEventHandler_AtTerminal, // TERM3 = 4
- AircraftEventHandler_AtTerminal, // TERM4 = 5
- AircraftEventHandler_AtTerminal, // TERM5 = 6
- AircraftEventHandler_AtTerminal, // TERM6 = 7
- AircraftEventHandler_AtTerminal, // HELIPAD1 = 8
- AircraftEventHandler_AtTerminal, // HELIPAD2 = 9
- AircraftEventHandler_TakeOff, // TAKEOFF = 10
- AircraftEventHandler_StartTakeOff, // STARTTAKEOFF = 11
- AircraftEventHandler_EndTakeOff, // ENDTAKEOFF = 12
- AircraftEventHandler_HeliTakeOff, // HELITAKEOFF = 13
- AircraftEventHandler_Flying, // FLYING = 14
- AircraftEventHandler_Landing, // LANDING = 15
- AircraftEventHandler_EndLanding, // ENDLANDING = 16
- AircraftEventHandler_HeliLanding, // HELILANDING = 17
- AircraftEventHandler_HeliEndLanding, // HELIENDLANDING = 18
- AircraftEventHandler_AtTerminal, // TERM7 = 19
- AircraftEventHandler_AtTerminal, // TERM8 = 20
- AircraftEventHandler_AtTerminal, // HELIPAD3 = 21
-};
-
-static void AirportClearBlock(const Aircraft *v, const AirportFTAClass *apc)
+void Aircraft::SetDestTile(TileIndex tile)
{
- /* we have left the previous block, and entered the new one. Free the previous block */
- if (apc->layout[v->previous_pos].block != apc->layout[v->pos].block) {
- Station *st = Station::Get(v->targetairport);
+ if (tile != 0) {
+ assert(IsValidTile(tile));
+ assert(IsAirportTile(tile));
+ assert(IsHangar(tile) || IsApron(tile));
+ }
- CLRBITS(st->airport.flags, apc->layout[v->previous_pos].block);
+ if (this->dest_tile == tile) return;
+
+ this->dest_tile = tile;
+ this->targetairport = GetTargetDestination(this->current_order, true);
+
+ if (this->IsAircraftFreelyFlying()) {
+ this->state = AS_FLYING;
+ AircraftUpdateNextPos(this);
}
+
+ SetWindowWidgetDirty(WC_VEHICLE_VIEW, this->index, WID_VV_START_STOP);
}
-static void AirportGoToNextPosition(Aircraft *v)
+/**
+ * For moving aircraft, it lifts its current path
+ * and looks for the best path. It will find the same
+ * starting path or a best one.
+ * @param v A moving aircraft.
+ */
+void UpdatePath(Aircraft *v)
{
- /* if aircraft is not in position, wait until it is */
- if (!AircraftController(v)) return;
+ assert(v->state == AS_RUNNING);
+ assert(v->next_trackdir == INVALID_TRACKDIR);
+ LiftAirportPathReservation(v, false);
+
+ /* Look for a path again with the same destination. */
+ PBSTileInfo best_dest;
+ bool path_found;
+ Trackdir first_trackdir = YapfAircraftFindPath(v, best_dest, path_found, v->Next()->state, v->path);
- const AirportFTAClass *apc = Station::Get(v->targetairport)->airport.GetFTA();
+ /* If a reservable path existed, a reservable path must exist. */
+ assert(path_found);
+ assert(first_trackdir != INVALID_TRACKDIR);
+ assert(best_dest.okay);
+ assert(IsValidTile(best_dest.tile));
+ v->UpdateNextTile(best_dest.tile);
- AirportClearBlock(v, apc);
- AirportMove(v, apc); // move aircraft to next position
+ if (v->trackdir != first_trackdir) {
+ v->SetWaitTime(AIRCRAFT_ROTATION_STEP_TICKS);
+ v->next_trackdir = first_trackdir;
+ }
}
-/* gets pos from vehicle and next orders */
-static bool AirportMove(Aircraft *v, const AirportFTAClass *apc)
+/**
+ * Checks if a path reservation can be made towards
+ * next target of the aircraft.
+ * @param v Aircraft to check.
+ * @return Whether a path can be reserved.
+ */
+bool TryReservePath(Aircraft *v)
{
- /* error handling */
- if (v->pos >= apc->nofelements) {
- Debug(misc, 0, "[Ap] position {} is not valid for current airport. Max position is {}", v->pos, apc->nofelements-1);
- assert(v->pos < apc->nofelements);
- }
+ assert(v->state < AS_MOVING);
- const AirportFTA *current = &apc->layout[v->pos];
- /* we have arrived in an important state (eg terminal, hangar, etc.) */
- if (current->heading == v->state) {
- uint8_t prev_pos = v->pos; // location could be changed in state, so save it before-hand
- uint8_t prev_state = v->state;
- _aircraft_state_handlers[v->state](v, apc);
- if (v->state != FLYING) v->previous_pos = prev_pos;
- if (v->state != prev_state || v->pos != prev_pos) UpdateAircraftCache(v);
- return true;
- }
+ /* First, assert diagonal diadgir.
+ * We shouldn't start paths in stranger tracks. */
+ assert(IsDiagonalTrackdir(v->GetVehicleTrackdir()));
- v->previous_pos = v->pos; // save previous location
+ v->UpdateNextTile(INVALID_TILE);
- /* there is only one choice to move to */
- if (current->next == nullptr) {
- if (AirportSetBlocks(v, current, apc)) {
- v->pos = current->next_position;
- UpdateAircraftCache(v);
- } // move to next position
+ /* Then, if inside a standard hangar, make sure it is not reserved. */
+ if (v->vehstatus & VS_HIDDEN) {
+ assert(IsHangarTile(v->tile));
+ if (IsStandardHangar(v->tile) && HasAirportTrackReserved(v->tile)) return false;
+ }
+
+ if (IsApron(v->tile) &&
+ v->targetairport == GetStationIndex(v->tile) &&
+ IsTerminalState(v->state)) {
return false;
}
- /* there are more choices to choose from, choose the one that
- * matches our heading */
- do {
- if (v->state == current->heading || current->heading == TO_ALL) {
- if (AirportSetBlocks(v, current, apc)) {
- v->pos = current->next_position;
- UpdateAircraftCache(v);
- } // move to next position
- return false;
- }
- current = current->next;
- } while (current != nullptr);
+ PBSTileInfo best_dest;
+ bool path_found;
+ AircraftState dest_state = GetNextAircraftState(*v);
+ Trackdir first_trackdir = YapfAircraftFindPath(v, best_dest, path_found, dest_state, v->path);
+ v->HandlePathfindingResult(path_found);
- Debug(misc, 0, "[Ap] cannot move further on Airport! (pos {} state {}) for vehicle {}", v->pos, v->state, v->index);
- NOT_REACHED();
-}
+ if (!path_found) return false;
-/** returns true if the road ahead is busy, eg. you must wait before proceeding. */
-static bool AirportHasBlock(Aircraft *v, const AirportFTA *current_pos, const AirportFTAClass *apc)
-{
- const AirportFTA *reference = &apc->layout[v->pos];
- const AirportFTA *next = &apc->layout[current_pos->next_position];
+ assert(first_trackdir != INVALID_TRACKDIR);
+ assert(IsValidTile(best_dest.tile));
- /* same block, then of course we can move */
- if (apc->layout[current_pos->position].block != next->block) {
- const Station *st = Station::Get(v->targetairport);
- uint64_t airport_flags = next->block;
+ /* A path exists but right now cannot be reserved. */
+ if (!best_dest.okay) return false;
- /* check additional possible extra blocks */
- if (current_pos != reference && current_pos->block != NOTHING_block) {
- airport_flags |= current_pos->block;
- }
+ // revise possible unneeded servicing here
+ if (v->state != AS_HANGAR && dest_state == AS_HANGAR && !v->current_order.IsType(OT_GOTO_DEPOT)) {
+ /* Create the hangar order. */
+ // revise
+ Depot *hangar = Station::GetByTile(v->tile)->airport.hangar;
+ assert(hangar != nullptr);
+ v->current_order.MakeGoToDepot(hangar->index, ODTFB_SERVICE);
+ SetWindowWidgetDirty(WC_VEHICLE_VIEW, v->index, WID_VV_START_STOP);
+ }
- if (st->airport.flags & airport_flags) {
- v->cur_speed = 0;
- v->subspeed = 0;
- return true;
+ v->UpdateNextTile(best_dest.tile);
+
+ /* If a path is found, service, reserve and return true. */
+ if (IsHangarTile(v->tile)) {
+ assert(IsValidTrackdir(first_trackdir));
+ SetAirportTracksReservation(v->tile, TrackToTrackBits(TrackdirToTrack(first_trackdir)));
+
+ if (v->cur_speed != 0) SetWindowWidgetDirty(WC_VEHICLE_VIEW, v->index, WID_VV_START_STOP);
+ v->cur_speed = 0;
+ v->subspeed = 0;
+ v->progress = 0;
+
+ /* Rotor blades */
+ if (v->Next()->Next() != nullptr) {
+ v->Next()->Next()->cur_speed = 80;
}
- }
- return false;
-}
-/**
- * "reserve" a block for the plane
- * @param v airplane that requires the operation
- * @param current_pos of the vehicle in the list of blocks
- * @param apc airport on which block is requested to be set
- * @returns true on success. Eg, next block was free and we have occupied it
- */
-static bool AirportSetBlocks(Aircraft *v, const AirportFTA *current_pos, const AirportFTAClass *apc)
-{
- const AirportFTA *next = &apc->layout[current_pos->next_position];
- const AirportFTA *reference = &apc->layout[v->pos];
-
- /* if the next position is in another block, check it and wait until it is free */
- if ((apc->layout[current_pos->position].block & next->block) != next->block) {
- uint64_t airport_flags = next->block;
- /* search for all all elements in the list with the same state, and blocks != N
- * this means more blocks should be checked/set */
- const AirportFTA *current = current_pos;
- if (current == reference) current = current->next;
- while (current != nullptr) {
- if (current->heading == current_pos->heading && current->block != 0) {
- airport_flags |= current->block;
- break;
- }
- current = current->next;
+ if (!IsExtendedHangar(v->tile)) {
+ v->trackdir = v->Next()->trackdir = first_trackdir;
+ SetVisibility(v, true);
}
- /* if the block to be checked is in the next position, then exclude that from
- * checking, because it has been set by the airplane before */
- if (current_pos->block == next->block) airport_flags ^= next->block;
+ AircraftLeavesHangar(v);
+ v->PlayLeaveStationSound();
+ }
- Station *st = Station::Get(v->targetairport);
- if (st->airport.flags & airport_flags) {
- v->cur_speed = 0;
- v->subspeed = 0;
- return false;
+ assert(IsDiagonalTrackdir(first_trackdir));
+ if (first_trackdir != v->GetVehicleTrackdir()) {
+ v->SetWaitTime(AIRCRAFT_ROTATION_STEP_TICKS);
+ v->next_trackdir = first_trackdir;
+ if (GetReservedAirportTracks(v->tile) == TRACK_BIT_CROSS) {
+ assert(IsValidTrackdir(v->trackdir));
+ RemoveAirportTrackReservation(v->tile, TrackdirToTrack(v->trackdir));
}
+ }
- if (next->block != NOTHING_block) {
- SETBITS(st->airport.flags, airport_flags); // occupy next block
- }
+ if (v->tile != v->GetNextTile() && v->GetNextTile() != INVALID_TILE) {
+ v->state = AS_RUNNING;
+ v->Next()->state = dest_state;
}
+
return true;
}
/**
- * Combination of aircraft state for going to a certain terminal and the
- * airport flag for that terminal block.
+ * While aircraft is on land and moving through an airport,
+ * check whether it is in the middle of a tile. If it is the middle of
+ * a tile, try updating the path and the next trackdir, if needed.
+ * @param v Aircraft to check.
+ * @param gp New position of the aircraft.
+ * @return Whether it needs to rotate.
*/
-struct MovementTerminalMapping {
- AirportMovementStates state; ///< Aircraft movement state when going to this terminal.
- uint64_t airport_flag; ///< Bitmask in the airport flags that need to be free for this terminal.
-};
+bool TryRotateInMiddleOfTile(Aircraft *v, const GetNewVehiclePosResult &gp) {
+ assert(v->state == AS_RUNNING);
+ assert(IsAirportTile(gp.new_tile));
+ assert(MayHaveAirTracks(gp.new_tile));
-/** A list of all valid terminals and their associated blocks. */
-static const MovementTerminalMapping _airport_terminal_mapping[] = {
- {TERM1, TERM1_block},
- {TERM2, TERM2_block},
- {TERM3, TERM3_block},
- {TERM4, TERM4_block},
- {TERM5, TERM5_block},
- {TERM6, TERM6_block},
- {TERM7, TERM7_block},
- {TERM8, TERM8_block},
- {HELIPAD1, HELIPAD1_block},
- {HELIPAD2, HELIPAD2_block},
- {HELIPAD3, HELIPAD3_block},
-};
+ if ((gp.x & 0xF) != 8 || (gp.y & 0xF) != 8) return false;
-/**
- * Find a free terminal or helipad, and if available, assign it.
- * @param v Aircraft looking for a free terminal or helipad.
- * @param i First terminal to examine.
- * @param last_terminal Terminal number to stop examining.
- * @return A terminal or helipad has been found, and has been assigned to the aircraft.
- */
-static bool FreeTerminal(Aircraft *v, uint8_t i, uint8_t last_terminal)
-{
- assert(last_terminal <= lengthof(_airport_terminal_mapping));
- Station *st = Station::Get(v->targetairport);
- for (; i < last_terminal; i++) {
- if ((st->airport.flags & _airport_terminal_mapping[i].airport_flag) == 0) {
- /* TERMINAL# HELIPAD# */
- v->state = _airport_terminal_mapping[i].state; // start moving to that terminal/helipad
- SETBITS(st->airport.flags, _airport_terminal_mapping[i].airport_flag); // occupy terminal/helipad
- return true;
- }
- }
- return false;
-}
+ /* Check whether the aircraft must rotate in the middle of the tile. */
+ if (GetReservedAirportTracks(gp.new_tile) != TRACK_BIT_CROSS) return false;
-/**
- * Get the number of terminals at the airport.
- * @param apc Airport description.
- * @return Number of terminals.
- */
-static uint GetNumTerminals(const AirportFTAClass *apc)
-{
- uint num = 0;
+ assert(IsValidTrackdir(v->trackdir));
+ assert(v->next_trackdir == INVALID_TRACKDIR);
+
+ /* A good moment to update the path. */
+ //UpdatePath(v);
- for (uint i = apc->terminals[0]; i > 0; i--) num += apc->terminals[i];
+ if (DoesAircraftNeedRotation(v)) return true;
- return num;
+ if (GetReservedAirportTracks(gp.new_tile) == TRACK_BIT_CROSS) {
+ RemoveAirportTrackReservation(gp.new_tile, TrackdirToTrack(v->trackdir));
+ assert(!v->path.empty());
+ assert(v->path.tile.front() == gp.new_tile);
+ v->SetWaitTime(AIRCRAFT_ROTATION_STEP_TICKS);
+ v->next_trackdir = v->path.td.front();
+ v->path.pop_front();
+ }
+
+ return true;
}
/**
- * Find a free terminal, and assign it if available.
- * @param v Aircraft to handle.
- * @param apc Airport state machine.
- * @return Found a free terminal and assigned it.
+ * Moves an aircraft one time.
+ * @param v Aircraft to move.
+ * @param nudge_towards_target Indicates whether v is flying and close to its target.
*/
-static bool AirportFindFreeTerminal(Aircraft *v, const AirportFTAClass *apc)
-{
- /* example of more terminalgroups
- * {0,HANGAR,NOTHING_block,1}, {0,TERMGROUP,TERM_GROUP1_block,0}, {0,TERMGROUP,TERM_GROUP2_ENTER_block,1}, {0,0,N,1},
- * Heading TERMGROUP denotes a group. We see 2 groups here:
- * 1. group 0 -- TERM_GROUP1_block (check block)
- * 2. group 1 -- TERM_GROUP2_ENTER_block (check block)
- * First in line is checked first, group 0. If the block (TERM_GROUP1_block) is free, it
- * looks at the corresponding terminals of that group. If no free ones are found, other
- * possible groups are checked (in this case group 1, since that is after group 0). If that
- * fails, then attempt fails and plane waits
- */
- if (apc->terminals[0] > 1) {
- const Station *st = Station::Get(v->targetairport);
- const AirportFTA *temp = apc->layout[v->pos].next;
-
- while (temp != nullptr) {
- if (temp->heading == TERMGROUP) {
- if (!(st->airport.flags & temp->block)) {
- /* read which group do we want to go to?
- * (the first free group) */
- uint target_group = temp->next_position + 1;
-
- /* at what terminal does the group start?
- * that means, sum up all terminals of
- * groups with lower number */
- uint group_start = 0;
- for (uint i = 1; i < target_group; i++) {
- group_start += apc->terminals[i];
- }
+void MoveAircraft(Aircraft *v, const bool nudge_towards_target)
+{
+ GetNewVehiclePosResult gp;
+
+ if (nudge_towards_target) {
+ /* Move vehicle one pixel towards target. */
+ gp.x = (v->x_pos != v->next_pos.x) ? v->x_pos + ((v->next_pos.x > v->x_pos) ? 1 : -1) : v->x_pos;
+ gp.y = (v->y_pos != v->next_pos.y) ? v->y_pos + ((v->next_pos.y > v->y_pos) ? 1 : -1) : v->y_pos;
+
+ /* Builtin heliports keep v->tile as the terminal tile, since the landing pad is in a non-airport tile. */
+ gp.new_tile = IsBuiltInHeliportTile(v->GetNextTile()) ? v->GetNextTile() : TileVirtXY(gp.x, gp.y);
+ } else if (v->state > AS_RUNNING) {
+ /* Aircraft is flying or moving in a runway. */
+ assert(!v->IsHelicopter() || ((v->state != AS_LANDED && v->state != AS_START_TAKEOFF)));
+
+ /* Turn. Do it slowly if in the air. */
+ if (v->turn_counter != 0) v->turn_counter--;
+ Direction newdir = GetDirectionTowards(v, v->next_pos.x, v->next_pos.y);
+ if (newdir == v->direction) {
+ v->number_consecutive_turns = 0;
+ } else if (v->turn_counter == 0 || newdir == v->last_direction) {
+ if (newdir == v->last_direction) {
+ v->number_consecutive_turns = 0;
+ } else {
+ v->number_consecutive_turns++;
+ }
+ v->turn_counter = v->IsHelicopter() ? 0 : (2 * _settings_game.vehicle.plane_speed);
+ v->last_direction = v->direction;
+ v->direction = v->Next()->direction = newdir;
+ }
+
+ gp = GetNewVehiclePos(v);
+ } else {
+ /* Aircraft is taxiing on the airport. */
+ assert(v->state == AS_RUNNING);
+
+ gp = GetNewVehiclePos(v);
- uint group_end = group_start + apc->terminals[target_group];
- if (FreeTerminal(v, group_start, group_end)) return true;
+ if (gp.old_tile == gp.new_tile) {
+ if (TryRotateInMiddleOfTile(v, gp)) return;
+ } else {
+ /* Entering a new tile. */
+ assert(IsTileType(gp.new_tile, MP_STATION));
+ assert(IsAirportTile(gp.new_tile));
+ assert(MayHaveAirTracks(gp.new_tile));
+ assert(IsValidTrackdir(v->trackdir));
+ assert(v->next_trackdir == INVALID_TRACKDIR);
+
+ //UpdatePath(v);
+
+ if (DoesAircraftNeedRotation(v)) return;
+
+ RemoveAirportTrackReservation(gp.old_tile, TrackdirToTrack(v->trackdir));
+ TrackdirBits trackdirs = TrackdirReachesTrackdirs(v->trackdir) &
+ TrackBitsToTrackdirBits(GetReservedAirportTracks(gp.new_tile));
+
+ if (trackdirs == TRACKDIR_BIT_NONE) {
+ /* Rotate at the end of the tile. */
+ DiagDirection exit_dir = TrackdirToExitdir(v->trackdir);
+ trackdirs = DiagdirReachesTrackdirs(ReverseDiagDir(exit_dir)) &
+ TrackBitsToTrackdirBits(GetReservedAirportTracks(gp.old_tile));
+
+ /* Must reverse now and rotate in the middle of the tile. */
+ if (CountBits(trackdirs) == 0) {
+ [[maybe_unused]] TrackBits reserved_tracks = GetReservedAirportTracks(gp.old_tile);
+ assert(CountBits(reserved_tracks) == 1);
+ assert(IsDiagonalTrack(RemoveFirstTrack(&reserved_tracks)));
+ v->SetWaitTime(AIRCRAFT_ROTATION_STEP_TICKS);
+ v->next_trackdir = ReverseTrackdir(v->trackdir);
+ SetAirportTrackReservation(gp.old_tile, TrackdirToTrack(v->next_trackdir));
+ return;
}
- } else {
- /* once the heading isn't 255, we've exhausted the possible blocks.
- * So we cannot move */
- return false;
+
+ assert(CountBits(trackdirs) == 1);
+ v->next_trackdir = RemoveFirstTrackdir(&trackdirs);
+ assert(trackdirs == TRACKDIR_BIT_NONE);
+ SetAircraftPosition(v, v->x_pos, v->y_pos, v->z_pos);
+ return;
}
- temp = temp->next;
+
+ v->trackdir = v->Next()->trackdir = RemoveFirstTrackdir(&trackdirs);
+ assert(IsValidTrackdir(v->trackdir));
+
+ DiagDirection diagdir = DiagdirBetweenTiles(gp.old_tile, gp.new_tile);
+ const AircraftSubcoordData &b = _aircraft_subcoord[diagdir][TrackdirToTrack(v->trackdir)];
+ gp.x = (gp.x & ~0xF) | b.x_subcoord;
+ gp.y = (gp.y & ~0xF) | b.y_subcoord;
+
+ uint32_t r = VehicleEnterTile(v, gp.new_tile, gp.x, gp.y);
+ if (HasBit(r, VETS_CANNOT_ENTER)) NOT_REACHED();
+
+ v->direction = v->Next()->direction = b.dir;
}
}
- /* if there is only 1 terminalgroup, all terminals are checked (starting from 0 to max) */
- return FreeTerminal(v, 0, GetNumTerminals(apc));
-}
-
-/**
- * Find a free helipad, and assign it if available.
- * @param v Aircraft to handle.
- * @param apc Airport state machine.
- * @return Found a free helipad and assigned it.
- */
-static bool AirportFindFreeHelipad(Aircraft *v, const AirportFTAClass *apc)
-{
- /* if an airport doesn't have helipads, use terminals */
- if (apc->num_helipads == 0) return AirportFindFreeTerminal(v, apc);
+ v->tile = gp.new_tile;
+ v->x_pos = gp.x;
+ v->y_pos = gp.y;
- /* only 1 helicoptergroup, check all helipads
- * The blocks for helipads start after the last terminal (MAX_TERMINALS) */
- return FreeTerminal(v, MAX_TERMINALS, apc->num_helipads + MAX_TERMINALS);
+ if (v->IsAircraftFlying()) HandleAircraftFlightLevel(v);
}
/**
- * Handle the 'dest too far' flag and the corresponding news message for aircraft.
- * @param v The aircraft.
- * @param too_far True if the current destination is too far away.
+ * Moves the aircraft one time.
+ * @param v Aircraft to move.
+ * @return whether the vehicle can move more times during this tick.
*/
-static void AircraftHandleDestTooFar(Aircraft *v, bool too_far)
+bool HandleAircraftMovement(Aircraft *v)
{
- if (too_far) {
- if (!HasBit(v->flags, VAF_DEST_TOO_FAR)) {
- SetBit(v->flags, VAF_DEST_TOO_FAR);
- SetWindowWidgetDirty(WC_VEHICLE_VIEW, v->index, WID_VV_START_STOP);
- AI::NewEvent(v->owner, new ScriptEventAircraftDestTooFar(v->index));
- if (v->owner == _local_company) {
- /* Post a news message. */
- SetDParam(0, v->index);
- AddVehicleAdviceNewsItem(STR_NEWS_AIRCRAFT_DEST_TOO_FAR, v->index);
- }
+ if (v->IsAircraftFalling()) {
+ HandleAircraftFalling(v);
+ return true;
+ }
+
+ if (DoesAircraftNeedRotation(v)) {
+ DoRotationStep(v);
+ if (v->state == AS_START_TAKEOFF && !DoesAircraftNeedRotation(v)) {
+ /* Take off starts right now. */
+ PlayAircraftTakeoffSound(v);
}
- return;
+ return true;
}
- if (HasBit(v->flags, VAF_DEST_TOO_FAR)) {
- /* Not too far anymore, clear flag and message. */
- ClrBit(v->flags, VAF_DEST_TOO_FAR);
- SetWindowWidgetDirty(WC_VEHICLE_VIEW, v->index, WID_VV_START_STOP);
- DeleteVehicleNews(v->index, STR_NEWS_AIRCRAFT_DEST_TOO_FAR);
+ if (v->IsHelicopter() && RaiseLowerHelicopter(v)) return true;
+
+ if (HandleAircraftState(v)) return true;
+
+ if (v->state < AS_MOVING) return false;
+
+ /* Maybe crash the airplane if landing too fast. */
+ assert(v->state != AS_LANDED || IsAirportTile(v->tile));
+ if (v->state == AS_LANDED &&
+ v->cur_speed > GetAirTypeInfo(GetAirType(v->tile))->max_speed * _settings_game.vehicle.plane_speed) {
+ if (MaybeCrashAirplane(v)) return true;
+ }
+
+ int count = UpdateAircraftSpeed(v);
+
+ if (v->next_trackdir != INVALID_TRACKDIR) return true;
+
+ /* If the plane will be a few subpixels away from the destination after
+ * this movement loop, start nudging it towards the exact position for
+ * the whole loop. Otherwise, heavily depending on the speed of the plane,
+ * it is possible we totally overshoot the target, causing the plane to
+ * make a loop, and trying again, and again, and again .. */
+ bool nudge_towards_target = v->IsAircraftFlying() &&
+ count + 3 > abs(v->next_pos.x - v->x_pos) + abs(v->next_pos.y - v->y_pos);
+
+ for (; count > 0; count--) {
+ MoveAircraft(v, nudge_towards_target);
+ if (HandleAircraftState(v)) break;
}
+
+ SetAircraftPosition(v, v->x_pos, v->y_pos, v->z_pos);
+ return true;
}
-static bool AircraftEventHandler(Aircraft *v, int loop)
+/**
+ * Aircraft controller.
+ * @param v Aircraft to move.
+ * @param mode False during the first call in each tick, true during second call.
+ * @return whether the vehicle is still valid.
+ */
+static bool AircraftController(Aircraft *v, bool mode)
{
+ /* Aircraft crashed? */
if (v->vehstatus & VS_CRASHED) {
- return HandleCrashedAircraft(v);
+ return mode ? true : HandleCrashedAircraft(v); // 'v' can be deleted here
}
- if (v->vehstatus & VS_STOPPED) return true;
+ if ((v->vehstatus & VS_STOPPED) && v->cur_speed == 0) return true;
+
+ if (v->IsServicing()) {
+ if (mode) v->ContinueServicing();
+ return true;
+ }
v->HandleBreakdown();
- HandleAircraftSmoke(v, loop != 0);
+ HandleAircraftSmoke(v, mode);
+
+ if (v->IsWaiting()) {
+ if (mode) v->AdvanceWaitTime();
+ return true;
+ }
+
ProcessOrders(v);
- v->HandleLoading(loop != 0);
-
- if (v->current_order.IsType(OT_LOADING) || v->current_order.IsType(OT_LEAVESTATION)) return true;
-
- if (v->state >= ENDTAKEOFF && v->state <= HELIENDLANDING) {
- /* If we are flying, unconditionally clear the 'dest too far' state. */
- AircraftHandleDestTooFar(v, false);
- } else if (v->acache.cached_max_range_sqr != 0) {
- /* Check the distance to the next destination. This code works because the target
- * airport is only updated after take off and not on the ground. */
- Station *cur_st = Station::GetIfValid(v->targetairport);
- Station *next_st = v->current_order.IsType(OT_GOTO_STATION) || v->current_order.IsType(OT_GOTO_DEPOT) ? Station::GetIfValid(v->current_order.GetDestination()) : nullptr;
-
- if (cur_st != nullptr && cur_st->airport.tile != INVALID_TILE && next_st != nullptr && next_st->airport.tile != INVALID_TILE) {
- uint dist = DistanceSquare(cur_st->airport.tile, next_st->airport.tile);
- AircraftHandleDestTooFar(v, dist > v->acache.cached_max_range_sqr);
- }
+
+ v->HandleLoading(mode);
+ if (v->current_order.IsType(OT_LOADING)) return true;
+
+ /* Check if we should wait here for unbunching. */
+ if (v->state == AS_HANGAR && v->IsWaitingForUnbunching()) return true;
+
+ if (HandleAircraftMovement(v)) return true;
+
+ /* Check if next destination is too far. */
+ if (!IsReachableDest(v)) {
+ if (!v->IsWaiting()) v->SetWaitTime(AIRCRAFT_WAIT_FREE_PATH_TICKS);
+ return true;
}
- if (!HasBit(v->flags, VAF_DEST_TOO_FAR)) AirportGoToNextPosition(v);
+ /* Check whether aircraft can reserve a path towards its next target. */
+ if (!TryReservePath(v)) {
+ /* Aircraft cannot reserve a path now. */
+ v->SetWaitTime(v->state == AS_HANGAR ? AIRCRAFT_WAIT_LEAVE_HANGAR_TICKS : AIRCRAFT_WAIT_FREE_PATH_TICKS);
+ }
return true;
}
+/**
+ * Update aircraft vehicle data for a tick.
+ * @return True if the vehicle still exists, false if it has ceased to exist (normal aircraft only).
+ */
bool Aircraft::Tick()
{
if (!this->IsNormalAircraft()) return true;
@@ -2126,61 +3004,14 @@ bool Aircraft::Tick()
if (!(this->vehstatus & VS_STOPPED)) this->running_ticks++;
- if (this->subtype == AIR_HELICOPTER) HelicopterTickHandler(this);
+ if (this->IsHelicopter()) HandleHelicopterRotor(this);
this->current_order_time++;
for (uint i = 0; i != 2; i++) {
/* stop if the aircraft was deleted */
- if (!AircraftEventHandler(this, i)) return false;
+ if (!AircraftController(this, i)) return false;
}
return true;
}
-
-
-/**
- * Returns aircraft's target station if v->target_airport
- * is a valid station with airport.
- * @param v vehicle to get target airport for
- * @return pointer to target station, nullptr if invalid
- */
-Station *GetTargetAirportIfValid(const Aircraft *v)
-{
- assert(v->type == VEH_AIRCRAFT);
-
- Station *st = Station::GetIfValid(v->targetairport);
- if (st == nullptr) return nullptr;
-
- return st->airport.tile == INVALID_TILE ? nullptr : st;
-}
-
-/**
- * Updates the status of the Aircraft heading or in the station
- * @param st Station been updated
- */
-void UpdateAirplanesOnNewStation(const Station *st)
-{
- /* only 1 station is updated per function call, so it is enough to get entry_point once */
- const AirportFTAClass *ap = st->airport.GetFTA();
- Direction rotation = st->airport.tile == INVALID_TILE ? DIR_N : st->airport.rotation;
-
- for (Aircraft *v : Aircraft::Iterate()) {
- if (!v->IsNormalAircraft() || v->targetairport != st->index) continue;
- assert(v->state == FLYING);
-
- Order *o = &v->current_order;
- /* The aircraft is heading to a hangar, but the new station doesn't have one,
- * or the aircraft can't land on the new station. Cancel current order. */
- if (o->IsType(OT_GOTO_DEPOT) && !(o->GetDepotOrderType() & ODTFB_PART_OF_ORDERS) && o->GetDestination() == st->index &&
- (!st->airport.HasHangar() || !CanVehicleUseStation(v, st))) {
- o->MakeDummy();
- SetWindowWidgetDirty(WC_VEHICLE_VIEW, v->index, WID_VV_START_STOP);
- }
- v->pos = v->previous_pos = AircraftGetEntryPoint(v, ap, rotation);
- UpdateAircraftCache(v);
- }
-
- /* Heliports don't have a hangar. Invalidate all go to hangar orders from all aircraft. */
- if (!st->airport.HasHangar()) RemoveOrderFromAllVehicles(OT_GOTO_DEPOT, st->index, true);
-}
diff --git a/src/airport.cpp b/src/airport.cpp
index a1e68b0629207..f8081ab1c513c 100644
--- a/src/airport.cpp
+++ b/src/airport.cpp
@@ -9,225 +9,687 @@
#include "stdafx.h"
#include "station_base.h"
+#include "newgrf_airtype.h"
+#include "depot_base.h"
+#include "air_map.h"
+#include "window_func.h"
+
+#include "debug.h"
+
+#include "table/airtypes.h"
#include "table/strings.h"
-#include "table/airport_movement.h"
#include "table/airporttile_ids.h"
+#include "table/airport_defaults.h"
#include "safeguards.h"
+/** Helper type for lists/vectors of trains */
+typedef std::vector AircraftList;
+
+AirTypeInfo _airtypes[AIRTYPE_END];
+std::vector _sorted_airtypes;
+AirTypes _airtypes_hidden_mask;
+
+
+const AirportSpec AirportSpec::dummy = {{APC_BEGIN, 0}, {}, INVALID_AIRTYPE, 0, 0, 0, 0, 0, CalendarTime::MIN_YEAR, CalendarTime::MIN_YEAR, STR_NULL, ATP_TTDP_LARGE, 0, false, false, false, GRFFileProps(AT_INVALID)};
+const AirportSpec AirportSpec::custom = {{APC_CUSTOM, 0}, {}, INVALID_AIRTYPE, 0, 0, 0, 0, 0, CalendarTime::MIN_YEAR, CalendarTime::MIN_YEAR, STR_AIRPORT_CUSTOM, ATP_TTDP_LARGE, 0, false, false, false, GRFFileProps(AT_INVALID)};
+
+
+void ResolveAirTypeGUISprites(AirTypeInfo *ati)
+{
+ SpriteID cursors_base = GetCustomAirSprite(ati, INVALID_TILE, ATSG_CURSORS);
+ if (cursors_base != 0) {
+ ati->gui_sprites.add_airport_tiles = cursors_base + 0;
+ ati->gui_sprites.build_track_tile = cursors_base + 1;
+ ati->gui_sprites.change_airtype = cursors_base + 2;
+ ati->gui_sprites.build_catchment_infra = cursors_base + 3;
+ ati->gui_sprites.build_noncatchment_infra = cursors_base + 4;
+ ati->gui_sprites.define_landing_runway = cursors_base + 5;
+ ati->gui_sprites.define_nonlanding_runway = cursors_base + 6;
+ ati->gui_sprites.build_apron = cursors_base + 7;
+ ati->gui_sprites.build_helipad = cursors_base + 8;
+ ati->gui_sprites.build_heliport = cursors_base + 9;
+ ati->gui_sprites.build_hangar = cursors_base + 10;
+
+ ati->cursor.add_airport_tiles = cursors_base + 11;
+ ati->cursor.build_track_tile = cursors_base + 12;
+ ati->cursor.change_airtype = cursors_base + 13;
+ ati->cursor.build_catchment_infra = cursors_base + 14;
+ ati->cursor.build_noncatchment_infra = cursors_base + 15;
+ ati->cursor.define_landing_runway = cursors_base + 16;
+ ati->cursor.define_nonlanding_runway = cursors_base + 17;
+ ati->cursor.build_apron = cursors_base + 18;
+ ati->cursor.build_helipad = cursors_base + 19;
+ ati->cursor.build_heliport = cursors_base + 20;
+ ati->cursor.build_hangar = cursors_base + 21;
+ }
+}
+
+
/**
- * Define a generic airport.
- * @param name Suffix of the names of the airport data.
- * @param terminals The terminals.
- * @param num_helipads Number of heli pads.
- * @param flags Information about the class of FTA.
- * @param delta_z Height of the airport above the land.
+ * Reset all air type information to its default values.
*/
-#define AIRPORT_GENERIC(name, terminals, num_helipads, flags, delta_z) \
- static const AirportFTAClass _airportfta_ ## name(_airport_moving_data_ ## name, terminals, \
- num_helipads, _airport_entries_ ## name, flags, _airport_fta_ ## name, delta_z);
+void ResetAirTypes()
+{
+ static_assert(lengthof(_original_airtypes) <= lengthof(_airtypes));
+
+ uint i = 0;
+ for (; i < lengthof(_original_airtypes); i++) _airtypes[i] = _original_airtypes[i];
+
+ static const AirTypeInfo empty_airtype = {
+ { { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // Ground sprite
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ },
+ {
+ { // Airport buildings with infrastructure: non-snowed/snowed + 5 building + 4 rotations
+ { 0, 0, 0, 0 },
+ { 0, 0, 0, 0 },
+ { 0, 0, 0, 0 },
+ { 0, 0, 0, 0 },
+ { 0, 0, 0, 0 }
+ },
+ {
+ { 0, 0, 0, 0 },
+ { 0, 0, 0, 0 },
+ { 0, 0, 0, 0 },
+ { 0, 0, 0, 0 },
+ { 0, 0, 0, 0 }
+ }
+ },
+ { // Airport animated flag revise: maybe 4 sprites instead of 16 is enough
+ { 0, 0, 0, 0 },
+ { 0, 0, 0, 0 },
+ { 0, 0, 0, 0 },
+ { 0, 0, 0, 0 }
+ },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // Radar sprites
+ { // Infrastructure with no catchment (with 4 rotated sprites): transmitter, snowed transmitter, tower, snowed tower
+ { 0, 0, 0, 0 },
+ { 0, 0, 0, 0 },
+ { 0, 0, 0, 0 },
+ { 0, 0, 0, 0 }
+ },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // Runway sprites
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // Sprites for normal apron, helipad, 4 rotated sprites for heliports and 4 more for snowed heliports
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } // Hangar sprites
+ },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // Icons
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // Cursors
+ { 0, 0, 0, 0 }, // Strings
+ 0, AIRTYPES_NONE, AIRTYPES_NONE, 0, 0, 0, 0, AirTypeLabelList(), 0, 0,
+ AIRTYPES_NONE, AIRTYPES_NONE, 0,
+ {}, {}, 0, 0, 0, 0, 0, false, false };
+ for (; i < lengthof(_airtypes); i++) _airtypes[i] = empty_airtype;
+}
/**
- * Define an airport.
- * @param name Suffix of the names of the airport data.
- * @param num_helipads Number of heli pads.
- * @param short_strip Airport has a short land/take-off strip.
+ * Compare airtypes based on their sorting order.
+ * @param first The airtype to compare to.
+ * @param second The airtype to compare.
+ * @return True iff the first should be sorted before the second.
*/
-#define AIRPORT(name, num_helipads, short_strip) \
- AIRPORT_GENERIC(name, _airport_terminal_ ## name, num_helipads, AirportFTAClass::ALL | (short_strip ? AirportFTAClass::SHORT_STRIP : (AirportFTAClass::Flags)0), 0)
+static bool CompareAirTypes(const AirType &first, const AirType &second)
+{
+ return GetAirTypeInfo(first)->sorting_order < GetAirTypeInfo(second)->sorting_order;
+}
/**
- * Define a heliport.
- * @param name Suffix of the names of the helipad data.
- * @param num_helipads Number of heli pads.
- * @param delta_z Height of the airport above the land.
+ * Resolve sprites of custom air types
*/
-#define HELIPORT(name, num_helipads, delta_z) \
- AIRPORT_GENERIC(name, nullptr, num_helipads, AirportFTAClass::HELICOPTERS, delta_z)
-
-AIRPORT(country, 0, true)
-AIRPORT(city, 0, false)
-HELIPORT(heliport, 1, 60)
-AIRPORT(metropolitan, 0, false)
-AIRPORT(international, 2, false)
-AIRPORT(commuter, 2, true)
-HELIPORT(helidepot, 1, 0)
-AIRPORT(intercontinental, 2, false)
-HELIPORT(helistation, 3, 0)
-HELIPORT(oilrig, 1, 54)
-AIRPORT_GENERIC(dummy, nullptr, 0, AirportFTAClass::ALL, 0)
-
-#undef HELIPORT
-#undef AIRPORT
-#undef AIRPORT_GENERIC
+void InitAirTypes()
+{
+ for (AirType at = AIRTYPE_BEGIN; at != AIRTYPE_END; at++) {
+ AirTypeInfo *ati = &_airtypes[at];
+ ResolveAirTypeGUISprites(ati);
+ }
-#include "table/airport_defaults.h"
+ _sorted_airtypes.clear();
+ for (AirType at = AIRTYPE_BEGIN; at != AIRTYPE_END; at++) {
+ if (_airtypes[at].label != 0 && !HasBit(_airtypes_hidden_mask, at)) {
+ _sorted_airtypes.push_back(at);
+ }
+ }
+ std::sort(_sorted_airtypes.begin(), _sorted_airtypes.end(), CompareAirTypes);
+
+}
+
+/**
+ * Allocate a new air type label
+ */
+AirType AllocateAirType(AirTypeLabel label)
+{
+ for (AirType at = AIRTYPE_BEGIN; at != AIRTYPE_END; at++) {
+ AirTypeInfo *ati = &_airtypes[at];
+ if (ati->label == 0) {
+ /* Set up new air type */
+ *ati = _original_airtypes[AIRTYPE_BEGIN];
+ ati->label = label;
+ ati->alternate_labels.clear();
-static uint16_t AirportGetNofElements(const AirportFTAbuildup *apFA);
-static AirportFTA *AirportBuildAutomata(uint nofelements, const AirportFTAbuildup *apFA);
+ /* Make us compatible with ourself. */
+ ati->compatible_airtypes = (AirTypes)(1 << at);
+ /* We also introduce ourself. */
+ ati->introduces_airtypes = (AirTypes)(1 << at);
+
+ /* Default sort order; order of allocation, but with some
+ * offsets so it's easier for NewGRF to pick a spot without
+ * changing the order of other (original) air types.
+ * The << is so you can place other airtypes in between the
+ * other airtypes, the 7 is to be able to place something
+ * before the first (default) air type. */
+ ati->sorting_order = at << 4 | 7;
+ return at;
+ }
+ }
+
+ return INVALID_AIRTYPE;
+}
/**
- * Rotate the airport moving data to another rotation.
- * @param orig Pointer to the moving data to rotate.
- * @param rotation How to rotate the moving data.
- * @param num_tiles_x Number of tiles in x direction.
- * @param num_tiles_y Number of tiles in y direction.
- * @return The rotated moving data.
+ * Set the new airport layout, rotation and airtype of old airports
+ * (even those built with OpenGRF+Airports).
+ * @param st Station with the airport to update, if there is one.
*/
-AirportMovingData RotateAirportMovingData(const AirportMovingData *orig, Direction rotation, uint num_tiles_x, uint num_tiles_y)
-{
- AirportMovingData amd;
- amd.flag = orig->flag;
- amd.direction = ChangeDir(orig->direction, (DirDiff)rotation);
- switch (rotation) {
- case DIR_N:
- amd.x = orig->x;
- amd.y = orig->y;
- break;
+void ConvertOldAirportData(Station *st) {
+ if (st->airport.tile == INVALID_TILE) return;
- case DIR_E:
- amd.x = orig->y;
- amd.y = num_tiles_y * TILE_SIZE - orig->x - 1;
- break;
+ st->airport.air_type = INVALID_AIRTYPE;
- case DIR_S:
- amd.x = num_tiles_x * TILE_SIZE - orig->x - 1;
- amd.y = num_tiles_y * TILE_SIZE - orig->y - 1;
- break;
+ if (st->airport.type < NEW_AIRPORT_OFFSET) return;
- case DIR_W:
- amd.x = num_tiles_x * TILE_SIZE - orig->y - 1;
- amd.y = orig->x;
- break;
+ if (st->airport.type == NEW_AIRPORT_OFFSET + 1) {
+ /* Already existing water airports built on land cannot be converted properly.
+ * Handle them as standard small gravel airports. */
+ st->airport.type = NEW_AIRPORT_OFFSET;
+ }
- default: NOT_REACHED();
+ st->airport.layout = 0;
+
+ /* Old rotation dir values are 0, 2, 4, 6.
+ * Convert them to 0, 1, 2, 3 (DiagDirection values). */
+ assert(st->airport.rotation % 2 == 0);
+ assert(st->airport.rotation <= 6);
+ st->airport.rotation = (Direction)(st->airport.rotation / 2);
+
+ if (st->airport.type > 23) {
+ /* OpenGRF+Airports includes airport types from NEW_AIRPORT_OFFSET to 23.
+ * Do not try to convert a completely unknown airport type. */
+ Debug(misc, 0, "Unknown airport type: station {} airport type {}", st->index, st->airport.type);
+ NOT_REACHED();
}
- return amd;
-}
-
-AirportFTAClass::AirportFTAClass(
- const AirportMovingData *moving_data_,
- const uint8_t *terminals_,
- const uint8_t num_helipads_,
- const uint8_t *entry_points_,
- Flags flags_,
- const AirportFTAbuildup *apFA,
- uint8_t delta_z_
-) :
- moving_data(moving_data_),
- terminals(terminals_),
- num_helipads(num_helipads_),
- flags(flags_),
- nofelements(AirportGetNofElements(apFA)),
- entry_points(entry_points_),
- delta_z(delta_z_)
-{
- /* Build the state machine itself */
- this->layout = AirportBuildAutomata(this->nofelements, apFA);
-}
-
-AirportFTAClass::~AirportFTAClass()
-{
- for (uint i = 0; i < nofelements; i++) {
- AirportFTA *current = layout[i].next;
- while (current != nullptr) {
- AirportFTA *next = current->next;
- free(current);
- current = next;
- }
+}
+
+/**
+ * After loading an old savegame, update type and tracks of airport tiles.
+ */
+void AfterLoadSetAirportTileTypes()
+{
+ for (Station *st : Station::Iterate()) {
+ ConvertOldAirportData(st);
+ st->LoadAirportTilesFromSpec(st->airport, (DiagDirection)st->airport.rotation, st->airport.air_type);
}
- free(layout);
+}
+
+/** Clear data about infrastructure of airport */
+void Station::ClearAirportDataInfrastructure() {
+ this->airport.Clear();
+ this->airport.air_type = INVALID_AIRTYPE;
+ this->airport.aprons.clear();
+ this->airport.helipads.clear();
+ this->airport.runways.clear();
+ if (this->airport.HasHangar()) {
+ this->airport.hangar->depot_tiles.clear();
+ }
+}
+
+void UpdateTracks(TileIndex tile)
+{
+ assert(IsAirportTile(tile));
+ if (!MayHaveAirTracks(tile) || IsHangar(tile)) return;
+ TrackBits tracks = GetAllowedTracks(tile) & GetAirportTileTracks(tile);
+ if (tracks != GetAirportTileTracks(tile)) {
+ Debug(misc, 0, "Removing invalid track on tile {}", tile);
+ }
+
+ SetAirportTileTracks(tile, tracks);
}
/**
- * Get the number of elements of a source Airport state automata
- * Since it is actually just a big array of AirportFTA types, we only
- * know one element from the other by differing 'position' identifiers
+ * Get the start or end of a runway.
+ * @param tile Tile belonging a runway.
+ * @param dir Direction to follow.
+ * @return a runway extreme.
*/
-static uint16_t AirportGetNofElements(const AirportFTAbuildup *apFA)
+TileIndex GetRunwayExtreme(TileIndex tile, DiagDirection dir)
{
- uint16_t nofelements = 0;
- int temp = apFA[0].position;
+ assert(IsAirportTile(tile) && IsRunway(tile));
- for (uint i = 0; i < MAX_ELEMENTS; i++) {
- if (temp != apFA[i].position) {
- nofelements++;
- temp = apFA[i].position;
+ TileIndexDiff delta = TileOffsByDiagDir(dir);
+ TileIndex t = tile;
+
+ for (;;) {
+ assert(IsAirportTile(t));
+ assert(IsRunway(t));
+ if (IsRunwayExtreme(t)) {
+ DiagDirection last_dir = GetRunwayExtremeDirection(t);
+ if (IsRunwayEnd(t)) {
+ if (last_dir == dir) return t;
+ assert(last_dir == ReverseDiagDir(dir));
+ } else {
+ assert(IsRunwayStart(t));
+ if (last_dir == ReverseDiagDir(dir)) return t;
+ assert(last_dir == dir);
+ }
}
- if (apFA[i].position == MAX_ELEMENTS) break;
+ t += delta;
}
- return nofelements;
}
/**
- * Construct the FTA given a description.
- * @param nofelements The number of elements in the FTA.
- * @param apFA The description of the FTA.
- * @return The FTA describing the airport.
+ * Check if a tile is a valid continuation of a runway.
+ * The tile \a test_tile is a valid continuation to \a runway, if all of the following are true:
+ * \li \a test_tile is an airport tile
+ * \li \a test_tile and \a start_tile are in the same station
+ * \li the tracks on \a test_tile and \a start_tile are in the same direction
+ * @param test_tile Tile to test
+ * @param start_tile Depot tile to compare with
+ * @pre IsAirport && IsRunwayStart(start_tile)
+ * @return true if the two tiles are compatible
*/
-static AirportFTA *AirportBuildAutomata(uint nofelements, const AirportFTAbuildup *apFA)
-{
- AirportFTA *FAutomata = MallocT(nofelements);
- uint16_t internalcounter = 0;
-
- for (uint i = 0; i < nofelements; i++) {
- AirportFTA *current = &FAutomata[i];
- current->position = apFA[internalcounter].position;
- current->heading = apFA[internalcounter].heading;
- current->block = apFA[internalcounter].block;
- current->next_position = apFA[internalcounter].next;
-
- /* outgoing nodes from the same position, create linked list */
- while (current->position == apFA[internalcounter + 1].position) {
- AirportFTA *newNode = MallocT(1);
-
- newNode->position = apFA[internalcounter + 1].position;
- newNode->heading = apFA[internalcounter + 1].heading;
- newNode->block = apFA[internalcounter + 1].block;
- newNode->next_position = apFA[internalcounter + 1].next;
- /* create link */
- current->next = newNode;
- current = current->next;
- internalcounter++;
+inline bool IsCompatibleRunwayTile(TileIndex test_tile, TileIndex start_tile)
+{
+ assert(IsAirportTile(start_tile) && IsRunwayStart(start_tile));
+ return IsAirportTile(test_tile) &&
+ GetStationIndex(test_tile) == GetStationIndex(start_tile) &&
+ (GetRunwayTrackdirs(start_tile) & GetRunwayTrackdirs(test_tile)) != TRACKDIR_BIT_NONE;
+}
+
+/**
+ * Get the runway length.
+ * @param tile Runway start tile
+ * @return the length of the runway.
+ */
+uint GetRunwayLength(TileIndex tile)
+{
+ assert(IsAirport(tile) && IsRunwayStart(tile));
+ DiagDirection dir = GetRunwayExtremeDirection(tile);
+ assert(IsValidDiagDirection(dir));
+
+ uint length = 1;
+ [[maybe_unused]] TileIndex start_tile = tile;
+
+ do {
+ length++;
+ tile += TileOffsByDiagDir(dir);
+ assert(IsCompatibleRunwayTile(tile, start_tile));
+ } while (!IsRunwayEnd(tile));
+
+ return length;
+}
+
+/**
+ * Set the reservation for a complete station platform.
+ * @pre IsRailStationTile(start)
+ * @param tile starting tile of the platform
+ * @param b the state the reservation should be set to
+ */
+void SetRunwayReservation(TileIndex tile, bool b)
+{
+ assert(IsRunwayExtreme(tile));
+ DiagDirection runway_dir = GetRunwayExtremeDirection(tile);
+ if (IsRunwayEnd(tile)) runway_dir = ReverseDiagDir(runway_dir);
+ TileIndexDiff diff = TileOffsByDiagDir(runway_dir);
+
+ do {
+ assert(IsAirportTile(tile));
+ assert(!HasAirportTrackReserved(tile));
+ SetReservationAsRunway(tile, b);
+ if (_show_airport_tracks) MarkTileDirtyByTile(tile);
+ tile = TileAdd(tile, diff);
+ } while (!IsRunwayExtreme(tile));
+
+ SetReservationAsRunway(tile, b);
+ if (_show_airport_tracks) MarkTileDirtyByTile(tile);
+}
+
+/**
+ * Get the position in the tile spec of a tile of a tilearea.
+ * @param tile one tile of the area of the airport.
+ * @param tile_area of the airport.
+ * @param rotation the rotation of the airport.
+ */
+uint RotatedAirportSpecPosition(const TileIndex tile, const TileArea tile_area, const DiagDirection rotation)
+{
+ /* Get the tile difference between current tile and northern tile of the airport.
+ * @revise the function TileIndexToTileIndexDiffC as it seems to return the difference
+ * between the first tile minus the second one (one would expect to be the opposite).
+ */
+ TileIndexDiffC tile_diff = TileIndexToTileIndexDiffC(tile, tile_area.tile);
+
+ switch (rotation) {
+ case 0:
+ break;
+ case 1:
+ tile_diff = {(int16_t)(tile_area.h - 1 - tile_diff.y), (int16_t)tile_diff.x};
+ break;
+ case 2:
+ tile_diff = {(int16_t)(tile_area.w - 1 - tile_diff.x), (int16_t)(tile_area.h - 1 - tile_diff.y)};
+ break;
+ case 3:
+ tile_diff = {(int16_t)tile_diff.y, (int16_t)(tile_area.w - 1 - tile_diff.x)};
+ break;
+ default: NOT_REACHED();
+ }
+
+ return tile_diff.x + tile_diff.y * (rotation % 2 == 0 ? tile_area.w : tile_area.h);
+}
+
+/**
+ * Rotate the trackbits as indicated by a direction
+ * @param track_bits to rotate.
+ * @param dir indicating how to rotate track_bits.
+ * (0 -> no rotation,
+ * 1 -> 90 clockwise,
+ * 2 -> 180 clockwise,
+ * 3 -> 270 clockwise).
+ * @return the rotated trackbits.
+ */
+TrackBits RotateTrackBits(TrackBits track_bits, DiagDirection dir)
+{
+ static const TrackBits rotation_table[DIAGDIR_END][TRACK_END] = {
+ { TRACK_BIT_X, TRACK_BIT_Y, TRACK_BIT_UPPER, TRACK_BIT_LOWER, TRACK_BIT_LEFT, TRACK_BIT_RIGHT },
+ { TRACK_BIT_Y, TRACK_BIT_X, TRACK_BIT_RIGHT, TRACK_BIT_LEFT, TRACK_BIT_UPPER, TRACK_BIT_LOWER },
+ { TRACK_BIT_X, TRACK_BIT_Y, TRACK_BIT_LOWER, TRACK_BIT_UPPER, TRACK_BIT_RIGHT, TRACK_BIT_LEFT },
+ { TRACK_BIT_Y, TRACK_BIT_X, TRACK_BIT_LEFT, TRACK_BIT_RIGHT, TRACK_BIT_LOWER, TRACK_BIT_UPPER }
+ };
+
+ TrackBits rotated = TRACK_BIT_NONE;
+ for (Track track : SetTrackBitIterator(track_bits)) {
+ rotated |= rotation_table[dir][track];
+ }
+
+ return rotated;
+}
+
+void Station::LoadAirportTilesFromSpec(TileArea ta, DiagDirection rotation, AirType airtype)
+{
+ if (this->airport.tile == INVALID_TILE) return;
+
+ const AirportSpec *as = this->airport.GetSpec();
+ if (airtype == INVALID_AIRTYPE) airtype = as->airtype;
+ this->airport.air_type = airtype;
+
+ for (Tile t : ta) {
+ uint pos = RotatedAirportSpecPosition(t, ta, rotation);
+ const AirportTileTable *airport_tile_desc = &as->layouts[this->airport.layout].tiles[pos];
+ if (airport_tile_desc->type == ATT_INVALID) continue;
+ assert(this->TileBelongsToAirport(t));
+
+ t.m5() = 0;
+
+ SetStationType(t, STATION_AIRPORT);
+ SetAirType(t, airtype);
+ SetAirportTileType(t, airport_tile_desc->type);
+
+ bool airtype_gfx = airtype != as->airtype || airport_tile_desc->gfx[rotation] == INVALID_AIRPORTTILE;
+ SetAirGfxType(t, airtype_gfx);
+ SetTileAirportGfx(t, airtype_gfx ? airport_tile_desc->at_gfx : airport_tile_desc->gfx[rotation]);
+
+ switch (GetAirportTileType(t)) {
+ case ATT_INFRASTRUCTURE_WITH_CATCH:
+ case ATT_INFRASTRUCTURE_NO_CATCH:
+ SetAirportTileRotation(t, (DiagDirection)((rotation + airport_tile_desc->dir) % DIAGDIR_END));
+ if (!airtype_gfx) SetAirportGfxForAirtype(t, airport_tile_desc->at_gfx);
+ break;
+
+ case ATT_SIMPLE_TRACK:
+ break;
+ case ATT_HANGAR_STANDARD:
+ case ATT_HANGAR_EXTENDED:
+ SetHangarDirection(t, RotateDiagDir(airport_tile_desc->dir, rotation));
+ break;
+
+ case ATT_APRON_NORMAL:
+ case ATT_APRON_HELIPAD:
+ case ATT_APRON_HELIPORT:
+ case ATT_APRON_BUILTIN_HELIPORT:
+ SetAirportTileRotation(t, (DiagDirection)((rotation + airport_tile_desc->dir) % DIAGDIR_END));
+ break;
+
+ case ATT_RUNWAY_MIDDLE:
+ SB(t.m8(), 12, 2, RotateDirection(airport_tile_desc->runway_directions, rotation));
+ break;
+
+ case ATT_RUNWAY_START_NO_LANDING:
+ case ATT_RUNWAY_START_ALLOW_LANDING:
+ case ATT_RUNWAY_END:
+ SB(t.m8(), 12, 2, RotateDiagDir(airport_tile_desc->dir, rotation));
+ break;
+ case ATT_WAITING_POINT:
+ NOT_REACHED();
+ default: NOT_REACHED();
+ }
+
+ if (!IsInfrastructure(t)) {
+ SetAirportTileTracks(t, RotateTrackBits(airport_tile_desc->trackbits, rotation));
}
- current->next = nullptr;
- internalcounter++;
}
- return FAutomata;
+
+ this->UpdateAirportDataStructure();
+}
+
+/** Update cached variables after loading a game or modifying an airport */
+void Station::UpdateAirportDataStructure()
+{
+ this->ClearAirportDataInfrastructure();
+
+ /* Recover the airport area tile rescanning the rect of the station */
+ TileArea ta(TileXY(this->rect.left, this->rect.top), TileXY(this->rect.right, this->rect.bottom));
+ /* At the same time, detect any hangar. */
+ TileIndex first_hangar = INVALID_TILE;
+
+ TileArea airport_area;
+ for (TileIndex t : ta) {
+ if (!this->TileBelongsToAirport(t)) continue;
+ airport_area.Add(t);
+
+ if (first_hangar != INVALID_TILE) continue;
+
+ if (this->airport.air_type == INVALID_AIRTYPE) this->airport.air_type = GetAirType(t);
+
+ assert(this->airport.air_type == GetAirType(t));
+
+ if (IsHangar(t)) first_hangar = t;
+ }
+
+ /* Set/Clear depot. */
+ if (first_hangar != INVALID_TILE && this->airport.hangar == nullptr) {
+ if (!Depot::CanAllocateItem()) NOT_REACHED();
+ this->airport.hangar = new Depot(first_hangar, VEH_AIRCRAFT, GetTileOwner(first_hangar), this);
+ this->airport.hangar->build_date = this->build_date;
+ this->airport.hangar->town = this->town;
+ SetBit(this->airport.flags, AFB_HANGAR);
+ } else if (this->airport.hangar != nullptr) {
+ if (first_hangar == INVALID_TILE) {
+ ClrBit(this->airport.flags, AFB_HANGAR);
+ if (this->airport.hangar->IsInUse()) {
+ this->airport.hangar->Disuse();
+ }
+ } else {
+ SetBit(this->airport.flags, AFB_HANGAR);
+ if (!this->airport.hangar->IsInUse()) {
+ /* Reuse current hangar. */
+ this->airport.hangar->Reuse(first_hangar);
+ }
+ }
+ }
+
+ if (airport_area.tile == INVALID_TILE) return;
+
+ if (this->airport.hangar != nullptr) this->airport.hangar->r_types.air_types = AIRTYPES_NONE;
+
+ bool allow_landing = false;
+ for (TileIndex t : airport_area) {
+ if (!this->TileBelongsToAirport(t)) continue;
+ this->airport.Add(t);
+
+ assert(this->airport.air_type == GetAirType(t));
+
+ if (!MayHaveAirTracks(t)) continue;
+
+ UpdateTracks(t);
+
+ switch (GetAirportTileType(t)) {
+ case ATT_HANGAR_STANDARD:
+ case ATT_HANGAR_EXTENDED:
+ assert(this->airport.HasHangar());
+ this->airport.hangar->depot_tiles.emplace_back(t);
+ this->airport.hangar->xy = t;
+ this->airport.hangar->r_types.air_types |= (AirTypes)(1 << this->airport.air_type);
+ break;
+
+ case ATT_APRON_NORMAL:
+ this->airport.aprons.emplace_back(t);
+ break;
+ case ATT_APRON_HELIPAD:
+ this->airport.helipads.emplace_back(t);
+ break;
+ case ATT_APRON_HELIPORT:
+ case ATT_APRON_BUILTIN_HELIPORT:
+ this->airport.heliports.emplace_back(t);
+ break;
+
+ case ATT_RUNWAY_START_ALLOW_LANDING:
+ allow_landing = true;
+ [[fallthrough]];
+ case ATT_RUNWAY_START_NO_LANDING:
+ this->airport.runways.emplace_back(t);
+ break;
+
+ default: break;
+ }
+ }
+
+ if (this->airport.hangar != nullptr) InvalidateWindowData(WC_BUILD_VEHICLE, this->airport.hangar->index);
+
+ if (this->airport.HasLandingRunway() != allow_landing) {
+ ToggleBit(this->airport.flags, AFB_LANDING_RUNWAY);
+ }
}
/**
- * Get the finite state machine of an airport type.
- * @param airport_type %Airport type to query FTA from. @see AirportTypes
- * @return Finite state machine of the airport.
+ * Return the tracks a tile could have.
+ * It returns the tracks the tile has plus the extra tracks that
+ * could also exist on the tile.
+ * @param tile
+ * @return The tracks the tile could have.
*/
-const AirportFTAClass *GetAirport(const uint8_t airport_type)
+TrackBits GetAllowedTracks(TileIndex tile)
{
- if (airport_type == AT_DUMMY) return &_airportfta_dummy;
- return AirportSpec::Get(airport_type)->fsm;
+ assert(IsAirportTile(tile));
+ switch (GetAirportTileType(tile)) {
+ case ATT_INFRASTRUCTURE_NO_CATCH:
+ case ATT_INFRASTRUCTURE_WITH_CATCH:
+ return TRACK_BIT_NONE;
+
+ case ATT_HANGAR_STANDARD:
+ case ATT_HANGAR_EXTENDED:
+ return HasBit(Tile(tile).m8(), 15) ? TRACK_BIT_Y: TRACK_BIT_X;
+
+ case ATT_APRON_HELIPORT:
+ case ATT_APRON_BUILTIN_HELIPORT:
+ return TRACK_BIT_CROSS;
+
+ case ATT_APRON_NORMAL:
+ case ATT_APRON_HELIPAD:
+ case ATT_SIMPLE_TRACK:
+ case ATT_RUNWAY_MIDDLE:
+ case ATT_RUNWAY_END:
+ case ATT_RUNWAY_START_NO_LANDING:
+ case ATT_RUNWAY_START_ALLOW_LANDING: {
+ TrackBits tracks = TRACK_BIT_ALL;
+
+ const TrackBits rem_tracks[] = {
+ ~TRACK_BIT_UPPER,
+ ~(TRACK_BIT_UPPER | TRACK_BIT_RIGHT),
+ ~TRACK_BIT_RIGHT,
+ ~(TRACK_BIT_LOWER | TRACK_BIT_RIGHT),
+ ~TRACK_BIT_LOWER,
+ ~(TRACK_BIT_LOWER | TRACK_BIT_LEFT),
+ ~TRACK_BIT_LEFT,
+ ~(TRACK_BIT_UPPER | TRACK_BIT_LEFT),
+ };
+
+ for (Direction dir = DIR_BEGIN; dir < DIR_END; dir++) {
+ TileIndex t = TileAddByDir(tile, dir);
+ if (!IsValidTile(t) || !IsAirportTile(t) ||
+ GetStationIndex(t) != GetStationIndex(tile) || !MayHaveAirTracks(t)) {
+ tracks &= rem_tracks[dir];
+ } else if (IsHangar(t)) {
+ tracks &= rem_tracks[dir];
+ }
+ }
+
+ return tracks;
+ }
+
+ default: NOT_REACHED();
+ }
}
/**
- * Get the vehicle position when an aircraft is build at the given tile
- * @param hangar_tile The tile on which the vehicle is build
- * @return The position (index in airport node array) where the aircraft ends up
+ * Get the sprite for an airport tile.
+ * @param t Tile to get the sprite of.
+ * @return AirportTile ID.
*/
-uint8_t GetVehiclePosOnBuild(TileIndex hangar_tile)
-{
- const Station *st = Station::GetByTile(hangar_tile);
- const AirportFTAClass *apc = st->airport.GetFTA();
- /* When we click on hangar we know the tile it is on. By that we know
- * its position in the array of depots the airport has.....we can search
- * layout for #th position of depot. Since layout must start with a listing
- * of all depots, it is simple */
- for (uint i = 0;; i++) {
- if (st->airport.GetHangarTile(i) == hangar_tile) {
- assert(apc->layout[i].heading == HANGAR);
- return apc->layout[i].position;
+StationGfx GetAirportGfx(TileIndex t)
+{
+ assert(IsTileType(t, MP_STATION));
+ assert(IsAirport(t));
+
+ if (!HasAirtypeGfx(t)) return GetTranslatedAirportTileID(GetTileAirportGfx(t));
+
+ switch (GetAirportTileType(t)) {
+ case ATT_INFRASTRUCTURE_NO_CATCH:
+ case ATT_INFRASTRUCTURE_WITH_CATCH:
+ return GetTileAirportGfx(t);
+
+ case ATT_SIMPLE_TRACK:
+ return (StationGfx)0;
+
+ case ATT_HANGAR_STANDARD:
+ case ATT_HANGAR_EXTENDED:
+ return (StationGfx)0;
+
+ case ATT_APRON_NORMAL:
+ case ATT_APRON_HELIPAD:
+ case ATT_APRON_HELIPORT:
+ case ATT_APRON_BUILTIN_HELIPORT:
+ switch (GetApronType(t)) {
+ case APRON_APRON:
+ case APRON_HELIPAD:
+ case APRON_HELIPORT:
+ return (StationGfx)0;
+ case APRON_BUILTIN_HELIPORT:
+ return (StationGfx)0; // oil rig heliport
+ default: NOT_REACHED();
+ }
+
+ case ATT_RUNWAY_MIDDLE:
+ case ATT_RUNWAY_START_NO_LANDING:
+ case ATT_RUNWAY_START_ALLOW_LANDING:
+ case ATT_RUNWAY_END: {
+ return (StationGfx)0;
+ break;
}
+
+ case ATT_WAITING_POINT:
+ NOT_REACHED();
+ default:
+ NOT_REACHED();
}
- NOT_REACHED();
}
+
diff --git a/src/airport.h b/src/airport.h
index d5df10e275c9e..9ea84384838fa 100644
--- a/src/airport.h
+++ b/src/airport.h
@@ -11,6 +11,7 @@
#define AIRPORT_H
#include "direction_type.h"
+#include "track_type.h"
#include "tile_type.h"
/** Some airport-related constants */
@@ -21,8 +22,8 @@ static const uint MAX_ELEMENTS = 255; ///< maximum number
static const uint NUM_AIRPORTTILES_PER_GRF = 255; ///< Number of airport tiles per NewGRF; limited to 255 to allow extending Action3 with an extended byte later on.
static const uint NUM_AIRPORTTILES = 256; ///< Total number of airport tiles.
-static const uint NEW_AIRPORTTILE_OFFSET = 74; ///< offset of first newgrf airport tile
-static const uint INVALID_AIRPORTTILE = NUM_AIRPORTTILES; ///< id for an invalid airport tile
+static const uint NEW_AIRPORTTILE_OFFSET = 74; ///< offset of first newgrf airport tilex
+static const uint NUM_AIRTYPE_INFRATILES = 11; ///< Total number of infrastructure tiles by airtype.
/** Airport types */
enum AirportTypes {
@@ -39,163 +40,30 @@ enum AirportTypes {
NEW_AIRPORT_OFFSET = 10, ///< Number of the first newgrf airport.
NUM_AIRPORTS_PER_GRF = 128, ///< Maximal number of airports per NewGRF.
NUM_AIRPORTS = 128, ///< Maximal number of airports in total.
+ AT_CUSTOM = 253, ///< Customized airport.
AT_INVALID = 254, ///< Invalid airport.
AT_DUMMY = 255, ///< Dummy airport.
};
-/** Flags for airport movement data. */
-enum AirportMovingDataFlags {
- AMED_NOSPDCLAMP = 1 << 0, ///< No speed restrictions.
- AMED_TAKEOFF = 1 << 1, ///< Takeoff movement.
- AMED_SLOWTURN = 1 << 2, ///< Turn slowly (mostly used in the air).
- AMED_LAND = 1 << 3, ///< Landing onto landing strip.
- AMED_EXACTPOS = 1 << 4, ///< Go exactly to the destination coordinates.
- AMED_BRAKE = 1 << 5, ///< Taxiing at the airport.
- AMED_HELI_RAISE = 1 << 6, ///< Helicopter take-off.
- AMED_HELI_LOWER = 1 << 7, ///< Helicopter landing.
- AMED_HOLD = 1 << 8, ///< Holding pattern movement (above the airport).
-};
-
-/** Movement States on Airports (headings target) */
-enum AirportMovementStates {
- TO_ALL = 0, ///< Go in this direction for every target.
- HANGAR = 1, ///< Heading for hangar.
- TERM1 = 2, ///< Heading for terminal 1.
- TERM2 = 3, ///< Heading for terminal 2.
- TERM3 = 4, ///< Heading for terminal 3.
- TERM4 = 5, ///< Heading for terminal 4.
- TERM5 = 6, ///< Heading for terminal 5.
- TERM6 = 7, ///< Heading for terminal 6.
- HELIPAD1 = 8, ///< Heading for helipad 1.
- HELIPAD2 = 9, ///< Heading for helipad 2.
- TAKEOFF = 10, ///< Airplane wants to leave the airport.
- STARTTAKEOFF = 11, ///< Airplane has arrived at a runway for take-off.
- ENDTAKEOFF = 12, ///< Airplane has reached end-point of the take-off runway.
- HELITAKEOFF = 13, ///< Helicopter wants to leave the airport.
- FLYING = 14, ///< %Vehicle is flying in the air.
- LANDING = 15, ///< Airplane wants to land.
- ENDLANDING = 16, ///< Airplane wants to finish landing.
- HELILANDING = 17, ///< Helicopter wants to land.
- HELIENDLANDING = 18, ///< Helicopter wants to finish landing.
- TERM7 = 19, ///< Heading for terminal 7.
- TERM8 = 20, ///< Heading for terminal 8.
- HELIPAD3 = 21, ///< Heading for helipad 3.
- MAX_HEADINGS = 21, ///< Last valid target to head for.
- TERMGROUP = 255, ///< Aircraft is looking for a free terminal in a terminalgroup.
-};
-
-/** Movement Blocks on Airports blocks (eg_airport_flags). */
-static const uint64_t
- TERM1_block = 1ULL << 0, ///< Block belonging to terminal 1.
- TERM2_block = 1ULL << 1, ///< Block belonging to terminal 2.
- TERM3_block = 1ULL << 2, ///< Block belonging to terminal 3.
- TERM4_block = 1ULL << 3, ///< Block belonging to terminal 4.
- TERM5_block = 1ULL << 4, ///< Block belonging to terminal 5.
- TERM6_block = 1ULL << 5, ///< Block belonging to terminal 6.
- HELIPAD1_block = 1ULL << 6, ///< Block belonging to helipad 1.
- HELIPAD2_block = 1ULL << 7, ///< Block belonging to helipad 2.
- RUNWAY_IN_OUT_block = 1ULL << 8,
- RUNWAY_IN_block = 1ULL << 8,
- AIRPORT_BUSY_block = 1ULL << 8,
- RUNWAY_OUT_block = 1ULL << 9,
- TAXIWAY_BUSY_block = 1ULL << 10,
- OUT_WAY_block = 1ULL << 11,
- IN_WAY_block = 1ULL << 12,
- AIRPORT_ENTRANCE_block = 1ULL << 13,
- TERM_GROUP1_block = 1ULL << 14,
- TERM_GROUP2_block = 1ULL << 15,
- HANGAR2_AREA_block = 1ULL << 16,
- TERM_GROUP2_ENTER1_block = 1ULL << 17,
- TERM_GROUP2_ENTER2_block = 1ULL << 18,
- TERM_GROUP2_EXIT1_block = 1ULL << 19,
- TERM_GROUP2_EXIT2_block = 1ULL << 20,
- PRE_HELIPAD_block = 1ULL << 21,
-
- /* blocks for new airports */
- TERM7_block = 1ULL << 22, ///< Block belonging to terminal 7.
- TERM8_block = 1ULL << 23, ///< Block belonging to terminal 8.
- HELIPAD3_block = 1ULL << 24, ///< Block belonging to helipad 3.
- HANGAR1_AREA_block = 1ULL << 26,
- OUT_WAY2_block = 1ULL << 27,
- IN_WAY2_block = 1ULL << 28,
- RUNWAY_IN2_block = 1ULL << 29,
- RUNWAY_OUT2_block = 1ULL << 10, ///< @note re-uses #TAXIWAY_BUSY_block
- HELIPAD_GROUP_block = 1ULL << 13, ///< @note re-uses #AIRPORT_ENTRANCE_block
- OUT_WAY_block2 = 1ULL << 31,
- /* end of new blocks */
-
- NOTHING_block = 1ULL << 30,
- AIRPORT_CLOSED_block = 1ULL << 63; ///< Dummy block for indicating a closed airport.
-
-/** A single location on an airport where aircraft can move to. */
-struct AirportMovingData {
- int16_t x; ///< x-coordinate of the destination.
- int16_t y; ///< y-coordinate of the destination.
- uint16_t flag; ///< special flags when moving towards the destination.
- Direction direction; ///< Direction to turn the aircraft after reaching the destination.
-};
-
-AirportMovingData RotateAirportMovingData(const AirportMovingData *orig, Direction rotation, uint num_tiles_x, uint num_tiles_y);
-
-struct AirportFTAbuildup;
-
-/** Finite sTate mAchine (FTA) of an airport. */
-struct AirportFTAClass {
-public:
- /** Bitmask of airport flags. */
- enum Flags {
- AIRPLANES = 0x1, ///< Can planes land on this airport type?
- HELICOPTERS = 0x2, ///< Can helicopters land on this airport type?
- ALL = AIRPLANES | HELICOPTERS, ///< Mask to check for both planes and helicopters.
- SHORT_STRIP = 0x4, ///< This airport has a short landing strip, dangerous for fast aircraft.
- };
-
- AirportFTAClass(
- const AirportMovingData *moving_data,
- const uint8_t *terminals,
- const uint8_t num_helipads,
- const uint8_t *entry_points,
- Flags flags,
- const AirportFTAbuildup *apFA,
- uint8_t delta_z
- );
-
- ~AirportFTAClass();
+uint8_t GetVehiclePosOnBuild(TileIndex hangar_tile);
- /**
- * Get movement data at a position.
- * @param position Element number to get movement data about.
- * @return Pointer to the movement data.
- */
- const AirportMovingData *MovingData(uint8_t position) const
- {
- assert(position < nofelements);
- return &moving_data[position];
- }
+TrackBits GetAllowedTracks(TileIndex tile);
+void SetRunwayReservation(TileIndex tile, bool b);
+TileIndex GetRunwayExtreme(TileIndex tile, DiagDirection dir);
+uint GetRunwayLength(TileIndex tile);
- const AirportMovingData *moving_data; ///< Movement data.
- struct AirportFTA *layout; ///< state machine for airport
- const uint8_t *terminals; ///< %Array with the number of terminal groups, followed by the number of terminals in each group.
- const uint8_t num_helipads; ///< Number of helipads on this airport. When 0 helicopters will go to normal terminals.
- Flags flags; ///< Flags for this airport type.
- uint8_t nofelements; ///< number of positions the airport consists of
- const uint8_t *entry_points; ///< when an airplane arrives at this airport, enter it at position entry_point, index depends on direction
- uint8_t delta_z; ///< Z adjustment for helicopter pads
+enum AirportFlagBits : uint8_t {
+ AFB_CLOSED_MANUAL = 0, ///< Airport closed: manually closed.
+ AFB_HANGAR = 1, ///< Airport has at least one hangar tile.
+ AFB_LANDING_RUNWAY = 2, ///< Airport has a landing runway.
};
-DECLARE_ENUM_AS_BIT_SET(AirportFTAClass::Flags)
-
-
-/** Internal structure used in openttd - Finite sTate mAchine --> FTA */
-struct AirportFTA {
- AirportFTA *next; ///< possible extra movement choices from this position
- uint64_t block; ///< 64 bit blocks (st->airport.flags), should be enough for the most complex airports
- uint8_t position; ///< the position that an airplane is at
- uint8_t next_position; ///< next position from this position
- uint8_t heading; ///< heading (current orders), guiding an airplane to its target on an airport
+enum AirportFlags : uint16_t {
+ AF_NONE = 0, ///< No flag.
+ AF_CLOSED_MANUAL = 1 << AFB_CLOSED_MANUAL,
+ AF_HANGAR = 1 << AFB_HANGAR,
+ AF_LANDING_RUNWAY = 1 << AFB_LANDING_RUNWAY,
};
-
-const AirportFTAClass *GetAirport(const uint8_t airport_type);
-uint8_t GetVehiclePosOnBuild(TileIndex hangar_tile);
+DECLARE_ENUM_AS_BIT_SET(AirportFlags)
#endif /* AIRPORT_H */
diff --git a/src/airport_cmd.cpp b/src/airport_cmd.cpp
new file mode 100644
index 0000000000000..d7be13dc1b4d4
--- /dev/null
+++ b/src/airport_cmd.cpp
@@ -0,0 +1,2035 @@
+/*
+ * 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 airport_cmd.cpp Handling of airport commands. */
+
+#include "station_map.h"
+#include "stdafx.h"
+#include "command_func.h"
+#include "core/backup_type.hpp"
+#include "air.h"
+#include "air_map.h"
+#include "aircraft.h"
+#include "airport_cmd.h"
+#include "animated_tile_func.h"
+#include "autoslope.h"
+#include "bitmap_type.h"
+#include "company_base.h"
+#include "company_gui.h"
+#include "depot_base.h"
+#include "industry.h"
+#include "landscape.h"
+#include "landscape_cmd.h"
+#include "newgrf_debug.h"
+#include "newgrf_airport.h"
+#include "newgrf_airporttiles.h"
+#include "order_backup.h"
+#include "pathfinder/yapf/yapf_cache.h"
+#include "sound_func.h"
+#include "station_func.h"
+#include "station_kdtree.h"
+#include "strings_func.h"
+#include "town.h"
+#include "vehicle_func.h"
+#include "viewport_func.h"
+#include "water.h"
+#include "window_func.h"
+
+#include "pathfinder/follow_track.hpp"
+
+#include "table/airporttile_ids.h"
+#include "table/airport_defaults.h"
+#include "table/airtypes.h"
+#include "table/strings.h"
+#include "table/track_land.h"
+
+#include "widgets/station_widget.h"
+
+/**
+ * Updates the status of the Aircraft heading or in the airport
+ * @param st Updated airport
+ */
+void UpdateAircraftOnUpdatedAirport(const Station *st)
+{
+ for (Aircraft *v : Aircraft::Iterate()) {
+ if (!v->IsPrimaryVehicle() || v->targetairport != st->index) continue;
+ if (v->IsAircraftFreelyFlying()) {
+ AircraftUpdateNextPos(v);
+ continue;
+ }
+
+ switch (v->current_order.GetType()) {
+ case OT_GOTO_STATION:
+ if (v->current_order.GetDestination() != st->index) continue;
+ v->dest_tile = v->GetOrderStationLocation(st->index);
+ break;
+
+ case OT_GOTO_DEPOT:
+ if (!st->airport.HasHangar()) continue;
+ if (v->current_order.GetDestination() != st->airport.hangar->index) continue;
+ v->dest_tile = v->GetOrderHangarLocation(st->airport.hangar->index);
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ SetWindowClassesDirty(WC_VEHICLE_ORDERS);
+}
+
+extern CommandCost CheckBuildableTile(TileIndex tile, uint invalid_dirs, int &allowed_z, bool allow_steep, bool check_bridge = true);
+
+extern CommandCost FindJoiningAirport(StationID existing_station, StationID station_to_join, bool adjacent, TileArea ta, Station **st);
+
+extern CommandCost BuildStationPart(Station **st, DoCommandFlag flags, bool reuse, TileArea area, StationNaming name_class);
+
+/**
+ * Calculate the noise level an airport would have if it were of a given airtype.
+ * @param airport The airport
+ * @param airtype The airtype to check, or INVALID_AIRTYPE (when INVALID_AIRTYPE is
+ * given, it will use the current airtype of the airport).
+ * @return The total noise generated by the airport if it has the given airtype.
+ */
+uint8_t CalculateAirportNoiseLevel(const Airport airport, AirType airtype = INVALID_AIRTYPE)
+{
+
+ if (!IsValidTile(airport.tile)) return 0;
+
+ if (airport.heliports.size() == 1 && IsBuiltInHeliportTile(airport.heliports[0])) return 0;
+
+ if (airtype == INVALID_AIRTYPE) airtype = airport.air_type;
+
+ const AirTypeInfo *air_type_info = GetAirTypeInfo(airtype);
+
+ return (uint8_t)(air_type_info->base_noise_level +
+ airport.runways.size() * air_type_info->runway_noise_level +
+ airport.helipads.size() +
+ airport.heliports.size() +
+ airport.aprons.size());
+}
+
+/**
+ * Get a possible noise reduction factor based on distance from town center.
+ * The further you get, the less noise you generate.
+ * So all those folks at city council can now happily slee... work in their offices
+ * @param noise_level noise level of the airport
+ * @param distance minimum distance between town and airport
+ * @return the noise that will be generated, according to distance
+ */
+uint8_t GetAirportNoiseLevelForDistance(uint noise_level, uint distance)
+{
+ /* 0 cannot be accounted, and 1 is the lowest that can be reduced from town.
+ * So no need to go any further*/
+ if (noise_level < 2) return noise_level;
+
+ /* The steps for measuring noise reduction are based on the "magical" (and arbitrary) 8 base distance
+ * adding the town_council_tolerance 4 times, as a way to graduate, depending of the tolerance.
+ * Basically, it says that the less tolerant a town is, the bigger the distance before
+ * an actual decrease can be granted */
+ uint8_t town_tolerance_distance = 8 + (_settings_game.difficulty.town_council_tolerance * 4);
+
+ /* now, we want to have the distance segmented using the distance judged bareable by town
+ * This will give us the coefficient of reduction the distance provides. */
+ uint noise_reduction = distance / town_tolerance_distance;
+
+ /* If the noise reduction equals the airport noise itself, don't give it for free.
+ * Otherwise, simply reduce the airport's level. */
+ return noise_reduction >= noise_level ? 1 : noise_level - noise_reduction;
+}
+
+extern uint RotatedAirportSpecPosition(const TileIndex tile, const TileArea tile_area, const DiagDirection rotation);
+
+CommandCost AddAirportTileTableToBitmapTileArea(const AirportTileLayout &atl, BitmapTileArea *bta, DiagDirection rotation, uint cost_multiplier)
+{
+ CommandCost cost(EXPENSES_CONSTRUCTION);
+
+ uint tile_count = 0;
+ for (TileIndex t : (TileArea)*bta) {
+ uint pos = RotatedAirportSpecPosition(t, *bta, rotation);
+ if (atl.tiles[pos].type != ATT_INVALID) {
+ bta->SetTile(t);
+ ++tile_count;
+ }
+ }
+ cost.AddCost(_price[PR_BUILD_STATION_AIRPORT] * tile_count * cost_multiplier);
+ return cost;
+}
+
+/**
+ * Finds the town nearest to given airport. Based on minimal manhattan distance to any airport's tile.
+ * If two towns have the same distance, town with lower index is returned.
+ * @param bta BitmapTileArea of the tiles of the airport
+ * @param[out] mindist Minimum distance to town
+ * @return nearest town to airport
+ */
+Town *AirportGetNearestTown(BitmapTileArea bta, uint &mindist)
+{
+ assert(Town::GetNumItems() > 0);
+
+ Town *nearest = nullptr;
+ mindist = UINT_MAX - 1; // prevent overflow
+
+ for (TileIndex tile : bta) {
+ if (!bta.HasTile(tile)) continue;
+ /* Iterate over all tiles of the airport, as airport may not be rectangular. */
+ Town *t = CalcClosestTownFromTile(tile, mindist + 1);
+ if (t == nullptr) continue;
+
+ uint dist = DistanceManhattan(t->xy, tile);
+ if (dist == mindist && t->index < nearest->index) nearest = t;
+ if (dist < mindist) {
+ nearest = t;
+ mindist = dist;
+ }
+ }
+
+ return nearest;
+}
+
+
+/** Recalculate the noise generated by the airports of each town */
+void UpdateAirportsNoise()
+{
+ for (Town *t : Town::Iterate()) t->noise_reached = 0;
+
+ for (const Station *st : Station::Iterate()) {
+ if (st->airport.tile != INVALID_TILE && st->airport.type != AT_OILRIG) {
+ uint8_t noise_level = CalculateAirportNoiseLevel(st->airport);
+ uint dist;
+ BitmapTileArea bta;
+ bta.Initialize(st->airport);
+ for (TileIndex t : st->airport) {
+ if (st->TileBelongsToAirport(t)) bta.SetTile(t);
+ }
+
+ Town *nearest = AirportGetNearestTown(bta, dist);
+ nearest->noise_reached += GetAirportNoiseLevelForDistance(noise_level, dist);
+ }
+ }
+}
+
+void GetAirportArea(const Station *st, const BitmapTileArea new_tiles, BitmapTileArea &all_tiles)
+{
+ TileArea complete_tilearea;
+ if (st != nullptr) {
+ for (TileIndex t : st->airport) if (st->TileBelongsToAirport(t)) complete_tilearea.Add(t);
+ }
+ for (TileIndex t : new_tiles) complete_tilearea.Add(t);
+ all_tiles.Initialize(complete_tilearea);
+ for (TileIndex t : new_tiles) all_tiles.SetTile(t);
+ if (st != nullptr) {
+ for (TileIndex t : st->airport) if (st->TileBelongsToAirport(t)) all_tiles.SetTile(t);
+ }
+}
+
+/**
+ * Checks noise and whether there are too many airports in a town.
+ * @param st Station with preexisting airport
+ * @param added_area BitmapTileArea containing the tiles of the airport to be added.
+ * @param[out] pre_town The town closest to preexisting airport.
+ * @param[out] post_town The town closest to modified airport.
+ * @param[out] pre_noise The current noise that the airport of station \b st in \b pre_town
+ * @param[in,out] post_noise The addition of noise of the new tiles as in, the noise the airport generates for post_town as out.
+ * @return Whether it is possible to add the tiles due to noise restriction or limit of airports.
+ */
+CommandCost CheckTownAuthorityForAirports(TileIndex tile, Station *st, const BitmapTileArea added_area, Town **pre_town, Town **post_town, uint &pre_noise, uint &post_noise)
+{
+ /* Get the area containing previous airport tiles from station st and new added tiles. */
+ BitmapTileArea modified_area, initial_area;
+ GetAirportArea(st, BitmapTileArea(), initial_area);
+ GetAirportArea(st, added_area, modified_area);
+
+ /* Get the closest town of the preexisting airport to be joined to and the closest town for the modified airport. */
+ uint pre_dist = 0;
+ uint post_dist = 0;
+ *pre_town = (st != nullptr && (st->facilities & FACIL_AIRPORT) != FACIL_NONE) ? AirportGetNearestTown(initial_area, pre_dist) : nullptr;
+ *post_town = AirportGetNearestTown(modified_area, post_dist);
+
+ /* Get the current noise for preexisting airport and the noise for the modified airport. */
+ pre_noise = *pre_town != nullptr ? CalculateAirportNoiseLevel(st->airport) : 0;
+ post_noise = GetAirportNoiseLevelForDistance(post_noise + pre_noise, post_dist);
+ pre_noise = GetAirportNoiseLevelForDistance(pre_noise, pre_dist);
+
+ /* Check if local auth would allow a new airport */
+ StringID authority_refuse_message = STR_NULL;
+ Town *authority_refuse_town = *post_town;
+
+ if (_settings_game.economy.station_noise_level) {
+ /* Do not allow to build a new airport if this raises the town noise over the maximum allowed by town. */
+ if (*post_town == *pre_town) {
+ /* Preexisting airport and the same town to get noise... */
+ if ((*post_town)->noise_reached - pre_noise + post_noise > (*post_town)->MaxTownNoise()) {
+ authority_refuse_message = STR_ERROR_LOCAL_AUTHORITY_REFUSES_NOISE;
+ }
+ } else {
+ /* Check that adding the airport noise to post_town doesn't exceed the maximum allowed by town. */
+ if (((*post_town)->noise_reached + post_noise) > (*post_town)->MaxTownNoise()) {
+ authority_refuse_message = STR_ERROR_LOCAL_AUTHORITY_REFUSES_NOISE;
+ }
+ }
+ } else if (_settings_game.difficulty.town_council_tolerance != TOWN_COUNCIL_PERMISSIVE) {
+ if (st != nullptr && (st->facilities & FACIL_AIRPORT) != FACIL_NONE) return CommandCost();
+ authority_refuse_town = ClosestTownFromTile(tile, UINT_MAX);
+ uint num = 0;
+ for (const Station *s : Station::Iterate()) {
+ if (s->town == authority_refuse_town && (s->facilities & FACIL_AIRPORT) && s->airport.type != AT_OILRIG) num++;
+ }
+ if (num >= 2) {
+ authority_refuse_message = STR_ERROR_LOCAL_AUTHORITY_REFUSES_AIRPORT;
+ }
+ }
+
+ if (authority_refuse_message != STR_NULL) {
+ SetDParam(0, authority_refuse_town->index);
+ return_cmd_error(authority_refuse_message);
+ }
+
+ return CommandCost();
+}
+
+void UpdateNoiseForTowns(Town *pre_nearest, Town *post_nearest, const uint pre_noise, const uint post_noise)
+{
+ /* Subtract previous noise. */
+ if (pre_noise > 0) pre_nearest->noise_reached -= pre_noise;
+
+ /* Add noise to updated airport. */
+ post_nearest->noise_reached += post_noise;
+
+ if (_settings_game.economy.station_noise_level) {
+ SetWindowDirty(WC_TOWN_VIEW, post_nearest->index);
+ if (pre_nearest != nullptr && pre_nearest != post_nearest) SetWindowDirty(WC_TOWN_VIEW, pre_nearest->index);
+ }
+}
+
+CommandCost inline CheckSettingBuildByTile()
+{
+ if (!_settings_game.station.allow_modify_airports) return_cmd_error(STR_ERROR_AIRPORT_DISABLED_BY_TILE);
+ return CommandCost();
+}
+
+extern CommandCost ValidateAutoDrag(Trackdir *trackdir, TileIndex start, TileIndex end);
+
+/**
+ * Checks if there is a vehicle in an airport given by one of its tiles.
+ * @param st Station to check.
+ * @return A command cost with an error if a vehicle is found on ground or in a runway.
+ */
+CommandCost AircraftInAirport(const Station *st)
+{
+ if (st == nullptr) return CommandCost();
+
+ for (TileIndex tile : st->airport) {
+ if (!st->TileBelongsToAirport(tile)) continue;
+
+ if ((MayHaveAirTracks(tile) && HasAirportTrackReserved(tile)) ||
+ (IsRunway(tile) && GetReservationAsRunway(tile)))
+ return_cmd_error(STR_ERROR_AIRPORT_PRESENT_AIRCRAFT);
+
+ /* Aircraft can be hidden inside depots with no associated reservation. */
+ if (IsStandardHangarTile(tile)) {
+ CommandCost ret = EnsureFreeHangar(tile);
+ if (ret.Failed()) return ret;
+ }
+ }
+
+ return CommandCost();
+}
+
+CommandCost CheckRunwayLength(AirType air_type, uint length)
+{
+ if (GetAirTypeInfo(air_type)->min_runway_length > length) return_cmd_error(STR_ERROR_AIRPORT_RUNWAY_TOO_SHORT);
+ return CommandCost();
+}
+
+CommandCost AddAirportTrack(TileIndex tile, Track track, DoCommandFlag flags)
+{
+ assert(IsAirportTile(tile) && !IsHangar(tile));
+
+ if (!IsValidTile(tile)) return CMD_ERROR;
+ CommandCost ret = CheckTileOwnership(tile);
+ if (ret.Failed()) return ret;
+ if (!MayHaveAirTracks(tile)) return_cmd_error(STR_ERROR_AIRPORT_CAN_T_HAVE_TRACKS);
+ if (HasAirportTileTrack(tile, track)) return_cmd_error(STR_ERROR_ALREADY_BUILT);
+ AirportTileType att = GetAirportTileType(tile);
+ if ((att == ATT_HANGAR_STANDARD || att == ATT_HANGAR_EXTENDED) && !IsDiagonalTrack(track)) return_cmd_error(STR_ERROR_AIRPORT_CAN_T_ADD_TRACK_HANGAR);
+
+ if (!HasTrack(GetAllowedTracks(tile), track)) return_cmd_error(STR_ERROR_AIRPORT_NO_COMPATIBLE_NEIGHBOURS);
+
+ if (flags & DC_EXEC) {
+ SetAirportTileTracks(tile, GetAirportTileTracks(tile) | TrackToTrackBits(track));
+ if (_show_airport_tracks) MarkTileDirtyByTile(tile);
+ }
+
+ return CommandCost();
+}
+
+CommandCost RemoveAirportTrack(TileIndex tile, Track track, DoCommandFlag flags)
+{
+ assert(IsAirportTile(tile) && !IsHangar(tile));
+
+ if (!IsValidTile(tile)) return CMD_ERROR;
+ CommandCost ret = CheckTileOwnership(tile);
+ if (ret.Failed()) return ret;
+
+ if (!MayHaveAirTracks(tile) || !HasAirportTileTrack(tile, track)) return CommandCost();
+
+ if (HasAirportTrackReserved(tile, track) || (IsRunwayExtreme(tile) && GetReservationAsRunway(tile))) return CMD_ERROR;
+
+ if (flags & DC_EXEC) {
+ SetAirportTileTracks(tile, GetAirportTileTracks(tile) & ~TrackToTrackBits(track));
+ if (_show_airport_tracks) MarkTileDirtyByTile(tile);
+ }
+
+ return CommandCost();
+}
+
+static const TileIndexDiffC _trackdelta[] = {
+ { -1, 0 }, { 0, 1 }, { -1, 0 }, { 0, 1 }, { 1, 0 }, { 0, 1 },
+ { 0, 0 },
+ { 0, 0 },
+ { 1, 0 }, { 0, -1 }, { 0, -1 }, { 1, 0 }, { 0, -1 }, { -1, 0 },
+ { 0, 0 },
+ { 0, 0 }
+};
+
+/**
+ * Add/Remove tracks for an airport.
+ * @param flags operation to perform
+ * @param start_tile start tile of drag
+ * @param end_tile end tile of drag
+ * @param air_type Air type (gravel, asphalt, water...)
+ * @param add whether to add or remove tracks
+ * @param track starting track
+ * @return the cost of this operation or an error
+ */
+CommandCost CmdAddRemoveTracksToAirport(DoCommandFlag flags, TileIndex start_tile, TileIndex end_tile, AirType air_type, bool add, Track track)
+{
+ Trackdir trackdir = TrackToTrackdir(track);
+
+ CommandCost ret = ValidateAutoDrag(&trackdir, start_tile, end_tile);
+ if (ret.Failed()) return ret;
+
+ if ((flags & DC_EXEC) && _settings_client.sound.confirm) SndPlayTileFx(SND_1F_CONSTRUCTION_OTHER, start_tile);
+
+ bool had_success = false;
+ std::vector affected_stations;
+ CommandCost last_error = CMD_ERROR;
+ CommandCost total_cost;
+ TileIndex tile = start_tile;
+ for (;;) {
+ if (!IsValidTile(tile) || !IsAirportTile(tile) || !MayHaveAirTracks(tile) || IsHangar(tile) || (IsApron(tile) && IsHeliport(tile)))
+ goto fill_next_track;
+
+ if (air_type != GetAirType(tile)) {
+ ret.MakeError(STR_ERROR_AIRPORT_INVALID_AIR_TYPE);
+ } else {
+ assert(IsValidTrackdir(trackdir));
+ if (add) {
+ ret = AddAirportTrack(tile, TrackdirToTrack(trackdir), flags);
+ } else {
+ ret = RemoveAirportTrack(tile, TrackdirToTrack(trackdir), flags);
+ }
+ }
+
+ if (ret.Failed()) {
+ last_error = ret;
+ if (last_error.GetErrorMessage() != STR_ERROR_ALREADY_BUILT) {
+ return last_error;
+ }
+
+ /* Ownership errors are more important. */
+ if (last_error.GetErrorMessage() == STR_ERROR_OWNED_BY) break;
+ } else {
+ had_success = true;
+ affected_stations.emplace_back(Station::GetByTile(tile));
+ total_cost.AddCost(ret);
+ }
+
+ fill_next_track:
+ if (tile == end_tile) break; // end tile
+
+ tile += ToTileIndexDiff(_trackdelta[trackdir]);
+
+ /* toggle railbit for the non-diagonal tracks. */
+ if (!IsDiagonalTrackdir(trackdir)) ToggleBit(trackdir, 0);
+ }
+
+ if (flags & DC_EXEC) {
+ /* Do all station specific functions here. */
+ for (Station *st : affected_stations) {
+ assert(st != nullptr);
+ st->airport.type = AT_CUSTOM;
+ st->UpdateAirportDataStructure();
+ UpdateAircraftOnUpdatedAirport(st);
+ }
+ }
+
+ if (had_success) return total_cost;
+
+ return last_error;
+}
+
+/**
+ * Define/undefine a runway.
+ * @param flags operation to perform
+ * @param start_tile start tile of drag
+ * @param end_tile end tile of drag
+ * @param air_type air type to deal with
+ * @param runway_type type of runway start (allow/don' allow landing)
+ * @return the cost of this operation or an error
+ */
+CommandCost AddRunway(DoCommandFlag flags, TileIndex start_tile, TileIndex end_tile, AirType air_type, AirportTileType runway_type)
+{
+ assert(IsValidTile(start_tile));
+ assert(IsAirportTile(start_tile));
+
+ if (!ValParamAirType(air_type)) return_cmd_error(STR_ERROR_AIRPORT_INCORRECT_AIRTYPE);
+
+ if (air_type != GetAirType(start_tile)) return_cmd_error(STR_ERROR_AIRPORT_INVALID_AIR_TYPE);
+
+ Station *st = Station::GetByTile(start_tile);
+ assert(st != nullptr);
+
+ if (st->airport.runways.size() + 1 > GetAirTypeInfo(air_type)->max_num_runways) return_cmd_error(STR_ERROR_AIRPORT_TOO_MUCH_RUNWAYS);
+
+ Town *pre_town = nullptr;
+ Town *post_town = nullptr;
+ uint pre_noise;
+ uint post_noise = GetAirTypeInfo(air_type)->runway_noise_level;
+ CommandCost ret = CheckTownAuthorityForAirports(start_tile, st, BitmapTileArea(), &pre_town, &post_town, pre_noise, post_noise);
+ if (ret.Failed()) return ret;
+
+ TileArea ta(start_tile, end_tile);
+ assert(ta.h == 1 || ta.w == 1); // Diagonal area. //
+ uint8_t length = ta.h * ta.w;
+ ret = CheckRunwayLength(air_type, length);
+ if (ret.Failed()) return ret;
+
+ DiagDirection dir = DiagdirBetweenTiles(start_tile, end_tile);
+ Direction adding_dir = DiagDirToDir(dir);
+
+ for (TileIndex tile_iter : ta) {
+ if (!IsAirportTile(tile_iter)) return_cmd_error(STR_ERROR_AIRPORT_RUNWAY_INCOMPLETE);
+ if (GetStationIndex(tile_iter) != st->index) return_cmd_error(STR_ERROR_AIRPORT_RUNWAY_INCOMPLETE);
+ if (!IsSimpleTrack(tile_iter) && !IsPlainRunway(tile_iter)) return_cmd_error(STR_ERROR_AIRPORT_RUNWAY_CAN_T_BUILD_OVER);
+ if (IsPlainRunway(tile_iter)) {
+ /* Cannot build an extreme runway tile on an already existing runway. */
+ if (tile_iter == start_tile || tile_iter == end_tile) return_cmd_error(STR_ERROR_AIRPORT_RUNWAY_OVERLAP);
+
+ /* Some directions are not compatible.
+ * There is no need to do this check.
+ * Anyway, it will be checked. */
+ Direction cur_dir = GetPlainRunwayDirections(tile_iter);
+ if (!IsDiagonalDirection(cur_dir) ||
+ (cur_dir != ((adding_dir + 2) % DIR_END) && (cur_dir + 2) % DIR_END != adding_dir))
+ return_cmd_error(STR_ERROR_AIRPORT_RUNWAY_CAN_T_BUILD_OVER);
+ }
+ }
+
+ CommandCost cost(EXPENSES_CONSTRUCTION);
+ cost.AddCost(_price[PR_BUILD_STATION_AIRPORT] * length * GetAirTypeInfo(air_type)->cost_multiplier);
+
+ if (flags & DC_EXEC) {
+ /* Always update the noise, so there will be no need to recalculate when option toggles. */
+ UpdateNoiseForTowns(pre_town, post_town, pre_noise, post_noise);
+
+ for (TileIndex tile_iter : ta) {
+ TrackBits prev_tracks = MayHaveAirTracks(tile_iter) ? GetAirportTileTracks(tile_iter) : TRACK_BIT_NONE;
+ TrackBits reserved_tracks = prev_tracks != TRACK_BIT_NONE ? GetReservedAirportTracks(tile_iter) : TRACK_BIT_NONE;
+ bool reserved_runway = IsRunway(tile_iter) && GetReservationAsRunway(tile_iter);
+ if ((tile_iter == start_tile) || (tile_iter == end_tile)) {
+ assert(!IsRunway(tile_iter));
+ Tile(tile_iter).m8() = 0;
+ AirportTileType att = start_tile == tile_iter ? runway_type : ATT_RUNWAY_END;
+ SetAirportTileType(tile_iter, att);
+ SetRunwayExtremeDirection(tile_iter, dir);
+ } else {
+ if (IsPlainRunway(tile_iter)) {
+ [[maybe_unused]] Direction cur_dir = GetPlainRunwayDirections(tile_iter);
+ assert(IsDiagonalDirection(cur_dir));
+ assert(cur_dir == ((adding_dir + 2) % DIR_END) || (cur_dir + 2) % DIR_END == adding_dir);
+ AddPlainRunwayDirections(tile_iter, dir, false);
+ } else {
+ SetAirportTileType(tile_iter, ATT_RUNWAY_MIDDLE);
+ Tile(tile_iter).m8() = 0;
+ AddPlainRunwayDirections(tile_iter, dir, true);
+ }
+ }
+ SetAirportTileTracks(tile_iter, prev_tracks);
+ SetAirportTracksReservation(tile_iter, reserved_tracks);
+ SetReservationAsRunway(tile_iter, reserved_runway);
+ }
+
+ st->airport.type = AT_CUSTOM;
+ st->UpdateAirportDataStructure();
+ UpdateAircraftOnUpdatedAirport(st);
+ }
+
+ return cost;
+}
+
+/**
+ * Get the tiles of a runway.
+ * @param tile An extreme tile of a runway.
+ * @return the tile area of the runway.
+ */
+TileArea GetRunwayTileArea(TileIndex tile)
+{
+ assert(IsRunwayExtreme(tile));
+ DiagDirection dir = GetRunwayExtremeDirection(tile);
+ return TileArea(GetRunwayExtreme(tile, dir), GetRunwayExtreme(tile, ReverseDiagDir(dir)));
+}
+
+/**
+ * Define/undefine a apron.
+ * @param flags operation to perform
+ * @param start_tile start tile of drag
+ * @param end_tile end tile of drag
+ * @return the cost of this operation or an error
+ */
+CommandCost RemoveRunway(DoCommandFlag flags, TileIndex start_tile, TileIndex end_tile)
+{
+ if (start_tile == end_tile || !IsRunwayExtreme(start_tile) || !IsRunwayExtreme(end_tile)) return_cmd_error(STR_ERROR_AIRPORT_RUNWAY_CAN_T_REMOVE);
+
+ assert(GetRunwayTileArea(start_tile).Contains(end_tile) && IsRunwayExtreme(start_tile) && IsRunwayExtreme(end_tile));
+
+ TileArea ta(start_tile, end_tile);
+ assert(ta.w == 1 || ta.h == 1);
+ const AirTypeInfo *ati = GetAirTypeInfo(GetAirType(start_tile));
+ CommandCost cost(EXPENSES_CONSTRUCTION);
+ cost.AddCost(_price[PR_CLEAR_STATION_AIRPORT] * ta.w * ta.h * ati->cost_multiplier);
+
+ Station *st = Station::GetByTile(start_tile);
+ assert(st != nullptr);
+
+ for (TileIndex tile_iter : ta) {
+ assert(IsRunway(tile_iter));
+ if (GetReservationAsRunway(tile_iter)) return CMD_ERROR;
+ }
+
+ if (flags & DC_EXEC) {
+ uint dist;
+ BitmapTileArea bta;
+ bta.Initialize(st->airport);
+ for (TileIndex t : st->airport) {
+ if (st->TileBelongsToAirport(t)) bta.SetTile(t);
+ }
+ Town *nearest = AirportGetNearestTown(bta, dist);
+ uint8_t noise_level = CalculateAirportNoiseLevel(st->airport);
+ nearest->noise_reached -= GetAirportNoiseLevelForDistance(noise_level, dist);
+
+ DiagDirection diag_dir = GetRunwayExtremeDirection(start_tile);
+ for (Tile tile_iter : ta) {
+ assert(IsRunway(tile_iter));
+ if (IsPlainRunway(tile_iter) && !IsDiagonalDirection(GetPlainRunwayDirections(tile_iter))) {
+ RemovePlainRunwayDirections(tile_iter, diag_dir);
+ } else {
+ SetAirportTileType(tile_iter, ATT_SIMPLE_TRACK);
+ SB(tile_iter.m8(), 12, 4, 0);
+ SetAirGfxType(tile_iter, true);
+ }
+ MarkTileDirtyByTile(tile_iter);
+ }
+
+ st->airport.type = AT_CUSTOM;
+ st->UpdateAirportDataStructure();
+
+ noise_level = CalculateAirportNoiseLevel(st->airport);
+ nearest->noise_reached += GetAirportNoiseLevelForDistance(noise_level, dist);
+ if (_settings_game.economy.station_noise_level) {
+ SetWindowDirty(WC_TOWN_VIEW, nearest->index);
+ }
+
+ UpdateAircraftOnUpdatedAirport(st);
+ }
+
+ return cost;
+}
+
+/**
+ * When modifying an airport tile, neighbour tiles may lose some tracks.
+ * Return true if any affected neighbour tile is reserved.
+ * @param tile Tile to check.
+ * @pre IsAirportTile
+ */
+bool HasReservationsOnNeighbourTiles(TileIndex tile)
+{
+ assert(IsAirportTile(tile));
+
+ StationID station_id = GetStationIndex(tile);
+
+ for (Direction dir = DIR_BEGIN; dir < DIR_END; dir++) {
+ TileIndex neighbour = TileAddByDir(tile, dir);
+ if (!IsValidTile(neighbour)) continue;
+ if (!IsAirportTileOfStation(neighbour, station_id)) continue;
+ if (!MayHaveAirTracks(neighbour)) continue;
+
+ TrackBits reserved = GetReservedAirportTracks(neighbour);
+ switch (dir) {
+ case DIR_N:
+ reserved &= TRACK_BIT_LOWER;
+ break;
+ case DIR_NE:
+ reserved &= (TRACK_BIT_LOWER | TRACK_BIT_LEFT);
+ break;
+ case DIR_E:
+ reserved &= TRACK_BIT_LEFT;
+ break;
+ case DIR_SE:
+ reserved &= (TRACK_BIT_LEFT | TRACK_BIT_UPPER);
+ break;
+ case DIR_S:
+ reserved &= TRACK_BIT_UPPER;
+ break;
+ case DIR_SW:
+ reserved &= (TRACK_BIT_UPPER | TRACK_BIT_RIGHT);
+ break;
+ case DIR_W:
+ reserved &= TRACK_BIT_RIGHT;
+ break;
+ case DIR_NW:
+ reserved &= (TRACK_BIT_RIGHT | TRACK_BIT_LOWER);
+ break;
+
+ default: NOT_REACHED();
+ }
+
+ if (reserved != TRACK_BIT_NONE) return true;
+ }
+
+ return false;
+}
+
+/**
+ * Change the airtype graphics of an airport tile.
+ * @param tile
+ * @param at New airtype tile.
+ */
+void SetGfxByAirtypeTile(TileIndex tile, AirportTiles at) {
+ DeleteAnimatedTile(tile);
+ SetAirGfxType(tile, true);
+ SetTileAirportGfx(tile, at);
+ if (AirportTileSpec::GetByTile(tile)->animation.status != ANIM_STATUS_NO_ANIMATION) AddAnimatedTile(tile);
+}
+
+/**
+ * Redefine use of airport tiles.
+ * @param flags operation to perform
+ * @param start_tile start tile of drag
+ * @param end_tile end tile of drag
+ * @param air_type air type to build or to remove
+ * @param air_tile_type (hangar, helipad...)
+ * @param infra_type infrastrucutre type (building 1, building 2...)
+ * @param direction heliport/infrastructure direction or hangar exit direction
+ * @param adding whether adding or removing tiles
+ * @param diagonal whether remove/add a diagonal tile area
+ * @return the cost of this operation or an error
+ */
+CommandCost CmdChangeAirportTiles(DoCommandFlag flags, TileIndex start_tile, TileIndex end_tile, AirType air_type, AirportTileType air_tile_type, AirportTiles infra_type, DiagDirection direction, bool adding, bool diagonal)
+{
+ if (!IsAirportTile(start_tile)) return CMD_ERROR;
+ bool adds_noise = false;
+ const AirTypeInfo *ati = GetAirTypeInfo(air_type);
+
+ switch (air_tile_type) {
+ case ATT_APRON_NORMAL:
+ case ATT_APRON_HELIPAD:
+ case ATT_APRON_HELIPORT:
+ adds_noise = adding;
+ break;
+ case ATT_HANGAR_STANDARD:
+ case ATT_HANGAR_EXTENDED:
+ if (adding && Company::IsValidHumanID(_current_company) &&
+ !HasBit(_settings_game.depot.hangar_types, air_tile_type == ATT_HANGAR_EXTENDED)) {
+ return_cmd_error(STR_ERROR_DEPOT_TYPE_NOT_AVAILABLE);
+ }
+ break;
+ case ATT_INFRASTRUCTURE_WITH_CATCH:
+ case ATT_INFRASTRUCTURE_NO_CATCH:
+ case ATT_RUNWAY_START_ALLOW_LANDING:
+ case ATT_RUNWAY_START_NO_LANDING:
+ break;
+ case ATT_SIMPLE_TRACK:
+ case ATT_RUNWAY_MIDDLE:
+ case ATT_RUNWAY_END:
+ case ATT_APRON_BUILTIN_HELIPORT:
+ default:
+ /* Not handled in this command. */
+ NOT_REACHED();
+ case ATT_WAITING_POINT:
+ /* Not implemented. */
+ NOT_REACHED();
+ }
+
+ CommandCost ret = CheckSettingBuildByTile();
+ if (ret.Failed()) return ret;
+
+ if (!ValParamAirType(air_type)) return_cmd_error(STR_ERROR_AIRPORT_INCORRECT_AIRTYPE);
+
+ TileArea ta(start_tile, end_tile);
+
+ if (air_tile_type == ATT_RUNWAY_START_ALLOW_LANDING || air_tile_type == ATT_RUNWAY_START_NO_LANDING) {
+ if (air_type != GetAirType(start_tile)) return_cmd_error(STR_ERROR_AIRPORT_INVALID_AIR_TYPE);
+ if (GetTileOwner(start_tile) != _current_company) return_cmd_error(STR_ERROR_AREA_IS_OWNED_BY_ANOTHER);
+
+ if (adding) {
+ /* check same station and free */
+ return AddRunway(flags, start_tile, end_tile, air_type, air_tile_type);
+ } else {
+ return RemoveRunway(flags, start_tile, end_tile);
+ }
+ }
+
+ CommandCost cost(EXPENSES_CONSTRUCTION);
+
+ std::unique_ptr iter;
+ if (diagonal) {
+ iter = std::make_unique(start_tile, end_tile);
+ } else {
+ iter = std::make_unique(start_tile, end_tile);
+ }
+
+ Station *st = nullptr;
+ Town *nearest = nullptr;
+ uint dist = 0;
+ uint noise_increase = 0;
+ for (; *iter != INVALID_TILE; ++(*iter)) {
+ Tile tile_iter = Tile(*iter);
+ if (!IsAirportTile(tile_iter)) return CMD_ERROR;
+ if (GetTileOwner(tile_iter) != _current_company) return CMD_ERROR;
+ if (air_type != GetAirType(tile_iter)) continue;
+
+ if (IsStandardHangarTile(tile_iter)) {
+ CommandCost ret = EnsureFreeHangar(tile_iter);
+ if (ret.Failed()) return ret;
+ }
+
+ if (st == nullptr) {
+ st = Station::GetByTile(tile_iter);
+
+ BitmapTileArea bta;
+ bta.Initialize(st->airport);
+ for (TileIndex t : st->airport) {
+ if (st->TileBelongsToAirport(t)) bta.SetTile(t);
+ }
+ nearest = AirportGetNearestTown(bta, dist);
+ if (flags & DC_EXEC) {
+ uint8_t noise_level = CalculateAirportNoiseLevel(st->airport);
+ nearest->noise_reached -= GetAirportNoiseLevelForDistance(noise_level, dist);
+ }
+ } else {
+ if (st->index != GetStationIndex(tile_iter)) return CMD_ERROR;
+ }
+
+ TrackBits tracks = MayHaveAirTracks(tile_iter) ? GetAirportTileTracks(tile_iter) : TRACK_BIT_NONE;
+ TrackBits reserved_tracks = tracks != TRACK_BIT_NONE ? GetReservedAirportTracks(tile_iter) : TRACK_BIT_NONE;
+ bool close_tracks = adding && HasReservationsOnNeighbourTiles(tile_iter);
+
+ if (adding) {
+ if (!IsSimpleTrack(tile_iter)) continue;
+ if (air_tile_type == ATT_APRON_HELIPORT &&
+ !AreHeliportsAvailable(air_type)) return_cmd_error(STR_ERROR_AIRPORT_CAN_T_BUILD_HELIPORT);
+ if (GetAirportTileType(tile_iter) == air_tile_type) continue;
+
+ cost.AddCost(_price[PR_BUILD_STATION_AIRPORT] * ati->cost_multiplier);
+ if (adds_noise && !IsApron(tile_iter)) {
+ /* Noise concerns... */
+ noise_increase++;
+ }
+
+ switch (air_tile_type) {
+ case ATT_INFRASTRUCTURE_WITH_CATCH:
+ case ATT_INFRASTRUCTURE_NO_CATCH:
+ case ATT_HANGAR_STANDARD:
+ case ATT_HANGAR_EXTENDED:
+ case ATT_APRON_HELIPORT:
+ if (close_tracks || reserved_tracks != TRACK_BIT_NONE) return_cmd_error(STR_ERROR_AIRPORT_PRESENT_AIRCRAFT);
+ break;
+ default:
+ break;
+ }
+
+ if (flags & DC_EXEC) {
+ switch (air_tile_type) {
+ default: NOT_REACHED();
+ case ATT_INFRASTRUCTURE_WITH_CATCH:
+ case ATT_INFRASTRUCTURE_NO_CATCH: {
+ SetAirportTileType(tile_iter, air_tile_type);
+ tile_iter.m8() = 0;
+ SetAirportTileRotation(tile_iter, direction);
+ break;
+ }
+ case ATT_APRON_NORMAL:
+ case ATT_APRON_HELIPAD:
+ case ATT_APRON_HELIPORT:
+ SetAirportTileType(tile_iter, air_tile_type);
+ SetAirportTileRotation(tile_iter, direction);
+ if (air_tile_type == ATT_APRON_HELIPORT) {
+ SetAirportTileTracks(tile_iter, TRACK_BIT_CROSS);
+ } else {
+ SetAirportTileTracks(tile_iter, tracks);
+ SetAirportTracksReservation(tile_iter, reserved_tracks);
+ }
+ break;
+ case ATT_HANGAR_STANDARD:
+ case ATT_HANGAR_EXTENDED:
+ SetAirportTileType(tile_iter, air_tile_type);
+ SetHangarDirection(tile_iter, direction);
+ SetAirportTileTracks(tile_iter, HasBit(tile_iter.m8(), 14) ? TRACK_BIT_Y : TRACK_BIT_X);
+ break;
+ }
+ SetGfxByAirtypeTile(tile_iter, infra_type);
+ MarkTileDirtyByTile(tile_iter);
+ }
+
+ } else {
+ /* Removing. */
+
+ if (GetAirportTileType(tile_iter) != air_tile_type) continue;
+ if (air_tile_type == ATT_HANGAR_STANDARD || air_tile_type == ATT_HANGAR_EXTENDED) {
+ if (!IsHangar(tile_iter) || GetAirportTileType(tile_iter) != air_tile_type) continue;
+ } else if (air_tile_type == ATT_INFRASTRUCTURE_WITH_CATCH || air_tile_type == ATT_INFRASTRUCTURE_NO_CATCH) {
+ if (!IsInfrastructure(tile_iter) || GetAirportTileType(tile_iter) != air_tile_type) continue;
+ }
+
+ if ((reserved_tracks & TRACK_BIT_CROSS) != TRACK_BIT_NONE) return_cmd_error(STR_ERROR_AIRPORT_PRESENT_AIRCRAFT);
+
+ cost.AddCost(_price[PR_CLEAR_STATION_AIRPORT] * ati->cost_multiplier);
+ if (flags & DC_EXEC) {
+ switch (air_tile_type) {
+ default: NOT_REACHED();
+ case ATT_INFRASTRUCTURE_WITH_CATCH:
+ case ATT_INFRASTRUCTURE_NO_CATCH:
+ case ATT_APRON_NORMAL:
+ case ATT_APRON_HELIPAD:
+ case ATT_APRON_HELIPORT:
+ case ATT_HANGAR_STANDARD:
+ case ATT_HANGAR_EXTENDED:
+ tile_iter.m8() = 0;
+ SetAirportTileType(tile_iter, ATT_SIMPLE_TRACK);
+ SetAirGfxType(tile_iter, true);
+ break;
+ }
+ SetAirportTileTracks(tile_iter, tracks);
+ SetAirportTracksReservation(tile_iter, reserved_tracks);
+
+ MarkTileDirtyByTile(tile_iter);
+ }
+ }
+ }
+
+ if (st == nullptr) return CMD_ERROR;
+
+ if (flags & DC_EXEC) {
+ st->airport.type = AT_CUSTOM;
+
+ st->UpdateAirportDataStructure();
+ st->AfterStationTileSetChange(true, STATION_AIRPORT);
+
+ uint8_t noise_level = CalculateAirportNoiseLevel(st->airport);
+ assert(nearest != nullptr);
+ nearest->noise_reached += GetAirportNoiseLevelForDistance(noise_level, dist);
+ if (_settings_game.economy.station_noise_level) {
+ SetWindowDirty(WC_TOWN_VIEW, nearest->index);
+ }
+
+ UpdateAircraftOnUpdatedAirport(st);
+
+ } else if (noise_increase != 0) {
+ /* Check noise limits. */
+ Town *pre_town = nullptr;
+ Town *post_town = nullptr;
+ uint pre_noise;
+ uint post_noise = noise_increase;
+ ret = CheckTownAuthorityForAirports(start_tile, st, BitmapTileArea(), &pre_town, &post_town, pre_noise, post_noise);
+ assert(pre_town == nearest);
+ assert(post_town == nearest);
+ if (ret.Failed()) return ret;
+ }
+
+ return cost;
+}
+
+
+/**
+ * Change the air type of an airport.
+ * @param flags operation to perform
+ * @param tile with one tile of the airport
+ * @param air_type new air type for the airport
+ */
+CommandCost CmdChangeAirType(DoCommandFlag flags, TileIndex tile, AirType air_type)
+{
+ if (!IsAirportTile(tile)) return_cmd_error(STR_ERROR_SITE_UNSUITABLE);
+ if (!IsValidTile(tile)) return CMD_ERROR;
+
+ CommandCost ret = CheckSettingBuildByTile();
+ if (ret.Failed()) return ret;
+
+ ret = CheckTileOwnership(tile);
+ if (ret.Failed()) return ret;
+
+ assert(Station::IsValidID(GetStationIndex(tile)));
+ Station *st = Station::GetByTile(tile);
+ assert(st != nullptr);
+
+ /* Check air type. */
+ if (!ValParamAirType(air_type)) return_cmd_error(STR_ERROR_AIRPORT_INCORRECT_AIRTYPE);
+ if (st->airport.air_type == air_type) return_cmd_error(STR_ERROR_AIRPORT_ALREADY_AIRTYPE);
+
+ const AirTypeInfo *orig_ati = GetAirTypeInfo(st->airport.air_type);
+ const AirTypeInfo *new_ati = GetAirTypeInfo(air_type);
+ if (orig_ati->build_on_water != new_ati->build_on_water) return_cmd_error(STR_ERROR_AIRPORT_CAN_T_CONVERT_WATER);
+ if (st->airport.runways.size() > GetAirTypeInfo(air_type)->max_num_runways) return_cmd_error(STR_ERROR_AIRPORT_TOO_MUCH_RUNWAYS);
+
+ if (!AreHeliportsAvailable(air_type) && !st->airport.heliports.empty()) {
+ return_cmd_error(STR_ERROR_AIRPORT_CAN_T_CONVERT_HELIPORT);
+ }
+
+ for (TileIndex t : st->airport.runways) {
+ uint length = GetRunwayLength(t);
+ ret = CheckRunwayLength(air_type, length);
+ if (ret.Failed()) return ret;
+ }
+
+ ret = AircraftInAirport(st);
+ if (ret.Failed()) return ret;
+
+ Town *pre_town = nullptr;
+ Town *post_town = nullptr;
+ uint pre_noise;
+ uint post_noise = CalculateAirportNoiseLevel(st->airport, air_type) - CalculateAirportNoiseLevel(st->airport);
+ if (st != nullptr && (st->facilities & FACIL_AIRPORT) != FACIL_NONE) post_noise -= GetAirTypeInfo(st->airport.air_type)->base_noise_level;
+
+ ret = CheckTownAuthorityForAirports(tile, st, BitmapTileArea(), &pre_town, &post_town, pre_noise, post_noise);
+ if (ret.Failed()) return ret;
+ assert(pre_town == post_town);
+
+ uint tiles = 0;
+ for (TileIndex tile_iter : st->airport) {
+ if (!IsAirportTileOfStation(tile_iter, st->index)) continue;
+
+ tiles++;
+ if (flags & DC_EXEC) {
+ SetAirType(tile_iter, air_type);
+ MarkTileDirtyByTile(tile_iter);
+
+ if (!HasAirtypeGfx(tile_iter)) {
+ bool is_infrastructure = IsInfrastructure(tile_iter);
+ AirportTiles at = is_infrastructure ? GetAirportGfxForAirtype(tile_iter) : ATTG_DEFAULT_GFX;
+ SetGfxByAirtypeTile(tile_iter, at);
+ if (is_infrastructure) SetAirportGfxForAirtype(tile_iter, ATTG_DEFAULT_GFX);
+ }
+ }
+ }
+
+ CommandCost cost(EXPENSES_CONSTRUCTION);
+ cost.AddCost((_price[PR_CLEAR_STATION_AIRPORT] * GetAirTypeInfo(st->airport.air_type)->cost_multiplier +
+ _price[PR_BUILD_STATION_AIRPORT] * GetAirTypeInfo(air_type)->cost_multiplier) * tiles);
+
+ if (flags & DC_EXEC) {
+ Company *c = Company::Get(st->owner);
+ c->infrastructure.air[air_type] += tiles;
+ c->infrastructure.air[st->airport.air_type] -= tiles;
+
+ st->airport.air_type = air_type;
+ st->UpdateAirportDataStructure();
+ st->AfterStationTileSetChange(true, STATION_AIRPORT);
+ if (st->airport.HasHangar()) InvalidateWindowData(WC_BUILD_VEHICLE, st->airport.hangar->index);
+
+ /* Always update the noise, so there will be no need to recalculate when option toggles. */
+ UpdateNoiseForTowns(pre_town, post_town, pre_noise, post_noise);
+
+ UpdateAircraftOnUpdatedAirport(st);
+
+ if (_settings_game.economy.station_noise_level) {
+ SetWindowDirty(WC_TOWN_VIEW, st->town->index);
+ }
+ }
+
+ return cost;
+}
+
+/**
+ * Try to set the best possible graphics for an airport track tile.
+ * @param tile The tile to set the specific graphics.
+ * @pre IsAirportTile(tile)
+ * @pre IsSimpleTrack(tile)
+ * @pre CheckTileOwnerShip(tile).Succeeded()
+ */
+void SetAdequateGfxForAirportTrack(TileIndex tile)
+{
+ assert(IsAirportTile(tile));
+ assert(IsSimpleTrack(tile));
+ assert(HasAirtypeGfx(tile));
+ assert(CheckTileOwnership(tile).Succeeded());
+
+ TrackBits tracks = GetAirportTileTracks(tile);
+ uint8_t sides = 0; // The four lower bits correspond to each diagdir. The corresponding bit is set if tracks
+ // from the tile and the neighbour tile are connected at the corresponding edge.
+ StationID st_id = GetStationIndex(tile);
+ CFollowTrackAirport fs(INVALID_COMPANY);
+
+ const TrackBits three_ways[] = {TRACK_BIT_3WAY_NE, TRACK_BIT_3WAY_SE, TRACK_BIT_3WAY_SW, TRACK_BIT_3WAY_NW};
+ for (DiagDirection dir = DIAGDIR_BEGIN; dir < DIAGDIR_END; dir++) {
+ if ((tracks & three_ways[dir]) == TRACK_BIT_NONE) continue;
+ Trackdir trackdir = DiagDirToDiagTrackdir(dir);
+ if (!fs.Follow(tile, trackdir)) continue;
+ SetBit(sides, dir);
+ }
+
+ switch (CountBits(sides)) {
+ case 0:
+ case 1:
+ SetTileAirportGfx(tile, (AirportTiles)(0));
+ break;
+ case 2: {
+ Track track = INVALID_TRACK;
+ switch (sides) {
+ case 3:
+ track = TRACK_RIGHT;
+ break;
+ case 12:
+ track = TRACK_LEFT;
+ break;
+ case 9:
+ track = TRACK_UPPER;
+ break;
+ case 6:
+ track = TRACK_LOWER;
+ break;
+ case 5:
+ track = TRACK_X;
+ break;
+ case 10:
+ track = TRACK_Y;
+ break;
+ default:
+ NOT_REACHED();
+ }
+ switch (track) {
+ case TRACK_X:
+ case TRACK_Y:
+ SetTileAirportGfx(tile, (AirportTiles)(6 + track));
+ break;
+ case TRACK_UPPER:
+ case TRACK_LOWER:
+ case TRACK_LEFT:
+ case TRACK_RIGHT: {
+ uint8_t orthogonal = 0; // Whether the tile looks a vertical or horizontal path.
+ for (; sides != 0;) {
+ DiagDirection dir = (DiagDirection)FindFirstBit(sides);
+ ClrBit(sides, dir);
+ TileIndex neighbour = TileAddByDiagDir(tile, dir);
+ if (!IsValidTile(neighbour)) continue;
+ if (!IsAirportTileOfStation(neighbour, st_id)) continue;
+ if (!MayHaveAirTracks(neighbour)) continue;
+ if (!HasBit(GetAirportTileTracks(neighbour), TrackToOppositeTrack(track))) continue;
+ orthogonal++;
+ }
+ orthogonal = orthogonal == 2 ? 8 : 0;
+ SetTileAirportGfx(tile, (AirportTiles)(track + 7 + orthogonal));
+ break;
+ }
+ default:
+ NOT_REACHED();
+ }
+ break;
+ }
+ case 3:
+ switch (sides) {
+ default:
+ NOT_REACHED();
+ case 7:
+ SetTileAirportGfx(tile, (AirportTiles)(13));
+ break;
+ case 11:
+ SetTileAirportGfx(tile, (AirportTiles)(16));
+ break;
+ case 13:
+ SetTileAirportGfx(tile, (AirportTiles)(15));
+ break;
+ case 14:
+ SetTileAirportGfx(tile, (AirportTiles)(14));
+ break;
+ }
+ break;
+ case 4:
+ SetTileAirportGfx(tile, (AirportTiles)(8));
+ break;
+ }
+
+ MarkTileDirtyByTile(tile);
+}
+
+/**
+ * Change the
+ */
+CommandCost CmdAirportChangeTrackGFX(DoCommandFlag flags, TileIndex start_tile, TileIndex end_tile, AirType air_type, uint8_t gfx_index, bool diagonal)
+{
+ CommandCost ret = CheckSettingBuildByTile();
+ if (ret.Failed()) return ret;
+
+ /* Check air type. */
+ if (!ValParamAirType(air_type)) return_cmd_error(STR_ERROR_AIRPORT_INCORRECT_AIRTYPE);
+ const AirTypeInfo *ati = GetAirTypeInfo(air_type);
+ assert(!ati->build_on_water);
+
+ std::unique_ptr iter;
+ if (diagonal) {
+ iter = std::make_unique(start_tile, end_tile);
+ } else {
+ iter = std::make_unique(start_tile, end_tile);
+ }
+
+ for (; *iter != INVALID_TILE; ++(*iter)) {
+ Tile tile = Tile(*iter);
+ if (!IsAirportTile(tile)) continue;
+ if (air_type != GetAirType(tile)) return_cmd_error(STR_ERROR_AIRPORT_INCORRECT_AIRTYPE);
+
+ if (CheckTileOwnership(tile).Failed()) {
+ /* We don't own it!. */
+ return_cmd_error(STR_ERROR_OWNED_BY);
+ }
+
+ if (!HasAirtypeGfx(tile)) continue;
+ if (!IsSimpleTrack(tile)) continue;
+ if (flags & DC_EXEC) {
+ if (gfx_index == 0) {
+ SetAdequateGfxForAirportTrack(tile);
+ } else {
+ SetTileAirportGfx(tile, (AirportTiles)(gfx_index - 1));
+ MarkTileDirtyByTile(tile);
+ }
+ }
+ }
+
+ return CommandCost();
+}
+
+CommandCost CmdAirportToggleGround(DoCommandFlag flags, TileIndex start_tile, TileIndex end_tile, AirType air_type, bool diagonal)
+{
+ CommandCost ret = CheckSettingBuildByTile();
+ if (ret.Failed()) return ret;
+
+ /* Check air type. */
+ if (!ValParamAirType(air_type)) return_cmd_error(STR_ERROR_AIRPORT_INCORRECT_AIRTYPE);
+ const AirTypeInfo *ati = GetAirTypeInfo(air_type);
+ assert(!ati->build_on_water);
+
+ std::unique_ptr iter;
+ if (diagonal) {
+ iter = std::make_unique(start_tile, end_tile);
+ } else {
+ iter = std::make_unique(start_tile, end_tile);
+ }
+
+ for (; *iter != INVALID_TILE; ++(*iter)) {
+ Tile tile = Tile(*iter);
+ if (!IsAirportTile(tile)) continue;
+ if (air_type != GetAirType(tile)) return_cmd_error(STR_ERROR_AIRPORT_INCORRECT_AIRTYPE);
+
+ if (CheckTileOwnership(tile).Failed()) {
+ /* We don't own it!. */
+ return_cmd_error(STR_ERROR_OWNED_BY);
+ }
+
+ if (flags & DC_EXEC) {
+ if (!HasAirtypeGfx(tile)) continue;
+ SetAirportGroundAndDensity(
+ tile,
+ GetAirportGround(tile) == AG_AIRTYPE ? AG_GRASS : AG_AIRTYPE,
+ 0);
+ MarkTileDirtyByTile(tile);
+ }
+ }
+
+ return CommandCost();
+}
+
+CommandCost RemoveAirportTiles(DoCommandFlag flags, TileIndex start_tile, TileIndex end_tile, AirType air_type)
+{
+ if (!IsValidTile(end_tile)) return CMD_ERROR;
+
+ if (_current_company < MAX_COMPANIES && !ValParamAirType(air_type)) return_cmd_error(STR_ERROR_AIRPORT_INCORRECT_AIRTYPE);
+
+ TileArea ta(start_tile, end_tile);
+ std::vector affected_stations;
+
+ CommandCost cost(EXPENSES_CONSTRUCTION);
+ bool any_change = false;
+
+ for (TileIndex tile : ta) {
+ if (!IsAirportTile(tile)) continue;
+ if (air_type != GetAirType(tile)) continue;
+ if (MayHaveAirTracks(tile) && (HasAirportTrackReserved(tile) || HasReservationsOnNeighbourTiles(tile))) continue;
+
+ if (_current_company != OWNER_WATER) {
+ CommandCost ret = CheckTileOwnership(tile);
+ if (ret.Failed()) continue;
+ }
+
+ if (IsStandardHangarTile(tile)) {
+ CommandCost ret = EnsureFreeHangar(tile);
+ if (ret.Failed()) continue;
+ }
+
+ /* Check aircraft in airport. */
+ Station *st = Station::GetByTile(tile);
+ if (std::find(affected_stations.begin(), affected_stations.end(), st) == affected_stations.end()) {
+ assert(st != nullptr);
+
+ if (flags & DC_EXEC) {
+ /* Remove airport noise in closest town. */
+ uint dist;
+ BitmapTileArea bta;
+ bta.Initialize(st->airport);
+ for (TileIndex t : st->airport) {
+ if (st->TileBelongsToAirport(t)) bta.SetTile(t);
+ }
+ Town *nearest = AirportGetNearestTown(bta, dist);
+ uint8_t noise_level = CalculateAirportNoiseLevel(st->airport);
+ nearest->noise_reached -= GetAirportNoiseLevelForDistance(noise_level, dist);
+
+ affected_stations.emplace_back(st);
+ }
+ }
+
+ if (IsRunway(tile)) {
+ if (_current_company < MAX_COMPANIES && (flags & DC_FORCE_CLEAR_TILE) == 0) return_cmd_error(STR_ERROR_AIRPORT_REMOVE_RUNWAYS_FIRST);
+ if (GetReservationAsRunway(tile)) {
+ // in fact, if water flooding, check for landing aircraft and crash it
+ return CMD_ERROR;
+ }
+ }
+
+ cost.AddCost(AirClearCost(air_type));
+ any_change = true;
+
+ if (!IsSimpleTrack(tile)) cost.AddCost(AirClearCost(air_type));
+
+ if (flags & DC_EXEC) {
+ WaterClass wc = HasTileWaterClass(tile) ? GetWaterClass(tile) : WATER_CLASS_INVALID;
+ DoClearSquare(tile);
+ /* Maybe change to water */
+ if (wc != WATER_CLASS_INVALID) {
+ Owner o = (wc == WATER_CLASS_CANAL) ? st->owner : OWNER_WATER;
+ MakeWater(tile, o, wc, Random());
+ }
+
+ Company *c = Company::Get(st->owner);
+ c->infrastructure.air[air_type]--;
+ c->infrastructure.station--;
+ DeleteNewGRFInspectWindow(GSF_AIRPORTTILES, tile.base());
+ DeleteNewGRFInspectWindow(GSF_AIRTYPES, tile.base());
+
+ st->rect.AfterRemoveTile(st, tile);
+ MarkTileDirtyByTile(tile);
+ }
+ }
+
+ if (flags & DC_EXEC) {
+ /* Do all station specific functions here. */
+ for (Station *st : affected_stations) {
+ TileArea temp(ta.tile, ta.w, ta.h);
+ st->airport.Clear();
+ for (TileIndex tile : temp) {
+ if (IsAirportTile(tile) && st->index == GetStationIndex(tile)) st->airport.Add(tile);
+ }
+
+ st->airport.type = AT_CUSTOM;
+ st->UpdateAirportDataStructure();
+ if (st->airport.tile == INVALID_TILE) {
+ st->facilities &= ~FACIL_AIRPORT;
+ DeleteNewGRFInspectWindow(GSF_AIRPORTS, st->index);
+ } else {
+ uint dist;
+ BitmapTileArea bta;
+ bta.Initialize(st->airport);
+ for (TileIndex t : st->airport) {
+ if (st->TileBelongsToAirport(t)) bta.SetTile(t);
+ }
+ Town *nearest = AirportGetNearestTown(bta, dist);
+ uint8_t noise_level = CalculateAirportNoiseLevel(st->airport);
+ nearest->noise_reached += GetAirportNoiseLevelForDistance(noise_level, dist);
+ }
+ st->AfterStationTileSetChange(true, STATION_AIRPORT);
+ st->MarkTilesDirty(false);
+ UpdateAircraftOnUpdatedAirport(st);
+ }
+
+ }
+
+ if (!any_change) return CMD_ERROR;
+
+ return cost;
+}
+
+/**
+ * Clear an airport tile, removing also its runways if any.
+ * @param tile The tile to be cleared.
+ * @param flags Command flags
+ * @return The cost in case of success, or an error code if it failed.
+ */
+CommandCost ClearAirportTile(TileIndex tile, DoCommandFlag flags)
+{
+ assert(IsAirportTile(tile));
+ CommandCost cost(EXPENSES_CONSTRUCTION);
+
+ if (IsRunway(tile)) {
+ TileIndex start_tile = tile;
+ TileIndex end_tile = tile;
+ DiagDirection dir = INVALID_DIAGDIR;
+
+ if (IsPlainRunway(tile)) {
+ Direction direction = GetPlainRunwayDirections(tile);
+ if (!IsDiagonalDirection(direction)) {
+ dir = DirToDiagDir((Direction)((direction - 1) & (DIR_END - 1)));
+ end_tile = GetRunwayExtreme(tile, dir);
+ start_tile = GetRunwayExtreme(tile, ReverseDiagDir(dir));
+ cost.AddCost(RemoveRunway(flags, start_tile, end_tile));
+ direction++;
+ }
+ assert(IsDiagonalDirection(direction));
+ dir = DirToDiagDir(direction);
+ } else {
+ assert(IsRunwayExtreme(tile));
+ dir = GetRunwayExtremeDirection(tile);
+ }
+ end_tile = GetRunwayExtreme(tile, dir);
+ start_tile = GetRunwayExtreme(tile, ReverseDiagDir(dir));
+ cost.AddCost(RemoveRunway(flags, start_tile, end_tile));
+ }
+
+ cost.AddCost(RemoveAirportTiles(flags | DC_FORCE_CLEAR_TILE, tile, tile, GetAirType(tile)));
+ return cost;
+}
+
+TileIndex GetAnAirportTile(Station* st)
+{
+ if (st == nullptr || st->airport.tile == INVALID_TILE) return INVALID_TILE;
+
+ for (TileIndex tile : st->airport) {
+ if (st->TileBelongsToAirport(tile)) return tile;
+ }
+
+ NOT_REACHED();
+}
+
+/**
+ * Checks if an airport can be built at the given area.
+ * @param tile_area Area to check.
+ * @param flags Operation to perform.
+ * @param station StationID to be queried and returned if available.
+ * @param allow_extending Whether to allow already airport tiles in the area.
+ * @return The cost in case of success, or an error code if it failed.
+ */
+static CommandCost CheckFlatLandAirport(BitmapTileArea tile_area, DoCommandFlag flags, StationID &station, bool build_on_water, bool allow_extending)
+{
+ CommandCost cost(EXPENSES_CONSTRUCTION);
+ int allowed_z = -1;
+
+ /* Distant join */
+ Station *st = Station::GetIfValid(station);
+
+ if (st != nullptr && (st->facilities & FACIL_AIRPORT) != FACIL_NONE) {
+ TileIndex tile = GetAnAirportTile(st);
+ auto [tileh, z] = GetTileSlopeZ(tile);
+ allowed_z = z + GetSlopeMaxZ(tileh);
+ }
+
+ for (TileIndex tile : tile_area) {
+ if (!tile_area.HasTile(tile)) continue;
+
+ /* if station is set, then we have special handling to allow building on top of already existing stations.
+ * so station points to INVALID_STATION if we can build on any station.
+ * Or it points to a station if we're only allowed to build on exactly that station. */
+ if (allow_extending && IsAirportTile(tile)) {
+ StationID st_id = GetStationIndex(tile);
+ if (station == INVALID_STATION) {
+ station = st_id;
+ } else if (station != st_id) {
+ return_cmd_error(STR_ERROR_ADJOINS_MORE_THAN_ONE_EXISTING);
+ }
+ continue;
+ }
+
+ CommandCost ret = CheckBuildableTile(tile, 0, allowed_z, false, true);
+ if (ret.Failed()) return ret;
+
+ if (build_on_water) {
+ if (!IsWaterTile(tile) || !IsTileFlat(tile)) return_cmd_error(STR_ERROR_AIRPORT_PLAIN_WATER);
+ } else {
+ if (IsTileType(tile, MP_WATER)) {
+ Slope tileh = GetTileSlope(tile);
+ if (!IsSlopeWithOneCornerRaised(tileh)) return_cmd_error(STR_ERROR_CAN_T_BUILD_ON_WATER);
+ cost.AddCost(-_price[PR_CLEAR_WATER]);
+ cost.AddCost(_price[PR_CLEAR_ROUGH]);
+ }
+ }
+ if (!build_on_water) cost.AddCost(Command::Do(flags, tile));
+ if (cost.Failed()) return cost;
+ }
+
+ return cost;
+}
+
+/**
+ * Add/remove tiles for an airport.
+ * @param flags operation to perform
+ * @param start_tile start tile of drag
+ * @param end_tile end tile of drag
+ * @param adding whether to add or remove tiles
+ * @param at air type
+ * @param station_to_join StationID where to apply the command (NEW_STATION for a new station).
+ * @param adjacent allow airports directly adjacent to other airports.
+ * @return the cost of this operation or an error
+ */
+CommandCost CmdAddRemoveAirportTiles(DoCommandFlag flags, TileIndex start_tile, TileIndex end_tile, bool adding, AirType at, StationID station_to_join, bool adjacent)
+{
+ CommandCost ret = CheckSettingBuildByTile();
+ if (ret.Failed()) return ret;
+
+ if (!adding) return RemoveAirportTiles(flags, start_tile, end_tile, at);
+
+ /* Does the authority allow this? */
+ ret = CheckIfAuthorityAllowsNewStation(start_tile, flags);
+ if (ret.Failed()) return ret;
+
+ /* Check air type. */
+ if (!ValParamAirType(at)) return_cmd_error(STR_ERROR_AIRPORT_INCORRECT_AIRTYPE);
+ const AirTypeInfo *ati = GetAirTypeInfo(at);
+
+ TileArea new_location(start_tile, end_tile);
+ BitmapTileArea bta;
+ bta.Initialize(new_location);
+ for (TileIndex t : new_location) bta.SetTile(t);
+
+ if (new_location.w > _settings_game.station.station_spread || new_location.h > _settings_game.station.station_spread) return CMD_ERROR;
+
+ bool reuse = (station_to_join != NEW_STATION);
+ if (!reuse) station_to_join = INVALID_STATION;
+ bool distant_join = (station_to_join != INVALID_STATION);
+
+ if (distant_join && (!_settings_game.station.distant_join_stations || !Station::IsValidID(station_to_join))) return CMD_ERROR;
+
+ Station *st = nullptr;
+ if (Station::IsValidID(station_to_join)) {
+ st = Station::Get(station_to_join);
+ if ((st->facilities & FACIL_AIRPORT) && st->airport.air_type != at)
+ return_cmd_error(STR_ERROR_AIRPORT_INCORRECT_AIRTYPE);
+ }
+
+ /* Make sure the area below consists of clear tiles. */
+ StationID est = st == nullptr ? INVALID_STATION : st->index;
+ /* Clear the land below the station. */
+ CommandCost cost = CheckFlatLandAirport(bta, flags, est, ati->build_on_water, true);
+ if (cost.Failed()) return cost;
+
+ st = nullptr;
+ ret = FindJoiningAirport(est, station_to_join, adjacent, new_location, &st);
+ if (ret.Failed()) return ret;
+
+ if (st != nullptr && st->airport.tile != INVALID_TILE && st->airport.air_type != at) return_cmd_error(STR_ERROR_AIRPORT_INCORRECT_AIRTYPE);
+
+ Town *pre_town = nullptr;
+ Town *post_town = nullptr;
+ uint pre_noise;
+ uint post_noise = (st == nullptr || (st->facilities & FACIL_AIRPORT) == FACIL_NONE) ? GetAirTypeInfo(at)->base_noise_level : 0;
+ ret = CheckTownAuthorityForAirports(new_location.tile, st, bta, &pre_town, &post_town, pre_noise, post_noise);
+ if (ret.Failed()) return ret;
+
+ ret = BuildStationPart(&st, flags, reuse, new_location, STATIONNAMING_AIRPORT);
+ if (ret.Failed()) return ret;
+
+ TileIndex airport_tile = GetAnAirportTile(st);
+ int z = -1;
+ if (airport_tile != INVALID_TILE) {
+ auto [tileh, z] = GetTileSlopeZ(airport_tile);
+ z = z + GetSlopeMaxZ(tileh);
+ }
+
+ Company *c = Company::GetIfValid(_current_company);
+
+ bool tile_changed = false;
+ for (TileIndex tile : new_location) {
+ if (IsAirportTile(tile)) {
+ assert(GetStationIndex(tile) == st->index);
+ continue;
+ }
+ ret = CheckBuildableTile(tile, 0, z, false, true);
+ if (ret.Failed()) return_cmd_error(STR_ERROR_AIRPORT_NOT_SAME_LEVEL);
+
+ cost.AddCost(_price[PR_BUILD_STATION_AIRPORT] * ati->cost_multiplier);
+ tile_changed = true;
+
+ if (flags & DC_EXEC) {
+ st->airport.Add(tile);
+
+ /* Initialize an empty station. */
+ st->AddFacility(FACIL_AIRPORT, tile);
+
+ st->rect.BeforeAddTile(tile, StationRect::ADD_TRY);
+ WaterClass wc = WATER_CLASS_INVALID;
+ if (ati->build_on_water && HasTileWaterClass(tile)) wc = GetWaterClass(tile);
+
+ MakeAirport(tile, st->owner, st->index, 0, wc);
+ SetAirType(tile, at);
+ SetAirportTileType(tile, ATT_SIMPLE_TRACK);
+ SetAirGfxType(tile, true);
+ SetAirportGroundAndDensity(tile, AG_AIRTYPE, 0);
+
+ c->infrastructure.air[at]++;
+ c->infrastructure.station++;
+ DirtyCompanyInfrastructureWindows(c->index);
+ MarkTileDirtyByTile(tile);
+ }
+ }
+
+ if (!tile_changed) return CMD_ERROR;
+
+ if (flags & DC_EXEC) {
+ assert(st != nullptr);
+ st->UpdateAirportDataStructure();
+ st->AfterStationTileSetChange(true, STATION_AIRPORT);
+ st->airport.type = AT_CUSTOM;
+
+ UpdateNoiseForTowns(pre_town, post_town, pre_noise, post_noise);
+ UpdateAircraftOnUpdatedAirport(st);
+ }
+
+ return cost;
+}
+
+/**
+ * Place an Airport.
+ * @param flags operation to perform
+ * @param tile tile where airport will be built
+ * @param airport_type airport type, @see airport.h
+ * @param layout airport layout
+ * @param air_type airport airtype (if modify airports is enabled)
+ * @param rotation rotation for the airport to be built
+ * @param station_to_join station ID to join (NEW_STATION if build new one)
+ * @param allow_adjacent allow airports directly adjacent to other airports.
+ * @return the cost of this operation or an error
+ */
+CommandCost CmdBuildAirport(DoCommandFlag flags, TileIndex tile, uint8_t airport_type, uint8_t layout, AirType air_type, DiagDirection rotation, StationID station_to_join, bool allow_adjacent)
+{
+ bool reuse = (station_to_join != NEW_STATION);
+ if (!reuse) station_to_join = INVALID_STATION;
+ bool distant_join = (station_to_join != INVALID_STATION);
+
+ if (distant_join && (!_settings_game.station.distant_join_stations || !Station::IsValidID(station_to_join))) return CMD_ERROR;
+
+ CommandCost ret = CheckIfAuthorityAllowsNewStation(tile, flags);
+ if (ret.Failed()) return ret;
+
+ /* Check if a valid, buildable airport was chosen for construction */
+ const AirportSpec *as = AirportSpec::Get(airport_type);
+ if (air_type == INVALID_AIRTYPE) air_type = as->airtype;
+
+ if (_settings_game.station.allow_modify_airports &&
+ as->min_runway_length > 0 &&
+ as->min_runway_length < GetAirTypeInfo(air_type)->min_runway_length)
+ return_cmd_error(STR_ERROR_AIRPORT_RUNWAY_TOO_SHORT);
+
+ if (!ValParamAirType(air_type)) return_cmd_error(STR_ERROR_AIRPORT_INCORRECT_AIRTYPE);
+ const AirTypeInfo *ati = GetAirTypeInfo(air_type);
+
+ if (as->has_hangar && !Depot::CanAllocateItem()) return CMD_ERROR;
+ if (!as->IsAvailable()) return CMD_ERROR;
+
+ uint16_t w = as->layouts[layout].size_x;
+ uint16_t h = as->layouts[layout].size_y;
+ if (rotation % 2 != 0) Swap(w, h);
+
+ TileArea airport_area = TileArea(tile, w, h);
+
+ /* Check we are within map bounds. */
+ if (TileX(tile) + w >= Map::SizeX() || TileY(tile) + h >= Map::SizeY()) return CMD_ERROR;
+
+ if (w > _settings_game.station.station_spread || h > _settings_game.station.station_spread) {
+ return_cmd_error(STR_ERROR_STATION_TOO_SPREAD_OUT);
+ }
+
+ BitmapTileArea new_airport_tiles; // New airport tiles to be built.
+ new_airport_tiles.Initialize(airport_area);
+ CommandCost cost = AddAirportTileTableToBitmapTileArea(as->layouts[layout], &new_airport_tiles, rotation, ati->cost_multiplier);
+
+ Station *st = nullptr;
+ ret = FindJoiningAirport(INVALID_STATION, station_to_join, allow_adjacent, airport_area, &st);
+ if (ret.Failed()) return ret;
+
+ /* Distant join */
+ if (st == nullptr && distant_join) st = Station::GetIfValid(station_to_join);
+
+ cost.AddCost(CheckFlatLandAirport(new_airport_tiles, flags, station_to_join, ati->build_on_water, false));
+ if (cost.Failed()) return cost;
+
+ Town *pre_town = nullptr;
+ Town *post_town = nullptr;
+ uint pre_noise;
+ uint post_noise = as->GetAirportNoise(air_type);
+ if (st != nullptr && (st->facilities & FACIL_AIRPORT) != FACIL_NONE) post_noise -= GetAirTypeInfo(st->airport.air_type)->base_noise_level;
+
+ ret = CheckTownAuthorityForAirports(tile, st, new_airport_tiles, &pre_town, &post_town, pre_noise, post_noise);
+ if (ret.Failed()) return ret;
+
+ if (st != nullptr && (st->facilities & FACIL_AIRPORT) && !_settings_game.station.allow_modify_airports) {
+ return_cmd_error(STR_ERROR_TOO_CLOSE_TO_ANOTHER_AIRPORT);
+ }
+
+ uint total_runways = as->num_runways;
+ if (st != nullptr && (st->facilities & FACIL_AIRPORT)) total_runways += (uint)st->airport.runways.size();
+ if (total_runways > GetAirTypeInfo(air_type)->max_num_runways) return_cmd_error(STR_ERROR_AIRPORT_TOO_MUCH_RUNWAYS);
+
+ bool custom_airport = (st != nullptr && (st->facilities & FACIL_AIRPORT) != FACIL_NONE);
+
+ ret = BuildStationPart(&st, flags, reuse, airport_area,
+ as->has_heliport ? STATIONNAMING_HELIPORT : STATIONNAMING_AIRPORT);
+ if (ret.Failed()) return ret;
+
+ if (flags & DC_EXEC) {
+ assert(st != nullptr);
+
+ /* Always update the noise, so there will be no need to recalculate when option toggles. */
+ UpdateNoiseForTowns(pre_town, post_town, pre_noise, post_noise);
+
+ st->AddFacility(FACIL_AIRPORT, tile);
+ st->airport.type = airport_type;
+ st->airport.layout = layout;
+
+ st->rect.BeforeAddRect(tile, w, h, StationRect::ADD_TRY);
+
+ uint tiles = 0;
+ for (TileIndex t : new_airport_tiles) {
+ uint pos = RotatedAirportSpecPosition(t, new_airport_tiles, rotation);
+ if (as->layouts[layout].tiles[pos].type == ATT_INVALID) continue; // does not belong to new airport tiles.
+ tiles++;
+ WaterClass wc = HasTileWaterClass(t) ? GetWaterClass(t) : WATER_CLASS_INVALID;
+ MakeAirport(t, st->owner, st->index, 0, wc);
+ SetStationTileRandomBits(t, GB(Random(), 0, 4));
+ st->airport.Add(t);
+ MarkTileDirtyByTile(t);
+ }
+
+ st->LoadAirportTilesFromSpec(airport_area, rotation, air_type);
+
+ if (custom_airport) st->airport.type = AT_CUSTOM;
+
+ /* Replace all hangars with hangars the human user can build. */
+ bool allow_standard_hangars = HasBit(_settings_game.depot.hangar_types, 0);
+ bool allow_extended_hangars = HasBit(_settings_game.depot.hangar_types, 1);
+ if (Company::IsValidHumanID(_current_company) && (!allow_standard_hangars || !allow_extended_hangars)) {
+ for (TileIndex t : new_airport_tiles) {
+ uint pos = RotatedAirportSpecPosition(t, new_airport_tiles, rotation);
+ if (as->layouts[layout].tiles[pos].type == ATT_INVALID) continue; // does not belong to new airport tiles.
+ assert(IsAirportTileOfStation(t, st->index));
+
+ /* Should we allow standard and extended hangars? */
+ if (!IsHangar(t)) continue;
+ if (IsExtendedHangar(t)) {
+ if (!allow_extended_hangars) SetExtendedHangar(t, false);
+ } else {
+ if (!allow_standard_hangars) SetExtendedHangar(t, true);
+ }
+ }
+ }
+
+ /* Set animated tiles. */
+ for (TileIndex t : new_airport_tiles) {
+ if (!st->TileBelongsToAirport(t)) continue;
+ if (AirportTileSpec::GetByTile(t)->animation.status != ANIM_STATUS_NO_ANIMATION) AddAnimatedTile(t);
+ }
+
+ /* Only call the animation trigger after all tiles have been built */
+ for (TileIndex t : new_airport_tiles) {
+ if (!st->TileBelongsToAirport(t)) continue;
+ AirportTileAnimationTrigger(st, t, AAT_BUILT);
+ }
+
+ Company *c = Company::Get(st->owner);
+ c->infrastructure.air[air_type] += tiles;
+ c->infrastructure.station += tiles;
+
+ st->AfterStationTileSetChange(true, STATION_AIRPORT);
+ InvalidateWindowData(WC_STATION_VIEW, st->index, -1);
+
+ UpdateAircraftOnUpdatedAirport(st);
+ }
+
+ return cost;
+}
+
+/**
+ * Remove an airport
+ * @param tile TileIndex been queried
+ * @param flags operation to perform
+ * @return cost or failure of operation
+ */
+CommandCost RemoveAirport(TileIndex tile, DoCommandFlag flags)
+{
+ Station *st = Station::GetByTile(tile);
+
+ CommandCost ret = AircraftInAirport(st);
+ if (ret.Failed()) return ret;
+
+ if (_current_company != OWNER_WATER) {
+ ret = CheckOwnership(st->owner);
+ if (ret.Failed()) return ret;
+ }
+
+ CommandCost cost(EXPENSES_CONSTRUCTION);
+
+ Town *nearest;
+ if (flags & DC_EXEC) {
+ if (st->airport.HasHangar()) {
+ CloseWindowById(WC_VEHICLE_DEPOT, st->airport.hangar->index);
+ OrderBackup::Reset(st->airport.hangar->index, false);
+ }
+
+ /* The noise level is the noise from the airport and reduce it to account for the distance to the town center.
+ * And as for construction, always remove it, even if the setting is not set, in order to avoid the
+ * need of recalculation */
+ uint dist;
+ BitmapTileArea bta;
+ bta.Initialize(st->airport);
+ for (TileIndex t : st->airport) {
+ if (st->TileBelongsToAirport(t)) bta.SetTile(t);
+ }
+ nearest = AirportGetNearestTown(bta, dist);
+ uint8_t noise_level = CalculateAirportNoiseLevel(st->airport);
+ nearest->noise_reached -= GetAirportNoiseLevelForDistance(noise_level, dist);
+
+ if (_settings_game.economy.station_noise_level) {
+ SetWindowDirty(WC_TOWN_VIEW, nearest->index);
+ }
+ }
+
+
+ uint tiles = 0;
+ for (TileIndex tile_cur : st->airport) {
+ if (!st->TileBelongsToAirport(tile_cur)) continue;
+
+ tiles++;
+
+ if (flags & DC_EXEC) {
+ Company *c = Company::Get(st->owner);
+ c->infrastructure.air[st->airport.air_type]--;
+ c->infrastructure.station--;
+ DeleteAnimatedTile(tile_cur);
+ DoClearSquare(tile_cur);
+ DeleteNewGRFInspectWindow(GSF_AIRPORTTILES, tile_cur.base());
+ DeleteNewGRFInspectWindow(GSF_AIRTYPES, tile_cur.base());
+ }
+ }
+
+ const AirTypeInfo *ati = GetAirTypeInfo(st->airport.air_type);
+ cost.AddCost(_price[PR_CLEAR_STATION_AIRPORT] * ati->cost_multiplier * tiles);
+
+ if (flags & DC_EXEC) {
+ /* Clear the persistent storage. */
+ delete st->airport.psa;
+
+ st->rect.AfterRemoveRect(st, st->airport);
+
+ st->UpdateAirportDataStructure();
+ st->airport.Clear();
+ st->facilities &= ~FACIL_AIRPORT;
+
+ InvalidateWindowData(WC_STATION_VIEW, st->index, -1);
+
+ st->AfterStationTileSetChange(false, STATION_AIRPORT);
+
+ DeleteNewGRFInspectWindow(GSF_AIRPORTS, st->index);
+
+ UpdateAircraftOnUpdatedAirport(st);
+ }
+
+ return cost;
+}
+
+void BuildBuiltInHeliport(Tile tile)
+{
+ if (!Station::CanAllocateItem()) {
+ Debug(misc, 0, "Can't allocate built in station for industry at 0x{}. Built in station won't be built.", static_cast(tile));
+ return;
+ }
+
+ Station *st = new Station(tile);
+ _station_kdtree.Insert(st->index);
+ st->town = ClosestTownFromTile(tile, UINT_MAX);
+
+ st->string_id = GenerateStationName(st, tile, STATIONNAMING_OILRIG);
+
+ assert(IsTileType(tile, MP_INDUSTRY));
+ /* Mark industry as associated both ways */
+ st->industry = Industry::GetByTile(tile);
+ st->industry->neutral_station = st;
+ DeleteAnimatedTile(tile);
+ MakeStation(tile, OWNER_NONE, st->index, STATION_AIRPORT, 0, GetWaterClass(tile));
+
+ const AirportSpec *as = AirportSpec::Get(AT_OILRIG);
+ const AirportTileTable *tile_desc = &as->layouts[0].tiles[0];
+ tile.m4() = 0;
+ tile.m5() = 0;
+ SetAirType(tile, as->airtype);
+ SetAirportTileType(tile, tile_desc->type);
+ SetAirportTileTracks(tile, tile_desc->trackbits);
+ SetApronType(tile, tile_desc->apron_type);
+ st->airport.flags = AF_NONE;
+
+ st->owner = OWNER_NONE;
+ st->airport.type = AT_OILRIG;
+ st->airport.air_type = as->airtype;
+ st->airport.Add(tile);
+ st->ship_station.Add(tile);
+ st->facilities = FACIL_AIRPORT | FACIL_DOCK;
+ st->build_date = TimerGameCalendar::date;
+ UpdateStationDockingTiles(st);
+
+ st->rect.BeforeAddTile(tile, StationRect::ADD_FORCE);
+
+ st->UpdateAirportDataStructure();
+ st->UpdateVirtCoord();
+
+ /* An industry tile has now been replaced with a station tile, this may change the overlap between station catchments and industry tiles.
+ * Recalculate the station catchment for all stations currently in the industry's nearby list.
+ * Clear the industry's station nearby list first because Station::RecomputeCatchment cannot remove nearby industries in this case. */
+ if (_settings_game.station.serve_neutral_industries) {
+ StationList nearby = std::move(st->industry->stations_near);
+ st->industry->stations_near.clear();
+ for (Station *near : nearby) {
+ near->RecomputeCatchment(true);
+ UpdateStationAcceptance(near, true);
+ }
+ }
+
+ st->RecomputeCatchment();
+ UpdateStationAcceptance(st, false);
+}
+
+void DeleteOldBuiltInHeliport(TileIndex tile)
+{
+ Station *st = Station::GetByTile(tile);
+
+ MakeWaterKeepingClass(tile, OWNER_NONE);
+
+ /* The oil rig station is not supposed to be shared with anything else */
+ assert(st->facilities == (FACIL_AIRPORT | FACIL_DOCK) && st->airport.type == AT_OILRIG);
+ if (st->industry != nullptr && st->industry->neutral_station == st) {
+ /* Don't leave dangling neutral station pointer */
+ st->industry->neutral_station = nullptr;
+ }
+
+ delete st;
+}
+
+void DeleteBuiltInHeliport(TileIndex tile)
+{
+ assert(IsBuiltInHeliportTile(tile));
+ Station *st = Station::GetByTile(tile);
+
+ bool helicopter_present = HasAirportTrackReserved(tile);
+ MakeWaterKeepingClass(tile, OWNER_NONE);
+
+ /* The oil rig station is not supposed to be shared with anything else */
+ assert(st->facilities == (FACIL_AIRPORT | FACIL_DOCK) && st->airport.type == AT_OILRIG);
+ if (st->industry != nullptr && st->industry->neutral_station == st) {
+ /* Don't leave dangling neutral station pointer */
+ st->industry->neutral_station = nullptr;
+ }
+
+ st->airport.tile = INVALID_TILE;
+ st->airport.air_type = INVALID_AIRTYPE;
+ st->facilities = FACIL_NONE;
+
+ if (helicopter_present) {
+ for (Aircraft *a : Aircraft::Iterate()) {
+ if (!a->IsNormalAircraft()) continue;
+ if (!a->IsHelicopter()) continue;
+ if (a->tile != tile) continue;
+ if (a->state == AS_BUILTIN_HELIPORT ||
+ a->state == AS_START_TAKEOFF ||
+ a->state == AS_LANDED ||
+ a->state == AS_FLYING_HELICOPTER_TAKEOFF ||
+ a->state == AS_FLYING_HELICOPTER_LANDING ||
+ a->state == AS_ON_HOLD_APPROACHING) {
+ a->state = AS_FLYING;
+ ProcessOrders(a);
+ a->SetDestTile(0);
+ }
+ }
+ }
+
+ UpdateAircraftOnUpdatedAirport(st);
+ delete st;
+}
+
+/**
+ * Open/close an airport to incoming aircraft.
+ * @param flags Operation to perform.
+ * @param station_id Station ID of the airport.
+ * @return the cost of this operation or an error
+ */
+CommandCost CmdOpenCloseAirport(DoCommandFlag flags, StationID station_id)
+{
+ if (!Station::IsValidID(station_id)) return CMD_ERROR;
+ Station *st = Station::Get(station_id);
+
+ if (!(st->facilities & FACIL_AIRPORT) || st->owner == OWNER_NONE) return CMD_ERROR;
+
+ CommandCost ret = CheckOwnership(st->owner);
+ if (ret.Failed()) return ret;
+
+ if (flags & DC_EXEC) {
+ st->airport.flags ^= AF_CLOSED_MANUAL;
+ SetWindowWidgetDirty(WC_STATION_VIEW, st->index, WID_SV_CLOSE_AIRPORT);
+ UpdateAircraftOnUpdatedAirport(st);
+ }
+ return CommandCost();
+}
+
+extern void CrashAircraft(Aircraft *a);
+
+/**
+ * When an airport is being flooded, update all aircraft in that airport
+ * and update all aircraft trying to land on it.
+ * @param st Station being flooded.
+ */
+void FloodAircraftOnAirport(const Station *st)
+{
+ for (Aircraft *a : Aircraft::Iterate()) {
+ if (!a->IsNormalAircraft() ||
+ a->IsAircraftFreelyFlying() ||
+ a->vehstatus & VS_CRASHED) {
+ continue;
+ }
+
+ if (a->IsAircraftFlying()) {
+ if (!a->IsHelicopter()) {
+ TileIndex test_tile = a->state == AS_FLYING_TAKEOFF ? a->tile : a->GetNextTile();
+ if (!IsAirportTileOfStation(test_tile, st->index)) continue;
+ /* Remove reservation and keep flying. */
+ a->state = AS_FLYING_LEAVING_AIRPORT;
+ DiagDirection diagdir = TrackdirToExitdir(a->trackdir);
+ TileIndex start_tile = (a->state == AS_FLYING_TAKEOFF || a->state == AS_FLYING_LANDING) ?
+ GetRunwayExtreme(a->tile, ReverseDiagDir(diagdir)) : a->GetNextTile();
+ assert(IsRunwayStart(start_tile));
+ SetRunwayReservation(start_tile, false);
+ } else {
+ a->state = AS_FLYING_NO_DEST;
+ a->next_pos.pos = AP_DEFAULT;
+ assert(IsValidTrackdir(a->trackdir));
+ RemoveAirportTrackReservation(a->tile, TrackdirToTrack(a->trackdir));
+ }
+ } else {
+ if (!IsAirportTileOfStation(a->tile, st->index)) continue;
+ CrashAircraft(a);
+ }
+ }
+}
diff --git a/src/airport_cmd.h b/src/airport_cmd.h
index 2e27057d8ca29..9c91708fcd9c5 100644
--- a/src/airport_cmd.h
+++ b/src/airport_cmd.h
@@ -11,6 +11,33 @@
#define AIRPORT_CMD_H
#include "command_type.h"
+#include "air.h"
+#include "air_type.h"
+#include "direction_type.h"
+#include "station_type.h"
+#include "track_type.h"
+
+enum AirportTiles : uint8_t;
+
+CommandCost CmdChangeAirportTiles(DoCommandFlag flags, TileIndex start_tile, TileIndex end_tile, AirType air_type, AirportTileType air_tile_type, AirportTiles infra_type, DiagDirection direction, bool adding, bool diagonal);
+CommandCost CmdAddRemoveAirportTiles(DoCommandFlag flags, TileIndex start_tile, TileIndex end_tile, bool adding, AirType at, StationID station_to_join, bool adjacent);
+CommandCost CmdAddRemoveTracksToAirport(DoCommandFlag flags, TileIndex start_tile, TileIndex end_tile, AirType air_type, bool add, Track track);
+CommandCost CmdChangeAirType(DoCommandFlag flags, TileIndex tile, AirType air_type);
+CommandCost CmdAirportChangeTrackGFX(DoCommandFlag flags, TileIndex start_tile, TileIndex end_tile, AirType air_type, uint8_t gfx_index, bool diagonal);
+CommandCost CmdAirportToggleGround(DoCommandFlag flags, TileIndex start_tile, TileIndex end_tile, AirType air_type, bool diagonal);
+
+CommandCost CmdBuildAirport(DoCommandFlag flags, TileIndex tile, uint8_t airport_type, uint8_t layout, AirType air_type, DiagDirection rotation, StationID station_to_join, bool allow_adjacent);
+CommandCost CmdOpenCloseAirport(DoCommandFlag flags, StationID station_id);
+
+DEF_CMD_TRAIT(CMD_CHANGE_AIRPORT, CmdChangeAirportTiles, CMD_AUTO, CMDT_LANDSCAPE_CONSTRUCTION)
+DEF_CMD_TRAIT(CMD_ADD_REM_AIRPORT, CmdAddRemoveAirportTiles, CMD_AUTO, CMDT_LANDSCAPE_CONSTRUCTION)
+DEF_CMD_TRAIT(CMD_ADD_REM_TRACKS, CmdAddRemoveTracksToAirport, CMD_AUTO, CMDT_LANDSCAPE_CONSTRUCTION)
+DEF_CMD_TRAIT(CMD_CONVERT_AIRPORT, CmdChangeAirType, CMD_AUTO, CMDT_LANDSCAPE_CONSTRUCTION)
+DEF_CMD_TRAIT(CMD_AIRPORT_CHANGE_GFX, CmdAirportChangeTrackGFX, CMD_AUTO, CMDT_LANDSCAPE_CONSTRUCTION)
+DEF_CMD_TRAIT(CMD_AIRPORT_TOGGLE_GROUND, CmdAirportToggleGround, CMD_AUTO, CMDT_LANDSCAPE_CONSTRUCTION)
+
+DEF_CMD_TRAIT(CMD_BUILD_AIRPORT, CmdBuildAirport, CMD_AUTO | CMD_NO_WATER, CMDT_LANDSCAPE_CONSTRUCTION)
+DEF_CMD_TRAIT(CMD_OPEN_CLOSE_AIRPORT, CmdOpenCloseAirport, 0, CMDT_ROUTE_MANAGEMENT)
CommandCallback CcBuildAirport;
diff --git a/src/airport_gui.cpp b/src/airport_gui.cpp
index 727a8e1f6448e..f7a004584cc56 100644
--- a/src/airport_gui.cpp
+++ b/src/airport_gui.cpp
@@ -9,6 +9,7 @@
#include "stdafx.h"
#include "economy_func.h"
+#include "widget_type.h"
#include "window_gui.h"
#include "station_gui.h"
#include "terraform_gui.h"
@@ -17,6 +18,7 @@
#include "strings_func.h"
#include "viewport_func.h"
#include "company_func.h"
+#include "command_func.h"
#include "tilehighlight_func.h"
#include "company_base.h"
#include "station_type.h"
@@ -31,6 +33,12 @@
#include "command_func.h"
#include "airport_cmd.h"
#include "station_cmd.h"
+#include "air_type.h"
+#include "air.h"
+#include "air_map.h"
+#include "station_map.h"
+#include "engine_base.h"
+#include "window_type.h"
#include "zoom_func.h"
#include "timer/timer.h"
#include "timer/timer_game_calendar.h"
@@ -39,12 +47,28 @@
#include "safeguards.h"
-
-static AirportClassID _selected_airport_class; ///< the currently visible airport class
-static int _selected_airport_index; ///< the index of the selected airport in the current class or -1
-static uint8_t _selected_airport_layout; ///< selected airport layout number.
+static AirType _cur_airtype; ///< Air type of the current build-air toolbar.
+static AirportTileType _airport_tile_type; ///< Current airport tile type (hangar, infrastructure...
+static DiagDirection _rotation_dir; ///< Exit direction for new hangars, or rotation for heliports and infrastructure.
+static bool _remove_button_clicked; ///< Flag whether 'remove' toggle-button is currently enabled
+static AirportClassID _selected_airport_class; ///< the currently visible airport class
+static int _selected_airport_index; ///< the index of the selected airport in the current class or -1
+static uint8_t _selected_airport_layout; ///< selected airport layout number.
+bool _show_airport_tracks = 0; ///< whether to show the airport tracks on viewports.
+static DiagDirection _selected_rotation; ///< selected rotation for airport.
+static uint8_t _selected_infra_catch_rotation; ///< selected rotation for infrastructure.
+static AirportTiles _selected_infra_catch; ///< selected infrastructure type.
+static uint8_t _selected_infra_nocatch_rotation; ///< selected rotation for infrastructure.
+static AirportTiles _selected_infra_nocatch; ///< selected infrastructure type.
+static uint8_t _selected_track_gfx_index; ///< selected track gfx index.
static void ShowBuildAirportPicker(Window *parent);
+static void ShowHangarPicker(Window *parent);
+static void ShowHeliportPicker(Window *parent);
+static void ShowAirportInfraNoCatchPicker(Window *parent);
+static void ShowAirportInfraWithCatchPicker(Window *parent);
+static void ShowTrackGfxPicker(Window *parent);
+Window *ShowBuildAirToolbar(AirType airtype);
SpriteID GetCustomAirportSprite(const AirportSpec *as, uint8_t layout);
@@ -70,29 +94,57 @@ static void PlaceAirport(TileIndex tile)
auto proc = [=](bool test, StationID to_join) -> bool {
if (test) {
- return Command::Do(CommandFlagsToDCFlags(GetCommandFlags()), tile, airport_type, layout, INVALID_STATION, adjacent).Succeeded();
+ return adjacent || Command::Do(CommandFlagsToDCFlags(GetCommandFlags()), tile, airport_type, layout, _cur_airtype, _selected_rotation, INVALID_STATION, adjacent).Succeeded();
} else {
- return Command::Post(STR_ERROR_CAN_T_BUILD_AIRPORT_HERE, CcBuildAirport, tile, airport_type, layout, to_join, adjacent);
+ return Command::Post(STR_ERROR_CAN_T_BUILD_AIRPORT_HERE, CcBuildAirport, tile, airport_type, layout, _cur_airtype, _selected_rotation, to_join, adjacent);
}
};
ShowSelectStationIfNeeded(TileArea(tile, _thd.size.x / TILE_SIZE, _thd.size.y / TILE_SIZE), proc);
}
+/**
+ * Get the other tile of a runway.
+ * @param tile The tile.
+ * @return the other extreme of the runway if the tile checked is the start or end of a runway
+ * or the same tile otherwise.
+ */
+ TileIndex GetOtherEndOfRunway(TileIndex tile)
+{
+ if (IsValidTile(tile) && IsAirportTile(tile) && IsRunwayExtreme(tile)) {
+ AirportTileType att = GetAirportTileType(tile);
+ DiagDirection dir = GetRunwayExtremeDirection(tile);
+ if (att == ATT_RUNWAY_END) dir = ReverseDiagDir(dir);
+ return GetRunwayExtreme(tile, dir);
+ }
+ return tile;
+}
+
/** Airport build toolbar window handler. */
struct BuildAirToolbarWindow : Window {
+ const bool allow_by_tile;
int last_user_action; // Last started user action.
- BuildAirToolbarWindow(WindowDesc &desc, WindowNumber window_number) : Window(desc)
+ BuildAirToolbarWindow(bool allow_by_tile, WindowDesc &desc, AirType airtype) : Window(desc), allow_by_tile(allow_by_tile)
{
- this->InitNested(window_number);
+ this->CreateNestedTree();
+ this->SetupAirToolbar(airtype);
+ this->FinishInitNested(TRANSPORT_AIR);
+
+ this->DisableWidget(WID_AT_REMOVE);
+ this->last_user_action = INVALID_WID_AT;
+
this->OnInvalidateData();
if (_settings_client.gui.link_terraform_toolbar) ShowTerraformToolbar(this);
- this->last_user_action = INVALID_WID_AT;
+ _show_airport_tracks = true;
+ MarkWholeScreenDirty();
}
void Close([[maybe_unused]] int data = 0) override
{
+ _show_airport_tracks = false;
+ MarkWholeScreenDirty();
+
if (this->IsWidgetLowered(WID_AT_AIRPORT)) SetViewportCatchmentStation(nullptr, true);
if (_settings_client.gui.link_terraform_toolbar) CloseWindowById(WC_SCEN_LAND_GEN, 0, false);
this->Window::Close();
@@ -115,13 +167,134 @@ struct BuildAirToolbarWindow : Window {
/* Show in the tooltip why this button is disabled. */
this->GetWidget(WID_AT_AIRPORT)->SetToolTip(STR_TOOLBAR_DISABLED_NO_VEHICLE_AVAILABLE);
} else {
- this->GetWidget(WID_AT_AIRPORT)->SetToolTip(STR_TOOLBAR_AIRCRAFT_BUILD_AIRPORT_TOOLTIP);
+ this->GetWidget(WID_AT_AIRPORT)->SetToolTip(STR_TOOLBAR_AIRPORT_BUILD_AIRPORT_TOOLTIP);
+ }
+ }
+
+ void SetStringParameters(int widget) const override
+ {
+ if (widget == WID_AT_CAPTION) {
+ if (_settings_game.station.allow_modify_airports) {
+ SetDParam(0, GetAirTypeInfo(_cur_airtype)->strings.toolbar_caption);
+ } else {
+ SetDParam(0, STR_TOOLBAR_AIRPORT_CAPTION);
+ }
+ }
+ }
+
+ /**
+ * Configures the air toolbar for airtype given
+ * @param airtype the airtype to display
+ */
+ void SetupAirToolbar(AirType airtype)
+ {
+ if (!this->allow_by_tile) return;
+ assert(airtype < AIRTYPE_END);
+
+ _cur_airtype = airtype;
+ const AirTypeInfo *ati = GetAirTypeInfo(airtype);
+ SetWidgetDisabledState(WID_AT_TOGGLE_GROUND, ati->build_on_water);
+ SetWidgetDisabledState(WID_AT_CHANGE_GRAPHICS, ati->build_on_water);
+
+ this->GetWidget(WID_AT_BUILD_TILE)->widget_data = ati->gui_sprites.add_airport_tiles;
+ this->GetWidget(WID_AT_TRACKS)->widget_data = ati->gui_sprites.build_track_tile;
+ this->GetWidget(WID_AT_CONVERT)->widget_data = ati->gui_sprites.change_airtype;
+ this->GetWidget(WID_AT_INFRASTRUCTURE_CATCH)->widget_data = ati->gui_sprites.build_catchment_infra;
+ this->GetWidget(WID_AT_INFRASTRUCTURE_NO_CATCH)->widget_data = ati->gui_sprites.build_noncatchment_infra;
+ this->GetWidget(WID_AT_RUNWAY_LANDING)->widget_data = ati->gui_sprites.define_landing_runway;
+ this->GetWidget(WID_AT_RUNWAY_NO_LANDING)->widget_data = ati->gui_sprites.define_nonlanding_runway;
+ this->GetWidget(WID_AT_APRON)->widget_data = ati->gui_sprites.build_apron;
+ this->GetWidget(WID_AT_HELIPAD)->widget_data = ati->gui_sprites.build_helipad;
+ this->GetWidget(WID_AT_HELIPORT)->widget_data = ati->gui_sprites.build_heliport;
+ if (this->HasWidget(WID_AT_HANGAR_STANDARD)) this->GetWidget(WID_AT_HANGAR_STANDARD)->widget_data = ati->gui_sprites.build_hangar;
+ if (this->HasWidget(WID_AT_HANGAR_EXTENDED)) this->GetWidget(WID_AT_HANGAR_EXTENDED)->widget_data = ati->gui_sprites.build_hangar;
+
+ if (!AreHeliportsAvailable(airtype)) DisableWidget(WID_AT_HELIPORT);
+ }
+
+ /**
+ * Switch to another air type.
+ * @param airtype New air type.
+ */
+ void ModifyAirType(AirType airtype)
+ {
+ this->SetupAirToolbar(airtype);
+ this->ReInit();
+ }
+
+ /**
+ * The "remove"-button click proc of the build-air toolbar.
+ * @see BuildAirToolbarWindow::OnClick()
+ */
+ void BuildAirClick_Remove()
+ {
+ if (this->IsWidgetDisabled(WID_AT_REMOVE)) return;
+ CloseWindowById(WC_SELECT_STATION, 0);
+ this->ToggleWidgetLoweredState(WID_AT_REMOVE);
+ this->SetWidgetDirty(WID_AT_REMOVE);
+ _remove_button_clicked = this->IsWidgetLowered(WID_AT_REMOVE);
+
+ if (this->last_user_action == WID_AT_RUNWAY_LANDING ||
+ this->last_user_action == WID_AT_RUNWAY_NO_LANDING) {
+ SetObjectToPlace(GetAirTypeInfo(_cur_airtype)->cursor.build_hangar, PAL_NONE, _remove_button_clicked ? HT_SPECIAL : HT_RECT, this->window_class, this->window_number);
+ this->LowerWidget(this->last_user_action);
+ this->SetWidgetLoweredState(WID_AT_REMOVE, _remove_button_clicked);
+ }
+
+ SetSelectionRed(_remove_button_clicked);
+ if (_settings_client.sound.click_beep) SndPlayFx(SND_15_BEEP);
+ }
+
+ void UpdateWidgetSize(int widget, Dimension &size, const Dimension &padding, [[maybe_unused]] Dimension &fill, [[maybe_unused]] Dimension &resize) override
+ {
+ if (!IsInsideMM(widget, WID_AT_BUILD_TILE, WID_AT_REMOVE)) return;
+ NWidgetCore *wid = this->GetWidget(widget);
+
+ Dimension d = GetSpriteSize(wid->widget_data);
+ d.width += padding.width;
+ d.height += padding.height;
+ size = d;
+ }
+
+ void UpdateRemoveWidgetStatus(int clicked_widget)
+ {
+ if (!this->allow_by_tile) return;
+
+ assert(clicked_widget != WID_AT_REMOVE);
+
+ if (clicked_widget >= WID_AT_REMOVE_FIRST && clicked_widget <= WID_AT_REMOVE_LAST) {
+ bool is_button_lowered = this->IsWidgetLowered(clicked_widget);
+ _remove_button_clicked &= is_button_lowered;
+ this->SetWidgetDisabledState(WID_AT_REMOVE, !is_button_lowered);
+ this->SetWidgetLoweredState(WID_AT_REMOVE, _remove_button_clicked);
+ SetSelectionRed(_remove_button_clicked);
+ } else {
+ /* When any other buttons that do not accept "removal",
+ * raise and disable the removal button. */
+ this->DisableWidget(WID_AT_REMOVE);
+ this->RaiseWidget(WID_AT_REMOVE);
+ _remove_button_clicked = false;
}
}
void OnClick([[maybe_unused]] Point pt, WidgetID widget, [[maybe_unused]] int click_count) override
{
switch (widget) {
+ case WID_AT_BUILD_TILE:
+ HandlePlacePushButton(this, widget, GetAirTypeInfo(_cur_airtype)->cursor.add_airport_tiles, HT_RECT);
+ this->last_user_action = widget;
+ break;
+
+ case WID_AT_TRACKS:
+ _airport_tile_type = ATT_SIMPLE_TRACK;
+ HandlePlacePushButton(this, widget, GetAirTypeInfo(_cur_airtype)->cursor.build_track_tile, HT_RAIL);
+ this->last_user_action = widget;
+ break;
+
+ case WID_AT_REMOVE:
+ this->BuildAirClick_Remove();
+ return;
+
case WID_AT_AIRPORT:
if (HandlePlacePushButton(this, WID_AT_AIRPORT, SPR_CURSOR_AIRPORT, HT_RECT)) {
ShowBuildAirportPicker(this);
@@ -134,14 +307,97 @@ struct BuildAirToolbarWindow : Window {
this->last_user_action = widget;
break;
+ case WID_AT_CONVERT:
+ HandlePlacePushButton(this, widget, GetAirTypeInfo(_cur_airtype)->cursor.change_airtype, HT_RECT);
+ this->last_user_action = widget;
+ break;
+
+ case WID_AT_INFRASTRUCTURE_CATCH:
+ _airport_tile_type = ATT_INFRASTRUCTURE_WITH_CATCH;
+ if (HandlePlacePushButton(this, widget, GetAirTypeInfo(_cur_airtype)->cursor.build_catchment_infra, HT_RECT | HT_DIAGONAL)) {
+ ShowAirportInfraWithCatchPicker(this);
+ }
+ this->last_user_action = widget;
+ break;
+
+ case WID_AT_INFRASTRUCTURE_NO_CATCH:
+ _airport_tile_type = ATT_INFRASTRUCTURE_NO_CATCH;
+ if (HandlePlacePushButton(this, widget, GetAirTypeInfo(_cur_airtype)->cursor.build_noncatchment_infra, HT_RECT | HT_DIAGONAL)) {
+ ShowAirportInfraNoCatchPicker(this);
+ }
+ this->last_user_action = widget;
+ break;
+
+ case WID_AT_APRON:
+ _airport_tile_type = ATT_APRON_NORMAL;
+ HandlePlacePushButton(this, widget, GetAirTypeInfo(_cur_airtype)->cursor.build_apron, HT_RECT | HT_DIAGONAL);
+ this->last_user_action = widget;
+ break;
+
+ case WID_AT_HELIPAD:
+ _airport_tile_type = ATT_APRON_HELIPAD;
+ HandlePlacePushButton(this, widget, GetAirTypeInfo(_cur_airtype)->cursor.build_helipad, HT_RECT | HT_DIAGONAL);
+ this->last_user_action = widget;
+ break;
+
+ case WID_AT_HELIPORT:
+ _airport_tile_type = ATT_APRON_HELIPORT;
+ if (HandlePlacePushButton(this, widget, GetAirTypeInfo(_cur_airtype)->cursor.build_heliport, HT_RECT | HT_DIAGONAL)) {
+ ShowHeliportPicker(this);
+ }
+ this->last_user_action = widget;
+ break;
+
+ case WID_AT_HANGAR_STANDARD:
+ case WID_AT_HANGAR_EXTENDED:
+ _airport_tile_type = widget == WID_AT_HANGAR_STANDARD ? ATT_HANGAR_STANDARD : ATT_HANGAR_EXTENDED;
+ if (HandlePlacePushButton(this, widget, GetAirTypeInfo(_cur_airtype)->cursor.build_hangar, HT_RECT)) {
+ ShowHangarPicker(this);
+ }
+ this->last_user_action = widget;
+ break;
+
+ case WID_AT_RUNWAY_LANDING:
+ _airport_tile_type = ATT_RUNWAY_START_ALLOW_LANDING;
+ HandlePlacePushButton(this, widget, GetAirTypeInfo(_cur_airtype)->cursor.define_landing_runway, _remove_button_clicked ? HT_SPECIAL : HT_RECT);
+ this->last_user_action = widget;
+ break;
+
+ case WID_AT_RUNWAY_NO_LANDING:
+ _airport_tile_type = ATT_RUNWAY_START_NO_LANDING;
+ HandlePlacePushButton(this, widget, GetAirTypeInfo(_cur_airtype)->cursor.define_nonlanding_runway, _remove_button_clicked ? HT_SPECIAL : HT_RECT);
+ this->last_user_action = widget;
+ break;
+
+ case WID_AT_CHANGE_GRAPHICS:
+ if (HandlePlacePushButton(this, widget, SPR_CURSOR_MOUSE, HT_RECT)) {
+ ShowTrackGfxPicker(this);
+ }
+ this->last_user_action = widget;
+ break;
+
+ case WID_AT_TOGGLE_GROUND:
+ HandlePlacePushButton(this, widget, SPR_CURSOR_MOUSE, HT_RECT | HT_DIAGONAL);
+ this->last_user_action = widget;
+ break;
+
default: break;
}
- }
+ UpdateRemoveWidgetStatus(widget);
+ }
void OnPlaceObject([[maybe_unused]] Point pt, TileIndex tile) override
{
switch (this->last_user_action) {
+ case WID_AT_BUILD_TILE:
+ VpStartPlaceSizing(tile, VPM_X_AND_Y, DDSP_BUILD_STATION);
+ break;
+
+ case WID_AT_TRACKS:
+ VpStartPlaceSizing(tile, VPM_RAILDIRS, DDSP_PLACE_RAIL);
+ break;
+
case WID_AT_AIRPORT:
PlaceAirport(tile);
break;
@@ -150,6 +406,41 @@ struct BuildAirToolbarWindow : Window {
PlaceProc_DemolishArea(tile);
break;
+ case WID_AT_CONVERT:
+ Command::Post(STR_ERROR_CAN_T_DO_THIS, tile, _cur_airtype);
+ break;
+
+ case WID_AT_HANGAR_STANDARD:
+ case WID_AT_HANGAR_EXTENDED:
+ VpStartPlaceSizing(tile, HasBit(_rotation_dir, 0) ? VPM_FIX_Y : VPM_FIX_X, DDSP_BUILD_STATION);
+ break;
+
+ case WID_AT_INFRASTRUCTURE_CATCH:
+ case WID_AT_INFRASTRUCTURE_NO_CATCH:
+ case WID_AT_APRON:
+ case WID_AT_HELIPAD:
+ case WID_AT_HELIPORT:
+ VpStartPlaceSizing(tile, VPM_X_AND_Y, DDSP_BUILD_STATION);
+ break;
+
+ case WID_AT_RUNWAY_LANDING:
+ case WID_AT_RUNWAY_NO_LANDING:
+ if (_remove_button_clicked) {
+ Command::Post(STR_ERROR_CAN_T_DO_THIS, tile, GetOtherEndOfRunway(tile), _cur_airtype, _airport_tile_type, ATTG_DEFAULT_GFX, (DiagDirection)0, !_remove_button_clicked, false);
+ } else {
+ VpStartPlaceSizing(tile, VPM_X_OR_Y, DDSP_BUILD_STATION);
+ }
+ break;
+
+ case WID_AT_CHANGE_GRAPHICS:
+ VpStartPlaceSizing(tile, VPM_X_AND_Y, DDSP_BUILD_STATION);
+ break;
+
+
+ case WID_AT_TOGGLE_GROUND:
+ VpStartPlaceSizing(tile, VPM_X_AND_Y, DDSP_BUILD_STATION);
+ break;
+
default: NOT_REACHED();
}
}
@@ -161,19 +452,95 @@ struct BuildAirToolbarWindow : Window {
void OnPlaceMouseUp([[maybe_unused]] ViewportPlaceMethod select_method, ViewportDragDropSelectionProcess select_proc, [[maybe_unused]] Point pt, TileIndex start_tile, TileIndex end_tile) override
{
- if (pt.x != -1 && select_proc == DDSP_DEMOLISH_AREA) {
- GUIPlaceProcDragXY(select_proc, start_tile, end_tile);
+ if (pt.x == -1) return;
+
+ switch (this->last_user_action) {
+ case WID_AT_BUILD_TILE: {
+ assert(_settings_game.station.allow_modify_airports);
+
+ auto proc = [=](bool test, StationID to_join) -> bool {
+ if (test) {
+ return (_ctrl_pressed && !_remove_button_clicked) || Command::Do(CommandFlagsToDCFlags(GetCommandFlags()), start_tile, end_tile, !_remove_button_clicked, _cur_airtype, INVALID_STATION, _ctrl_pressed).Succeeded();
+ } else {
+ return Command::Post(STR_ERROR_CAN_T_DO_THIS, start_tile, end_tile, !_remove_button_clicked, _cur_airtype, to_join, _ctrl_pressed);
+ }
+ };
+
+ ShowSelectStationIfNeeded(TileArea(start_tile, end_tile), proc);
+ break;
+ }
+ case WID_AT_TRACKS:
+ assert(_settings_game.station.allow_modify_airports);
+ Command::Post(STR_ERROR_CAN_T_DO_THIS, start_tile, end_tile, _cur_airtype, !_remove_button_clicked, (Track)(_thd.drawstyle & HT_DIR_MASK));
+ break;
+ case WID_AT_AIRPORT:
+ assert(start_tile == end_tile);
+ PlaceAirport(end_tile);
+ break;
+ case WID_AT_DEMOLISH:
+ GUIPlaceProcDragXY(select_proc, start_tile, end_tile);
+ break;
+ case WID_AT_CONVERT:
+ NOT_REACHED();
+ case WID_AT_INFRASTRUCTURE_CATCH:
+ case WID_AT_INFRASTRUCTURE_NO_CATCH:
+ case WID_AT_APRON:
+ case WID_AT_HELIPAD:
+ case WID_AT_HELIPORT:
+ case WID_AT_HANGAR_STANDARD:
+ case WID_AT_HANGAR_EXTENDED:
+ case WID_AT_RUNWAY_LANDING:
+ case WID_AT_RUNWAY_NO_LANDING: {
+ DiagDirection dir = _rotation_dir;
+ AirportTiles gfx = _selected_infra_catch;
+ bool diagonal = _ctrl_pressed &&
+ (this->last_user_action != WID_AT_HANGAR_EXTENDED && this->last_user_action != WID_AT_HANGAR_STANDARD);
+ if (this->last_user_action == WID_AT_INFRASTRUCTURE_CATCH) {
+ dir = (DiagDirection)_selected_infra_catch_rotation;
+ } else if (this->last_user_action == WID_AT_INFRASTRUCTURE_NO_CATCH) {
+ dir = (DiagDirection)_selected_infra_nocatch_rotation;
+ gfx = (AirportTiles)(ATTG_NO_CATCH_FLAG + _selected_infra_nocatch);
+ }
+ bool ret = Command::Post(STR_ERROR_CAN_T_DO_THIS, start_tile, end_tile, _cur_airtype, _airport_tile_type, gfx, dir, !_remove_button_clicked, diagonal);
+ if (ret && _remove_button_clicked &&
+ (this->last_user_action == WID_AT_RUNWAY_LANDING || this->last_user_action == WID_AT_RUNWAY_NO_LANDING)) {
+ VpStartPlaceSizing(start_tile, VPM_X_OR_Y, DDSP_BUILD_STATION);
+ }
+ break;
+ }
+
+ case WID_AT_CHANGE_GRAPHICS:
+ Command::Post(STR_ERROR_CAN_T_DO_THIS, start_tile, end_tile, _cur_airtype, _selected_track_gfx_index, _ctrl_pressed);
+ break;
+
+ case WID_AT_TOGGLE_GROUND:
+ Command::Post(STR_ERROR_CAN_T_DO_THIS, start_tile, end_tile, _cur_airtype, _ctrl_pressed);
+ break;
+
+ default: NOT_REACHED();
}
}
+ void OnPlacePresize([[maybe_unused]] Point pt, TileIndex tile) override
+ {
+ assert(this->last_user_action == WID_AT_RUNWAY_LANDING ||
+ this->last_user_action == WID_AT_RUNWAY_NO_LANDING);
+ assert(_remove_button_clicked);
+ VpSetPresizeRange(tile, GetOtherEndOfRunway(tile));
+ }
+
void OnPlaceObjectAbort() override
{
if (this->IsWidgetLowered(WID_AT_AIRPORT)) SetViewportCatchmentStation(nullptr, true);
this->RaiseButtons();
+ CloseWindowById(WC_BUILD_DEPOT, TRANSPORT_AIR);
CloseWindowById(WC_BUILD_STATION, TRANSPORT_AIR);
CloseWindowById(WC_SELECT_STATION, 0);
+ CloseWindowById(WC_BUILD_HELIPORT, TRANSPORT_AIR);
+ CloseWindowById(WC_BUILD_AIRPORT_INFRASTRUCTURE, TRANSPORT_AIR);
+ CloseWindowById(WC_SELECT_TRACK_GFX, TRANSPORT_AIR);
}
/**
@@ -183,8 +550,9 @@ struct BuildAirToolbarWindow : Window {
*/
static EventState AirportToolbarGlobalHotkeys(int hotkey)
{
- if (_game_mode != GM_NORMAL) return ES_NOT_HANDLED;
- Window *w = ShowBuildAirToolbar();
+ if (_game_mode != GM_NORMAL || !CanBuildVehicleInfrastructure(VEH_AIRCRAFT)) return ES_NOT_HANDLED;
+ extern AirType _last_built_airtype;
+ Window *w = ShowBuildAirToolbar(_settings_game.station.allow_modify_airports ? _last_built_airtype : INVALID_AIRTYPE);
if (w == nullptr) return ES_NOT_HANDLED;
return w->OnHotkey(hotkey);
}
@@ -192,43 +560,143 @@ struct BuildAirToolbarWindow : Window {
static inline HotkeyList hotkeys{"airtoolbar", {
Hotkey('1', "airport", WID_AT_AIRPORT),
Hotkey('2', "demolish", WID_AT_DEMOLISH),
+ Hotkey('3', "remove", WID_AT_REMOVE),
+ Hotkey('4', "tiles", WID_AT_BUILD_TILE),
+ Hotkey('5', "tracks", WID_AT_TRACKS),
+ Hotkey('6', "infra_with_catch", WID_AT_INFRASTRUCTURE_CATCH),
+ Hotkey('7', "infra_no_catch", WID_AT_INFRASTRUCTURE_NO_CATCH),
}, AirportToolbarGlobalHotkeys};
};
-static constexpr NWidgetPart _nested_air_toolbar_widgets[] = {
+/**
+ * Add the depot icons depending on availability of construction.
+ * @return Panel with hangar buttons.
+ */
+static std::unique_ptr MakeNWidgetHangars()
+{
+ auto hor = std::make_unique();
+
+ if (HasBit(_settings_game.depot.hangar_types, 0)) {
+ /* Add the widget for building standard hangar. */
+ hor->Add(std::make_unique(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_AT_HANGAR_STANDARD, 0, STR_TOOLBAR_AIRPORT_BUILD_HANGAR_STANDARD));
+ }
+
+ if (HasBit(_settings_game.depot.hangar_types, 1)) {
+ /* Add the widget for building extended hangar. */
+ hor->Add(std::make_unique(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_AT_HANGAR_EXTENDED, 0, STR_TOOLBAR_AIRPORT_BUILD_HANGAR_EXTENDED));
+ }
+
+ return hor;
+}
+
+static constexpr NWidgetPart _nested_air_tile_toolbar_widgets[] = {
+ NWidget(NWID_HORIZONTAL),
+ NWidget(WWT_CLOSEBOX, COLOUR_DARK_GREEN),
+ NWidget(WWT_CAPTION, COLOUR_DARK_GREEN, WID_AT_CAPTION), SetDataTip(STR_JUST_STRING, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS), SetTextStyle(TC_WHITE),
+ NWidget(WWT_STICKYBOX, COLOUR_DARK_GREEN),
+ EndContainer(),
+ NWidget(NWID_VERTICAL),
+ NWidget(NWID_HORIZONTAL),
+ NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_AT_AIRPORT), SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_AIRPORT, STR_TOOLBAR_AIRPORT_BUILD_PRE_AIRPORT_TOOLTIP),
+
+ NWidget(WWT_PANEL, COLOUR_DARK_GREEN), SetMinimalSize(4, 22), SetFill(1, 1), EndContainer(),
+
+ NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_AT_DEMOLISH), SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_DYNAMITE, STR_TOOLTIP_DEMOLISH_BUILDINGS_ETC),
+ NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_AT_REMOVE), SetFill(0, 1),
+ SetDataTip(SPR_IMG_REMOVE, STR_TOOLBAR_AIRPORT_TOGGLE_BUILD_REMOVE),
+
+ NWidget(WWT_PANEL, COLOUR_DARK_GREEN), SetMinimalSize(4, 22), SetFill(1, 1), EndContainer(),
+
+ NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_AT_BUILD_TILE), SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(0, STR_TOOLBAR_AIRPORT_ADD_TILES),
+ NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_AT_TRACKS), SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(0, STR_TOOLBAR_AIRPORT_SET_TRACKS),
+
+ NWidget(WWT_PANEL, COLOUR_DARK_GREEN), SetMinimalSize(4, 22), SetFill(1, 1), EndContainer(),
+
+ NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_AT_INFRASTRUCTURE_CATCH), SetFill(0, 1),
+ SetDataTip(0, STR_TOOLBAR_AIRPORT_INFRASTRUCTURE_CATCHMENT),
+ NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_AT_INFRASTRUCTURE_NO_CATCH), SetFill(0, 1),
+ SetDataTip(0, STR_TOOLBAR_AIRPORT_INFRASTRUCTURE_NO_CATCHMENT),
+ EndContainer(),
+ NWidget(NWID_HORIZONTAL),
+ NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_AT_RUNWAY_LANDING), SetFill(0, 1),
+ SetDataTip(0, STR_TOOLBAR_AIRPORT_DEFINE_RUNWAY_LANDING),
+ NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_AT_RUNWAY_NO_LANDING), SetFill(0, 1),
+ SetDataTip(0, STR_TOOLBAR_AIRPORT_DEFINE_RUNWAY_NO_LANDING),
+ NWidget(WWT_PANEL, COLOUR_DARK_GREEN), SetMinimalSize(4, 22), SetFill(1, 1), EndContainer(),
+
+ NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_AT_APRON), SetFill(0, 1),
+ SetDataTip(0, STR_TOOLBAR_AIRPORT_BUILD_APRON),
+ NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_AT_HELIPAD), SetFill(0, 1),
+ SetDataTip(0, STR_TOOLBAR_AIRPORT_BUILD_HELIPAD),
+ NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_AT_HELIPORT), SetFill(0, 1),
+ SetDataTip(0, STR_TOOLBAR_AIRPORT_BUILD_HELIPORT),
+ NWidget(WWT_PANEL, COLOUR_DARK_GREEN), SetMinimalSize(4, 22), SetFill(1, 1), EndContainer(),
+ NWidgetFunction(MakeNWidgetHangars),
+ NWidget(WWT_PANEL, COLOUR_DARK_GREEN), SetMinimalSize(4, 22), SetFill(1, 1), EndContainer(),
+ NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_AT_CONVERT), SetFill(0, 1),
+ SetDataTip(0, STR_TOOLBAR_AIRPORT_CHANGE_AIRTYPE),
+ NWidget(WWT_TEXTBTN, COLOUR_DARK_GREEN, WID_AT_CHANGE_GRAPHICS), SetFill(0, 1),
+ SetDataTip(STR_TOOLBAR_AIRPORT_ROTATE_GRAPHICS, STR_TOOLBAR_AIRPORT_ROTATE_GRAPHICS_TOOLTIP),
+ NWidget(WWT_TEXTBTN, COLOUR_DARK_GREEN, WID_AT_TOGGLE_GROUND), SetFill(0, 1),
+ SetDataTip(STR_TOOLBAR_AIRPORT_TOGGLE_GROUND, STR_TOOLBAR_AIRPORT_TOGGLE_GROUND_TOOLBAR),
+ EndContainer(),
+ EndContainer(),
+};
+
+static const NWidgetPart _nested_air_nontile_toolbar_widgets[] = {
NWidget(NWID_HORIZONTAL),
NWidget(WWT_CLOSEBOX, COLOUR_DARK_GREEN),
- NWidget(WWT_CAPTION, COLOUR_DARK_GREEN), SetDataTip(STR_TOOLBAR_AIRCRAFT_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
+ NWidget(WWT_CAPTION, COLOUR_DARK_GREEN, WID_AT_CAPTION), SetDataTip(STR_JUST_STRING, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
NWidget(WWT_STICKYBOX, COLOUR_DARK_GREEN),
EndContainer(),
NWidget(NWID_HORIZONTAL),
- NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_AT_AIRPORT), SetFill(0, 1), SetMinimalSize(42, 22), SetDataTip(SPR_IMG_AIRPORT, STR_TOOLBAR_AIRCRAFT_BUILD_AIRPORT_TOOLTIP),
- NWidget(WWT_PANEL, COLOUR_DARK_GREEN), SetMinimalSize(4, 22), SetFill(1, 1), EndContainer(),
+ NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_AT_AIRPORT), SetFill(0, 1), SetMinimalSize(42, 22),
+ SetDataTip(SPR_IMG_AIRPORT, STR_TOOLBAR_AIRPORT_BUILD_PRE_AIRPORT_TOOLTIP),
+ NWidget(WWT_PANEL, COLOUR_DARK_GREEN), SetMinimalSize(4, 22), SetFill(1, 1), EndContainer(),
NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_AT_DEMOLISH), SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_DYNAMITE, STR_TOOLTIP_DEMOLISH_BUILDINGS_ETC),
EndContainer(),
};
-static WindowDesc _air_toolbar_desc(
+static WindowDesc _air_tile_toolbar_desc(
WDP_ALIGN_TOOLBAR, "toolbar_air", 0, 0,
WC_BUILD_TOOLBAR, WC_NONE,
WDF_CONSTRUCTION,
- _nested_air_toolbar_widgets,
+ _nested_air_tile_toolbar_widgets,
+ &BuildAirToolbarWindow::hotkeys
+);
+
+static WindowDesc _air_nontile_toolbar_desc(
+ WDP_ALIGN_TOOLBAR, "toolbar_air_nontile", 0, 0,
+ WC_BUILD_TOOLBAR, WC_NONE,
+ WDF_CONSTRUCTION,
+ _nested_air_nontile_toolbar_widgets,
&BuildAirToolbarWindow::hotkeys
);
+
/**
- * Open the build airport toolbar window
- *
+ * Open the build airport toolbar window.
* If the terraform toolbar is linked to the toolbar, that window is also opened.
- *
+ * @param airtype air type for constructing (a valid air type or
+ * INVALID_AIRTYPE if the build-airports-by-tile is dissabled).
* @return newly opened airport toolbar, or nullptr if the toolbar could not be opened.
*/
-Window *ShowBuildAirToolbar()
+Window *ShowBuildAirToolbar(AirType airtype)
{
if (!Company::IsValidID(_local_company)) return nullptr;
+ if (airtype != INVALID_AIRTYPE && !ValParamAirType(airtype)) return nullptr;
CloseWindowByClass(WC_BUILD_TOOLBAR);
- return AllocateWindowDescFront(_air_toolbar_desc, TRANSPORT_AIR);
+ assert((airtype == INVALID_AIRTYPE) != (_settings_game.station.allow_modify_airports));
+
+ _cur_airtype = airtype;
+ _remove_button_clicked = false;
+
+ if (airtype == INVALID_AIRTYPE) {
+ return new BuildAirToolbarWindow(false, _air_nontile_toolbar_desc, airtype);
+ } else {
+ return new BuildAirToolbarWindow(true, _air_tile_toolbar_desc, airtype);
+ }
}
class BuildAirportWindow : public PickerWindowBase {
@@ -242,7 +710,14 @@ class BuildAirportWindow : public PickerWindowBase {
DropDownList list;
for (const auto &cls : AirportClass::Classes()) {
- list.push_back(MakeDropDownListStringItem(cls.name, cls.Index()));
+ if (cls.Index() == APC_CUSTOM) continue;
+ bool unavailable = true;
+ for (const auto &as : cls.Specs()) {
+ if (!as->IsAvailable(_cur_airtype)) continue;
+ unavailable = false;
+ break;
+ }
+ list.push_back(MakeDropDownListStringItem(cls.name, cls.Index(), unavailable));
}
return list;
@@ -275,9 +750,10 @@ class BuildAirportWindow : public PickerWindowBase {
bool selectFirstAirport = true;
if (_selected_airport_index != -1) {
const AirportSpec *as = ac->GetSpec(_selected_airport_index);
- if (as->IsAvailable()) {
+ if (as->IsAvailable(_cur_airtype)) {
/* Ensure the airport layout is valid. */
- _selected_airport_layout = Clamp(_selected_airport_layout, 0, static_cast(as->layouts.size() - 1));
+ _selected_airport_layout = Clamp(_selected_airport_layout, 0, (uint8_t)as->layouts.size() - 1);
+ _selected_rotation = (DiagDirection)Clamp(_selected_rotation, 0, 3);
selectFirstAirport = false;
this->UpdateSelectSize();
}
@@ -313,6 +789,13 @@ class BuildAirportWindow : public PickerWindowBase {
}
break;
+ case WID_AP_ROTATION:
+ SetDParam(0, STR_EMPTY);
+ if (_selected_airport_index != -1) {
+ SetDParam(0, STR_AIRPORT_ROTATION_0 + _selected_rotation);
+ }
+ break;
+
default: break;
}
}
@@ -388,7 +871,7 @@ class BuildAirportWindow : public PickerWindowBase {
auto [first, last] = this->vscroll->GetVisibleRangeIterators(specs);
for (auto it = first; it != last; ++it) {
const AirportSpec *as = *it;
- if (!as->IsAvailable()) {
+ if (!as->IsAvailable(_cur_airtype)) {
GfxFillRect(row, PC_BLACK, FILLRECT_CHECKER);
}
DrawString(text, as->name, (static_cast(as->index) == _selected_airport_index) ? TC_WHITE : TC_BLACK);
@@ -410,7 +893,12 @@ class BuildAirportWindow : public PickerWindowBase {
const AirportSpec *as = AirportClass::Get(_selected_airport_class)->GetSpec(_selected_airport_index);
StringID string = GetAirportTextCallback(as, _selected_airport_layout, CBID_AIRPORT_ADDITIONAL_TEXT);
if (string != STR_UNDEFINED) {
- DrawStringMultiLine(r.left, r.right, r.top, r.bottom, string, TC_BLACK);
+
+ SetDParam(0, string);
+ DrawStringMultiLine(r.left, r.right, r.top, r.bottom, TC_BLACK);
+ } else if (as->layouts.size() > 1) {
+ SetDParam(0, STR_STATION_BUILD_AIRPORT_LAYOUT_NAME);
+ SetDParam(1, _selected_airport_layout + 1);
}
}
break;
@@ -426,23 +914,18 @@ class BuildAirportWindow : public PickerWindowBase {
if (_selected_airport_index != -1) {
const AirportSpec *as = AirportClass::Get(_selected_airport_class)->GetSpec(_selected_airport_index);
- int rad = _settings_game.station.modified_catchment ? as->catchment : (uint)CA_UNMODIFIED;
+ AirType airtype = _settings_game.station.allow_modify_airports ? _cur_airtype : as->airtype;
+ const AirTypeInfo *ati = GetAirTypeInfo(airtype);
+ int rad = _settings_game.station.modified_catchment ? ati->catchment_radius : (uint)CA_UNMODIFIED;
/* only show the station (airport) noise, if the noise option is activated */
if (_settings_game.economy.station_noise_level) {
/* show the noise of the selected airport */
- SetDParam(0, as->noise_level);
+ SetDParam(0, as->GetAirportNoise(airtype));
DrawString(r.left, r.right, top, STR_STATION_BUILD_NOISE);
top += GetCharacterHeight(FS_NORMAL) + WidgetDimensions::scaled.vsep_normal;
}
- if (_settings_game.economy.infrastructure_maintenance) {
- Money monthly = _price[PR_INFRASTRUCTURE_AIRPORT] * as->maintenance_cost >> 3;
- SetDParam(0, monthly * 12);
- DrawString(r.left, r.right, top, TimerGameEconomy::UsingWallclockUnits() ? STR_STATION_BUILD_INFRASTRUCTURE_COST_PERIOD : STR_STATION_BUILD_INFRASTRUCTURE_COST_YEAR);
- top += GetCharacterHeight(FS_NORMAL) + WidgetDimensions::scaled.vsep_normal;
- }
-
/* strings such as 'Size' and 'Coverage Area' */
top = DrawStationCoverageAreaText(r.left, r.right, top, SCT_ALL, rad, false) + WidgetDimensions::scaled.vsep_normal;
top = DrawStationCoverageAreaText(r.left, r.right, top, SCT_ALL, rad, true);
@@ -471,20 +954,22 @@ class BuildAirportWindow : public PickerWindowBase {
SetTileSelectSize(1, 1);
this->DisableWidget(WID_AP_LAYOUT_DECREASE);
this->DisableWidget(WID_AP_LAYOUT_INCREASE);
+ this->DisableWidget(WID_AP_ROTATION_DECREASE);
+ this->DisableWidget(WID_AP_ROTATION_INCREASE);
} else {
const AirportSpec *as = AirportClass::Get(_selected_airport_class)->GetSpec(_selected_airport_index);
- int w = as->size_x;
- int h = as->size_y;
- Direction rotation = as->layouts[_selected_airport_layout].rotation;
- if (rotation == DIR_E || rotation == DIR_W) Swap(w, h);
+ int w = as->layouts[_selected_airport_layout].size_x;
+ int h = as->layouts[_selected_airport_layout].size_y;
+ if (_selected_rotation % 2 != 0) Swap(w, h);
SetTileSelectSize(w, h);
- this->preview_sprite = GetCustomAirportSprite(as, _selected_airport_layout);
+ this->preview_sprite = GetCustomAirportSprite(as, _selected_airport_layout) + _selected_rotation;
this->SetWidgetDisabledState(WID_AP_LAYOUT_DECREASE, _selected_airport_layout == 0);
this->SetWidgetDisabledState(WID_AP_LAYOUT_INCREASE, _selected_airport_layout + 1U >= as->layouts.size());
- int rad = _settings_game.station.modified_catchment ? as->catchment : (uint)CA_UNMODIFIED;
+ const AirTypeInfo *ati = GetAirTypeInfo(as->airtype);
+ int rad = _settings_game.station.modified_catchment ? ati->catchment_radius : (uint)CA_UNMODIFIED;
if (_settings_client.gui.station_show_coverage) SetTileSelectBigSize(-rad, -rad, 2 * rad, 2 * rad);
}
}
@@ -500,7 +985,7 @@ class BuildAirportWindow : public PickerWindowBase {
int32_t num_clicked = this->vscroll->GetScrolledRowFromWidget(pt.y, this, widget, 0, this->line_height);
if (num_clicked == INT32_MAX) break;
const AirportSpec *as = AirportClass::Get(_selected_airport_class)->GetSpec(num_clicked);
- if (as->IsAvailable()) this->SelectOtherAirport(num_clicked);
+ if (as->IsAvailable(_cur_airtype)) this->SelectOtherAirport(num_clicked);
break;
}
@@ -525,6 +1010,18 @@ class BuildAirportWindow : public PickerWindowBase {
this->UpdateSelectSize();
this->SetDirty();
break;
+
+ case WID_AP_ROTATION_DECREASE:
+ _selected_rotation = (DiagDirection)((_selected_rotation + 3) % 4);
+ this->UpdateSelectSize();
+ this->SetDirty();
+ break;
+
+ case WID_AP_ROTATION_INCREASE:
+ _selected_rotation = (DiagDirection)((_selected_rotation + 1) % 4);
+ this->UpdateSelectSize();
+ this->SetDirty();
+ break;
}
}
@@ -538,7 +1035,7 @@ class BuildAirportWindow : public PickerWindowBase {
/* First try to select an airport in the selected class. */
AirportClass *sel_apclass = AirportClass::Get(_selected_airport_class);
for (const AirportSpec *as : sel_apclass->Specs()) {
- if (as->IsAvailable()) {
+ if (as->IsAvailable(_cur_airtype)) {
this->SelectOtherAirport(as->index);
return;
}
@@ -548,7 +1045,7 @@ class BuildAirportWindow : public PickerWindowBase {
* from the first class where airports are available. */
for (const auto &cls : AirportClass::Classes()) {
for (const auto &as : cls.Specs()) {
- if (as->IsAvailable()) {
+ if (as->IsAvailable(_cur_airtype)) {
_selected_airport_class = cls.Index();
this->vscroll->SetCount(cls.GetSpecCount());
this->SelectOtherAirport(as->GetIndex());
@@ -595,12 +1092,16 @@ static constexpr NWidgetPart _nested_build_airport_widgets[] = {
NWidget(WWT_MATRIX, COLOUR_GREY, WID_AP_AIRPORT_LIST), SetFill(1, 0), SetMatrixDataTip(1, 5, STR_STATION_BUILD_AIRPORT_TOOLTIP), SetScrollbar(WID_AP_SCROLLBAR),
NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_AP_SCROLLBAR),
EndContainer(),
- NWidget(WWT_LABEL, COLOUR_DARK_GREEN), SetDataTip(STR_STATION_BUILD_ORIENTATION, STR_NULL), SetFill(1, 0),
NWidget(NWID_HORIZONTAL),
NWidget(WWT_PUSHARROWBTN, COLOUR_GREY, WID_AP_LAYOUT_DECREASE), SetMinimalSize(12, 0), SetDataTip(AWV_DECREASE, STR_NULL),
NWidget(WWT_LABEL, COLOUR_GREY, WID_AP_LAYOUT_NUM), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_JUST_STRING1, STR_NULL),
NWidget(WWT_PUSHARROWBTN, COLOUR_GREY, WID_AP_LAYOUT_INCREASE), SetMinimalSize(12, 0), SetDataTip(AWV_INCREASE, STR_NULL),
EndContainer(),
+ NWidget(NWID_HORIZONTAL),
+ NWidget(WWT_PUSHARROWBTN, COLOUR_GREY, WID_AP_ROTATION_DECREASE), SetMinimalSize(12, 0), SetDataTip(AWV_DECREASE, STR_NULL),
+ NWidget(WWT_LABEL, COLOUR_GREY, WID_AP_ROTATION), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_JUST_STRING, STR_NULL),
+ NWidget(WWT_PUSHARROWBTN, COLOUR_GREY, WID_AP_ROTATION_INCREASE), SetMinimalSize(12, 0), SetDataTip(AWV_INCREASE, STR_NULL),
+ EndContainer(),
NWidget(WWT_EMPTY, COLOUR_DARK_GREEN, WID_AP_EXTRA_TEXT), SetFill(1, 0), SetMinimalSize(150, 0),
NWidget(WWT_LABEL, COLOUR_DARK_GREEN), SetDataTip(STR_STATION_BUILD_COVERAGE_AREA_TITLE, STR_NULL), SetFill(1, 0),
NWidget(NWID_HORIZONTAL), SetPIP(14, 0, 14), SetPIPRatio(1, 0, 1),
@@ -629,8 +1130,652 @@ static void ShowBuildAirportPicker(Window *parent)
new BuildAirportWindow(_build_airport_desc, parent);
}
+struct BuildHangarWindow : public PickerWindowBase {
+ BuildHangarWindow(WindowDesc &desc, Window *parent) : PickerWindowBase(desc, parent)
+ {
+ this->CreateNestedTree();
+ this->LowerWidget(WID_BHW_NE + _rotation_dir);
+ this->FinishInitNested(TRANSPORT_AIR);
+ }
+
+ uint GetHangarSpriteHeight() const { return 48; }
+
+ void UpdateWidgetSize(int widget, Dimension &size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension &fill, [[maybe_unused]] Dimension &resize) override
+ {
+ if (!IsInsideMM(widget, WID_BHW_NE, WID_BHW_NW + 1)) return;
+
+ size.width = ScaleGUITrad(64) + 2;
+ size.height = ScaleGUITrad(52) + 2;
+ }
+
+ void DrawWidget(const Rect &r, int widget) const override
+ {
+ if (!IsInsideMM(widget, WID_BHW_NE, WID_BHW_NW + 1)) return;
+
+ int x = r.left + 1 + ScaleGUITrad(TILE_PIXELS - 1);
+ /* Height of depot sprite in OpenGFX is TILE_PIXELS + GetHangarSpriteHeight(). */
+ int y = r.bottom - ScaleGUITrad(TILE_PIXELS - 1);
+
+ SpriteID ground = GetAirTypeInfo(_cur_airtype)->base_sprites.ground[0];
+ DiagDirection dir = (DiagDirection)(widget - WID_BHW_NE + DIAGDIR_NE);
+ PaletteID palette = COMPANY_SPRITE_COLOUR(_local_company);
+ extern const DrawTileSprites _airport_hangars[4];
+ const DrawTileSprites *dts = &_airport_hangars[dir];
+ DrawSprite(ground, PAL_NONE, x, y);
+ DrawRailTileSeqInGUI(x, y, dts, ground, 0, palette);
+ }
+
+ void OnClick([[maybe_unused]] Point pt, int widget, [[maybe_unused]] int click_count) override
+ {
+ if (!IsInsideMM(widget, WID_BHW_NE, WID_BHW_NW + 1)) return;
+
+ this->RaiseWidget(WID_BHW_NE + _rotation_dir);
+ _rotation_dir = (DiagDirection)(widget - WID_BHW_NE);
+ this->LowerWidget(WID_BHW_NE + _rotation_dir);
+ if (_settings_client.sound.click_beep) SndPlayFx(SND_15_BEEP);
+ this->SetDirty();
+ }
+};
+
+static const NWidgetPart _nested_build_hangar_widgets[] = {
+ NWidget(NWID_HORIZONTAL),
+ NWidget(WWT_CLOSEBOX, COLOUR_DARK_GREEN),
+ NWidget(WWT_CAPTION, COLOUR_DARK_GREEN, WID_BHW_CAPTION), SetDataTip(STR_BUILD_HANGAR_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
+ EndContainer(),
+ NWidget(WWT_PANEL, COLOUR_DARK_GREEN),
+ NWidget(NWID_HORIZONTAL_LTR), SetPIP(0, WidgetDimensions::unscaled.hsep_normal, 0), SetPIPRatio(1, 0, 1), SetPadding(WidgetDimensions::unscaled.picker),
+ NWidget(NWID_VERTICAL), SetPIP(0, WidgetDimensions::unscaled.vsep_normal, 0),
+ NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BHW_NW), SetDataTip(0x0, STR_BUILD_HANGAR_ORIENTATION_TOOLTIP),
+ NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BHW_SW), SetDataTip(0x0, STR_BUILD_HANGAR_ORIENTATION_TOOLTIP),
+ EndContainer(),
+ NWidget(NWID_VERTICAL), SetPIP(0, WidgetDimensions::unscaled.vsep_normal, 0),
+ NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BHW_NE), SetDataTip(0x0, STR_BUILD_HANGAR_ORIENTATION_TOOLTIP),
+ NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BHW_SE), SetDataTip(0x0, STR_BUILD_HANGAR_ORIENTATION_TOOLTIP),
+ EndContainer(),
+ EndContainer(),
+ EndContainer(),
+};
+
+static WindowDesc _build_hangar_desc(
+ WDP_AUTO, nullptr, 0, 0,
+ WC_BUILD_DEPOT, WC_BUILD_TOOLBAR,
+ WDF_CONSTRUCTION,
+ _nested_build_hangar_widgets
+);
+
+static void ShowHangarPicker(Window *parent)
+{
+ new BuildHangarWindow(_build_hangar_desc, parent);
+}
+
+struct SelectTrackGfxWindow : public PickerWindowBase {
+ SelectTrackGfxWindow(WindowDesc &desc, Window *parent) : PickerWindowBase(desc, parent)
+ {
+ this->CreateNestedTree();
+ this->LowerWidget(_selected_track_gfx_index);
+ this->FinishInitNested(TRANSPORT_AIR);
+ }
+
+ void UpdateWidgetSize(int widget, Dimension &size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension &fill, [[maybe_unused]] Dimension &resize) override
+ {
+ if (!IsInsideMM(widget, WID_BASGFAT_AUTO, WID_BASGFAT_20 + 1)) return;
+
+ size.width = ScaleGUITrad(64) + 2;
+ size.height = ScaleGUITrad(32) + 2;
+ }
+
+ void DrawWidget(const Rect &r, int widget) const override
+ {
+ if (!IsInsideMM(widget, WID_BASGFAT_01, WID_BASGFAT_20 + 1)) return;
+
+ int x = r.left + 1 + ScaleGUITrad(TILE_PIXELS - 1);
+ /* Height of depot sprite in OpenGFX is TILE_PIXELS + GetHangarSpriteHeight(). */
+ int y = r.bottom - ScaleGUITrad(TILE_PIXELS - 1);
+
+ SpriteID ground = GetAirTypeInfo(_cur_airtype)->base_sprites.ground[widget - WID_BASGFAT_01];
+ DrawSprite(ground, PAL_NONE, x, y);
+ }
+
+ void OnClick([[maybe_unused]] Point pt, int widget, [[maybe_unused]] int click_count) override
+ {
+ if (!IsInsideMM(widget, WID_BASGFAT_AUTO, WID_BASGFAT_20 + 1)) return;
+
+ this->RaiseWidget(WID_BASGFAT_AUTO + _selected_track_gfx_index);
+ _selected_track_gfx_index = widget - WID_BASGFAT_AUTO;
+ this->LowerWidget(WID_BASGFAT_AUTO + _selected_track_gfx_index);
+ if (_settings_client.sound.click_beep) SndPlayFx(SND_15_BEEP);
+ this->SetDirty();
+ }
+};
+
+static const NWidgetPart _nested_select_track_gfx_widgets[] = {
+ NWidget(NWID_HORIZONTAL),
+ NWidget(WWT_CLOSEBOX, COLOUR_DARK_GREEN),
+ NWidget(WWT_CAPTION, COLOUR_DARK_GREEN, WID_BASGFAT_CAPTION), SetDataTip(STR_SELECT_GFX_AIRPORT_TRACKS_CAPTION, STR_SELECT_GFX_AIRPORT_TRACKS_TOOLTIP),
+ EndContainer(),
+ NWidget(WWT_PANEL, COLOUR_DARK_GREEN),
+ NWidget(NWID_VERTICAL), SetPIP(0, WidgetDimensions::unscaled.vsep_normal, 0), SetPIPRatio(1, 0, 1), SetPadding(WidgetDimensions::unscaled.picker),
+ NWidget(NWID_HORIZONTAL_LTR), SetPIP(0, WidgetDimensions::unscaled.hsep_normal, 0),
+ NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BASGFAT_DEFAULT), SetFill(1, 0),
+ SetDataTip(STR_SELECT_GFX_AIRPORT_TRACKS_DEFAULT, STR_SELECT_GFX_AIRPORT_TRACKS_TOOLTIP),
+ NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BASGFAT_AUTO), SetFill(1, 0),
+ SetDataTip(STR_SELECT_GFX_AIRPORT_TRACKS_DETECT, STR_SELECT_GFX_AIRPORT_TRACKS_TOOLTIP),
+ EndContainer(),
+ NWidget(NWID_HORIZONTAL_LTR), SetPIP(0, WidgetDimensions::unscaled.hsep_normal, 0),
+ NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BASGFAT_01), SetDataTip(0x0, STR_SELECT_GFX_AIRPORT_TRACKS_TOOLTIP),
+ NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BASGFAT_06), SetDataTip(0x0, STR_SELECT_GFX_AIRPORT_TRACKS_TOOLTIP),
+ NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BASGFAT_07), SetDataTip(0x0, STR_SELECT_GFX_AIRPORT_TRACKS_TOOLTIP),
+ NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BASGFAT_08), SetDataTip(0x0, STR_SELECT_GFX_AIRPORT_TRACKS_TOOLTIP),
+ EndContainer(),
+ NWidget(NWID_HORIZONTAL_LTR), SetPIP(0, WidgetDimensions::unscaled.hsep_normal, 0),
+ NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BASGFAT_13), SetDataTip(0x0, STR_SELECT_GFX_AIRPORT_TRACKS_TOOLTIP),
+ NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BASGFAT_14), SetDataTip(0x0, STR_SELECT_GFX_AIRPORT_TRACKS_TOOLTIP),
+ NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BASGFAT_15), SetDataTip(0x0, STR_SELECT_GFX_AIRPORT_TRACKS_TOOLTIP),
+ NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BASGFAT_16), SetDataTip(0x0, STR_SELECT_GFX_AIRPORT_TRACKS_TOOLTIP),
+ EndContainer(),
+ NWidget(NWID_HORIZONTAL_LTR), SetPIP(0, WidgetDimensions::unscaled.hsep_normal, 0),
+ NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BASGFAT_09), SetDataTip(0x0, STR_SELECT_GFX_AIRPORT_TRACKS_TOOLTIP),
+ NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BASGFAT_10), SetDataTip(0x0, STR_SELECT_GFX_AIRPORT_TRACKS_TOOLTIP),
+ NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BASGFAT_11), SetDataTip(0x0, STR_SELECT_GFX_AIRPORT_TRACKS_TOOLTIP),
+ NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BASGFAT_12), SetDataTip(0x0, STR_SELECT_GFX_AIRPORT_TRACKS_TOOLTIP),
+ EndContainer(),
+ NWidget(NWID_HORIZONTAL_LTR), SetPIP(0, WidgetDimensions::unscaled.hsep_normal, 0),
+ NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BASGFAT_02), SetDataTip(0x0, STR_SELECT_GFX_AIRPORT_TRACKS_TOOLTIP),
+ NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BASGFAT_03), SetDataTip(0x0, STR_SELECT_GFX_AIRPORT_TRACKS_TOOLTIP),
+ NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BASGFAT_04), SetDataTip(0x0, STR_SELECT_GFX_AIRPORT_TRACKS_TOOLTIP),
+ NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BASGFAT_05), SetDataTip(0x0, STR_SELECT_GFX_AIRPORT_TRACKS_TOOLTIP),
+ EndContainer(),
+ NWidget(NWID_HORIZONTAL_LTR), SetPIP(0, WidgetDimensions::unscaled.hsep_normal, 0),
+ NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BASGFAT_17), SetDataTip(0x0, STR_SELECT_GFX_AIRPORT_TRACKS_TOOLTIP),
+ NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BASGFAT_18), SetDataTip(0x0, STR_SELECT_GFX_AIRPORT_TRACKS_TOOLTIP),
+ NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BASGFAT_19), SetDataTip(0x0, STR_SELECT_GFX_AIRPORT_TRACKS_TOOLTIP),
+ NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BASGFAT_20), SetDataTip(0x0, STR_SELECT_GFX_AIRPORT_TRACKS_TOOLTIP),
+ EndContainer(),
+ EndContainer(),
+ EndContainer(),
+};
+
+static WindowDesc _select_track_gfx_widgets(
+ WDP_AUTO, nullptr, 0, 0,
+ WC_SELECT_TRACK_GFX, WC_BUILD_TOOLBAR,
+ WDF_CONSTRUCTION,
+ _nested_select_track_gfx_widgets
+);
+
+static void ShowTrackGfxPicker(Window *parent)
+{
+ new SelectTrackGfxWindow(_select_track_gfx_widgets, parent);
+}
+
+struct BuildHeliportWindow : public PickerWindowBase {
+ BuildHeliportWindow(WindowDesc &desc, Window *parent) : PickerWindowBase(desc, parent)
+ {
+ this->CreateNestedTree();
+ this->LowerWidget(WID_BHW_NE + _rotation_dir);
+ this->FinishInitNested(TRANSPORT_AIR);
+ }
+
+ uint GetHeliportSpriteHeight() const { return 91; }
+
+ void UpdateWidgetSize(int widget, Dimension &size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension &fill, [[maybe_unused]] Dimension &resize) override
+ {
+ if (!IsInsideMM(widget, WID_BHW_NE, WID_BHW_NW + 1)) return;
+
+ size.width = ScaleGUITrad(64) + 2;
+ size.height = ScaleGUITrad(GetHeliportSpriteHeight()) + 2;
+ }
+
+ void DrawWidget(const Rect &r, int widget) const override
+ {
+ if (!IsInsideMM(widget, WID_BHW_NE, WID_BHW_NW + 1)) return;
+
+ int x = r.left + 1 + ScaleGUITrad(TILE_PIXELS - 1);
+ /* Height of depot sprite in OpenGFX is TILE_PIXELS + GetHangarSpriteHeight(). */
+ int y = r.bottom - ScaleGUITrad(TILE_PIXELS - 1);
+
+ SpriteID ground = GetAirTypeInfo(_cur_airtype)->base_sprites.ground[0];
+ DiagDirection dir = (DiagDirection)(widget - WID_BHW_NE + DIAGDIR_NE);
+ PaletteID palette = COMPANY_SPRITE_COLOUR(_local_company);
+ extern const DrawTileSprites _airport_heliports[];
+ const DrawTileSprites *dts = &_airport_heliports[0];
+ DrawSprite(ground, PAL_NONE, x, y);
+ DrawRailTileSeqInGUI(x, y, dts, ground + dir, 0, palette);
+ }
+
+ void OnClick([[maybe_unused]] Point pt, int widget, [[maybe_unused]] int click_count) override
+ {
+ switch (widget) {
+ case WID_BHW_NW:
+ case WID_BHW_NE:
+ case WID_BHW_SW:
+ case WID_BHW_SE:
+ this->RaiseWidget(WID_BHW_NE + _rotation_dir);
+ _rotation_dir = (DiagDirection)(widget - WID_BHW_NE);
+ this->LowerWidget(WID_BHW_NE + _rotation_dir);
+ if (_settings_client.sound.click_beep) SndPlayFx(SND_15_BEEP);
+ this->SetDirty();
+ break;
+
+ default:
+ break;
+ }
+ }
+};
+
+static const NWidgetPart _nested_build_heliport_widgets[] = {
+ NWidget(NWID_HORIZONTAL),
+ NWidget(WWT_CLOSEBOX, COLOUR_DARK_GREEN),
+ NWidget(WWT_CAPTION, COLOUR_DARK_GREEN, WID_BHW_CAPTION), SetDataTip(STR_BUILD_HELIPORT_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
+ EndContainer(),
+ NWidget(WWT_PANEL, COLOUR_DARK_GREEN),
+ NWidget(NWID_HORIZONTAL_LTR), SetPIP(0, WidgetDimensions::unscaled.hsep_normal, 0), SetPIPRatio(1, 0, 1), SetPadding(WidgetDimensions::unscaled.picker),
+ NWidget(NWID_VERTICAL), SetPIP(0, WidgetDimensions::unscaled.vsep_normal, 0),
+ NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BHW_NW), SetDataTip(0x0, STR_BUILD_HELIPORT_ORIENTATION_TOOLTIP),
+ NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BHW_SW), SetDataTip(0x0, STR_BUILD_HELIPORT_ORIENTATION_TOOLTIP),
+ EndContainer(),
+ NWidget(NWID_VERTICAL), SetPIP(0, WidgetDimensions::unscaled.vsep_normal, 0),
+ NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BHW_NE), SetDataTip(0x0, STR_BUILD_HELIPORT_ORIENTATION_TOOLTIP),
+ NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BHW_SE), SetDataTip(0x0, STR_BUILD_HELIPORT_ORIENTATION_TOOLTIP),
+ EndContainer(),
+ EndContainer(),
+ EndContainer(),
+};
+
+static WindowDesc _build_heliport_desc(
+ WDP_AUTO, nullptr, 0, 0,
+ WC_BUILD_HELIPORT, WC_BUILD_TOOLBAR,
+ WDF_CONSTRUCTION,
+ _nested_build_heliport_widgets
+);
+
+static void ShowHeliportPicker(Window *parent)
+{
+ new BuildHeliportWindow(_build_heliport_desc, parent);
+}
+
+struct BuildAirportInfraNoCatchWindow : public PickerWindowBase {
+ BuildAirportInfraNoCatchWindow(WindowDesc &desc, Window *parent) : PickerWindowBase(desc, parent)
+ {
+ this->CreateNestedTree();
+ this->LowerWidget(WID_BAINC_FLAG + (BuildAirportInfrastructureNoCatchmentWidgets)_selected_infra_nocatch);
+ this->FinishInitNested(TRANSPORT_AIR);
+ }
+
+ uint GetHeliportSpriteHeight() const { return 97; }
+
+ void UpdateWidgetSize(int widget, Dimension &size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension &fill, [[maybe_unused]] Dimension &resize) override
+ {
+ if (!IsInsideMM(widget, WID_BAINC_FLAG, WID_BAINC_EMPTY + 1)) return;
+
+ size.width = ScaleGUITrad(64) + 2;
+ size.height = ScaleGUITrad(GetHeliportSpriteHeight()) + 2;
+ }
+
+ void DrawWidget(const Rect &r, int widget) const override
+ {
+ if (!IsInsideMM(widget, WID_BAINC_FLAG, WID_BAINC_EMPTY + 1)) return;
+
+ int x = r.left + 1 + ScaleGUITrad(TILE_PIXELS - 1);
+ int y = r.bottom - ScaleGUITrad(TILE_PIXELS - 1);
+
+ SpriteID ground = GetAirTypeInfo(_cur_airtype)->base_sprites.ground[0];
+ PaletteID palette = COMPANY_SPRITE_COLOUR(_local_company);
+ extern const DrawTileSprites _airtype_display_datas[];
+ extern const DrawTileSprites _airtype_display_datas_radar[];
+ extern const DrawTileSprites _airtype_display_datas_tower[];
+ extern const DrawTileSprites _airtype_display_datas_transmitter[];
+ const DrawTileSprites *dts = nullptr;
+ DrawSprite(ground, PAL_NONE, x, y);
+
+ switch (widget) {
+ case WID_BAINC_FLAG: {
+ extern const DrawTileSprites _airtype_display_datas_flag_NE[];
+ extern const DrawTileSprites _airtype_display_datas_flag_SE[];
+ extern const DrawTileSprites _airtype_display_datas_flag_SW[];
+ extern const DrawTileSprites _airtype_display_datas_flag_NW[];
+
+ const DrawTileSprites *flags[4] = {
+ _airtype_display_datas_flag_NE,
+ _airtype_display_datas_flag_SE,
+ _airtype_display_datas_flag_SW,
+ _airtype_display_datas_flag_NW,
+ };
+
+ ground = 0;
+ dts = flags[_selected_infra_nocatch_rotation];
+ break;
+ }
+ case WID_BAINC_RADAR:
+ ground = 0;
+ dts = &_airtype_display_datas_radar[2];
+ break;
+ case WID_BAINC_TOWER:
+ dts = &_airtype_display_datas_tower[_selected_infra_nocatch_rotation];
+ break;
+ case WID_BAINC_TRANSMITTER:
+ dts = &_airtype_display_datas_transmitter[_selected_infra_nocatch_rotation];
+ break;
+
+ case WID_BAINC_EMPTY:
+ case WID_BAINC_PIER:
+ dts = &_airtype_display_datas[(widget - WID_BAINC_FLAG + ATTG_NO_CATCH_FLAG) * 4 + _selected_infra_nocatch_rotation];
+ break;
+ default:
+ NOT_REACHED();
+ }
+ if (dts != nullptr) DrawRailTileSeqInGUI(x, y, dts, ground, 0, palette);
+ }
+
+ void SetStringParameters(int widget) const override
+ {
+ if (widget != WID_BAINC_ROTATION) return;
+
+ SetDParam(0, STR_EMPTY);
+ SetDParam(0, STR_AIRPORT_ROTATION_0 + _selected_infra_nocatch_rotation);
+ }
+
+ void OnClick([[maybe_unused]] Point pt, int widget, [[maybe_unused]] int click_count) override
+ {
+ switch (widget) {
+ case WID_BAINC_FLAG:
+ case WID_BAINC_TRANSMITTER:
+ case WID_BAINC_TOWER:
+ case WID_BAINC_RADAR:
+ case WID_BAINC_PIER:
+ case WID_BAINC_EMPTY:
+ this->RaiseWidget(WID_BAINC_FLAG + (BuildAirportInfrastructureNoCatchmentWidgets)_selected_infra_nocatch);
+ _selected_infra_nocatch = (AirportTiles)(widget - WID_BAINC_FLAG);
+ this->LowerWidget(WID_BAINC_FLAG + (BuildAirportInfrastructureNoCatchmentWidgets)_selected_infra_nocatch);
+ if (_settings_client.sound.click_beep) SndPlayFx(SND_15_BEEP);
+ this->SetDirty();
+ break;
+
+ case WID_BAINC_ROTATION_DECREASE:
+ _selected_infra_nocatch_rotation = ((int)_selected_infra_nocatch_rotation + 3) % 4;
+ this->SetDirty();
+ break;
+
+ case WID_BAINC_ROTATION_INCREASE:
+ _selected_infra_nocatch_rotation = (_selected_infra_nocatch_rotation + 1) % 4;
+ this->SetDirty();
+ break;
+
+ default:
+ break;
+ }
+ }
+};
+
+static const NWidgetPart _nested_build_airport_infra_no_catch_widgets[] = {
+ NWidget(NWID_HORIZONTAL),
+ NWidget(WWT_CLOSEBOX, COLOUR_DARK_GREEN),
+ NWidget(WWT_CAPTION, COLOUR_DARK_GREEN, WID_BAINC_CAPTION), SetDataTip(STR_BUILD_AIRPORT_INFRA_NO_CATCH_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
+ EndContainer(),
+ NWidget(WWT_PANEL, COLOUR_DARK_GREEN),
+ /* Graphics */
+ NWidget(NWID_HORIZONTAL_LTR), SetPIP(0, WidgetDimensions::unscaled.hsep_normal, 0), SetPIPRatio(1, 0, 1), SetPadding(WidgetDimensions::unscaled.picker),
+ NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BAINC_FLAG), SetDataTip(0x0, STR_BUILD_AIRPORT_INFRA_TOOLTIP),
+ NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BAINC_TRANSMITTER), SetDataTip(0x0, STR_BUILD_AIRPORT_INFRA_TOOLTIP),
+ NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BAINC_TOWER), SetDataTip(0x0, STR_BUILD_AIRPORT_INFRA_TOOLTIP),
+ NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BAINC_RADAR), SetDataTip(0x0, STR_BUILD_AIRPORT_INFRA_TOOLTIP),
+ NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BAINC_PIER), SetDataTip(0x0, STR_BUILD_AIRPORT_INFRA_TOOLTIP),
+ NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BAINC_EMPTY), SetDataTip(0x0, STR_BUILD_AIRPORT_INFRA_TOOLTIP),
+ EndContainer(),
+ /* Rotation */
+ NWidget(NWID_HORIZONTAL),
+ NWidget(NWID_SPACER), SetMinimalSize(3, 0), SetFill(1, 0),
+ NWidget(WWT_PUSHARROWBTN, COLOUR_GREY, WID_BAINC_ROTATION_DECREASE), SetMinimalSize(12, 0), SetDataTip(AWV_DECREASE, STR_NULL),
+ NWidget(WWT_LABEL, COLOUR_GREY, WID_BAINC_ROTATION), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_JUST_STRING, STR_NULL),
+ NWidget(WWT_PUSHARROWBTN, COLOUR_GREY, WID_BAINC_ROTATION_INCREASE), SetMinimalSize(12, 0), SetDataTip(AWV_INCREASE, STR_NULL),
+ NWidget(NWID_SPACER), SetMinimalSize(3, 0), SetFill(1, 0),
+ EndContainer(),
+ NWidget(NWID_SPACER), SetMinimalSize(0, 3), SetFill(1, 0),
+ EndContainer(),
+};
+
+static WindowDesc _build_airport_infra_no_catch_desc(
+ WDP_ALIGN_TOOLBAR, nullptr, 0, 0,
+ WC_BUILD_AIRPORT_INFRASTRUCTURE, WC_BUILD_TOOLBAR,
+ WDF_CONSTRUCTION,
+ _nested_build_airport_infra_no_catch_widgets
+);
+
+static void ShowAirportInfraNoCatchPicker(Window *parent)
+{
+ new BuildAirportInfraNoCatchWindow(_build_airport_infra_no_catch_desc, parent);
+}
+
+struct BuildAirportInfraWithCatchWindow : public PickerWindowBase {
+ BuildAirportInfraWithCatchWindow(WindowDesc &desc, Window *parent) : PickerWindowBase(desc, parent)
+ {
+ this->CreateNestedTree();
+ this->LowerWidget(WID_BAIWC_BUILDING_1 + (BuildAirportInfrastructureNoCatchmentWidgets)_selected_infra_catch);
+ this->FinishInitNested(TRANSPORT_AIR);
+ }
+
+ uint GetHeliportSpriteHeight() const { return 91; }
+
+ void UpdateWidgetSize(int widget, Dimension &size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension &fill, [[maybe_unused]] Dimension &resize) override
+ {
+ if (!IsInsideMM(widget, WID_BAIWC_BUILDING_1, WID_BAIWC_BUILDING_TERMINAL + 1)) return;
+
+ size.width = ScaleGUITrad(64) + 2;
+ size.height = ScaleGUITrad(GetHeliportSpriteHeight()) + 2;
+ }
+
+ void DrawWidget(const Rect &r, int widget) const override
+ {
+ if (!IsInsideMM(widget, WID_BAIWC_BUILDING_1, WID_BAIWC_BUILDING_TERMINAL + 1)) return;
+
+ int x = r.left + 1 + ScaleGUITrad(TILE_PIXELS - 1);
+ int y = r.bottom - ScaleGUITrad(TILE_PIXELS - 1);
+
+ SpriteID ground = GetAirTypeInfo(_cur_airtype)->base_sprites.ground[0];
+ PaletteID palette = COMPANY_SPRITE_COLOUR(_local_company);
+ extern const DrawTileSprites _airtype_display_datas[];
+ const DrawTileSprites *dts = &_airtype_display_datas[
+ (widget - WID_BAIWC_BUILDING_1 + ATTG_WITH_CATCH_BUILDING_1) * 4 + _selected_infra_catch_rotation];
+ DrawSprite(ground, PAL_NONE, x, y);
+ DrawRailTileSeqInGUI(x, y, dts, ground, 0, palette);
+ }
+
+ void SetStringParameters(int widget) const override
+ {
+ if (widget != WID_BAIWC_ROTATION) return;
+
+ SetDParam(0, STR_EMPTY);
+ SetDParam(0, STR_AIRPORT_ROTATION_0 + _selected_infra_catch_rotation);
+ }
+
+ void OnClick([[maybe_unused]] Point pt, int widget, [[maybe_unused]] int click_count) override
+ {
+ switch (widget) {
+ case WID_BAIWC_BUILDING_1:
+ case WID_BAIWC_BUILDING_2:
+ case WID_BAIWC_BUILDING_3:
+ case WID_BAIWC_BUILDING_FLAT:
+ case WID_BAIWC_BUILDING_TERMINAL:
+ this->RaiseWidget(WID_BAIWC_BUILDING_1 + (BuildAirportInfrastructureNoCatchmentWidgets)_selected_infra_catch);
+ _selected_infra_catch = (AirportTiles)(widget - WID_BAIWC_BUILDING_1);
+ this->LowerWidget(WID_BAIWC_BUILDING_1 + (BuildAirportInfrastructureNoCatchmentWidgets)_selected_infra_catch);
+ if (_settings_client.sound.click_beep) SndPlayFx(SND_15_BEEP);
+ this->SetDirty();
+ break;
+
+ case WID_BAIWC_ROTATION_DECREASE:
+ _selected_infra_catch_rotation = (_selected_infra_catch_rotation + 3) % 4;
+ this->SetDirty();
+ break;
+
+ case WID_BAIWC_ROTATION_INCREASE:
+ _selected_infra_catch_rotation = (_selected_infra_catch_rotation + 1) % 4;
+ this->SetDirty();
+ break;
+
+ default:
+ break;
+ }
+ }
+};
+
+static const NWidgetPart _nested_build_airport_infra_with_catch_widgets[] = {
+ NWidget(NWID_HORIZONTAL),
+ NWidget(WWT_CLOSEBOX, COLOUR_DARK_GREEN),
+ NWidget(WWT_CAPTION, COLOUR_DARK_GREEN, WID_BAIWC_CAPTION), SetDataTip(STR_BUILD_AIRPORT_INFRA_WITH_CATCH_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
+ EndContainer(),
+ NWidget(WWT_PANEL, COLOUR_DARK_GREEN),
+ /* Graphics */
+ NWidget(NWID_HORIZONTAL_LTR), SetPIP(0, WidgetDimensions::unscaled.hsep_normal, 0), SetPIPRatio(1, 0, 1), SetPadding(WidgetDimensions::unscaled.picker),
+ NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BAIWC_BUILDING_1), SetDataTip(0x0, STR_BUILD_AIRPORT_INFRA_TOOLTIP),
+ NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BAIWC_BUILDING_2), SetDataTip(0x0, STR_BUILD_AIRPORT_INFRA_TOOLTIP),
+ NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BAIWC_BUILDING_3), SetDataTip(0x0, STR_BUILD_AIRPORT_INFRA_TOOLTIP),
+ NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BAIWC_BUILDING_FLAT), SetDataTip(0x0, STR_BUILD_AIRPORT_INFRA_TOOLTIP),
+ NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BAIWC_BUILDING_TERMINAL), SetDataTip(0x0, STR_BUILD_AIRPORT_INFRA_TOOLTIP),
+ EndContainer(),
+ /* Rotation */
+ NWidget(NWID_HORIZONTAL),
+ NWidget(NWID_SPACER), SetMinimalSize(3, 0), SetFill(1, 0),
+ NWidget(WWT_PUSHARROWBTN, COLOUR_GREY, WID_BAIWC_ROTATION_DECREASE), SetMinimalSize(12, 0), SetDataTip(AWV_DECREASE, STR_NULL),
+ NWidget(WWT_LABEL, COLOUR_GREY, WID_BAIWC_ROTATION), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_JUST_STRING, STR_NULL),
+ NWidget(WWT_PUSHARROWBTN, COLOUR_GREY, WID_BAIWC_ROTATION_INCREASE), SetMinimalSize(12, 0), SetDataTip(AWV_INCREASE, STR_NULL),
+ NWidget(NWID_SPACER), SetMinimalSize(3, 0), SetFill(1, 0),
+ EndContainer(),
+ NWidget(NWID_SPACER), SetMinimalSize(0, 3), SetFill(1, 0),
+ EndContainer(),
+};
+
+static WindowDesc _build_airport_infra_with_catch_desc(
+ WDP_ALIGN_TOOLBAR, nullptr, 0, 0,
+ WC_BUILD_AIRPORT_INFRASTRUCTURE, WC_BUILD_TOOLBAR,
+ WDF_CONSTRUCTION,
+ _nested_build_airport_infra_with_catch_widgets
+);
+
+static void ShowAirportInfraWithCatchPicker(Window *parent)
+{
+ new BuildAirportInfraWithCatchWindow(_build_airport_infra_with_catch_desc, parent);
+}
+
+
+/** Set the initial (default) airtype to use */
+static void SetDefaultAirGui()
+{
+ if (_local_company == COMPANY_SPECTATOR || !Company::IsValidID(_local_company)) return;
+
+ extern AirType _last_built_airtype;
+ AirType rt;
+ switch (_settings_client.gui.default_air_type) {
+ case 2: {
+ /* Find the most used air type */
+ uint count[AIRTYPE_END];
+ memset(count, 0, sizeof(count));
+ for (TileIndex t = 0; t < Map::Size(); t++) {
+ if (IsAirportTile(t) && GetTileOwner(t) == _local_company) {
+ count[GetAirType(t)]++;
+ }
+ }
+
+ rt = static_cast(std::max_element(count + AIRTYPE_BEGIN, count + AIRTYPE_END) - count);
+ if (count[rt] > 0) break;
+
+ /* No air, just get the first available one */
+ [[fallthrough]];
+ }
+ case 0: {
+ /* Use first available type */
+ std::vector::const_iterator it = std::find_if(_sorted_airtypes.begin(), _sorted_airtypes.end(),
+ [](AirType r){ return HasAirTypeAvail(_local_company, r); });
+ rt = it != _sorted_airtypes.end() ? *it : AIRTYPE_BEGIN;
+ break;
+ }
+ case 1: {
+ /* Use last available type */
+ std::vector::const_reverse_iterator it = std::find_if(_sorted_airtypes.rbegin(), _sorted_airtypes.rend(),
+ [](AirType r){ return HasAirTypeAvail(_local_company, r); });
+ rt = it != _sorted_airtypes.rend() ? *it : AIRTYPE_BEGIN;
+ break;
+ }
+ default:
+ NOT_REACHED();
+ }
+
+ _last_built_airtype = _cur_airtype = rt;
+ BuildAirToolbarWindow *w = dynamic_cast(FindWindowById(WC_BUILD_TOOLBAR, TRANSPORT_AIR));
+ if (w != nullptr) w->ModifyAirType(_cur_airtype);
+}
+
+/**
+ * Create a drop down list for all the air types of the local company.
+ * @param for_replacement Whether this list is for the replacement window.
+ * @param all_option Whether to add an 'all types' item.
+ * @return The populated and sorted #DropDownList.
+ */
+DropDownList GetAirTypeDropDownList(bool for_replacement, bool all_option)
+{
+ AirTypes used_airtypes;
+ AirTypes avail_airtypes;
+
+ const Company *c = Company::Get(_local_company);
+
+ /* Find the used airtypes. */
+ if (for_replacement) {
+ avail_airtypes = GetCompanyAirTypes(c->index, false);
+ used_airtypes = GetAirTypes(false);
+ } else {
+ avail_airtypes = c->avail_airtypes;
+ used_airtypes = GetAirTypes(true);
+ }
+
+ DropDownList list;
+
+ if (all_option) {
+ list.push_back(MakeDropDownListStringItem(STR_REPLACE_ALL_AIRTYPE, INVALID_AIRTYPE, false));
+ }
+
+ Dimension d = { 0, 0 };
+ /* Get largest icon size, to ensure text is aligned on each menu item. */
+ if (!for_replacement) {
+ for (const auto &at : _sorted_airtypes) {
+ if (!HasBit(used_airtypes, at)) continue;
+ const AirTypeInfo *ati = GetAirTypeInfo(at);
+ d = maxdim(d, GetSpriteSize(ati->gui_sprites.build_helipad));
+ }
+ }
+
+ for (const auto &at : _sorted_airtypes) {
+ /* If it's not used ever, don't show it to the user. */
+ if (!HasBit(used_airtypes, at)) continue;
+
+ const AirTypeInfo *ati = GetAirTypeInfo(at);
+ SetDParam(0, ati->strings.menu_text);
+ SetDParam(1, ati->max_speed);
+
+ if (for_replacement) {
+ list.push_back(MakeDropDownListStringItem(ati->strings.replace_text, at, !HasBit(avail_airtypes, at)));
+ } else {
+ StringID str = ati->max_speed > 0 ? STR_TOOLBAR_RAILTYPE_VELOCITY : STR_JUST_STRING;
+ auto iconitem = MakeDropDownListIconItem(d, ati->gui_sprites.build_helipad, PAL_NONE, str, at, !HasBit(avail_airtypes, at));
+ list.push_back(std::move(iconitem));
+ }
+ }
+
+ if (list.empty()) {
+ /* Empty dropdowns are not allowed */
+ list.push_back(MakeDropDownListStringItem(STR_NONE, INVALID_AIRTYPE, true));
+ }
+
+ return list;
+}
+
void InitializeAirportGui()
{
+ SetDefaultAirGui();
+
_selected_airport_class = APC_BEGIN;
_selected_airport_index = -1;
+ _selected_infra_catch_rotation = 0;
+ _selected_infra_catch = ATTG_DEFAULT_GFX;
+ _selected_infra_nocatch_rotation = 0;
+ _selected_infra_nocatch = ATTG_DEFAULT_GFX;
}
+
diff --git a/src/airport_gui.h b/src/airport_gui.h
new file mode 100644
index 0000000000000..cd64dcc2cd277
--- /dev/null
+++ b/src/airport_gui.h
@@ -0,0 +1,21 @@
+/*
+ * 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 airport_gui.h Various declarations for airports */
+
+#ifndef AIRPORT_GUI_H
+#define AIRPORT_GUI_H
+
+#include "air_type.h"
+#include "dropdown_type.h"
+
+struct Window *ShowBuildAirToolbar(AirType airtype);
+DropDownList GetAirTypeDropDownList(bool for_replacement = false, bool all_option = false);
+
+void InitializeAirportGui();
+
+#endif /* AIRPORT_GUI_H */
diff --git a/src/autoreplace_cmd.cpp b/src/autoreplace_cmd.cpp
index 757f18d371da3..d94249e188084 100644
--- a/src/autoreplace_cmd.cpp
+++ b/src/autoreplace_cmd.cpp
@@ -20,6 +20,7 @@
#include "core/random_func.hpp"
#include "vehiclelist.h"
#include "road.h"
+#include "ship.h"
#include "ai/ai.hpp"
#include "news_func.h"
#include "strings_func.h"
@@ -28,6 +29,9 @@
#include "order_cmd.h"
#include "train_cmd.h"
#include "vehicle_cmd.h"
+#include "depot_map.h"
+#include "train_placement.h"
+#include "news_func.h"
#include "table/strings.h"
@@ -71,7 +75,10 @@ bool CheckAutoreplaceValidity(EngineID from, EngineID to, CompanyID company)
switch (type) {
case VEH_TRAIN: {
/* make sure the railtypes are compatible */
- if ((GetRailTypeInfo(e_from->u.rail.railtype)->compatible_railtypes & GetRailTypeInfo(e_to->u.rail.railtype)->compatible_railtypes) == 0) return false;
+ if (!_settings_game.depot.allow_no_comp_railtype_replacements &&
+ (GetRailTypeInfo(e_from->u.rail.railtype)->compatible_railtypes & GetRailTypeInfo(e_to->u.rail.railtype)->compatible_railtypes) == 0) {
+ return false;
+ }
/* make sure we do not replace wagons with engines or vice versa */
if ((e_from->u.rail.railveh_type == RAILVEH_WAGON) != (e_to->u.rail.railveh_type == RAILVEH_WAGON)) return false;
@@ -79,11 +86,15 @@ bool CheckAutoreplaceValidity(EngineID from, EngineID to, CompanyID company)
}
case VEH_ROAD:
- /* make sure the roadtypes are compatible */
- if ((GetRoadTypeInfo(e_from->u.road.roadtype)->powered_roadtypes & GetRoadTypeInfo(e_to->u.road.roadtype)->powered_roadtypes) == ROADTYPES_NONE) return false;
+ if (!_settings_game.depot.allow_no_comp_roadtype_replacements) {
+ /* make sure the roadtypes are compatible */
+ if ((GetRoadTypeInfo(e_from->u.road.roadtype)->powered_roadtypes & GetRoadTypeInfo(e_to->u.road.roadtype)->powered_roadtypes) == ROADTYPES_NONE) {
+ return false;
+ }
- /* make sure that we do not replace a tram with a normal road vehicles or vice versa */
- if (HasBit(e_from->info.misc_flags, EF_ROAD_TRAM) != HasBit(e_to->info.misc_flags, EF_ROAD_TRAM)) return false;
+ /* make sure that we do not replace a tram with a normal road vehicles or vice versa */
+ if (HasBit(e_from->info.misc_flags, EF_ROAD_TRAM) != HasBit(e_to->info.misc_flags, EF_ROAD_TRAM)) return false;
+ }
break;
case VEH_AIRCRAFT:
@@ -364,13 +375,13 @@ static CommandCost BuildReplacementVehicle(Vehicle *old_veh, Vehicle **new_vehic
if (refit_cargo != CARGO_NO_REFIT) {
uint8_t subtype = GetBestFittingSubType(old_veh, new_veh, refit_cargo);
- cost.AddCost(std::get<0>(Command::Do(DC_EXEC, new_veh->index, refit_cargo, subtype, false, false, 0)));
+ cost.AddCost(std::get<0>(Command::Do(DC_EXEC | DC_AUTOREPLACE, new_veh->index, refit_cargo, subtype, false, false, 0)));
assert(cost.Succeeded()); // This should be ensured by GetNewCargoTypeForReplace()
}
/* Try to reverse the vehicle, but do not care if it fails as the new type might not be reversible */
if (new_veh->type == VEH_TRAIN && HasBit(Train::From(old_veh)->flags, VRF_REVERSE_DIRECTION)) {
- Command::Do(DC_EXEC, new_veh->index, true);
+ Command::Do(DC_EXEC | DC_AUTOREPLACE, new_veh->index, true);
}
return cost;
@@ -465,7 +476,7 @@ static CommandCost ReplaceFreeUnit(Vehicle **single_unit, DoCommandFlag flags, b
if ((flags & DC_EXEC) != 0) {
/* Move the new vehicle behind the old */
- CmdMoveVehicle(new_v, old_v, DC_EXEC, false);
+ CmdMoveVehicle(new_v, old_v, DC_EXEC | DC_AUTOREPLACE, false);
/* Take over cargo
* Note: We do only transfer cargo from the old to the new vehicle.
@@ -485,7 +496,7 @@ static CommandCost ReplaceFreeUnit(Vehicle **single_unit, DoCommandFlag flags, b
/* If we are not in DC_EXEC undo everything */
if ((flags & DC_EXEC) == 0) {
- Command::Do(DC_EXEC, new_v->index, false, false, INVALID_CLIENT_ID);
+ Command::Do(DC_EXEC | DC_AUTOREPLACE, new_v->index, false, false, INVALID_CLIENT_ID);
}
}
@@ -507,6 +518,25 @@ struct ReplaceChainItem {
Vehicle *GetVehicle() const { return new_veh == nullptr ? old_veh : new_veh; }
};
+/**
+ * When replacing a ship in an extended depot, copy the direction as well.
+ * @param old_ship The ship being replaced.
+ * @param new_ship The new ship that will replace the old one.
+ */
+void CopyShipStatusInExtendedDepot(const Ship *old_ship, Ship *new_ship)
+{
+ assert(IsExtendedDepotTile(old_ship->tile));
+ assert(old_ship->tile == new_ship->tile);
+
+ new_ship->x_pos = old_ship->x_pos;
+ new_ship->y_pos = old_ship->y_pos;
+ new_ship->z_pos = old_ship->z_pos;
+ new_ship->state = old_ship->state;
+ new_ship->direction = old_ship->direction;
+ new_ship->rotation = old_ship->rotation;
+ new_ship->GetImage(new_ship->direction, EIT_ON_MAP, &new_ship->sprite_cache.sprite_seq);
+}
+
/**
* Replace a whole vehicle chain
* @param chain vehicle chain to let autoreplace/renew operator on
@@ -517,8 +547,11 @@ struct ReplaceChainItem {
*/
static CommandCost ReplaceChain(Vehicle **chain, DoCommandFlag flags, bool wagon_removal, bool *nothing_to_do)
{
+ assert(flags & DC_AUTOREPLACE);
+
Vehicle *old_head = *chain;
assert(old_head->IsPrimaryVehicle());
+ TileIndex tile = old_head->tile;
CommandCost cost = CommandCost(EXPENSES_NEW_VEHICLES, (Money)0);
@@ -569,7 +602,7 @@ static CommandCost ReplaceChain(Vehicle **chain, DoCommandFlag flags, bool wagon
}
if (last_engine == nullptr) last_engine = append;
- cost.AddCost(CmdMoveVehicle(append, new_head, DC_EXEC, false));
+ cost.AddCost(CmdMoveVehicle(append, new_head, DC_EXEC | DC_AUTOREPLACE, false));
if (cost.Failed()) break;
}
if (last_engine == nullptr) last_engine = new_head;
@@ -588,7 +621,7 @@ static CommandCost ReplaceChain(Vehicle **chain, DoCommandFlag flags, bool wagon
if (RailVehInfo(append->engine_type)->railveh_type == RAILVEH_WAGON) {
/* Insert wagon after 'last_engine' */
- CommandCost res = CmdMoveVehicle(append, last_engine, DC_EXEC, false);
+ CommandCost res = CmdMoveVehicle(append, last_engine, DC_EXEC | DC_AUTOREPLACE, false);
/* When we allow removal of wagons, either the move failing due
* to the train becoming too long, or the train becoming longer
@@ -619,7 +652,7 @@ static CommandCost ReplaceChain(Vehicle **chain, DoCommandFlag flags, bool wagon
assert(RailVehInfo(wagon->engine_type)->railveh_type == RAILVEH_WAGON);
/* Sell wagon */
- [[maybe_unused]] CommandCost ret = Command::Do(DC_EXEC, wagon->index, false, false, INVALID_CLIENT_ID);
+ [[maybe_unused]] CommandCost ret = Command::Do(DC_EXEC | DC_AUTOREPLACE, wagon->index, false, false, INVALID_CLIENT_ID);
assert(ret.Succeeded());
it->new_veh = nullptr;
@@ -661,6 +694,9 @@ static CommandCost ReplaceChain(Vehicle **chain, DoCommandFlag flags, bool wagon
if ((flags & DC_EXEC) != 0) CheckCargoCapacity(new_head);
}
+ assert(IsValidTile(tile));
+ if (!HasCompatibleDepotTile(tile, Train::From(new_head))) cost.MakeError(STR_ERROR_UNABLE_TO_FIND_APPROPRIATE_DEPOT_TILE);
+
/* If we are not in DC_EXEC undo everything, i.e. rearrange old vehicles.
* We do this from back to front, so that the head of the temporary vehicle chain does not change all the time.
* Note: The vehicle attach callback is disabled here :) */
@@ -682,7 +718,7 @@ static CommandCost ReplaceChain(Vehicle **chain, DoCommandFlag flags, bool wagon
if ((flags & DC_EXEC) == 0) {
for (auto it = std::rbegin(replacements); it != std::rend(replacements); ++it) {
if (it->new_veh != nullptr) {
- Command::Do(DC_EXEC, it->new_veh->index, false, false, INVALID_CLIENT_ID);
+ Command::Do(DC_EXEC | DC_AUTOREPLACE, it->new_veh->index, false, false, INVALID_CLIENT_ID);
it->new_veh = nullptr;
}
}
@@ -700,6 +736,11 @@ static CommandCost ReplaceChain(Vehicle **chain, DoCommandFlag flags, bool wagon
cost.AddCost(CopyHeadSpecificThings(old_head, new_head, flags));
if (cost.Succeeded()) {
+ /* Copy position and direction for ships in extended depots. */
+ if (old_head->type == VEH_SHIP && IsExtendedDepotTile(old_head->tile)) {
+ CopyShipStatusInExtendedDepot(Ship::From(old_head), Ship::From(new_head));
+ }
+
/* The new vehicle is constructed, now take over cargo */
if ((flags & DC_EXEC) != 0) {
TransferCargo(old_head, new_head, true);
@@ -714,7 +755,7 @@ static CommandCost ReplaceChain(Vehicle **chain, DoCommandFlag flags, bool wagon
/* If we are not in DC_EXEC undo everything */
if ((flags & DC_EXEC) == 0) {
- Command::Do(DC_EXEC, new_head->index, false, false, INVALID_CLIENT_ID);
+ Command::Do(DC_EXEC | DC_AUTOREPLACE, new_head->index, false, false, INVALID_CLIENT_ID);
}
}
}
@@ -766,45 +807,78 @@ CommandCost CmdAutoreplaceVehicle(DoCommandFlag flags, VehicleID veh_id)
any_replacements |= (e != INVALID_ENGINE);
w = (!free_wagon && w->type == VEH_TRAIN ? Train::From(w)->GetNextUnit() : nullptr);
}
+ if (!any_replacements) return_cmd_error(STR_ERROR_AUTOREPLACE_NOTHING_TO_DO);
CommandCost cost = CommandCost(EXPENSES_NEW_VEHICLES, (Money)0);
bool nothing_to_do = true;
+ bool was_stopped = free_wagon || ((v->vehstatus & VS_STOPPED) != 0);
- if (any_replacements) {
- bool was_stopped = free_wagon || ((v->vehstatus & VS_STOPPED) != 0);
+ /* Stop the vehicle */
+ if (!was_stopped) cost.AddCost(DoCmdStartStopVehicle(v, true));
+ if (cost.Failed()) return cost;
- /* Stop the vehicle */
- if (!was_stopped) cost.AddCost(DoCmdStartStopVehicle(v, true));
- if (cost.Failed()) return cost;
+ assert(free_wagon || v->IsStoppedInDepot());
+ if (flags & DC_EXEC) v->StopServicing();
- assert(free_wagon || v->IsStoppedInDepot());
+ TrainPlacement train_placement;
+ if (v->type == VEH_TRAIN) {
+ train_placement.LiftTrain(Train::From(v), flags);
+ } else if (IsExtendedDepotTile(v->tile)) {
+ UpdateExtendedDepotReservation(v, false);
+ }
- /* We have to construct the new vehicle chain to test whether it is valid.
- * Vehicle construction needs random bits, so we have to save the random seeds
- * to prevent desyncs and to replay newgrf callbacks during DC_EXEC */
- SavedRandomSeeds saved_seeds;
- SaveRandomSeeds(&saved_seeds);
+ /* Start autoreplacing the vehicle. */
+ flags |= DC_AUTOREPLACE;
+
+ /* We have to construct the new vehicle chain to test whether it is valid.
+ * Vehicle construction needs random bits, so we have to save the random seeds
+ * to prevent desyncs and to replay newgrf callbacks during DC_EXEC */
+ SavedRandomSeeds saved_seeds;
+ SaveRandomSeeds(&saved_seeds);
+ if (free_wagon) {
+ cost.AddCost(ReplaceFreeUnit(&v, flags & ~DC_EXEC, ¬hing_to_do));
+ } else {
+ cost.AddCost(ReplaceChain(&v, flags & ~DC_EXEC, wagon_removal, ¬hing_to_do));
+ }
+ RestoreRandomSeeds(saved_seeds);
+
+ if (cost.Succeeded() && (flags & DC_EXEC) != 0) {
if (free_wagon) {
- cost.AddCost(ReplaceFreeUnit(&v, flags & ~DC_EXEC, ¬hing_to_do));
+ ret = ReplaceFreeUnit(&v, flags, ¬hing_to_do);
} else {
- cost.AddCost(ReplaceChain(&v, flags & ~DC_EXEC, wagon_removal, ¬hing_to_do));
+ ret = ReplaceChain(&v, flags, wagon_removal, ¬hing_to_do);
}
- RestoreRandomSeeds(saved_seeds);
+ assert(ret.Succeeded());
+ assert(ret.GetCost() == cost.GetCost());
+ }
+
+ /* Check whether the train can be placed on tracks. */
+ bool platform_error = false;
+
+ /* Autoreplacing is done. */
+ flags &= ~DC_AUTOREPLACE;
+
+ if (v->type == VEH_TRAIN) {
if (cost.Succeeded() && (flags & DC_EXEC) != 0) {
- if (free_wagon) {
- ret = ReplaceFreeUnit(&v, flags, ¬hing_to_do);
- } else {
- ret = ReplaceChain(&v, flags, wagon_removal, ¬hing_to_do);
+ train_placement.LookForPlaceInDepot(Train::From(v), false);
+ if (train_placement.info < PI_WONT_LEAVE) {
+ platform_error = true;
+ if (v->owner == _local_company && v->IsFrontEngine()) {
+ SetDParam(0, v->index);
+ AddVehicleAdviceNewsItem(STR_ADVICE_PLATFORM_TYPE + train_placement.info - PI_ERROR_BEGIN, v->index);
+ }
}
- assert(ret.Succeeded() && ret.GetCost() == cost.GetCost());
}
-
- /* Restart the vehicle */
- if (!was_stopped) cost.AddCost(DoCmdStartStopVehicle(v, false));
+ train_placement.PlaceTrain(Train::From(v), flags);
+ } else if (IsExtendedDepotTile(v->tile)) {
+ UpdateExtendedDepotReservation(v, true);
}
- if (cost.Succeeded() && nothing_to_do) cost = CommandCost(STR_ERROR_AUTOREPLACE_NOTHING_TO_DO);
+ /* Restart the vehicle */
+ if (!platform_error && !was_stopped) cost.AddCost(DoCmdStartStopVehicle(v, false));
+
+ assert(cost.Failed() || !nothing_to_do);
return cost;
}
diff --git a/src/base_station_base.h b/src/base_station_base.h
index 0c0ad2277078c..de8688006e613 100644
--- a/src/base_station_base.h
+++ b/src/base_station_base.h
@@ -140,24 +140,6 @@ struct BaseStation : StationPool::PoolItem<&_station_pool> {
*/
virtual void GetTileArea(TileArea *ta, StationType type) const = 0;
-
- /**
- * Obtain the length of a platform
- * @pre tile must be a rail station tile
- * @param tile A tile that contains the platform in question
- * @return The length of the platform
- */
- virtual uint GetPlatformLength(TileIndex tile) const = 0;
-
- /**
- * Determines the REMAINING length of a platform, starting at (and including)
- * the given tile.
- * @param tile the tile from which to start searching. Must be a rail station tile
- * @param dir The direction in which to search.
- * @return The platform length
- */
- virtual uint GetPlatformLength(TileIndex tile, DiagDirection dir) const = 0;
-
/**
* Get the base station belonging to a specific tile.
* @param tile The tile to get the base station from.
diff --git a/src/build_vehicle_gui.cpp b/src/build_vehicle_gui.cpp
index 1a985eb64a251..7311c5b9e91fe 100644
--- a/src/build_vehicle_gui.cpp
+++ b/src/build_vehicle_gui.cpp
@@ -38,6 +38,9 @@
#include "querystring_gui.h"
#include "stringfilter_type.h"
#include "hotkeys.h"
+#include "depot_base.h"
+#include "depot_func.h"
+#include "air.h"
#include "widgets/build_vehicle_widget.h"
@@ -1163,12 +1166,32 @@ enum BuildVehicleHotkeys {
BVHK_FOCUS_FILTER_BOX, ///< Focus the edit box for editing the filter string
};
+/**
+ * Return a unique window number for the BuildVehicleWindow.
+ * The BuildVehicleWindow can be opened for a valid depot or
+ * for a specific vehicle type ("Available Trains", "Available Ships"...).
+ * The corresponding unique window number is chosen as:
+ * - For existing depots, the depot id.
+ * - For vehicle types, it is MAX_DEPOTS + vehicle_type
+ * @param depot_id the depot id
+ * @param type the vehicle type
+ * @return the depot id for valid depots or MAX_DEPOTS + vehicle_type otherwise.
+ */
+DepotID GetBuildVehicleWindowNumber(DepotID depot_id, VehicleType type)
+{
+ assert(depot_id == INVALID_DEPOT || Depot::IsValidID(depot_id));
+ assert(IsCompanyBuildableVehicleType(type));
+ if (depot_id != INVALID_DEPOT) return depot_id;
+ return MAX_DEPOTS + type;
+}
+
/** GUI for building vehicles. */
struct BuildVehicleWindow : Window {
VehicleType vehicle_type; ///< Type of vehicles shown in the window.
union {
- RailType railtype; ///< Rail type to show, or #INVALID_RAILTYPE.
- RoadType roadtype; ///< Road type to show, or #INVALID_ROADTYPE.
+ RailTypes railtypes; ///< Rail types to show, or #INVALID_RAILTYPES.
+ RoadTypes roadtypes; ///< Road types to show, or #INVALID_ROADTYPES.
+ AirTypes airtypes; ///< Air types to show, or #INVALID_AIRTYPES.
} filter; ///< Filter to apply.
bool descending_sort_order; ///< Sort direction, @see _engine_sort_direction
uint8_t sort_criteria; ///< Current sort criterium.
@@ -1201,11 +1224,11 @@ struct BuildVehicleWindow : Window {
}
}
- BuildVehicleWindow(WindowDesc &desc, TileIndex tile, VehicleType type) : Window(desc), vehicle_editbox(MAX_LENGTH_VEHICLE_NAME_CHARS * MAX_CHAR_LENGTH, MAX_LENGTH_VEHICLE_NAME_CHARS)
+ BuildVehicleWindow(WindowDesc &desc, DepotID depot_id, VehicleType type) : Window(desc), vehicle_editbox(MAX_LENGTH_VEHICLE_NAME_CHARS * MAX_CHAR_LENGTH, MAX_LENGTH_VEHICLE_NAME_CHARS)
{
this->vehicle_type = type;
- this->listview_mode = tile == INVALID_TILE;
- this->window_number = this->listview_mode ? (int)type : tile.base();
+ this->listview_mode = depot_id == INVALID_DEPOT;
+ this->window_number = GetBuildVehicleWindowNumber(depot_id, type);
this->sel_engine = INVALID_ENGINE;
@@ -1240,16 +1263,13 @@ struct BuildVehicleWindow : Window {
this->details_height = ((this->vehicle_type == VEH_TRAIN) ? 10 : 9);
- if (tile == INVALID_TILE) {
- this->FinishInitNested(type);
- } else {
- this->FinishInitNested(tile);
- }
+ this->FinishInitNested(this->window_number);
this->querystrings[WID_BV_FILTER] = &this->vehicle_editbox;
this->vehicle_editbox.cancel_button = QueryString::ACTION_CLEAR;
- this->owner = (tile != INVALID_TILE) ? GetTileOwner(tile) : _local_company;
+ Depot *depot = Depot::GetIfValid(depot_id);
+ this->owner = depot != nullptr ? depot->owner : _local_company;
this->eng_list.ForceRebuild();
this->GenerateBuildList(); // generate the list, since we need it in the next line
@@ -1264,29 +1284,24 @@ struct BuildVehicleWindow : Window {
/** Set the filter type according to the depot type */
void UpdateFilterByTile()
{
+ Depot *depot = this->listview_mode ? nullptr : Depot::Get(this->window_number);
+
switch (this->vehicle_type) {
default: NOT_REACHED();
case VEH_TRAIN:
- if (this->listview_mode) {
- this->filter.railtype = INVALID_RAILTYPE;
- } else {
- this->filter.railtype = GetRailType(this->window_number);
- }
+ this->filter.railtypes = this->listview_mode ? INVALID_RAILTYPES : depot->r_types.rail_types;
break;
case VEH_ROAD:
- if (this->listview_mode) {
- this->filter.roadtype = INVALID_ROADTYPE;
- } else {
- this->filter.roadtype = GetRoadTypeRoad(this->window_number);
- if (this->filter.roadtype == INVALID_ROADTYPE) {
- this->filter.roadtype = GetRoadTypeTram(this->window_number);
- }
- }
+ this->filter.roadtypes = this->listview_mode ? INVALID_ROADTYPES : depot->r_types.road_types;
break;
case VEH_SHIP:
+ this->filter.railtypes = this->listview_mode ? INVALID_RAILTYPES : depot->r_types.rail_types;
+ break;
+
case VEH_AIRCRAFT:
+ this->filter.airtypes = this->listview_mode ? INVALID_AIRTYPES : depot->r_types.air_types;
break;
}
}
@@ -1326,7 +1341,7 @@ struct BuildVehicleWindow : Window {
if (!this->listview_mode) {
/* Query for cost and refitted capacity */
- auto [ret, veh_id, refit_capacity, refit_mail, cargo_capacities] = Command::Do(DC_QUERY_COST, this->window_number, this->sel_engine, true, cargo, INVALID_CLIENT_ID);
+ auto [ret, veh_id, refit_capacity, refit_mail, cargo_capacities] = Command::Do(DC_QUERY_COST, Depot::Get(this->window_number)->xy, this->sel_engine, true, cargo, INVALID_CLIENT_ID);
if (ret.Succeeded()) {
this->te.cost = ret.GetCost() - e->GetCost();
this->te.capacity = refit_capacity;
@@ -1401,7 +1416,7 @@ struct BuildVehicleWindow : Window {
EngineID eid = e->index;
const RailVehicleInfo *rvi = &e->u.rail;
- if (this->filter.railtype != INVALID_RAILTYPE && !HasPowerOnRail(rvi->railtype, this->filter.railtype)) continue;
+ if (!this->listview_mode && !HasPowerOnRails(rvi->railtype, this->filter.railtypes)) continue;
if (!IsEngineBuildable(eid, VEH_TRAIN, _local_company)) continue;
/* Filter now! So num_engines and num_wagons is valid */
@@ -1461,7 +1476,7 @@ struct BuildVehicleWindow : Window {
if (!this->show_hidden_engines && e->IsVariantHidden(_local_company)) continue;
EngineID eid = e->index;
if (!IsEngineBuildable(eid, VEH_ROAD, _local_company)) continue;
- if (this->filter.roadtype != INVALID_ROADTYPE && !HasPowerOnRoad(e->u.road.roadtype, this->filter.roadtype)) continue;
+ if (!this->listview_mode && !HasPowerOnRoads(e->u.road.roadtype, this->filter.roadtypes)) continue;
/* Filter by name or NewGRF extra text */
if (!FilterByText(e)) continue;
@@ -1479,18 +1494,17 @@ struct BuildVehicleWindow : Window {
EngineID sel_id = INVALID_ENGINE;
this->eng_list.clear();
- for (const Engine *e : Engine::IterateType(VEH_SHIP)) {
- if (!this->show_hidden_engines && e->IsVariantHidden(_local_company)) continue;
- EngineID eid = e->index;
- if (!IsEngineBuildable(eid, VEH_SHIP, _local_company)) continue;
-
- /* Filter by name or NewGRF extra text */
- if (!FilterByText(e)) continue;
-
- this->eng_list.emplace_back(eid, e->info.variant_id, e->display_flags, 0);
+ if (this->listview_mode || this->filter.railtypes != RAILTYPES_NONE) {
+ for (const Engine *e : Engine::IterateType(VEH_SHIP)) {
+ if (!this->show_hidden_engines && e->IsVariantHidden(_local_company)) continue;
+ EngineID eid = e->index;
+ if (!IsEngineBuildable(eid, VEH_SHIP, _local_company)) continue;
+ this->eng_list.emplace_back(eid, e->info.variant_id, e->display_flags, 0);
- if (eid == this->sel_engine) sel_id = eid;
+ if (eid == this->sel_engine) sel_id = eid;
+ }
}
+
this->SelectEngine(sel_id);
}
@@ -1501,25 +1515,25 @@ struct BuildVehicleWindow : Window {
this->eng_list.clear();
- const Station *st = this->listview_mode ? nullptr : Station::GetByTile(this->window_number);
-
- /* Make list of all available planes.
- * Also check to see if the previously selected plane is still available,
- * and if not, reset selection to INVALID_ENGINE. This could be the case
- * when planes become obsolete and are removed */
- for (const Engine *e : Engine::IterateType(VEH_AIRCRAFT)) {
- if (!this->show_hidden_engines && e->IsVariantHidden(_local_company)) continue;
- EngineID eid = e->index;
- if (!IsEngineBuildable(eid, VEH_AIRCRAFT, _local_company)) continue;
- /* First VEH_END window_numbers are fake to allow a window open for all different types at once */
- if (!this->listview_mode && !CanVehicleUseStation(eid, st)) continue;
+ if (this->listview_mode || !Depot::Get(this->window_number)->depot_tiles.empty()) {
+ const Station *st = this->listview_mode ? nullptr : Depot::Get(this->window_number)->station;
- /* Filter by name or NewGRF extra text */
- if (!FilterByText(e)) continue;
+ /* Make list of all available planes.
+ * Also check to see if the previously selected plane is still available,
+ * and if not, reset selection to INVALID_ENGINE. This could be the case
+ * when planes become obsolete and are removed */
+ for (const Engine *e : Engine::IterateType(VEH_AIRCRAFT)) {
+ if (!this->show_hidden_engines && e->IsVariantHidden(_local_company)) continue;
+ EngineID eid = e->index;
+ if (!IsEngineBuildable(eid, VEH_AIRCRAFT, _local_company)) continue;
+ if (!this->listview_mode && !CanVehicleUseStation(eid, st)) continue;
- this->eng_list.emplace_back(eid, e->info.variant_id, e->display_flags, 0);
+ /* Filter by name or NewGRF extra text */
+ if (!FilterByText(e)) continue;
- if (eid == this->sel_engine) sel_id = eid;
+ this->eng_list.emplace_back(eid, e->info.variant_id, e->display_flags, 0);
+ if (eid == this->sel_engine) sel_id = eid;
+ }
}
this->SelectEngine(sel_id);
@@ -1613,10 +1627,15 @@ struct BuildVehicleWindow : Window {
CargoID cargo = this->cargo_filter_criteria;
if (cargo == CargoFilterCriteria::CF_ANY || cargo == CargoFilterCriteria::CF_ENGINES || cargo == CargoFilterCriteria::CF_NONE) cargo = INVALID_CARGO;
+
+ assert(Depot::IsValidID(this->window_number));
+ Depot *depot = Depot::Get(this->window_number);
+ assert(depot->xy != INVALID_TILE);
+
if (this->vehicle_type == VEH_TRAIN && RailVehInfo(sel_eng)->railveh_type == RAILVEH_WAGON) {
- Command::Post(GetCmdBuildVehMsg(this->vehicle_type), CcBuildWagon, this->window_number, sel_eng, true, cargo, INVALID_CLIENT_ID);
+ Command::Post(GetCmdBuildVehMsg(this->vehicle_type), CcBuildWagon, depot->xy, sel_eng, true, cargo, INVALID_CLIENT_ID);
} else {
- Command::Post(GetCmdBuildVehMsg(this->vehicle_type), CcBuildPrimaryVehicle, this->window_number, sel_eng, true, cargo, INVALID_CLIENT_ID);
+ Command::Post(GetCmdBuildVehMsg(this->vehicle_type), CcBuildPrimaryVehicle, depot->xy, sel_eng, true, cargo, INVALID_CLIENT_ID);
}
/* Update last used variant in hierarchy and refresh if necessary. */
@@ -1736,11 +1755,21 @@ struct BuildVehicleWindow : Window {
switch (widget) {
case WID_BV_CAPTION:
if (this->vehicle_type == VEH_TRAIN && !this->listview_mode) {
- const RailTypeInfo *rti = GetRailTypeInfo(this->filter.railtype);
- SetDParam(0, rti->strings.build_caption);
+ uint num_railtypes = CountBits(this->filter.railtypes);
+ if (num_railtypes != 1) {
+ SetDParam(0, STR_BUY_VEHICLE_TRAIN_ALL_CAPTION);
+ } else {
+ const RailTypeInfo *rti = GetRailTypeInfo((RailType)FindFirstBit(this->filter.railtypes));
+ SetDParam(0, rti->strings.build_caption);
+ }
} else if (this->vehicle_type == VEH_ROAD && !this->listview_mode) {
- const RoadTypeInfo *rti = GetRoadTypeInfo(this->filter.roadtype);
- SetDParam(0, rti->strings.build_caption);
+ uint num_roadtypes = CountBits(this->filter.roadtypes);
+ if (num_roadtypes != 1) {
+ SetDParam(0, STR_BUY_VEHICLE_ROAD_VEHICLE_CAPTION);
+ } else {
+ const RoadTypeInfo *rti = GetRoadTypeInfo((RoadType)FindFirstBit(this->filter.roadtypes));
+ SetDParam(0, rti->strings.build_caption);
+ }
} else {
SetDParam(0, (this->listview_mode ? STR_VEHICLE_LIST_AVAILABLE_TRAINS : STR_BUY_VEHICLE_TRAIN_ALL_CAPTION) + this->vehicle_type);
}
@@ -1930,17 +1959,11 @@ static WindowDesc _build_vehicle_desc(
&BuildVehicleWindow::hotkeys
);
-void ShowBuildVehicleWindow(TileIndex tile, VehicleType type)
+void ShowBuildVehicleWindow(DepotID depot_id, VehicleType type)
{
- /* We want to be able to open both Available Train as Available Ships,
- * so if tile == INVALID_TILE (Available XXX Window), use 'type' as unique number.
- * As it always is a low value, it won't collide with any real tile
- * number. */
- uint num = (tile == INVALID_TILE) ? (int)type : tile.base();
-
assert(IsCompanyBuildableVehicleType(type));
- CloseWindowById(WC_BUILD_VEHICLE, num);
+ CloseWindowById(WC_BUILD_VEHICLE, GetBuildVehicleWindowNumber(depot_id, type));
- new BuildVehicleWindow(_build_vehicle_desc, tile, type);
+ new BuildVehicleWindow(_build_vehicle_desc, depot_id, type);
}
diff --git a/src/cheat_gui.cpp b/src/cheat_gui.cpp
index 5d3475ee80e4d..0a23c5a2dc68f 100644
--- a/src/cheat_gui.cpp
+++ b/src/cheat_gui.cpp
@@ -192,6 +192,7 @@ struct CheatEntry {
void *variable; ///< pointer to the variable
bool *been_used; ///< has this cheat been used before?
CheckButtonClick *proc;///< procedure
+ bool available;
};
/**
@@ -199,15 +200,15 @@ struct CheatEntry {
* Order matches with the values of #CheatNumbers
*/
static const CheatEntry _cheats_ui[] = {
- {SLE_INT32, STR_CHEAT_MONEY, &_money_cheat_amount, &_cheats.money.been_used, &ClickMoneyCheat },
- {SLE_UINT8, STR_CHEAT_CHANGE_COMPANY, &_local_company, &_cheats.switch_company.been_used, &ClickChangeCompanyCheat },
- {SLE_BOOL, STR_CHEAT_EXTRA_DYNAMITE, &_cheats.magic_bulldozer.value, &_cheats.magic_bulldozer.been_used, nullptr },
- {SLE_BOOL, STR_CHEAT_CROSSINGTUNNELS, &_cheats.crossing_tunnels.value, &_cheats.crossing_tunnels.been_used, nullptr },
- {SLE_BOOL, STR_CHEAT_NO_JETCRASH, &_cheats.no_jetcrash.value, &_cheats.no_jetcrash.been_used, nullptr },
- {SLE_BOOL, STR_CHEAT_SETUP_PROD, &_cheats.setup_prod.value, &_cheats.setup_prod.been_used, &ClickSetProdCheat },
- {SLE_BOOL, STR_CHEAT_STATION_RATING, &_cheats.station_rating.value, &_cheats.station_rating.been_used, nullptr },
- {SLE_UINT8, STR_CHEAT_EDIT_MAX_HL, &_settings_game.construction.map_height_limit, &_cheats.edit_max_hl.been_used, &ClickChangeMaxHlCheat },
- {SLE_INT32, STR_CHEAT_CHANGE_DATE, &TimerGameCalendar::year, &_cheats.change_date.been_used, &ClickChangeDateCheat },
+ {SLE_INT32, STR_CHEAT_MONEY, &_money_cheat_amount, &_cheats.money.been_used, &ClickMoneyCheat, true },
+ {SLE_UINT8, STR_CHEAT_CHANGE_COMPANY, &_local_company, &_cheats.switch_company.been_used, &ClickChangeCompanyCheat, true },
+ {SLE_BOOL, STR_CHEAT_EXTRA_DYNAMITE, &_cheats.magic_bulldozer.value, &_cheats.magic_bulldozer.been_used, nullptr, true },
+ {SLE_BOOL, STR_CHEAT_CROSSINGTUNNELS, &_cheats.crossing_tunnels.value, &_cheats.crossing_tunnels.been_used, nullptr, true },
+ {SLE_BOOL, STR_CHEAT_NO_JETCRASH, &_cheats.no_jetcrash.value, &_cheats.no_jetcrash.been_used, nullptr, false },
+ {SLE_BOOL, STR_CHEAT_SETUP_PROD, &_cheats.setup_prod.value, &_cheats.setup_prod.been_used, &ClickSetProdCheat, true },
+ {SLE_BOOL, STR_CHEAT_STATION_RATING, &_cheats.station_rating.value, &_cheats.station_rating.been_used, nullptr, true },
+ {SLE_UINT8, STR_CHEAT_EDIT_MAX_HL, &_settings_game.construction.map_height_limit, &_cheats.edit_max_hl.been_used, &ClickChangeMaxHlCheat, true },
+ {SLE_INT32, STR_CHEAT_CHANGE_DATE, &TimerGameCalendar::year, &_cheats.change_date.been_used, &ClickChangeDateCheat, true },
};
static_assert(CHT_NUM_CHEATS == lengthof(_cheats_ui));
@@ -263,7 +264,7 @@ struct CheatWindow : Window {
case SLE_BOOL: {
bool on = (*(bool*)ce->variable);
- DrawBoolButton(button_left, y + button_y_offset, on, true);
+ DrawBoolButton(button_left, y + button_y_offset, on, ce->available);
SetDParam(0, on ? STR_CONFIG_SETTING_ON : STR_CONFIG_SETTING_OFF);
break;
}
@@ -355,6 +356,9 @@ struct CheatWindow : Window {
if (btn >= lengthof(_cheats_ui)) return;
const CheatEntry *ce = &_cheats_ui[btn];
+
+ if (!ce->available) return;
+
int value = (int32_t)ReadValue(ce->variable, ce->type);
int oldvalue = value;
diff --git a/src/command.cpp b/src/command.cpp
index b975c51fbbd30..e98d64de6a4a7 100644
--- a/src/command.cpp
+++ b/src/command.cpp
@@ -24,6 +24,7 @@
#include "signal_func.h"
#include "core/backup_type.hpp"
#include "object_base.h"
+#include "airport_cmd.h"
#include "autoreplace_cmd.h"
#include "company_cmd.h"
#include "depot_cmd.h"
diff --git a/src/command_type.h b/src/command_type.h
index 2fd494b5cac06..f52dbb32dadec 100644
--- a/src/command_type.h
+++ b/src/command_type.h
@@ -193,6 +193,7 @@ enum Commands : uint16_t {
CMD_BUILD_BRIDGE, ///< build a bridge
CMD_BUILD_RAIL_STATION, ///< build a rail station
CMD_BUILD_TRAIN_DEPOT, ///< build a train depot
+ CMD_REMOVE_TRAIN_DEPOT, ///< remove a train depot
CMD_BUILD_SINGLE_SIGNAL, ///< build a signal
CMD_REMOVE_SINGLE_SIGNAL, ///< remove a signal
CMD_TERRAFORM_LAND, ///< terraform a tile
@@ -218,7 +219,13 @@ enum Commands : uint16_t {
CMD_BUILD_ROAD_DEPOT, ///< build a road depot
CMD_CONVERT_ROAD, ///< convert a road type
- CMD_BUILD_AIRPORT, ///< build an airport
+ CMD_CHANGE_AIRPORT, ///< change pieces of airport
+ CMD_ADD_REM_AIRPORT, ///< build/remove tiles for airport tracks
+ CMD_ADD_REM_TRACKS, ///< build/remove tracks
+ CMD_CONVERT_AIRPORT, ///< change the aiport type (gravel, asphalt, etc.)
+ CMD_BUILD_AIRPORT, ///< build an airport layout
+ CMD_AIRPORT_CHANGE_GFX, ///< change the graphics of an airport tile, if possible
+ CMD_AIRPORT_TOGGLE_GROUND, ///< toggle between showing the specific airtype ground or not, if possible
CMD_BUILD_DOCK, ///< build a dock
diff --git a/src/company_base.h b/src/company_base.h
index 814665777b2e9..e765c77fbbd92 100644
--- a/src/company_base.h
+++ b/src/company_base.h
@@ -32,10 +32,10 @@ struct CompanyEconomyEntry {
struct CompanyInfrastructure {
std::array rail{}; ///< Count of company owned track bits for each rail type.
std::array road{}; ///< Count of company owned track bits for each road type.
+ std::array air{}; ///< Count of company owned airport tiles for each air type.
uint32_t signal; ///< Count of company owned signals.
uint32_t water; ///< Count of company owned track bits for canals.
uint32_t station; ///< Count of company owned station tiles.
- uint32_t airport; ///< Count of company owned airports.
auto operator<=>(const CompanyInfrastructure &) const = default;
@@ -45,6 +45,12 @@ struct CompanyInfrastructure {
return std::accumulate(std::begin(this->rail), std::end(this->rail), 0U);
}
+ /** Get total sum of all owned airport tiles. */
+ uint32_t GetAirTotal() const
+ {
+ return std::accumulate(std::begin(this->air), std::end(this->air), 0U);
+ }
+
uint32_t GetRoadTotal() const;
uint32_t GetTramTotal() const;
};
@@ -133,6 +139,7 @@ struct Company : CompanyProperties, CompanyPool::PoolItem<&_company_pool> {
Company(uint16_t name_1 = 0, bool is_ai = false);
~Company();
+ AirTypes avail_airtypes; ///< Air types available to the company.
RailTypes avail_railtypes; ///< Rail types available to this company.
RoadTypes avail_roadtypes; ///< Road types available to this company.
diff --git a/src/company_cmd.cpp b/src/company_cmd.cpp
index 74ad2893f4054..304488702985a 100644
--- a/src/company_cmd.cpp
+++ b/src/company_cmd.cpp
@@ -26,6 +26,7 @@
#include "window_func.h"
#include "strings_func.h"
#include "sound_func.h"
+#include "air.h"
#include "rail.h"
#include "core/pool_func.hpp"
#include "settings_func.h"
@@ -607,6 +608,7 @@ Company *DoStartupNewCompany(bool is_ai, CompanyID company = INVALID_COMPANY)
c->avail_railtypes = GetCompanyRailTypes(c->index);
c->avail_roadtypes = GetCompanyRoadTypes(c->index);
+ c->avail_airtypes = GetCompanyAirTypes(c->index);
c->inaugurated_year = TimerGameEconomy::year;
/* If starting a player company in singleplayer and a favorite company manager face is selected, choose it. Otherwise, use a random face.
diff --git a/src/company_gui.cpp b/src/company_gui.cpp
index f7acd9831d7f6..6e48562f0a10a 100644
--- a/src/company_gui.cpp
+++ b/src/company_gui.cpp
@@ -31,6 +31,7 @@
#include "object_type.h"
#include "rail.h"
#include "road.h"
+#include "air.h"
#include "engine_base.h"
#include "window_func.h"
#include "road_func.h"
@@ -1748,6 +1749,10 @@ static constexpr NWidgetPart _nested_company_infrastructure_widgets[] = {
NWidget(WWT_EMPTY, COLOUR_GREY, WID_CI_WATER_DESC), SetMinimalTextLines(2, 0), SetFill(1, 0),
NWidget(WWT_EMPTY, COLOUR_GREY, WID_CI_WATER_COUNT), SetMinimalTextLines(2, 0), SetFill(0, 1),
EndContainer(),
+ NWidget(NWID_HORIZONTAL), SetPIP(0, WidgetDimensions::unscaled.hsep_wide, 0),
+ NWidget(WWT_EMPTY, COLOUR_GREY, WID_CI_AIRPORT_DESC), SetMinimalTextLines(2, 0), SetFill(1, 0),
+ NWidget(WWT_EMPTY, COLOUR_GREY, WID_CI_AIRPORT_COUNT), SetMinimalTextLines(2, 0), SetFill(0, 1),
+ EndContainer(),
NWidget(NWID_HORIZONTAL), SetPIP(0, WidgetDimensions::unscaled.hsep_wide, 0),
NWidget(WWT_EMPTY, COLOUR_GREY, WID_CI_STATION_DESC), SetMinimalTextLines(3, 0), SetFill(1, 0),
NWidget(WWT_EMPTY, COLOUR_GREY, WID_CI_STATION_COUNT), SetMinimalTextLines(3, 0), SetFill(0, 1),
@@ -1767,6 +1772,7 @@ struct CompanyInfrastructureWindow : Window
{
RailTypes railtypes; ///< Valid railtypes.
RoadTypes roadtypes; ///< Valid roadtypes.
+ AirTypes airtypes; ///< Valid airtypes.
uint total_width; ///< String width of the total cost line.
@@ -1782,6 +1788,7 @@ struct CompanyInfrastructureWindow : Window
{
this->railtypes = RAILTYPES_NONE;
this->roadtypes = ROADTYPES_NONE;
+ this->airtypes = AIRTYPES_NONE;
/* Find the used railtypes. */
for (const Engine *e : Engine::IterateType(VEH_TRAIN)) {
@@ -1793,6 +1800,16 @@ struct CompanyInfrastructureWindow : Window
/* Get the date introduced railtypes as well. */
this->railtypes = AddDateIntroducedRailTypes(this->railtypes, CalendarTime::MAX_DATE);
+ /* Find the used airtypes. */
+ for (const Engine *e : Engine::IterateType(VEH_AIRCRAFT)) {
+ if (!HasBit(e->info.climates, _settings_game.game_creation.landscape)) continue;
+
+ this->airtypes |= GetAirTypeInfo(e->u.air.airtype)->introduces_airtypes;
+ }
+
+ /* Get the date introduced airtypes as well. */
+ this->airtypes = AddDateIntroducedAirTypes(this->airtypes, CalendarTime::MAX_DATE);
+
/* Find the used roadtypes. */
for (const Engine *e : Engine::IterateType(VEH_ROAD)) {
if (!HasBit(e->info.climates, _settings_game.game_creation.landscape)) continue;
@@ -1823,9 +1840,13 @@ struct CompanyInfrastructureWindow : Window
if (HasBit(this->roadtypes, rt)) total += RoadMaintenanceCost(rt, c->infrastructure.road[rt], RoadTypeIsRoad(rt) ? road_total : tram_total);
}
+ uint32_t air_total = c->infrastructure.GetAirTotal();
+ for (AirType at = AIRTYPE_BEGIN; at != AIRTYPE_END; at++) {
+ if (HasBit(this->airtypes, at)) total += AirMaintenanceCost(at, c->infrastructure.air[at], air_total);
+ }
+
total += CanalMaintenanceCost(c->infrastructure.water);
total += StationMaintenanceCost(c->infrastructure.station);
- total += AirportMaintenanceCost(c->index);
return total;
}
@@ -1886,10 +1907,25 @@ struct CompanyInfrastructureWindow : Window
size.width = std::max(size.width, GetStringBoundingBox(STR_COMPANY_INFRASTRUCTURE_VIEW_CANALS).width + padding.width + WidgetDimensions::scaled.hsep_indent);
break;
+ case WID_CI_AIRPORT_DESC: {
+ uint lines = 1;
+ size.width = std::max(size.width, GetStringBoundingBox(STR_COMPANY_INFRASTRUCTURE_VIEW_AIRPORT_SECT).width);
+
+ for (const auto &at : _sorted_airtypes) {
+ if (HasBit(this->airtypes, at)) {
+ lines++;
+ SetDParam(0, GetAirTypeInfo(at)->strings.name);
+ size.width = std::max(size.width, GetStringBoundingBox(STR_JUST_STRING).width + WidgetDimensions::scaled.frametext.left);
+ }
+ }
+
+ size.height = std::max(size.height, lines * GetCharacterHeight(FS_NORMAL));
+ break;
+ }
+
case WID_CI_STATION_DESC:
size.width = std::max(size.width, GetStringBoundingBox(STR_COMPANY_INFRASTRUCTURE_VIEW_STATION_SECT).width + padding.width);
size.width = std::max(size.width, GetStringBoundingBox(STR_COMPANY_INFRASTRUCTURE_VIEW_STATIONS).width + padding.width + WidgetDimensions::scaled.hsep_indent);
- size.width = std::max(size.width, GetStringBoundingBox(STR_COMPANY_INFRASTRUCTURE_VIEW_AIRPORTS).width + padding.width + WidgetDimensions::scaled.hsep_indent);
break;
case WID_CI_RAIL_COUNT:
@@ -1897,6 +1933,7 @@ struct CompanyInfrastructureWindow : Window
case WID_CI_TRAM_COUNT:
case WID_CI_WATER_COUNT:
case WID_CI_STATION_COUNT:
+ case WID_CI_AIRPORT_COUNT:
case WID_CI_TOTAL: {
/* Find the maximum count that is displayed. */
uint32_t max_val = 1000; // Some random number to reserve enough space.
@@ -1917,10 +1954,13 @@ struct CompanyInfrastructureWindow : Window
}
max_val = std::max(max_val, c->infrastructure.water);
max_cost = std::max(max_cost, CanalMaintenanceCost(c->infrastructure.water));
+ uint32_t air_total = c->infrastructure.GetAirTotal();
+ for (AirType at = AIRTYPE_BEGIN; at < AIRTYPE_END; at++) {
+ max_val = std::max(max_val, c->infrastructure.air[at]);
+ max_cost = std::max(max_cost, AirMaintenanceCost(at, c->infrastructure.air[at], air_total));
+ }
max_val = std::max(max_val, c->infrastructure.station);
max_cost = std::max(max_cost, StationMaintenanceCost(c->infrastructure.station));
- max_val = std::max(max_val, c->infrastructure.airport);
- max_cost = std::max(max_cost, AirportMaintenanceCost(c->index));
SetDParamMaxValue(0, max_val);
uint count_width = GetStringBoundingBox(STR_JUST_COMMA).width + WidgetDimensions::scaled.hsep_indent; // Reserve some wiggle room
@@ -2041,6 +2081,35 @@ struct CompanyInfrastructureWindow : Window
this->DrawCountLine(r, y, c->infrastructure.water, CanalMaintenanceCost(c->infrastructure.water));
break;
+ case WID_CI_AIRPORT_DESC:
+ DrawString(r.left, r.right, y, STR_COMPANY_INFRASTRUCTURE_VIEW_AIRPORT_SECT);
+
+ if (this->airtypes != AIRTYPES_NONE) {
+ /* Draw name of each valid airtype. */
+ for (const auto &at : _sorted_airtypes) {
+ if (HasBit(this->railtypes, at)) {
+ SetDParam(0, GetAirTypeInfo(at)->strings.name);
+ DrawString(ir.left, ir.right, y += GetCharacterHeight(FS_NORMAL), STR_JUST_STRING, TC_WHITE);
+ }
+ }
+ } else {
+ /* No valid airtype. */
+ DrawString(ir.left, ir.right, y += GetCharacterHeight(FS_NORMAL), STR_COMPANY_VIEW_INFRASTRUCTURE_NONE);
+ }
+
+ break;
+
+ case WID_CI_AIRPORT_COUNT: {
+ /* Draw infrastructure count for each valid airtype. */
+ uint32_t air_total = c->infrastructure.GetAirTotal();
+ for (const auto &at : _sorted_airtypes) {
+ if (HasBit(this->airtypes, at)) {
+ this->DrawCountLine(r, y, c->infrastructure.air[at], AirMaintenanceCost(at, c->infrastructure.air[at], air_total));
+ }
+ }
+ break;
+ }
+
case WID_CI_TOTAL:
if (_settings_game.economy.infrastructure_maintenance) {
Rect tr = r.WithWidth(this->total_width, _current_text_dir == TD_RTL);
@@ -2056,12 +2125,10 @@ struct CompanyInfrastructureWindow : Window
case WID_CI_STATION_DESC:
DrawString(r.left, r.right, y, STR_COMPANY_INFRASTRUCTURE_VIEW_STATION_SECT);
DrawString(ir.left, ir.right, y += GetCharacterHeight(FS_NORMAL), STR_COMPANY_INFRASTRUCTURE_VIEW_STATIONS);
- DrawString(ir.left, ir.right, y += GetCharacterHeight(FS_NORMAL), STR_COMPANY_INFRASTRUCTURE_VIEW_AIRPORTS);
break;
case WID_CI_STATION_COUNT:
this->DrawCountLine(r, y, c->infrastructure.station, StationMaintenanceCost(c->infrastructure.station));
- this->DrawCountLine(r, y, c->infrastructure.airport, AirportMaintenanceCost(c->index));
break;
}
}
@@ -2349,10 +2416,10 @@ struct CompanyWindow : Window
y += GetCharacterHeight(FS_NORMAL);
}
- if (c->infrastructure.airport != 0) {
- SetDParam(0, c->infrastructure.airport);
+ uint air_pieces = c->infrastructure.GetAirTotal();
+ if (air_pieces != 0) {
+ SetDParam(0, air_pieces);
DrawString(r.left, r.right, y, STR_COMPANY_VIEW_INFRASTRUCTURE_AIRPORT);
- y += GetCharacterHeight(FS_NORMAL);
}
if (y == r.top) {
diff --git a/src/depot.cpp b/src/depot.cpp
index 13317e8a35dad..9bba7f67546f8 100644
--- a/src/depot.cpp
+++ b/src/depot.cpp
@@ -15,9 +15,15 @@
#include "core/pool_func.hpp"
#include "vehicle_gui.h"
#include "vehiclelist.h"
+#include "command_func.h"
+#include "vehicle_base.h"
+#include "viewport_kdtree.h"
+#include "platform_func.h"
#include "safeguards.h"
+#include "table/strings.h"
+
/** All our depots tucked away in a pool. */
DepotPool _depot_pool("Depot");
INSTANTIATE_POOL_METHODS(Depot)
@@ -29,21 +35,309 @@ Depot::~Depot()
{
if (CleaningPool()) return;
- if (!IsDepotTile(this->xy) || GetDepotIndex(this->xy) != this->index) {
- /* It can happen there is no depot here anymore (TTO/TTD savegames) */
+ if (this->owner == INVALID_OWNER) {
+ /* Deleting depot remnants of TTD savegames while saveload conversion. */
+ assert(this->veh_type == VEH_INVALID);
return;
}
/* Clear the order backup. */
- OrderBackup::Reset(this->xy, false);
+ OrderBackup::Reset(this->index, false);
+
+ if (this->veh_type == VEH_AIRCRAFT) {
+ this->station->airport.hangar = nullptr;
+ }
+
+ /* Make sure no vehicle is going to the old depot. */
+ for (Vehicle *v : Vehicle::Iterate()) {
+ if (v->First() != v) continue;
+ if (!v->current_order.IsType(OT_GOTO_DEPOT)) continue;
+ if (v->current_order.GetDestination() != this->index) continue;
+
+ v->current_order.MakeDummy();
+ }
/* Clear the depot from all order-lists */
RemoveOrderFromAllVehicles(OT_GOTO_DEPOT, this->index);
/* Delete the depot-window */
- CloseWindowById(WC_VEHICLE_DEPOT, this->xy);
+ CloseWindowById(WC_VEHICLE_DEPOT, this->index);
/* Delete the depot list */
- VehicleType vt = GetDepotVehicleType(this->xy);
- CloseWindowById(GetWindowClassForVehicleType(vt), VehicleListIdentifier(VL_DEPOT_LIST, vt, GetTileOwner(this->xy), this->index).Pack());
+ CloseWindowById(GetWindowClassForVehicleType(this->veh_type),
+ VehicleListIdentifier(VL_DEPOT_LIST,
+ this->veh_type, this->owner, this->index).Pack());
+
+ InvalidateWindowData(WC_SELECT_DEPOT, this->veh_type);
+
+ /* The sign will now disappear. */
+ _viewport_sign_kdtree.Remove(ViewportSignKdtreeItem::MakeDepot(this->index));
+ this->sign.MarkDirty();
+}
+
+/**
+ * Cancel deletion of this depot (reuse it).
+ * @param xy New location of the depot.
+ * @see Depot::IsInUse
+ * @see Depot::Disuse
+ */
+void Depot::Reuse(TileIndex xy)
+{
+ this->delete_ctr = 0;
+ this->xy = xy;
+ this->ta.tile = xy;
+ this->ta.h = this->ta.w = 1;
+
+ /* Ensure the sign is not drawn */
+ _viewport_sign_kdtree.Remove(ViewportSignKdtreeItem::MakeDepot(this->index));
+ this->sign.MarkDirty();
+}
+
+/**
+ * Schedule deletion of this depot.
+ *
+ * This method is ought to be called after demolishing last depot part.
+ * The depot will be kept in the pool for a while so it can be
+ * placed again later without messing vehicle orders.
+ *
+ * @see Depot::IsInUse
+ * @see Depot::Reuse
+ */
+void Depot::Disuse()
+{
+ /* Mark that the depot is demolished and start the countdown. */
+ this->delete_ctr = 8;
+
+ /* Update the sign, it will be visible from now. */
+ this->UpdateVirtCoord();
+ _viewport_sign_kdtree.Insert(ViewportSignKdtreeItem::MakeDepot(this->index));
+}
+
+/**
+ * Of all the depot parts a depot has, return the best destination for a vehicle.
+ * @param v The vehicle.
+ * @param dep The depot vehicle \a v is heading for.
+ * @return The free and closest (if none is free, just closest) part of depot to vehicle v.
+ */
+TileIndex Depot::GetBestDepotTile(Vehicle *v) const
+{
+ assert(this->veh_type == v->type);
+ TileIndex best_depot = INVALID_TILE;
+ DepotReservation best_found_type = DEPOT_RESERVATION_END;
+ uint best_distance = UINT_MAX;
+
+ for (const auto &tile : this->depot_tiles) {
+ bool check_south = v->type == VEH_ROAD;
+ uint new_distance = DistanceManhattan(v->tile, tile);
+again:
+ DepotReservation depot_reservation = GetDepotReservation(tile, check_south);
+ if (((best_found_type == depot_reservation) && new_distance < best_distance) || (depot_reservation < best_found_type)) {
+ best_depot = tile;
+ best_distance = new_distance;
+ best_found_type = depot_reservation;
+ }
+ if (check_south) {
+ /* For road vehicles, check north direction as well. */
+ check_south = false;
+ goto again;
+ }
+ }
+
+ return best_depot;
+}
+
+/**
+ * Check we can add some tiles to this depot.
+ * @param ta The affected tile area.
+ * @return Whether it is possible to add the tiles or an error message.
+ */
+CommandCost Depot::BeforeAddTiles(TileArea ta)
+{
+ assert(ta.tile != INVALID_TILE);
+
+ if (this->ta.tile != INVALID_TILE && this->IsInUse()) {
+ /* Important when the old rect is completely inside the new rect, resp. the old one was empty. */
+ ta.Add(this->ta.tile);
+ ta.Add(TileAddXY(this->ta.tile, this->ta.w - 1, this->ta.h - 1));
+ }
+
+ /* A max depot spread of 1 for VEH_SHIP is a special case,
+ * as ship depots consist of two tiles. */
+ if (this->veh_type == VEH_SHIP && _settings_game.depot.depot_spread == 1) {
+ /* (ta.w, ta.h) must be equal to (1, 2) or (2, 1).
+ * This means that ta.w * ta.h must be equal to 2. */
+ if (ta.w * ta.h != 2) return_cmd_error(STR_ERROR_DEPOT_TOO_SPREAD_OUT);
+ } else if (std::max(ta.w, ta.h) > _settings_game.depot.depot_spread) {
+ return_cmd_error(STR_ERROR_DEPOT_TOO_SPREAD_OUT);
+ }
+ return CommandCost();
+}
+
+/**
+ * Add some tiles to this depot and rescan area for depot_tiles.
+ * @param ta Affected tile area
+ * @param adding Whether adding or removing depot tiles.
+ */
+void Depot::AfterAddRemove(TileArea ta, bool adding)
+{
+ assert(ta.tile != INVALID_TILE);
+
+ if (adding) {
+ if (this->ta.tile != INVALID_TILE) {
+ ta.Add(this->ta.tile);
+ ta.Add(TileAddXY(this->ta.tile, this->ta.w - 1, this->ta.h - 1));
+ }
+ } else {
+ ta = this->ta;
+ }
+
+ this->ta.Clear();
+
+ for (TileIndex tile : ta) {
+ if (!IsDepotTile(tile)) continue;
+ if (GetDepotIndex(tile) != this->index) continue;
+ this->ta.Add(tile);
+ }
+
+ if (this->ta.tile != INVALID_TILE) {
+ this->RescanDepotTiles();
+ assert(!this->depot_tiles.empty());
+ this->xy = this->depot_tiles[0];
+ InvalidateWindowData(WC_VEHICLE_DEPOT, this->index);
+ } else {
+ assert(this->IsInUse());
+ this->Disuse();
+ TileIndex old_tile = this->xy;
+ this->RescanDepotTiles();
+ assert(this->depot_tiles.empty());
+ this->xy = old_tile;
+ }
+
+ InvalidateWindowData(WC_VEHICLE_DEPOT, this->index);
+ InvalidateWindowData(WC_SELECT_DEPOT, this->veh_type);
+}
+
+/**
+ * Check whether a tile is a destination tile, such as the starting tiles of
+ * rail platforms (and not the middle tiles of the platforms).
+ * @param dep The depot being checked
+ * @param tile The tile being checked
+ * @return Whether the tile is of the given depot.
+ */
+bool IsDepotDestTile(Depot *dep, TileIndex tile)
+{
+ assert(IsDepotTile(tile));
+ assert(GetDepotIndex(tile) == dep->index);
+
+ switch (dep->veh_type) {
+ case VEH_TRAIN:
+ assert(IsRailDepotTile(tile));
+ return !IsExtendedRailDepot(tile) || IsAnyStartPlatformTile(tile);
+ case VEH_ROAD:
+ case VEH_SHIP:
+ case VEH_AIRCRAFT:
+ return true;
+ default: NOT_REACHED();
+ }
+}
+
+/**
+ * Rescan depot_tiles. Done after AfterAddRemove and SaveLoad.
+ * Updates the tiles of the depot and its railtypes/roadtypes...
+ */
+void Depot::RescanDepotTiles()
+{
+ this->depot_tiles.clear();
+ RailTypes old_rail_types = this->r_types.rail_types;
+ this->r_types.rail_types = RAILTYPES_NONE;
+
+ for (TileIndex tile : this->ta) {
+ if (!IsDepotTile(tile)) continue;
+ if (GetDepotIndex(tile) != this->index) continue;
+ if (IsDepotDestTile(this, tile)) this->depot_tiles.push_back(tile);
+ switch (veh_type) {
+ case VEH_ROAD:
+ this->r_types.road_types |= GetPresentRoadTypes(tile);
+ break;
+ case VEH_TRAIN:
+ this->r_types.rail_types |= (RailTypes)(1 << GetRailType(tile));
+ break;
+ case VEH_SHIP:
+ /* Mark this ship depot has at least one part, so ships can be built. */
+ this->r_types.rail_types |= INVALID_RAILTYPES;
+ break;
+ case VEH_AIRCRAFT:
+ NOT_REACHED();
+ default: break;
+ }
+ }
+
+ if (old_rail_types != this->r_types.rail_types) {
+ InvalidateWindowData(WC_BUILD_VEHICLE, this->index, 0, true);
+ }
+}
+
+/**
+ * Fix tile reservations and vehicle on extended depots.
+ * @param v Vehicle to be checked.
+ * @param reserve Whether to reserve or free the position v is occupying.
+ */
+void UpdateExtendedDepotReservation(Vehicle *v, bool reserve)
+{
+ assert(v != nullptr);
+ assert(IsExtendedDepotTile(v->tile));
+ DepotReservation res_type = DEPOT_RESERVATION_EMPTY;
+
+ if (reserve) {
+ res_type = (v->vehstatus & VS_STOPPED) ?
+ DEPOT_RESERVATION_FULL_STOPPED_VEH : DEPOT_RESERVATION_IN_USE;
+ }
+
+ switch (v->type) {
+ case VEH_ROAD: {
+ assert(v == v->First());
+ assert(IsDiagonalDirection(v->direction));
+ bool is_facing_south = IsDiagDirFacingSouth(DirToDiagDir(v->direction));
+ TileArea ta;
+ for (Vehicle *u = v; u != nullptr; u = u->Next()) ta.Add(u->tile);
+ for (TileIndex t : ta) {
+ res_type = DEPOT_RESERVATION_EMPTY;
+
+ if (reserve) {
+ res_type = (v->vehstatus & VS_STOPPED) ?
+ DEPOT_RESERVATION_FULL_STOPPED_VEH : DEPOT_RESERVATION_IN_USE;
+ }
+ for (Vehicle *rv : Vehicle::Iterate()) {
+ if (res_type == DEPOT_RESERVATION_FULL_STOPPED_VEH) break;
+ if (rv->IsInDepot() && ta.Contains(rv->tile) && rv->First() != v) {
+ assert(rv->type == v->type);
+ [[maybe_unused]] DiagDirection diag_dir = DirToDiagDir(rv->direction);
+ assert(DiagDirToAxis(DirToDiagDir(v->direction)) == DiagDirToAxis(diag_dir));
+ if (is_facing_south == IsDiagDirFacingSouth(DirToDiagDir(rv->direction))) {
+ res_type = (rv->vehstatus & VS_STOPPED) ?
+ DEPOT_RESERVATION_FULL_STOPPED_VEH : DEPOT_RESERVATION_IN_USE;
+ }
+ }
+ }
+ SetDepotReservation(t, res_type, is_facing_south);
+ }
+ break;
+ }
+
+ case VEH_SHIP:
+ SetDepotReservation(v->tile, res_type);
+ break;
+
+ case VEH_TRAIN: {
+ DiagDirection dir = GetRailDepotDirection(v->tile);
+ SetDepotReservation(GetPlatformExtremeTile(v->tile, dir), res_type);
+ SetDepotReservation(GetPlatformExtremeTile(v->tile, ReverseDiagDir(dir)), res_type);
+ break;
+ }
+
+ case VEH_AIRCRAFT:
+ break;
+
+ default: NOT_REACHED();
+ }
}
diff --git a/src/depot_base.h b/src/depot_base.h
index 1d8330fc74035..246ec50ffa755 100644
--- a/src/depot_base.h
+++ b/src/depot_base.h
@@ -11,12 +11,26 @@
#define DEPOT_BASE_H
#include "depot_map.h"
+#include "viewport_type.h"
#include "core/pool_type.hpp"
#include "timer/timer_game_calendar.h"
+#include "rail_type.h"
+#include "road_type.h"
-typedef Pool DepotPool;
+static const DepotID MAX_DEPOTS = 64000;
+/**
+ * For build_vehicle_window, each vehicle type needs its own unique value.
+ * So we need some special indexes: MAX_DEPOTS + VEH_TYPE_XXX.
+ * @see GetBuildVehicleWindowNumber
+ */
+static_assert(MAX_DEPOTS + VEH_COMPANY_END - 1 <= INVALID_DEPOT);
+
+typedef Pool DepotPool;
extern DepotPool _depot_pool;
+class CommandCost;
+struct Vehicle;
+
struct Depot : DepotPool::PoolItem<&_depot_pool> {
/* DepotID index member of DepotPool is 2 bytes. */
uint16_t town_cn; ///< The N-1th depot for this town (consecutive number)
@@ -25,14 +39,38 @@ struct Depot : DepotPool::PoolItem<&_depot_pool> {
std::string name;
TimerGameCalendar::Date build_date; ///< Date of construction
- Depot(TileIndex xy = INVALID_TILE) : xy(xy) {}
+ VehicleType veh_type; ///< Vehicle type of the depot.
+ Owner owner; ///< Owner of the depot.
+ uint8_t delete_ctr; ///< Delete counter. If greater than 0 then it is decremented until it reaches 0; the depot is then deleted.
+ ViewportSign sign; ///< NOSAVE: Dimensions of sign
+ Station *station; ///< For aircraft, station associated with this hangar.
+
+ union {
+ RoadTypes road_types;
+ RailTypes rail_types;
+ AirTypes air_types;
+ } r_types;
+
+ TileArea ta;
+ std::vector depot_tiles;
+
+ Depot(TileIndex xy = INVALID_TILE, VehicleType type = VEH_INVALID, Owner owner = INVALID_OWNER, Station *station = nullptr) :
+ xy(xy),
+ veh_type(type),
+ owner(owner),
+ station(station),
+ ta(xy, 1, 1) {}
+
~Depot();
static inline Depot *GetByTile(TileIndex tile)
{
+ assert(Depot::IsValidID(GetDepotIndex(tile)));
return Depot::Get(GetDepotIndex(tile));
}
+ TileIndex GetBestDepotTile(Vehicle *v) const;
+
/**
* Is the "type" of depot the same as the given depot,
* i.e. are both a rail, road or ship depots?
@@ -41,8 +79,35 @@ struct Depot : DepotPool::PoolItem<&_depot_pool> {
*/
inline bool IsOfType(const Depot *d) const
{
- return GetTileType(d->xy) == GetTileType(this->xy);
+ return d->veh_type == this->veh_type;
}
+
+ /**
+ * Check whether the depot currently is in use; in use means
+ * that it is not scheduled for deletion and that it still has
+ * a building on the map. Otherwise the building is demolished
+ * and the depot awaits to be deleted.
+ * @return true iff still in use
+ * @see Depot::Disuse
+ * @see Depot::Reuse
+ */
+ inline bool IsInUse() const
+ {
+ return this->delete_ctr == 0;
+ }
+
+ void Reuse(TileIndex xy);
+ void Disuse();
+ void UpdateVirtCoord();
+
+ /* Check we can add some tiles to this depot. */
+ CommandCost BeforeAddTiles(TileArea ta);
+
+ /* Add some tiles to this depot and rescan area for depot_tiles. */
+ void AfterAddRemove(TileArea ta, bool adding);
+
+ /* Rescan depot_tiles. Done after AfterAddRemove and SaveLoad. */
+ void RescanDepotTiles();
};
#endif /* DEPOT_BASE_H */
diff --git a/src/depot_cmd.cpp b/src/depot_cmd.cpp
index 294de69e32ea7..1364800dd82b7 100644
--- a/src/depot_cmd.cpp
+++ b/src/depot_cmd.cpp
@@ -17,6 +17,10 @@
#include "vehiclelist.h"
#include "window_func.h"
#include "depot_cmd.h"
+#include "strings_func.h"
+#include "landscape.h"
+#include "viewport_kdtree.h"
+#include "timer/timer_game_tick.h"
#include "table/strings.h"
@@ -48,7 +52,7 @@ CommandCost CmdRenameDepot(DoCommandFlag flags, DepotID depot_id, const std::str
Depot *d = Depot::GetIfValid(depot_id);
if (d == nullptr) return CMD_ERROR;
- CommandCost ret = CheckTileOwnership(d->xy);
+ CommandCost ret = CheckOwnership(d->owner);
if (ret.Failed()) return ret;
bool reset = text.empty();
@@ -59,6 +63,8 @@ CommandCost CmdRenameDepot(DoCommandFlag flags, DepotID depot_id, const std::str
}
if (flags & DC_EXEC) {
+ /* _viewport_sign_kdtree does not need to be updated, only in-use depots can be renamed */
+
if (reset) {
d->name.clear();
MakeDefaultName(d);
@@ -68,11 +74,135 @@ CommandCost CmdRenameDepot(DoCommandFlag flags, DepotID depot_id, const std::str
/* Update the orders and depot */
SetWindowClassesDirty(WC_VEHICLE_ORDERS);
- SetWindowDirty(WC_VEHICLE_DEPOT, d->xy);
+ SetWindowDirty(WC_VEHICLE_DEPOT, d->index);
/* Update the depot list */
- VehicleType vt = GetDepotVehicleType(d->xy);
- SetWindowDirty(GetWindowClassForVehicleType(vt), VehicleListIdentifier(VL_DEPOT_LIST, vt, GetTileOwner(d->xy), d->index).Pack());
+ SetWindowDirty(GetWindowClassForVehicleType(d->veh_type), VehicleListIdentifier(VL_DEPOT_LIST, d->veh_type, d->owner, d->index).Pack());
+ }
+ return CommandCost();
+}
+
+/** Update the virtual coords needed to draw the depot sign. */
+void Depot::UpdateVirtCoord()
+{
+ Point pt = RemapCoords2(TileX(this->xy) * TILE_SIZE, TileY(this->xy) * TILE_SIZE);
+
+ pt.y -= 32 * ZOOM_BASE;
+
+ SetDParam(0, this->veh_type);
+ SetDParam(1, this->index);
+ this->sign.UpdatePosition(pt.x, pt.y, STR_VIEWPORT_DEPOT, STR_VIEWPORT_DEPOT_TINY);
+
+ SetWindowDirty(WC_VEHICLE_DEPOT, this->index);
+}
+
+/** Update the virtual coords needed to draw the depot sign for all depots. */
+void UpdateAllDepotVirtCoords()
+{
+ /* Only demolished depots have signs. */
+ for (Depot *d : Depot::Iterate()) if (!d->IsInUse()) d->UpdateVirtCoord();
+}
+
+/**
+ * Find a demolished depot close to a tile.
+ * @param ta Tile area to search for.
+ * @param type Depot type.
+ * @param cid Previous owner of the depot.
+ * @return The index of a demolished nearby depot, or INVALID_DEPOT if none.
+ */
+DepotID FindDeletedDepotCloseTo(TileArea ta, VehicleType type, CompanyID cid)
+{
+ for (Depot *depot : Depot::Iterate()) {
+ if (depot->IsInUse() || depot->veh_type != type || depot->owner != cid) continue;
+ if (ta.Contains(depot->xy)) return depot->index;
+ }
+
+ return INVALID_DEPOT;
+}
+
+void OnTick_Depot()
+{
+ if (_game_mode == GM_EDITOR) return;
+
+ /* Clean up demolished depots. */
+ for (Depot *d : Depot::Iterate()) {
+ if (d->IsInUse()) continue;
+ if ((TimerGameTick::counter + d->index) % Ticks::DEPOT_REMOVAL_TICKS != 0) continue;
+ if (--d->delete_ctr != 0) continue;
+ delete d;
+ }
+}
+
+
+/**
+ * Look for or check depot to join to, building a new one if necessary.
+ * @param ta The area of the new depot.
+ * @param veh_type The vehicle type of the new depot.
+ * @param join_to DepotID of the depot to join to.
+ * If INVALID_DEPOT, look whether it is possible to join to an existing depot.
+ * If NEW_DEPOT, directly create a new depot.
+ * @param depot The pointer to the depot.
+ * @param adjacent Whether adjacent depots are allowed
+ * @return command cost with the error or 'okay'
+ */
+CommandCost FindJoiningDepot(TileArea ta, VehicleType veh_type, DepotID &join_to, Depot *&depot, bool adjacent, DoCommandFlag flags)
+{
+ /* Look for a joining depot if needed. */
+ if (join_to == INVALID_DEPOT) {
+ assert(depot == nullptr);
+ DepotID closest_depot = INVALID_DEPOT;
+
+ TileArea check_area(ta);
+ check_area.Expand(1);
+
+ /* Check around to see if there's any depot there. */
+ for (TileIndex tile_cur : check_area) {
+ if (IsValidTile(tile_cur) && IsDepotTile(tile_cur)) {
+ Depot *d = Depot::GetByTile(tile_cur);
+ assert(d != nullptr);
+ if (d->veh_type != veh_type) continue;
+ if (d->owner != _current_company) continue;
+
+ if (closest_depot == INVALID_DEPOT) {
+ closest_depot = d->index;
+ } else if (closest_depot != d->index) {
+ if (!adjacent) return_cmd_error(STR_ERROR_ADJOINS_MORE_THAN_ONE_EXISTING_DEPOT);
+ }
+ }
+ }
+
+ if (closest_depot == INVALID_DEPOT) {
+ /* Check for close unused depots. */
+ check_area.Expand(7); // total distance of 8
+ closest_depot = FindDeletedDepotCloseTo(check_area, veh_type, _current_company);
+ }
+
+ if (closest_depot != INVALID_DEPOT) {
+ assert(Depot::IsValidID(closest_depot));
+ depot = Depot::Get(closest_depot);
+ }
+
+ join_to = depot == nullptr ? NEW_DEPOT : depot->index;
}
+
+ /* At this point, join_to is NEW_DEPOT or a valid DepotID. */
+
+ if (join_to == NEW_DEPOT) {
+ /* New depot needed. */
+ if (!Depot::CanAllocateItem()) return CMD_ERROR;
+ if (flags & DC_EXEC) {
+ depot = new Depot(ta.tile, veh_type, _current_company);
+ depot->build_date = TimerGameCalendar::date;
+ }
+ } else {
+ /* Joining depots. */
+ assert(Depot::IsValidID(join_to));
+ depot = Depot::Get(join_to);
+ assert(depot->owner == _current_company);
+ assert(depot->veh_type == veh_type);
+ if (!depot->IsInUse() && (flags & DC_EXEC)) depot->Reuse(ta.tile);
+ return depot->BeforeAddTiles(ta);
+ }
+
return CommandCost();
}
diff --git a/src/depot_func.h b/src/depot_func.h
index 208e02110c9f3..d1890c87285a3 100644
--- a/src/depot_func.h
+++ b/src/depot_func.h
@@ -10,13 +10,16 @@
#ifndef DEPOT_FUNC_H
#define DEPOT_FUNC_H
+#include "depot_type.h"
#include "vehicle_type.h"
#include "slope_func.h"
+#include "command_type.h"
-void ShowDepotWindow(TileIndex tile, VehicleType type);
+void ShowDepotWindow(DepotID depot_id);
void InitDepotWindowBlockSizes();
void DeleteDepotHighlightOfVehicle(const Vehicle *v);
+void UpdateAllDepotVirtCoords();
/**
* Find out if the slope of the tile is suitable to build a depot of given direction
@@ -33,4 +36,13 @@ inline bool CanBuildDepotByTileh(DiagDirection direction, Slope tileh)
return IsSteepSlope(tileh) ? (tileh & entrance_corners) == entrance_corners : (tileh & entrance_corners) != 0;
}
+struct Depot;
+CommandCost FindJoiningDepot(TileArea ta, VehicleType veh_type, DepotID &join_to, Depot *&depot, bool adjacent, DoCommandFlag flags);
+
+using DepotPickerCmdProc = std::function;
+void ShowSelectDepotIfNeeded(TileArea ta, DepotPickerCmdProc proc, VehicleType veh_type);
+
+struct Window;
+void CheckRedrawDepotHighlight(const Window *w, VehicleType veh_type);
+
#endif /* DEPOT_FUNC_H */
diff --git a/src/depot_gui.cpp b/src/depot_gui.cpp
index e62915fe2c5bb..d31eb47d8a59c 100644
--- a/src/depot_gui.cpp
+++ b/src/depot_gui.cpp
@@ -29,9 +29,12 @@
#include "zoom_func.h"
#include "error.h"
#include "depot_cmd.h"
+#include "station_base.h"
#include "train_cmd.h"
#include "vehicle_cmd.h"
#include "core/geometry_func.hpp"
+#include "depot_func.h"
+#include "train_placement.h"
#include "widgets/depot_widget.h"
@@ -78,6 +81,7 @@ static constexpr NWidgetPart _nested_train_depot_widgets[] = {
NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_D_BUILD), SetDataTip(0x0, STR_NULL), SetFill(1, 1), SetResize(1, 0),
NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_D_CLONE), SetDataTip(0x0, STR_NULL), SetFill(1, 1), SetResize(1, 0),
+ NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_D_HIGHLIGHT), SetDataTip(STR_BUTTON_HIGHLIGHT_DEPOT, STR_TOOLTIP_HIGHLIGHT_DEPOT), SetFill(1, 1), SetResize(1, 0),
NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_D_VEHICLE_LIST), SetDataTip(0x0, STR_NULL), SetAspect(WidgetDimensions::ASPECT_VEHICLE_ICON), SetFill(0, 1),
NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_D_STOP_ALL), SetDataTip(SPR_FLAG_VEH_STOPPED, STR_NULL), SetAspect(WidgetDimensions::ASPECT_VEHICLE_FLAG), SetFill(0, 1),
NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_D_START_ALL), SetDataTip(SPR_FLAG_VEH_RUNNING, STR_NULL), SetAspect(WidgetDimensions::ASPECT_VEHICLE_FLAG), SetFill(0, 1),
@@ -259,6 +263,7 @@ struct DepotWindow : Window {
VehicleType type;
bool generate_list;
WidgetID hovered_widget; ///< Index of the widget being hovered during drag/drop. -1 if no drag is in progress.
+ std::vector problematic_vehicles; ///< Vector associated to vehicle_list, with a value of true for vehicles that cannot leave the depot.
VehicleList vehicle_list;
VehicleList wagon_list;
uint unitnumber_digits;
@@ -266,15 +271,17 @@ struct DepotWindow : Window {
Scrollbar *hscroll; ///< Only for trains.
Scrollbar *vscroll;
- DepotWindow(WindowDesc &desc, TileIndex tile, VehicleType type) : Window(desc)
+ DepotWindow(WindowDesc &desc, DepotID depot_id) : Window(desc)
{
- assert(IsCompanyBuildableVehicleType(type)); // ensure that we make the call with a valid type
+ assert(Depot::IsValidID(depot_id));
+ Depot *depot = Depot::Get(depot_id);
+ assert(IsCompanyBuildableVehicleType(depot->veh_type));
this->sel = INVALID_VEHICLE;
this->vehicle_over = INVALID_VEHICLE;
this->generate_list = true;
this->hovered_widget = -1;
- this->type = type;
+ this->type = depot->veh_type;
this->num_columns = 1; // for non-trains this gets set in FinishInitNested()
this->unitnumber_digits = 2;
@@ -282,23 +289,24 @@ struct DepotWindow : Window {
this->hscroll = (this->type == VEH_TRAIN ? this->GetScrollbar(WID_D_H_SCROLL) : nullptr);
this->vscroll = this->GetScrollbar(WID_D_V_SCROLL);
/* Don't show 'rename button' of aircraft hangar */
- this->GetWidget(WID_D_SHOW_RENAME)->SetDisplayedPlane(type == VEH_AIRCRAFT ? SZSP_NONE : 0);
+ this->GetWidget(WID_D_SHOW_RENAME)->SetDisplayedPlane(this->type == VEH_AIRCRAFT ? SZSP_NONE : 0);
/* Only train depots have a horizontal scrollbar and a 'sell chain' button */
- if (type == VEH_TRAIN) this->GetWidget(WID_D_MATRIX)->widget_data = 1 << MAT_COL_START;
- this->GetWidget(WID_D_SHOW_H_SCROLL)->SetDisplayedPlane(type == VEH_TRAIN ? 0 : SZSP_HORIZONTAL);
- this->GetWidget(WID_D_SHOW_SELL_CHAIN)->SetDisplayedPlane(type == VEH_TRAIN ? 0 : SZSP_NONE);
- this->SetupWidgetData(type);
- this->FinishInitNested(tile);
+ if (this->type == VEH_TRAIN) this->GetWidget(WID_D_MATRIX)->widget_data = 1 << MAT_COL_START;
+ this->GetWidget(WID_D_SHOW_H_SCROLL)->SetDisplayedPlane(this->type == VEH_TRAIN ? 0 : SZSP_HORIZONTAL);
+ this->GetWidget(WID_D_SHOW_SELL_CHAIN)->SetDisplayedPlane(this->type == VEH_TRAIN ? 0 : SZSP_NONE);
+ this->SetupWidgetData(this->type);
+ this->FinishInitNested(depot_id);
- this->owner = GetTileOwner(tile);
+ this->owner = depot->owner;
OrderBackup::Reset();
}
void Close([[maybe_unused]] int data = 0) override
{
CloseWindowById(WC_BUILD_VEHICLE, this->window_number);
- CloseWindowById(GetWindowClassForVehicleType(this->type), VehicleListIdentifier(VL_DEPOT_LIST, this->type, this->owner, this->GetDepotIndex()).Pack(), false);
+ CloseWindowById(GetWindowClassForVehicleType(this->type), VehicleListIdentifier(VL_DEPOT_LIST, this->type, this->owner, this->window_number).Pack(), false);
OrderBackup::Reset(this->window_number);
+ SetViewportHighlightDepot(this->window_number, false);
this->Window::Close();
}
@@ -405,6 +413,11 @@ struct DepotWindow : Window {
for (; num < maxval; ir = ir.Translate(0, this->resize.step_height)) { // Draw the rows
Rect cell = ir; /* Keep track of horizontal cells */
for (uint i = 0; i < this->num_columns && num < maxval; i++, num++) {
+ /* Draw a dark red background if train cannot be placed. */
+ if (this->type == VEH_TRAIN && this->problematic_vehicles[num] == 1) {
+ GfxFillRect(cell.left, cell.top, cell.right, cell.bottom, PC_DARK_GREY);
+ }
+
/* Draw all vehicles in the current row */
const Vehicle *v = this->vehicle_list[num];
this->DrawVehicleInDepot(v, cell);
@@ -426,7 +439,7 @@ struct DepotWindow : Window {
if (widget != WID_D_CAPTION) return;
SetDParam(0, this->type);
- SetDParam(1, this->GetDepotIndex());
+ SetDParam(1, (this->type == VEH_AIRCRAFT) ? Depot::Get(this->window_number)->station->index : this->window_number);
}
struct GetDepotVehiclePtData {
@@ -708,12 +721,23 @@ struct DepotWindow : Window {
void OnPaint() override
{
+ extern DepotID _viewport_highlight_depot;
+ this->SetWidgetLoweredState(WID_D_HIGHLIGHT, _viewport_highlight_depot == this->window_number);
+
if (this->generate_list) {
/* Generate the vehicle list
* It's ok to use the wagon pointers for non-trains as they will be ignored */
BuildDepotVehicleList(this->type, this->window_number, &this->vehicle_list, &this->wagon_list);
this->generate_list = false;
DepotSortList(&this->vehicle_list);
+ if (this->type == VEH_TRAIN) {
+ this->problematic_vehicles.clear();
+ TrainPlacement tp;
+ for (uint num = 0; num < this->vehicle_list.size(); ++num) {
+ const Vehicle *v = this->vehicle_list[num];
+ this->problematic_vehicles.push_back(!tp.CanFindAppropriatePlatform(Train::From(v), false));
+ }
+ }
uint new_unitnumber_digits = GetUnitNumberDigits(this->vehicle_list);
/* Only increase the size; do not decrease to prevent constant changes */
@@ -742,8 +766,7 @@ struct DepotWindow : Window {
}
/* Setup disabled buttons. */
- TileIndex tile = this->window_number;
- this->SetWidgetsDisabledState(!IsTileOwner(tile, _local_company),
+ this->SetWidgetsDisabledState(this->owner != _local_company,
WID_D_STOP_ALL,
WID_D_START_ALL,
WID_D_SELL,
@@ -759,6 +782,8 @@ struct DepotWindow : Window {
void OnClick([[maybe_unused]] Point pt, WidgetID widget, [[maybe_unused]] int click_count) override
{
+ TileIndex tile = Depot::Get(this->window_number)->xy;
+
switch (widget) {
case WID_D_MATRIX: // List
this->DepotClick(pt.x, pt.y);
@@ -789,20 +814,25 @@ struct DepotWindow : Window {
if (_ctrl_pressed) {
ShowExtraViewportWindow(this->window_number);
} else {
- ScrollMainWindowToTile(this->window_number);
+ ScrollMainWindowToTile(tile);
}
break;
case WID_D_RENAME: // Rename button
SetDParam(0, this->type);
- SetDParam(1, Depot::GetByTile((TileIndex)this->window_number)->index);
+ SetDParam(1, this->window_number);
ShowQueryString(STR_DEPOT_NAME, STR_DEPOT_RENAME_DEPOT_CAPTION, MAX_LENGTH_DEPOT_NAME_CHARS, this, CS_ALPHANUMERAL, QSF_ENABLE_DEFAULT | QSF_LEN_IN_CHARS);
break;
+ case WID_D_HIGHLIGHT:
+ this->SetWidgetDirty(WID_D_HIGHLIGHT);
+ SetViewportHighlightDepot(this->window_number, !this->IsWidgetLowered(WID_D_HIGHLIGHT));
+ break;
+
case WID_D_STOP_ALL:
case WID_D_START_ALL: {
VehicleListIdentifier vli(VL_DEPOT_LIST, this->type, this->owner);
- Command::Post(this->window_number, widget == WID_D_START_ALL, false, vli);
+ Command::Post(STR_ERROR_CAN_T_START_STOP_VEHICLES, tile, widget == WID_D_START_ALL, false, vli);
break;
}
@@ -810,7 +840,7 @@ struct DepotWindow : Window {
/* Only open the confirmation window if there are anything to sell */
if (!this->vehicle_list.empty() || !this->wagon_list.empty()) {
SetDParam(0, this->type);
- SetDParam(1, this->GetDepotIndex());
+ SetDParam(1, this->window_number);
ShowQuery(
STR_DEPOT_CAPTION,
STR_DEPOT_SELL_CONFIRMATION_TEXT,
@@ -821,11 +851,11 @@ struct DepotWindow : Window {
break;
case WID_D_VEHICLE_LIST:
- ShowVehicleListWindow(GetTileOwner(this->window_number), this->type, (TileIndex)this->window_number);
+ ShowVehicleListWindowDepot(this->owner, this->type, this->window_number);
break;
case WID_D_AUTOREPLACE:
- Command::Post(this->window_number, this->type);
+ Command::Post(STR_ERROR_CAN_T_REPLACE_VEHICLES, tile, this->type);
break;
}
@@ -836,7 +866,7 @@ struct DepotWindow : Window {
if (!str.has_value()) return;
/* Do depot renaming */
- Command::Post(STR_ERROR_CAN_T_RENAME_DEPOT, this->GetDepotIndex(), *str);
+ Command::Post(STR_ERROR_CAN_T_RENAME_DEPOT, this->window_number, *str);
}
bool OnRightClick([[maybe_unused]] Point pt, WidgetID widget) override
@@ -902,10 +932,10 @@ struct DepotWindow : Window {
{
if (_ctrl_pressed) {
/* Share-clone, do not open new viewport, and keep tool active */
- Command::Post(STR_ERROR_CAN_T_BUY_TRAIN + v->type, this->window_number, v->index, true);
+ Command::Post(STR_ERROR_CAN_T_BUY_TRAIN + v->type, Depot::Get(this->window_number)->xy, v->index, true);
} else {
/* Copy-clone, open viewport for new vehicle, and deselect the tool (assume player wants to change things on new vehicle) */
- if (Command::Post(STR_ERROR_CAN_T_BUY_TRAIN + v->type, CcCloneVehicle, this->window_number, v->index, false)) {
+ if (Command::Post(STR_ERROR_CAN_T_BUY_TRAIN + v->type, CcCloneVehicle, Depot::Get(this->window_number)->xy, v->index, false)) {
ResetObjectToPlace();
}
}
@@ -1111,43 +1141,34 @@ struct DepotWindow : Window {
return ES_NOT_HANDLED;
}
-
- /**
- * Gets the DepotID of the current window.
- * In the case of airports, this is the station ID.
- * @return Depot or station ID of this window.
- */
- inline uint16_t GetDepotIndex() const
- {
- return (this->type == VEH_AIRCRAFT) ? ::GetStationIndex(this->window_number) : ::GetDepotIndex(this->window_number);
- }
};
static void DepotSellAllConfirmationCallback(Window *win, bool confirmed)
{
if (confirmed) {
- DepotWindow *w = (DepotWindow*)win;
- TileIndex tile = w->window_number;
- VehicleType vehtype = w->type;
- Command::Post(tile, vehtype);
+ assert(Depot::IsValidID(win->window_number));
+ Depot *d = Depot::Get(win->window_number);
+ if (!d->IsInUse()) return;
+ Command::Post(d->xy, d->veh_type);
}
}
/**
- * Opens a depot window
- * @param tile The tile where the depot/hangar is located
- * @param type The type of vehicles in the depot
+ * Opens a depot window.
+ * @param depot_id Index of the depot.
*/
-void ShowDepotWindow(TileIndex tile, VehicleType type)
+void ShowDepotWindow(DepotID depot_id)
{
- if (BringWindowToFrontById(WC_VEHICLE_DEPOT, tile) != nullptr) return;
+ assert(Depot::IsValidID(depot_id));
+ if (BringWindowToFrontById(WC_VEHICLE_DEPOT, depot_id) != nullptr) return;
- switch (type) {
+ Depot *d = Depot::Get(depot_id);
+ switch (d->veh_type) {
default: NOT_REACHED();
- case VEH_TRAIN: new DepotWindow(_train_depot_desc, tile, type); break;
- case VEH_ROAD: new DepotWindow(_road_depot_desc, tile, type); break;
- case VEH_SHIP: new DepotWindow(_ship_depot_desc, tile, type); break;
- case VEH_AIRCRAFT: new DepotWindow(_aircraft_depot_desc, tile, type); break;
+ case VEH_TRAIN: new DepotWindow(_train_depot_desc, depot_id); break;
+ case VEH_ROAD: new DepotWindow(_road_depot_desc, depot_id); break;
+ case VEH_SHIP: new DepotWindow(_ship_depot_desc, depot_id); break;
+ case VEH_AIRCRAFT: new DepotWindow(_aircraft_depot_desc, depot_id); break;
}
}
@@ -1164,8 +1185,356 @@ void DeleteDepotHighlightOfVehicle(const Vehicle *v)
*/
if (_special_mouse_mode != WSM_DRAGDROP) return;
- w = dynamic_cast(FindWindowById(WC_VEHICLE_DEPOT, v->tile));
+ /* For shadows and rotors, do nothing. */
+ if (v->type == VEH_AIRCRAFT && !Aircraft::From(v)->IsNormalAircraft()) return;
+
+ assert(IsDepotTile(v->tile));
+ w = dynamic_cast(FindWindowById(WC_VEHICLE_DEPOT, GetDepotIndex(v->tile)));
if (w != nullptr) {
if (w->sel == v->index) ResetObjectToPlace();
}
}
+
+static std::vector _depots_nearby_list;
+
+/** Structure with user-data for AddNearbyDepot. */
+struct AddNearbyDepotData {
+ TileArea search_area; ///< Search area.
+ VehicleType type; ///< Vehicle type of the searched depots.
+};
+
+/**
+ * Add depot on this tile to _depots_nearby_list if it's fully within the
+ * depot spread.
+ * @param tile Tile just being checked
+ * @param user_data Pointer to TileArea context
+ */
+static bool AddNearbyDepot(TileIndex tile, void *user_data)
+{
+ AddNearbyDepotData *andd = (AddNearbyDepotData *)user_data;
+
+ /* Check if own depot and if we stay within station spread */
+ if (!IsDepotTile(tile)) return false;
+ Depot *dep = Depot::GetByTile(tile);
+ if (dep->owner != _local_company || dep->veh_type != andd->type ||
+ (find(_depots_nearby_list.begin(), _depots_nearby_list.end(), dep->index) != _depots_nearby_list.end())) {
+ return false;
+ }
+
+ CommandCost cost = dep->BeforeAddTiles(andd->search_area);
+ if (cost.Succeeded()) {
+ _depots_nearby_list.push_back(dep->index);
+ }
+
+ return false;
+}
+
+/**
+ * Circulate around the to-be-built depot to find depots we could join.
+ * Make sure that only depots are returned where joining wouldn't exceed
+ * depot spread and are our own depot.
+ * @param ta Base tile area of the to-be-built depot
+ * @param veh_type Vehicle type depots to look for
+ * @param distant_join Search for adjacent depots (false) or depots fully
+ * within depot spread
+ */
+static const Depot *FindDepotsNearby(TileArea ta, VehicleType veh_type, bool distant_join)
+{
+ _depots_nearby_list.clear();
+ _depots_nearby_list.push_back(NEW_DEPOT);
+
+ /* Check the inside, to return, if we sit on another big depot */
+ Depot *depot;
+ for (TileIndex t : ta) {
+ if (!IsDepotTile(t)) continue;
+ depot = Depot::GetByTile(t);
+ if (depot->veh_type == veh_type && depot->owner == _current_company) return depot;
+ }
+
+ /* Only search tiles where we have a chance to stay within the depot spread.
+ * The complete check needs to be done in the callback as we don't know the
+ * extent of the found depot, yet. */
+ if (distant_join && std::min(ta.w, ta.h) >= _settings_game.depot.depot_spread) return nullptr;
+ uint max_dist = distant_join ? _settings_game.depot.depot_spread - std::min(ta.w, ta.h) : 1;
+
+ AddNearbyDepotData andd;
+ andd.search_area = ta;
+ andd.type = veh_type;
+
+ TileIndex tile = TileAddByDir(andd.search_area.tile, DIR_N);
+ CircularTileSearch(&tile, max_dist, ta.w, ta.h, AddNearbyDepot, &andd);
+
+ /* Add reusable depots. */
+ ta.Expand(8);
+ for (Depot *d : Depot::Iterate()) {
+ if (d->IsInUse()) continue;
+ if (d->veh_type != veh_type || d->owner != _current_company) continue;
+ if (!ta.Contains(d->xy)) continue;
+ _depots_nearby_list.push_back(d->index);
+ }
+
+ return nullptr;
+}
+
+/**
+ * Check whether we need to show the depot selection window.
+ * @param ta Tile area of the to-be-built depot.
+ * @param proc The procedure for the depot picker.
+ * @param veh_type the vehicle type of the depot.
+ * @return whether we need to show the depot selection window.
+ */
+static bool DepotJoinerNeeded(TileArea ta, VehicleType veh_type)
+{
+ /* If a window is already opened and we didn't ctrl-click,
+ * return true (i.e. just flash the old window) */
+ Window *selection_window = FindWindowById(WC_SELECT_DEPOT, veh_type);
+ if (selection_window != nullptr) {
+ /* Abort current distant-join and start new one */
+ selection_window->Close();
+ UpdateTileSelection();
+ }
+
+ /* Only show the popup if we press ctrl. */
+ if (!_ctrl_pressed) return false;
+
+ /* Test for adjacent depot or depot below selection.
+ * If adjacent-stations is disabled and we are building next to a depot, do not show the selection window.
+ * but join the other depot immediately. */
+ return FindDepotsNearby(ta, veh_type, false) == nullptr;
+}
+
+/**
+ * Window for selecting depots to (distant) join to.
+ */
+struct SelectDepotWindow : Window {
+ DepotPickerCmdProc select_depot_proc; ///< The procedure params
+ TileArea area; ///< Location of new depot
+ Scrollbar *vscroll; ///< Vertical scrollbar for the window
+
+ SelectDepotWindow(WindowDesc &desc, TileArea ta, DepotPickerCmdProc& proc, VehicleType veh_type) :
+ Window(desc),
+ select_depot_proc(std::move(proc)),
+ area(ta)
+ {
+ this->CreateNestedTree();
+ this->vscroll = this->GetScrollbar(WID_JD_SCROLLBAR);
+ this->FinishInitNested(veh_type);
+ this->OnInvalidateData(0);
+
+ _thd.freeze = true;
+ }
+
+ ~SelectDepotWindow()
+ {
+ SetViewportHighlightDepot(INVALID_DEPOT, true);
+
+ _thd.freeze = false;
+ }
+
+ void UpdateWidgetSize(int widget, Dimension &size, const Dimension &padding, [[maybe_unused]] Dimension &fill, Dimension &resize) override
+ {
+ if (widget != WID_JD_PANEL) return;
+
+ resize.height = GetCharacterHeight(FS_NORMAL);
+ size.height = 5 * resize.height + padding.height;
+
+ /* Determine the widest string. */
+ Dimension d = GetStringBoundingBox(STR_JOIN_DEPOT_CREATE_SPLITTED_DEPOT);
+ for (const auto &depot : _depots_nearby_list) {
+ if (depot == NEW_DEPOT) continue;
+ const Depot *dep = Depot::Get(depot);
+ SetDParam(0, this->window_number);
+ SetDParam(1, dep->index);
+ d = maxdim(d, GetStringBoundingBox(STR_DEPOT_LIST_DEPOT));
+ }
+
+ d.height = 5 * resize.height;
+ d.width += padding.width;
+ d.height += padding.height;
+ size = d;
+ }
+
+ void DrawWidget(const Rect &r, int widget) const override
+ {
+ if (widget != WID_JD_PANEL) return;
+
+ Rect tr = r.Shrink(WidgetDimensions::scaled.framerect);
+
+ auto [first, last] = this->vscroll->GetVisibleRangeIterators(_depots_nearby_list);
+ for (auto it = first; it != last; ++it, tr.top += this->resize.step_height) {
+ if (*it == NEW_DEPOT) {
+ DrawString(tr, STR_JOIN_DEPOT_CREATE_SPLITTED_DEPOT);
+ } else {
+ SetDParam(0, this->window_number);
+ SetDParam(1, *it);
+ [[maybe_unused]] Depot *depot = Depot::GetIfValid(*it);
+ assert(depot != nullptr);
+ DrawString(tr, STR_DEPOT_LIST_DEPOT);
+ }
+ }
+ }
+
+ void OnClick(Point pt, int widget, [[maybe_unused]] int click_count) override
+ {
+ if (widget != WID_JD_PANEL) return;
+
+ auto it = this->vscroll->GetScrolledItemFromWidget(_depots_nearby_list, pt.y, this, WID_JD_PANEL, WidgetDimensions::scaled.framerect.top);
+ if (it == _depots_nearby_list.end()) return;
+
+ /* Execute stored Command */
+ this->select_depot_proc(*it);
+
+ InvalidateWindowData(WC_SELECT_DEPOT, window_number);
+ this->Close();
+ }
+
+ void OnRealtimeTick([[maybe_unused]] uint delta_ms) override
+ {
+ if (_thd.dirty & 2) {
+ _thd.dirty &= ~2;
+ this->SetDirty();
+ }
+ }
+
+ void OnResize() override
+ {
+ this->vscroll->SetCapacityFromWidget(this, WID_JD_PANEL, WidgetDimensions::scaled.framerect.Vertical());
+ }
+
+ /**
+ * Some data on this window has become invalid.
+ * @param data Information about the changed data.
+ * @param gui_scope Whether the call is done from GUI scope. You may not do everything when not in GUI scope. See #InvalidateWindowData() for details.
+ */
+ void OnInvalidateData([[maybe_unused]] int data = 0, bool gui_scope = true) override
+ {
+ if (!gui_scope) return;
+ FindDepotsNearby(this->area, (VehicleType)this->window_number, true);
+ this->vscroll->SetCount((uint)_depots_nearby_list.size());
+ this->SetDirty();
+ }
+
+ void OnMouseOver(Point pt, int widget) override
+ {
+ if (widget != WID_JD_PANEL) {
+ SetViewportHighlightDepot(INVALID_DEPOT, true);
+ return;
+ }
+
+ /* Highlight depot under cursor */
+ auto it = this->vscroll->GetScrolledItemFromWidget(_depots_nearby_list, pt.y, this, WID_JD_PANEL, WidgetDimensions::scaled.framerect.top);
+ SetViewportHighlightDepot(*it, true);
+ }
+
+};
+
+static const NWidgetPart _nested_select_depot_widgets[] = {
+ NWidget(NWID_HORIZONTAL),
+ NWidget(WWT_CLOSEBOX, COLOUR_DARK_GREEN),
+ NWidget(WWT_CAPTION, COLOUR_DARK_GREEN, WID_JD_CAPTION), SetDataTip(STR_JOIN_DEPOT_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
+ NWidget(WWT_DEFSIZEBOX, COLOUR_DARK_GREEN),
+ EndContainer(),
+ NWidget(NWID_HORIZONTAL),
+ NWidget(WWT_PANEL, COLOUR_DARK_GREEN, WID_JD_PANEL), SetResize(1, 0), SetScrollbar(WID_JD_SCROLLBAR), EndContainer(),
+ NWidget(NWID_VERTICAL),
+ NWidget(NWID_VSCROLLBAR, COLOUR_DARK_GREEN, WID_JD_SCROLLBAR),
+ NWidget(WWT_RESIZEBOX, COLOUR_DARK_GREEN),
+ EndContainer(),
+ EndContainer(),
+};
+
+static WindowDesc _select_depot_desc(
+ WDP_AUTO, "build_depot_join", 200, 180,
+ WC_SELECT_DEPOT, WC_NONE,
+ WDF_CONSTRUCTION,
+ _nested_select_depot_widgets
+);
+
+/**
+ * Show the depot selection window when needed. If not, build the depot.
+ * @param ta Area to build the depot in.
+ * @param proc Details of the procedure for the depot picker.
+ * @param veh_type Vehicle type of the depot to be built.
+ */
+void ShowSelectDepotIfNeeded(TileArea ta, DepotPickerCmdProc proc, VehicleType veh_type)
+{
+ if (DepotJoinerNeeded(ta, veh_type)) {
+ if (!_settings_client.gui.persistent_buildingtools) ResetObjectToPlace();
+ new SelectDepotWindow(_select_depot_desc, ta, proc, veh_type);
+ } else {
+ proc(INVALID_DEPOT);
+ }
+}
+
+/**
+ * Find depots adjacent to the current tile highlight area, so that all depot tiles
+ * can be highlighted.
+ * @param v_type Vehicle type to check.
+ */
+static void HighlightSingleAdjacentDepot(VehicleType v_type)
+{
+ /* With distant join we don't know which depot will be selected, so don't show any */
+ if (_ctrl_pressed) {
+ SetViewportHighlightDepot(INVALID_DEPOT, true);
+ return;
+ }
+
+ /* Tile area for TileHighlightData */
+ TileArea location(TileVirtXY(_thd.pos.x, _thd.pos.y), _thd.size.x / TILE_SIZE - 1, _thd.size.y / TILE_SIZE - 1);
+
+ /* If the current tile is already a depot, then it must be the nearest depot. */
+ if (IsDepotTypeTile(location.tile, (TransportType)v_type) &&
+ GetTileOwner(location.tile) == _local_company) {
+ SetViewportHighlightDepot(GetDepotIndex(location.tile), true);
+ return;
+ }
+
+ /* Extended area by one tile */
+ uint x = TileX(location.tile);
+ uint y = TileY(location.tile);
+
+ int max_c = 1;
+ TileArea ta(TileXY(std::max(0, x - max_c), std::max(0, y - max_c)), TileXY(std::min(Map::MaxX(), x + location.w + max_c), std::min(Map::MaxY(), y + location.h + max_c)));
+
+ DepotID adjacent = INVALID_DEPOT;
+
+ for (TileIndex tile : ta) {
+ if (IsDepotTile(tile) && GetTileOwner(tile) == _local_company) {
+ Depot *depot = Depot::GetByTile(tile);
+ if (depot == nullptr) continue;
+ if (depot->veh_type != v_type) continue;
+ if (adjacent != INVALID_DEPOT && depot->index != adjacent) {
+ /* Multiple nearby, distant join is required. */
+ adjacent = INVALID_DEPOT;
+ break;
+ }
+ adjacent = depot->index;
+ }
+ }
+ SetViewportHighlightDepot(adjacent, true);
+}
+
+/**
+ * Check whether we need to redraw the depot highlight.
+ * If it is needed actually make the window for redrawing.
+ * @param w the window to check.
+ * @param veh_type vehicle type to check.
+ */
+void CheckRedrawDepotHighlight(const Window *w, VehicleType veh_type)
+{
+ /* Test if ctrl state changed */
+ static bool _last_ctrl_pressed;
+ if (_ctrl_pressed != _last_ctrl_pressed) {
+ _thd.dirty = 0xff;
+ _last_ctrl_pressed = _ctrl_pressed;
+ }
+
+ if (_thd.dirty & 1) {
+ _thd.dirty &= ~1;
+ w->SetDirty();
+
+ if (_thd.drawstyle == HT_RECT) {
+ HighlightSingleAdjacentDepot(veh_type);
+ }
+ }
+}
diff --git a/src/depot_map.h b/src/depot_map.h
index 87bd91543145c..223c30a2cfd06 100644
--- a/src/depot_map.h
+++ b/src/depot_map.h
@@ -12,24 +12,25 @@
#include "station_map.h"
+static const uint8_t DEPOT_TYPE = 0x02;
+
/**
* Check if a tile is a depot and it is a depot of the given type.
*/
inline bool IsDepotTypeTile(Tile tile, TransportType type)
{
+ if (GB(tile.m5(), 6, 2) != DEPOT_TYPE) return false;
+
switch (type) {
default: NOT_REACHED();
case TRANSPORT_RAIL:
- return IsRailDepotTile(tile);
-
+ return IsTileType(tile, MP_RAILWAY);
case TRANSPORT_ROAD:
- return IsRoadDepotTile(tile);
-
+ return IsTileType(tile, MP_ROAD);
case TRANSPORT_WATER:
- return IsShipDepotTile(tile);
-
+ return IsTileType(tile, MP_WATER);
case TRANSPORT_AIR:
- return IsHangarTile(tile);
+ return IsAirportTile(tile);
}
}
@@ -40,19 +41,26 @@ inline bool IsDepotTypeTile(Tile tile, TransportType type)
*/
inline bool IsDepotTile(Tile tile)
{
- return IsRailDepotTile(tile) || IsRoadDepotTile(tile) || IsShipDepotTile(tile) || IsHangarTile(tile);
+ if (GB(tile.m5(), 6, 2) != DEPOT_TYPE) return false;
+ TileType type = GetTileType(tile);
+ return type == MP_RAILWAY || type == MP_ROAD || type == MP_WATER || IsAirportTile(tile);
}
+extern DepotID GetHangarIndex(TileIndex t);
+
/**
* Get the index of which depot is attached to the tile.
* @param t the tile
- * @pre IsRailDepotTile(t) || IsRoadDepotTile(t) || IsShipDepotTile(t)
+ * @pre IsDepotTile(t)
* @return DepotID
*/
inline DepotID GetDepotIndex(Tile t)
{
- /* Hangars don't have a Depot class, thus store no DepotID. */
- assert(IsRailDepotTile(t) || IsRoadDepotTile(t) || IsShipDepotTile(t));
+ assert(IsDepotTile(t));
+
+ /* Hangars don't store depot id on m2. */
+ if (IsTileType(t, MP_STATION)) return GetHangarIndex(t);
+
return t.m2();
}
@@ -73,4 +81,97 @@ inline VehicleType GetDepotVehicleType(Tile t)
}
}
+/** Return true if a tile belongs to an extended depot. */
+static inline bool IsExtendedDepot(Tile tile) {
+ assert(IsValidTile(tile));
+ assert(IsDepotTile(tile));
+ return HasBit(tile.m5(), 5);
+}
+
+/** Return true if a tile belongs to an extended depot. */
+static inline bool IsExtendedDepotTile(TileIndex tile) {
+ if (!IsValidTile(tile)) return false;
+ if (!IsDepotTile(tile)) return false;
+ return IsExtendedDepot(tile);
+}
+
+/**
+ * Has this depot some vehicle servicing or stopped inside?
+ * @param tile tile of the depot.
+ * @param south_dir In case of road transport, return reservation facing south if true.
+ * @return The type of reservation on this tile (empty, servicing or occupied).
+ * @pre is a depot tile
+ */
+static inline DepotReservation GetDepotReservation(Tile t, bool south_dir = false)
+{
+ assert(IsDepotTile(t));
+ if (!IsExtendedDepot(t)) return DEPOT_RESERVATION_EMPTY;
+ if (south_dir) {
+ assert(GetDepotVehicleType(t) == VEH_ROAD);
+ return (DepotReservation)GB(t.m6(), 4, 2);
+ }
+ return (DepotReservation)GB(t.m4(), 6, 2);
+}
+
+/**
+ * Is this a platform/depot tile full with stopped vehicles?
+ * @param tile tile of the depot.
+ * @param south_dir In case of road transport, check reservation facing south if true.
+ * @return the type of reservation of the depot.
+ * @pre is a depot tile
+ */
+static inline bool IsDepotFullWithStoppedVehicles(TileIndex t, bool south_dir = false)
+{
+ assert(IsDepotTile(t));
+ if (!IsExtendedDepot(t)) return false;
+ return GetDepotReservation(t, south_dir) == DEPOT_RESERVATION_FULL_STOPPED_VEH;
+}
+
+
+/**
+ * Has this depot tile/platform some vehicle inside?
+ * @param tile tile of the depot.
+ * @param south_dir In case of road transport, check reservation facing south if true.
+ * @return true iff depot tile/platform has no vehicle.
+ * @pre IsExtendedDepotTile
+ */
+static inline bool IsExtendedDepotEmpty(TileIndex t, bool south_dir = false)
+{
+ assert(IsExtendedDepotTile(t));
+ return GetDepotReservation(t, south_dir) == DEPOT_RESERVATION_EMPTY;
+}
+
+/**
+ * Mark whether this depot has a ship inside.
+ * @param tile of the depot.
+ * @param reservation type of reservation
+ * @param south_dir Whether to set south direction reservation.
+ * @pre tile is an extended ship depot.
+ */
+static inline void SetDepotReservation(Tile t, DepotReservation reservation, bool south_dir = false)
+{
+ assert(IsDepotTile(t));
+ if (!IsExtendedDepot(t)) return;
+ switch (GetTileType(t)) {
+ default: NOT_REACHED();
+ case MP_RAILWAY:
+ break;
+ case MP_ROAD:
+ if (south_dir) {
+ SB(t.m6(), 4, 2, reservation);
+ return;
+ }
+ break;
+ case MP_WATER:
+ assert(GetDepotReservation(t) == GetDepotReservation(GetOtherShipDepotTile(t)));
+ SB(Tile(GetOtherShipDepotTile(t)).m4(), 6, 2, reservation);
+ break;
+ case MP_STATION: return;
+ }
+
+ SB(t.m4(), 6, 2, reservation);
+}
+
+void UpdateExtendedDepotReservation(Vehicle *v, bool state);
+
#endif /* DEPOT_MAP_H */
diff --git a/src/depot_type.h b/src/depot_type.h
index 4e61c1bcbd5e7..62b222289ad34 100644
--- a/src/depot_type.h
+++ b/src/depot_type.h
@@ -14,7 +14,18 @@ typedef uint16_t DepotID; ///< Type for the unique identifier of depots.
struct Depot;
static const DepotID INVALID_DEPOT = UINT16_MAX;
+static const DepotID NEW_DEPOT = INVALID_DEPOT - 1;
static const uint MAX_LENGTH_DEPOT_NAME_CHARS = 32; ///< The maximum length of a depot name in characters including '\0'
+static const uint DEF_MAX_DEPOT_SPREAD = 12;
+
+/** Type of reservation of extended ship depots. */
+enum DepotReservation {
+ DEPOT_RESERVATION_EMPTY = 0, ///< No vehicle servicing/stopped on depot tile/platform.
+ DEPOT_RESERVATION_IN_USE = 1, ///< At least a vehicle is in the depot, but the depot tile is not full of stopped vehicles.
+ DEPOT_RESERVATION_FULL_STOPPED_VEH = 2, ///< The depot tile/platform is full with stopped vehicles.
+ DEPOT_RESERVATION_END
+};
+
#endif /* DEPOT_TYPE_H */
diff --git a/src/direction_func.h b/src/direction_func.h
index c554873a0d6f1..861a8c0e33a88 100644
--- a/src/direction_func.h
+++ b/src/direction_func.h
@@ -108,6 +108,19 @@ inline Direction ChangeDir(Direction d, DirDiff delta)
return static_cast((static_cast(d) + static_cast(delta)) % 8);
}
+/**
+ * Calculate the non-oriented difference between two Direction values
+ *
+ * @param d0 The first direction as the base
+ * @param d1 The second direction as the offset from the base
+ * @return The number of non-oriented 45 degrees difference.
+ */
+static inline DirDiff NonOrientedDirDifference(Direction d0, Direction d1)
+{
+ assert(IsValidDirection(d0));
+ assert(IsValidDirection(d1));
+ return std::min(DirDifference(d0, d1), DirDifference(d1, d0));
+}
/**
* Returns the reverse direction of the given DiagDirection
@@ -276,4 +289,36 @@ inline bool IsDiagonalDirection(Direction dir)
return (dir & 1) != 0;
}
+/**
+ * Checks if a given DiagDirection is facing south.
+ * @param diag_dir Diagonal direction to check
+ * @return true iff the diagonal direction is facing south.
+ */
+static inline bool IsDiagDirFacingSouth(DiagDirection diag_dir)
+{
+ return diag_dir == DIAGDIR_SE || diag_dir == DIAGDIR_SW;
+}
+
+/**
+ * Rotate a diagonal direction with a given number of right angles clockwise.
+ * @param dir original diagonal direcion.
+ * @param rotate number of right angles for clockwise rotation.
+ * @return the original diagonal direction rotated 90 degrees clockwise as many times as indicaded by rotate.
+ */
+static inline Direction RotateDirection(Direction dir, DiagDirection rotate)
+{
+ return (Direction)((dir + 2 * rotate) % DIR_END);
+}
+
+/**
+ * Rotate a direction with a given number of right angles clockwise.
+ * @param dir original direcion.
+ * @param rotate number of right angles for clockwise rotation.
+ * @return the original direction rotated 90 degrees clockwise as many times as indicaded by rotate.
+ */
+static inline DiagDirection RotateDiagDir(DiagDirection dir, DiagDirection rotate)
+{
+ return (DiagDirection)((dir + rotate) % DIAGDIR_END);
+}
+
#endif /* DIRECTION_FUNC_H */
diff --git a/src/disaster_vehicle.cpp b/src/disaster_vehicle.cpp
index f5b311f835ef4..82fdab5c7f16a 100644
--- a/src/disaster_vehicle.cpp
+++ b/src/disaster_vehicle.cpp
@@ -40,6 +40,8 @@
#include "effectvehicle_func.h"
#include "roadveh.h"
#include "train.h"
+#include "air_map.h"
+#include "pbs_air.h"
#include "ai/ai.hpp"
#include "game/game.hpp"
#include "company_base.h"
@@ -213,6 +215,17 @@ void DisasterVehicle::UpdatePosition(int x, int y, int z)
}
}
+/**
+ * Return whether a zeppelin can crash on a given tile.
+ * @param tile The tile to check.
+ * @return whether tile is a free plane apron to crash into.
+ */
+bool IsValidZeppelinCrashSite(TileIndex tile)
+{
+ assert(IsValidTile(tile));
+ return IsPlaneApronTile(tile) && !HasAirportTileAnyReservation(tile);
+}
+
/**
* Zeppeliner handling, v->state states:
* 0: Zeppeliner initialization has found a small airport, go there and crash
@@ -241,13 +254,18 @@ static bool DisasterTick_Zeppeliner(DisasterVehicle *v)
if (GB(v->tick_counter, 0, 3) == 0) CreateEffectVehicleRel(v, 0, -17, 2, EV_CRASH_SMOKE);
} else if (v->state == 0) {
- if (IsValidTile(v->tile) && IsAirportTile(v->tile)) {
+ if (IsValidZeppelinCrashSite(v->tile)) {
v->state = 1;
v->age = 0;
+ assert(IsAirportTile(v->tile));
SetDParam(0, GetStationIndex(v->tile));
AddTileNewsItem(STR_NEWS_DISASTER_ZEPPELIN, NT_ACCIDENT, v->tile);
AI::NewEvent(GetTileOwner(v->tile), new ScriptEventDisasterZeppelinerCrashed(GetStationIndex(v->tile)));
+ TrackBits tracks = GetAirportTileTracks(v->tile);
+ for (Track track = RemoveFirstTrack(&tracks); track != INVALID_TRACK; track = RemoveFirstTrack(&tracks)) {
+ SetAirportTrackReservation(v->tile, track);
+ }
}
}
@@ -262,10 +280,14 @@ static bool DisasterTick_Zeppeliner(DisasterVehicle *v)
if (v->state > 2) {
if (++v->age <= 13320) return true;
- if (IsValidTile(v->tile) && IsAirportTile(v->tile)) {
+ if (IsValidZeppelinCrashSite(v->tile)) {
Station *st = Station::GetByTile(v->tile);
- CLRBITS(st->airport.flags, RUNWAY_IN_block);
AI::NewEvent(GetTileOwner(v->tile), new ScriptEventDisasterZeppelinerCleared(st->index));
+
+ TrackBits tracks = GetAirportTileTracks(v->tile);
+ for (Track track = RemoveFirstTrack(&tracks); track != INVALID_TRACK; track = RemoveFirstTrack(&tracks)) {
+ RemoveAirportTrackReservation(v->tile, track);
+ }
}
v->UpdatePosition(v->x_pos, v->y_pos, GetAircraftFlightLevel(v));
@@ -300,10 +322,6 @@ static bool DisasterTick_Zeppeliner(DisasterVehicle *v)
v->age = 0;
}
- if (IsValidTile(v->tile) && IsAirportTile(v->tile)) {
- SETBITS(Station::GetByTile(v->tile)->airport.flags, RUNWAY_IN_block);
- }
-
return true;
}
@@ -736,8 +754,8 @@ static void Disaster_Zeppeliner_Init()
int x = TileX(Random()) * TILE_SIZE + TILE_SIZE / 2;
for (const Station *st : Station::Iterate()) {
- if (st->airport.tile != INVALID_TILE && (st->airport.type == AT_SMALL || st->airport.type == AT_LARGE)) {
- x = (TileX(st->airport.tile) + 2) * TILE_SIZE;
+ if (st->airport.tile != INVALID_TILE && !st->airport.aprons.empty()) {
+ x = TileX(st->airport.aprons[0]) * TILE_SIZE;
break;
}
}
@@ -994,6 +1012,26 @@ void ReleaseDisasterVehicle(VehicleID vehicle)
v->age = 0;
}
+/**
+ * Delete old crashed zeppelins, as they are not adapted to new airports.
+ */
+void DeleteCrashedZeppelins()
+{
+ for (DisasterVehicle *v : DisasterVehicle::Iterate()) {
+ if (v->subtype == ST_ZEPPELINER && v->current_order.GetDestination() > 2) {
+ /* If a zeppelin has crashed... */
+ if (IsValidTile(v->tile) && IsAirportTile(v->tile)) {
+ // revise: is this called after script initialization.
+ Station *st = Station::GetByTile(v->tile);
+ AI::NewEvent(GetTileOwner(v->tile), new ScriptEventDisasterZeppelinerCleared(st->index));
+ }
+
+ v->UpdatePosition(v->x_pos, v->y_pos, GetAircraftFlightLevel(v));
+ delete v;
+ }
+ }
+}
+
void DisasterVehicle::UpdateDeltaXY()
{
this->x_offs = -1;
diff --git a/src/dock_gui.cpp b/src/dock_gui.cpp
index ffbf5aa63e3c2..2b2a369665f12 100644
--- a/src/dock_gui.cpp
+++ b/src/dock_gui.cpp
@@ -32,6 +32,7 @@
#include "waypoint_cmd.h"
#include "timer/timer.h"
#include "timer/timer_game_calendar.h"
+#include "depot_func.h"
#include "widgets/dock_widget.h"
@@ -112,6 +113,13 @@ struct BuildDocksToolbarWindow : Window {
{
if (_game_mode == GM_NORMAL && this->IsWidgetLowered(WID_DT_STATION)) SetViewportCatchmentStation(nullptr, true);
if (_settings_client.gui.link_terraform_toolbar) CloseWindowById(WC_SCEN_LAND_GEN, 0, false);
+
+ if (_game_mode == GM_NORMAL &&
+ ((this->HasWidget(WID_DT_DEPOT) && this->IsWidgetLowered(WID_DT_DEPOT)) ||
+ (this->HasWidget(WID_DT_EXTENDED_DEPOT) && this->IsWidgetLowered(WID_DT_EXTENDED_DEPOT)))) {
+ SetViewportHighlightDepot(INVALID_DEPOT, true);
+ }
+
this->Window::Close();
}
@@ -127,6 +135,7 @@ struct BuildDocksToolbarWindow : Window {
bool can_build = CanBuildVehicleInfrastructure(VEH_SHIP);
this->SetWidgetsDisabledState(!can_build,
WID_DT_DEPOT,
+ WID_DT_EXTENDED_DEPOT,
WID_DT_STATION,
WID_DT_BUOY);
if (!can_build) {
@@ -137,11 +146,13 @@ struct BuildDocksToolbarWindow : Window {
if (_game_mode != GM_EDITOR) {
if (!can_build) {
/* Show in the tooltip why this button is disabled. */
- this->GetWidget(WID_DT_DEPOT)->SetToolTip(STR_TOOLBAR_DISABLED_NO_VEHICLE_AVAILABLE);
+ if (this->HasWidget(WID_DT_DEPOT)) this->GetWidget(WID_DT_DEPOT)->SetToolTip(STR_TOOLBAR_DISABLED_NO_VEHICLE_AVAILABLE);
+ if (this->HasWidget(WID_DT_EXTENDED_DEPOT)) this->GetWidget(WID_DT_EXTENDED_DEPOT)->SetToolTip(STR_TOOLBAR_DISABLED_NO_VEHICLE_AVAILABLE);
this->GetWidget(WID_DT_STATION)->SetToolTip(STR_TOOLBAR_DISABLED_NO_VEHICLE_AVAILABLE);
this->GetWidget(WID_DT_BUOY)->SetToolTip(STR_TOOLBAR_DISABLED_NO_VEHICLE_AVAILABLE);
} else {
- this->GetWidget(WID_DT_DEPOT)->SetToolTip(STR_WATERWAYS_TOOLBAR_BUILD_DEPOT_TOOLTIP);
+ if (this->HasWidget(WID_DT_DEPOT)) this->GetWidget(WID_DT_DEPOT)->SetToolTip(STR_WATERWAYS_TOOLBAR_BUILD_DEPOT_TOOLTIP);
+ if (this->HasWidget(WID_DT_EXTENDED_DEPOT)) this->GetWidget(WID_DT_EXTENDED_DEPOT)->SetToolTip(STR_WATERWAYS_TOOLBAR_BUILD_EXTENDED_DEPOT_TOOLTIP);
this->GetWidget(WID_DT_STATION)->SetToolTip(STR_WATERWAYS_TOOLBAR_BUILD_DOCK_TOOLTIP);
this->GetWidget(WID_DT_BUOY)->SetToolTip(STR_WATERWAYS_TOOLBAR_BUOY_TOOLTIP);
}
@@ -164,7 +175,10 @@ struct BuildDocksToolbarWindow : Window {
break;
case WID_DT_DEPOT: // Build depot button
- if (HandlePlacePushButton(this, WID_DT_DEPOT, SPR_CURSOR_SHIP_DEPOT, HT_RECT)) ShowBuildDocksDepotPicker(this);
+ case WID_DT_EXTENDED_DEPOT:
+ if (HandlePlacePushButton(this, widget, SPR_CURSOR_SHIP_DEPOT, HT_RECT)) {
+ ShowBuildDocksDepotPicker(this);
+ }
break;
case WID_DT_STATION: // Build station button
@@ -205,8 +219,16 @@ struct BuildDocksToolbarWindow : Window {
break;
case WID_DT_DEPOT: // Build depot button
- Command::Post(STR_ERROR_CAN_T_BUILD_SHIP_DEPOT, CcBuildDocks, tile, _ship_depot_direction);
+ case WID_DT_EXTENDED_DEPOT: {
+ CloseWindowById(WC_SELECT_DEPOT, VEH_SHIP);
+
+ ViewportPlaceMethod vpm = _ship_depot_direction != AXIS_X ? VPM_LIMITED_X_FIXED_Y : VPM_LIMITED_Y_FIXED_X;
+ VpSetPlaceSizingLimit(_settings_game.depot.depot_spread);
+ VpStartPlaceSizing(tile, vpm, DDSP_BUILD_DEPOT);
+ /* Select tiles now to prevent selection from flickering. */
+ VpSelectTilesWithMethod(pt.x, pt.y, vpm);
break;
+ }
case WID_DT_STATION: { // Build station button
/* Determine the watery part of the dock. */
@@ -260,6 +282,17 @@ struct BuildDocksToolbarWindow : Window {
case DDSP_CREATE_RIVER:
Command::Post(STR_ERROR_CAN_T_PLACE_RIVERS, CcPlaySound_CONSTRUCTION_WATER, end_tile, start_tile, WATER_CLASS_RIVER, _ctrl_pressed);
break;
+ case DDSP_BUILD_DEPOT: {
+ bool adjacent = _ctrl_pressed;
+ bool extended = this->last_clicked_widget == WID_DT_EXTENDED_DEPOT;
+
+ auto proc = [=](DepotID join_to) -> bool {
+ return Command::Post(STR_ERROR_CAN_T_BUILD_SHIP_DEPOT, CcBuildDocks, start_tile, _ship_depot_direction, adjacent, extended, join_to, end_tile);
+ };
+
+ ShowSelectDepotIfNeeded(TileArea(start_tile, end_tile), proc, VEH_SHIP);
+ break;
+ }
default: break;
}
@@ -270,11 +303,18 @@ struct BuildDocksToolbarWindow : Window {
{
if (_game_mode != GM_EDITOR && this->IsWidgetLowered(WID_DT_STATION)) SetViewportCatchmentStation(nullptr, true);
+ if (_game_mode != GM_EDITOR &&
+ ((this->HasWidget(WID_DT_DEPOT) && this->IsWidgetLowered(WID_DT_DEPOT)) ||
+ (this->HasWidget(WID_DT_EXTENDED_DEPOT) && this->IsWidgetLowered(WID_DT_EXTENDED_DEPOT)))) {
+ SetViewportHighlightDepot(INVALID_DEPOT, true);
+ }
+
this->RaiseButtons();
CloseWindowById(WC_BUILD_STATION, TRANSPORT_WATER);
CloseWindowById(WC_BUILD_DEPOT, TRANSPORT_WATER);
CloseWindowById(WC_SELECT_STATION, 0);
+ CloseWindowById(WC_SELECT_DEPOT, VEH_SHIP);
CloseWindowByClass(WC_BUILD_BRIDGE);
}
@@ -322,6 +362,25 @@ struct BuildDocksToolbarWindow : Window {
}, DockToolbarGlobalHotkeys};
};
+/**
+ * Add the depot icons depending on availability of construction.
+ * @return Panel with water depot buttons.
+ */
+static std::unique_ptr MakeNWidgetWaterDepot()
+{
+ auto hor = std::make_unique();
+
+ if (HasBit(_settings_game.depot.water_depot_types, 0)) {
+ hor->Add(std::make_unique(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_DT_DEPOT, SPR_IMG_SHIP_DEPOT, STR_WATERWAYS_TOOLBAR_BUILD_DEPOT_TOOLTIP));
+ }
+
+ if (HasBit(_settings_game.depot.water_depot_types, 1)) {
+ hor->Add(std::make_unique(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_DT_EXTENDED_DEPOT, SPR_IMG_SHIP_DEPOT, STR_WATERWAYS_TOOLBAR_BUILD_EXTENDED_DEPOT_TOOLTIP));
+ }
+
+ return hor;
+}
+
/**
* Nested widget parts of docks toolbar, game version.
* Position of #WID_DT_RIVER widget has changed.
@@ -337,7 +396,7 @@ static constexpr NWidgetPart _nested_build_docks_toolbar_widgets[] = {
NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_DT_LOCK), SetMinimalSize(22, 22), SetFill(0, 1), SetDataTip(SPR_IMG_BUILD_LOCK, STR_WATERWAYS_TOOLBAR_BUILD_LOCKS_TOOLTIP),
NWidget(WWT_PANEL, COLOUR_DARK_GREEN), SetMinimalSize(5, 22), SetFill(1, 1), EndContainer(),
NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_DT_DEMOLISH), SetMinimalSize(22, 22), SetFill(0, 1), SetDataTip(SPR_IMG_DYNAMITE, STR_TOOLTIP_DEMOLISH_BUILDINGS_ETC),
- NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_DT_DEPOT), SetMinimalSize(22, 22), SetFill(0, 1), SetDataTip(SPR_IMG_SHIP_DEPOT, STR_WATERWAYS_TOOLBAR_BUILD_DEPOT_TOOLTIP),
+ NWidgetFunction(MakeNWidgetWaterDepot),
NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_DT_STATION), SetMinimalSize(22, 22), SetFill(0, 1), SetDataTip(SPR_IMG_SHIP_DOCK, STR_WATERWAYS_TOOLBAR_BUILD_DOCK_TOOLTIP),
NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_DT_BUOY), SetMinimalSize(22, 22), SetFill(0, 1), SetDataTip(SPR_IMG_BUOY, STR_WATERWAYS_TOOLBAR_BUOY_TOOLTIP),
NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_DT_BUILD_AQUEDUCT), SetMinimalSize(23, 22), SetFill(0, 1), SetDataTip(SPR_IMG_AQUEDUCT, STR_WATERWAYS_TOOLBAR_BUILD_AQUEDUCT_TOOLTIP),
@@ -514,10 +573,13 @@ struct BuildDocksDepotWindow : public PickerWindowBase {
private:
static void UpdateDocksDirection()
{
+ VpSetPlaceFixedSize(2);
if (_ship_depot_direction != AXIS_X) {
SetTileSelectSize(1, 2);
+ _thd.select_method = VPM_LIMITED_X_FIXED_Y;
} else {
SetTileSelectSize(2, 1);
+ _thd.select_method = VPM_LIMITED_Y_FIXED_X;
}
}
@@ -529,6 +591,13 @@ struct BuildDocksDepotWindow : public PickerWindowBase {
UpdateDocksDirection();
}
+ void Close([[maybe_unused]] int data = 0) override
+ {
+ CloseWindowById(WC_SELECT_DEPOT, VEH_SHIP);
+ VpResetFixedSize();
+ this->PickerWindowBase::Close();
+ }
+
void UpdateWidgetSize(WidgetID widget, Dimension &size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension &fill, [[maybe_unused]] Dimension &resize) override
{
switch (widget) {
@@ -569,6 +638,7 @@ struct BuildDocksDepotWindow : public PickerWindowBase {
switch (widget) {
case WID_BDD_X:
case WID_BDD_Y:
+ CloseWindowById(WC_SELECT_DEPOT, VEH_SHIP);
this->RaiseWidget(WID_BDD_X + _ship_depot_direction);
_ship_depot_direction = (widget == WID_BDD_X ? AXIS_X : AXIS_Y);
this->LowerWidget(WID_BDD_X + _ship_depot_direction);
@@ -578,6 +648,11 @@ struct BuildDocksDepotWindow : public PickerWindowBase {
break;
}
}
+
+ void OnRealtimeTick([[maybe_unused]] uint delta_ms) override
+ {
+ CheckRedrawDepotHighlight(this, VEH_SHIP);
+ }
};
static constexpr NWidgetPart _nested_build_docks_depot_widgets[] = {
diff --git a/src/economy.cpp b/src/economy.cpp
index cd990942399de..46fa3c3ea640e 100644
--- a/src/economy.cpp
+++ b/src/economy.cpp
@@ -44,6 +44,7 @@
#include "core/container_func.hpp"
#include "cargo_type.h"
#include "water.h"
+#include "air.h"
#include "game/game.hpp"
#include "cargomonitor.h"
#include "goal_base.h"
@@ -55,6 +56,8 @@
#include "timer/timer.h"
#include "timer/timer_game_calendar.h"
#include "timer/timer_game_economy.h"
+#include "depot_base.h"
+#include "platform_func.h"
#include "table/strings.h"
#include "table/pricebase.h"
@@ -374,6 +377,12 @@ void ChangeOwnershipOfCompanyItems(Owner old_owner, Owner new_owner)
}
if (new_owner == INVALID_OWNER) RebuildSubsidisedSourceAndDestinationCache();
+ for (Depot *dep : Depot::Iterate()) {
+ if (dep->owner == old_owner && new_owner != INVALID_OWNER) {
+ dep->owner = new_owner;
+ }
+ }
+
/* Take care of rating and transport rights in towns */
for (Town *t : Town::Iterate()) {
/* If a company takes over, give the ratings to that company. */
@@ -518,9 +527,6 @@ void ChangeOwnershipOfCompanyItems(Owner old_owner, Owner new_owner)
UpdateSignalsInBuffer();
}
- /* Add airport infrastructure count of the old company to the new one. */
- if (new_owner != INVALID_OWNER) Company::Get(new_owner)->infrastructure.airport += Company::Get(old_owner)->infrastructure.airport;
-
/* convert owner of stations (including deleted ones, but excluding buoys) */
for (Station *st : Station::Iterate()) {
if (st->owner == old_owner) {
@@ -684,9 +690,12 @@ static void CompaniesGenStatistics()
for (RoadType rt = ROADTYPE_BEGIN; rt < ROADTYPE_END; rt++) {
if (c->infrastructure.road[rt] != 0) cost.AddCost(RoadMaintenanceCost(rt, c->infrastructure.road[rt], RoadTypeIsRoad(rt) ? road_total : tram_total));
}
+ uint32_t air_total = c->infrastructure.GetAirTotal();
+ for (AirType at = AIRTYPE_BEGIN; at < AIRTYPE_END; at++) {
+ if (c->infrastructure.air[at] != 0) cost.AddCost(AirMaintenanceCost(at, c->infrastructure.air[at], air_total));
+ }
cost.AddCost(CanalMaintenanceCost(c->infrastructure.water));
cost.AddCost(StationMaintenanceCost(c->infrastructure.station));
- cost.AddCost(AirportMaintenanceCost(c->index));
SubtractMoneyFromCompany(cost);
}
@@ -1614,14 +1623,13 @@ static void ReserveConsist(Station *st, Vehicle *u, CargoArray *consist_capleft,
* Update the vehicle's load_unload_ticks, the time it will wait until it tries to load or unload
* again. Adjust for overhang of trains and set it at least to 1.
* @param front The vehicle to be updated.
- * @param st The station the vehicle is loading at.
* @param ticks The time it would normally wait, based on cargo loaded and unloaded.
*/
-static void UpdateLoadUnloadTicks(Vehicle *front, const Station *st, int ticks)
+static void UpdateLoadUnloadTicks(Vehicle *front, int ticks)
{
if (front->type == VEH_TRAIN && _settings_game.order.station_length_loading_penalty) {
/* Each platform tile is worth 2 rail vehicles. */
- int overhang = front->GetGroundVehicleCache()->cached_total_length - st->GetPlatformLength(front->tile) * TILE_SIZE;
+ int overhang = front->GetGroundVehicleCache()->cached_total_length - GetPlatformLength(front->tile) * TILE_SIZE;
if (overhang > 0) {
ticks <<= 1;
ticks += (overhang * ticks) / 8;
@@ -1883,9 +1891,9 @@ static void LoadUnloadVehicle(Vehicle *front)
SetBit(front->vehicle_flags, VF_STOP_LOADING);
}
- UpdateLoadUnloadTicks(front, st, new_load_unload_ticks);
+ UpdateLoadUnloadTicks(front, new_load_unload_ticks);
} else {
- UpdateLoadUnloadTicks(front, st, 20); // We need the ticks for link refreshing.
+ UpdateLoadUnloadTicks(front, 20); // We need the ticks for link refreshing.
bool finished_loading = true;
if (front->current_order.GetLoadType() & OLFB_FULL_LOAD) {
if (front->current_order.GetLoadType() == OLF_FULL_LOAD_ANY) {
diff --git a/src/economy_type.h b/src/economy_type.h
index e4d0ea077acf3..a4c08c4919463 100644
--- a/src/economy_type.h
+++ b/src/economy_type.h
@@ -241,8 +241,6 @@ static const int INVALID_PRICE_MODIFIER = MIN_PRICE_MODIFIER - 1;
static const uint TUNNELBRIDGE_TRACKBIT_FACTOR = 4;
/** Multiplier for how many regular track bits a level crossing counts. */
static const uint LEVELCROSSING_TRACKBIT_FACTOR = 2;
-/** Multiplier for how many regular track bits a road depot counts. */
-static const uint ROAD_DEPOT_TRACKBIT_FACTOR = 2;
/** Multiplier for how many regular track bits a bay stop counts. */
static const uint ROAD_STOP_TRACKBIT_FACTOR = 2;
/** Multiplier for how many regular tiles a lock counts. */
diff --git a/src/engine.cpp b/src/engine.cpp
index 979c6a7e3199f..da3e950c6f59e 100644
--- a/src/engine.cpp
+++ b/src/engine.cpp
@@ -32,6 +32,7 @@
#include "timer/timer.h"
#include "timer/timer_game_tick.h"
#include "timer/timer_game_calendar.h"
+#include "air.h"
#include "table/strings.h"
#include "table/engines.h"
@@ -778,6 +779,7 @@ void StartupEngines()
for (Company *c : Company::Iterate()) {
c->avail_railtypes = GetCompanyRailTypes(c->index);
c->avail_roadtypes = GetCompanyRoadTypes(c->index);
+ c->avail_airtypes = GetCompanyAirTypes(c->index);
}
/* Invalidate any open purchase lists */
@@ -798,10 +800,22 @@ static void EnableEngineForCompany(EngineID eid, CompanyID company)
Company *c = Company::Get(company);
SetBit(e->company_avail, company);
- if (e->type == VEH_TRAIN) {
- c->avail_railtypes = GetCompanyRailTypes(c->index);
- } else if (e->type == VEH_ROAD) {
- c->avail_roadtypes = GetCompanyRoadTypes(c->index);
+ switch (e->type) {
+ case VEH_TRAIN:
+ c->avail_railtypes = GetCompanyRailTypes(c->index);
+ break;
+
+ case VEH_ROAD:
+ c->avail_roadtypes = GetCompanyRoadTypes(c->index);
+ break;
+
+ case VEH_AIRCRAFT:
+ assert(e->u.air.airtype < AIRTYPE_END);
+ c->avail_airtypes = GetCompanyAirTypes(c->index);
+ break;
+
+ default:
+ break;
}
if (company == _local_company) {
@@ -830,6 +844,8 @@ static void DisableEngineForCompany(EngineID eid, CompanyID company)
c->avail_railtypes = GetCompanyRailTypes(c->index);
} else if (e->type == VEH_ROAD) {
c->avail_roadtypes = GetCompanyRoadTypes(c->index);
+ } else if (e->type == VEH_AIRCRAFT) {
+ c->avail_airtypes = GetCompanyAirTypes(c->index);
}
if (company == _local_company) {
@@ -928,6 +944,7 @@ static IntervalTimer _calendar_engines_daily({TimerGameCalend
for (Company *c : Company::Iterate()) {
c->avail_railtypes = AddDateIntroducedRailTypes(c->avail_railtypes, TimerGameCalendar::date);
c->avail_roadtypes = AddDateIntroducedRoadTypes(c->avail_roadtypes, TimerGameCalendar::date);
+ c->avail_airtypes = AddDateIntroducedAirTypes(c->avail_airtypes, TimerGameCalendar::date);
}
if (TimerGameCalendar::year >= _year_engine_aging_stops) return;
@@ -1086,6 +1103,10 @@ static void NewVehicleAvailable(Engine *e)
/* maybe make another road type available */
assert(e->u.road.roadtype < ROADTYPE_END);
for (Company *c : Company::Iterate()) c->avail_roadtypes = AddDateIntroducedRoadTypes(c->avail_roadtypes | GetRoadTypeInfo(e->u.road.roadtype)->introduces_roadtypes, TimerGameCalendar::date);
+ } else if (e->type == VEH_AIRCRAFT) {
+ /* maybe make another air type available */
+ assert(e->u.air.airtype < AIRTYPE_END);
+ for (Company *c : Company::Iterate()) c->avail_airtypes = AddDateIntroducedAirTypes(c->avail_airtypes | GetAirTypeInfo(e->u.air.airtype)->introduces_airtypes, TimerGameCalendar::date);
}
/* Only broadcast event if AIs are able to build this vehicle type. */
@@ -1236,15 +1257,28 @@ bool IsEngineBuildable(EngineID engine, VehicleType type, CompanyID company)
if (!e->IsEnabled()) return false;
- if (type == VEH_TRAIN && company != OWNER_DEITY) {
- /* Check if the rail type is available to this company */
- const Company *c = Company::Get(company);
- if (((GetRailTypeInfo(e->u.rail.railtype))->compatible_railtypes & c->avail_railtypes) == 0) return false;
- }
- if (type == VEH_ROAD && company != OWNER_DEITY) {
- /* Check if the road type is available to this company */
- const Company *c = Company::Get(company);
- if ((GetRoadTypeInfo(e->u.road.roadtype)->powered_roadtypes & c->avail_roadtypes) == ROADTYPES_NONE) return false;
+ if (company == OWNER_DEITY) return true;
+
+ const Company *c = Company::Get(company);
+
+ switch (type) {
+ case VEH_TRAIN:
+ /* Check if the rail type is available to this company. */
+ if (((GetRailTypeInfo(e->u.rail.railtype))->compatible_railtypes & c->avail_railtypes) == RAILTYPES_NONE) return false;
+ break;
+
+ case VEH_AIRCRAFT:
+ /* Check whether the air type is available to this company. */
+ if (((GetAirTypeInfo(e->u.air.airtype))->compatible_airtypes & c->avail_airtypes) == AIRTYPES_NONE) return false;
+ break;
+
+ case VEH_ROAD:
+ /* Check if the road type is available to this company. */
+ if ((GetRoadTypeInfo(e->u.road.roadtype)->powered_roadtypes & c->avail_roadtypes) == ROADTYPES_NONE) return false;
+ break;
+
+ default:
+ break;
}
return true;
diff --git a/src/engine_type.h b/src/engine_type.h
index d5a28ced3ea15..f7fcbdd8e3c60 100644
--- a/src/engine_type.h
+++ b/src/engine_type.h
@@ -11,6 +11,7 @@
#define ENGINE_TYPE_H
#include "economy_type.h"
+#include "air_type.h"
#include "rail_type.h"
#include "road_type.h"
#include "cargo_type.h"
@@ -108,6 +109,7 @@ struct AircraftVehicleInfo {
uint8_t mail_capacity; ///< Mail capacity (bags).
uint16_t passenger_capacity; ///< Passenger capacity (persons).
uint16_t max_range; ///< Maximum range of this aircraft.
+ AirType airtype; ///< Airport tile types this aircraft can use.
};
/** Information about a road vehicle. */
diff --git a/src/ground_vehicle.cpp b/src/ground_vehicle.cpp
index a6ab9758c0cbd..5981d22b7715a 100644
--- a/src/ground_vehicle.cpp
+++ b/src/ground_vehicle.cpp
@@ -197,7 +197,10 @@ bool GroundVehicle::IsChainInDepot() const
/* Check whether the rest is also already trying to enter the depot. */
for (; v != nullptr; v = v->Next()) {
- if (!v->T::IsInDepot() || v->tile != this->tile) return false;
+ if (!v->T::IsInDepot()) return false;
+ assert(IsDepotTile(v->tile));
+ assert(IsDepotTile(this->tile));
+ assert(GetDepotIndex(this->tile) == GetDepotIndex(v->tile));
}
return true;
diff --git a/src/group_cmd.cpp b/src/group_cmd.cpp
index 96aa2d1c9fe79..3930c81469d93 100644
--- a/src/group_cmd.cpp
+++ b/src/group_cmd.cpp
@@ -19,6 +19,7 @@
#include "core/pool_func.hpp"
#include "order_backup.h"
#include "group_cmd.h"
+#include "depot_map.h"
#include "table/strings.h"
@@ -579,7 +580,7 @@ std::tuple CmdAddVehicleGroup(DoCommandFlag flags, GroupID
}
}
- SetWindowDirty(WC_VEHICLE_DEPOT, v->tile);
+ if (IsDepotTypeTile(v->tile, (TransportType)v->type)) SetWindowDirty(WC_VEHICLE_DEPOT, GetDepotIndex(v->tile));
SetWindowDirty(WC_VEHICLE_VIEW, v->index);
SetWindowDirty(WC_VEHICLE_DETAILS, v->index);
InvalidateWindowData(WC_VEHICLE_VIEW, v->index);
diff --git a/src/group_gui.cpp b/src/group_gui.cpp
index bb2a8d840d410..ff3244c3a8972 100644
--- a/src/group_gui.cpp
+++ b/src/group_gui.cpp
@@ -842,7 +842,7 @@ class VehicleGroupWindow : public BaseVehicleListWindow {
break;
case WID_GL_AVAILABLE_VEHICLES:
- ShowBuildVehicleWindow(INVALID_TILE, this->vli.vtype);
+ ShowBuildVehicleWindow(INVALID_DEPOT, this->vli.vtype);
break;
case WID_GL_MANAGE_VEHICLES_DROPDOWN: {
diff --git a/src/gui.h b/src/gui.h
index 87df7a3283ddf..d38f5d22f51f2 100644
--- a/src/gui.h
+++ b/src/gui.h
@@ -33,9 +33,6 @@ void ShowOrdersWindow(const Vehicle *v);
Window *ShowBuildDocksToolbar();
Window *ShowBuildDocksScenToolbar();
-/* airport_gui.cpp */
-Window *ShowBuildAirToolbar();
-
/* tgp_gui.cpp */
void ShowGenerateLandscape();
void ShowHeightmapLoad();
diff --git a/src/industry_cmd.cpp b/src/industry_cmd.cpp
index 0707ec9c89829..b56e1d90c68b1 100644
--- a/src/industry_cmd.cpp
+++ b/src/industry_cmd.cpp
@@ -58,7 +58,7 @@ IndustryPool _industry_pool("Industry");
INSTANTIATE_POOL_METHODS(Industry)
void ShowIndustryViewWindow(int industry);
-void BuildOilRig(TileIndex tile);
+void BuildBuiltInHeliport(Tile tile);
static uint8_t _industry_sound_ctr;
static TileIndex _industry_sound_tile;
@@ -159,8 +159,8 @@ Industry::~Industry()
/* MakeWaterKeepingClass() can also handle 'land' */
MakeWaterKeepingClass(tile_cur, OWNER_NONE);
}
- } else if (IsTileType(tile_cur, MP_STATION) && IsOilRig(tile_cur)) {
- DeleteOilRig(tile_cur);
+ } else if (IsBuiltInHeliportTile(tile_cur)) {
+ DeleteBuiltInHeliport(tile_cur);
}
}
@@ -784,7 +784,7 @@ static void MakeIndustryTileBigger(TileIndex tile)
if (IsTileType(other, MP_INDUSTRY) &&
GetIndustryGfx(other) == GFX_OILRIG_1 &&
GetIndustryIndex(tile) == GetIndustryIndex(other)) {
- BuildOilRig(tile);
+ BuildBuiltInHeliport(tile);
}
break;
}
diff --git a/src/landscape.cpp b/src/landscape.cpp
index 7964e3ba02b5a..5da1a2995f7b4 100644
--- a/src/landscape.cpp
+++ b/src/landscape.cpp
@@ -1654,6 +1654,7 @@ bool GenerateLandscape(uint8_t mode)
void OnTick_Town();
void OnTick_Trees();
void OnTick_Station();
+void OnTick_Depot();
void OnTick_Industry();
void OnTick_Companies();
@@ -1667,6 +1668,7 @@ void CallLandscapeTick()
OnTick_Town();
OnTick_Trees();
OnTick_Station();
+ OnTick_Depot();
OnTick_Industry();
}
diff --git a/src/lang/english.txt b/src/lang/english.txt
index 87a8d3c594425..75e44e6940ce0 100644
--- a/src/lang/english.txt
+++ b/src/lang/english.txt
@@ -278,6 +278,8 @@ STR_TOOLTIP_FILTER_CRITERIA :{BLACK}Select f
STR_BUTTON_SORT_BY :{BLACK}Sort by
STR_BUTTON_CATCHMENT :{BLACK}Coverage
STR_TOOLTIP_CATCHMENT :{BLACK}Toggle coverage area display
+STR_BUTTON_HIGHLIGHT_DEPOT :{BLACK}Highlight
+STR_TOOLTIP_HIGHLIGHT_DEPOT :{BLACK}Toggle highlight on viewport for this depot
STR_TOOLTIP_CLOSE_WINDOW :{BLACK}Close window
STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS :{BLACK}Window title - drag this to move window
@@ -509,9 +511,6 @@ STR_ROAD_MENU_TRAM_CONSTRUCTION :Tramway constru
# Waterways construction menu
STR_WATERWAYS_MENU_WATERWAYS_CONSTRUCTION :Waterways construction
-# Aairport construction menu
-STR_AIRCRAFT_MENU_AIRPORT_CONSTRUCTION :Airport construction
-
# Landscaping menu
STR_LANDSCAPING_MENU_LANDSCAPING :Landscaping
STR_LANDSCAPING_MENU_PLANT_TREES :Plant trees
@@ -843,7 +842,7 @@ STR_NEWS_TRAIN_CRASH :{BIG_FONT}{BLAC
STR_NEWS_ROAD_VEHICLE_CRASH_DRIVER :{BIG_FONT}{BLACK}Road Vehicle Crash!{}Driver dies in fireball after collision with train
STR_NEWS_ROAD_VEHICLE_CRASH :{BIG_FONT}{BLACK}Road Vehicle Crash!{}{COMMA} die in fireball after collision with train
STR_NEWS_AIRCRAFT_CRASH :{BIG_FONT}{BLACK}Plane Crash!{}{COMMA} die in fireball at {STATION}
-STR_NEWS_PLANE_CRASH_OUT_OF_FUEL :{BIG_FONT}{BLACK}Plane Crash!{}Aircraft ran out of fuel, {COMMA} die in fireball
+STR_NEWS_AIRCRAFT_CRASH_NO_AIRPORT :{BIG_FONT}{BLACK}Aircraft Crash!{}{COMMA} die in fireball
STR_NEWS_DISASTER_ZEPPELIN :{BIG_FONT}{BLACK}Zeppelin disaster at {STATION}!
STR_NEWS_DISASTER_SMALL_UFO :{BIG_FONT}{BLACK}Road vehicle destroyed in 'UFO' collision!
@@ -899,7 +898,6 @@ STR_NEWS_VEHICLE_HAS_TOO_FEW_ORDERS :{WHITE}{VEHICLE
STR_NEWS_VEHICLE_HAS_VOID_ORDER :{WHITE}{VEHICLE} has a void order
STR_NEWS_VEHICLE_HAS_DUPLICATE_ENTRY :{WHITE}{VEHICLE} has duplicate orders
STR_NEWS_VEHICLE_HAS_INVALID_ENTRY :{WHITE}{VEHICLE} has an invalid station in its orders
-STR_NEWS_PLANE_USES_TOO_SHORT_RUNWAY :{WHITE}{VEHICLE} has in its orders an airport whose runway is too short
STR_NEWS_VEHICLE_IS_GETTING_OLD :{WHITE}{VEHICLE} is getting old
STR_NEWS_VEHICLE_IS_GETTING_VERY_OLD :{WHITE}{VEHICLE} is getting very old
@@ -908,7 +906,10 @@ STR_NEWS_TRAIN_IS_STUCK :{WHITE}{VEHICLE
STR_NEWS_VEHICLE_IS_LOST :{WHITE}{VEHICLE} is lost
STR_NEWS_VEHICLE_UNPROFITABLE_YEAR :{WHITE}{VEHICLE}'s profit last year was {CURRENCY_LONG}
STR_NEWS_VEHICLE_UNPROFITABLE_PERIOD :{WHITE}{VEHICLE}'s profit last period was {CURRENCY_LONG}
+STR_NEWS_VEHICLE_CAN_T_FIND_FREE_DEPOT :{WHITE}{VEHICLE} can't find a free depot
+STR_NEWS_VEHICLE_TOO_LONG_FOR_SERVICING :{WHITE}{VEHICLE} couldn't service in short platform
STR_NEWS_AIRCRAFT_DEST_TOO_FAR :{WHITE}{VEHICLE} can't get to the next destination because it is out of range
+STR_NEWS_AIRCRAFT_CAN_T_LAND :{WHITE}{VEHICLE} can't land on airport
STR_NEWS_ORDER_REFIT_FAILED :{WHITE}{VEHICLE} stopped because an ordered refit failed
STR_NEWS_VEHICLE_AUTORENEW_FAILED :{WHITE}Autorenew failed on {VEHICLE}{}{STRING2}
@@ -1448,9 +1449,9 @@ STR_CONFIG_SETTING_PLANE_SPEED_HELPTEXT :Set the relativ
STR_CONFIG_SETTING_PLANE_SPEED_VALUE :1 / {COMMA}
STR_CONFIG_SETTING_PLANE_CRASHES :Number of plane crashes: {STRING2}
-STR_CONFIG_SETTING_PLANE_CRASHES_HELPTEXT :Set the chance of a random aircraft crash happening.{}* Large airplanes always have a risk of crashing when landing on small airports
+STR_CONFIG_SETTING_PLANE_CRASHES_HELPTEXT :Set the chance of a random aircraft crash happening.
###length 3
-STR_CONFIG_SETTING_PLANE_CRASHES_NONE :None*
+STR_CONFIG_SETTING_PLANE_CRASHES_NONE :None
STR_CONFIG_SETTING_PLANE_CRASHES_REDUCED :Reduced
STR_CONFIG_SETTING_PLANE_CRASHES_NORMAL :Normal
@@ -1462,6 +1463,7 @@ STR_CONFIG_SETTING_STOP_ON_TOWN_ROAD_HELPTEXT :Allow construct
STR_CONFIG_SETTING_STOP_ON_COMPETITOR_ROAD :Allow drive-through road stops on roads owned by competitors: {STRING2}
STR_CONFIG_SETTING_STOP_ON_COMPETITOR_ROAD_HELPTEXT :Allow construction of drive-through road stops on roads owned by other companies
STR_CONFIG_SETTING_DYNAMIC_ENGINES_EXISTING_VEHICLES :{WHITE}Changing this setting is not possible when there are vehicles
+STR_CONFIG_SETTING_REPLACEMENTS_DIFF_TYPE :{WHITE}Disabling this setting is not possible during a game
STR_CONFIG_SETTING_INFRASTRUCTURE_MAINTENANCE :Infrastructure maintenance: {STRING2}
STR_CONFIG_SETTING_INFRASTRUCTURE_MAINTENANCE_HELPTEXT :When enabled, infrastructure causes maintenance costs. The cost grows over-proportional with the network size, thus affecting bigger companies more than smaller ones
@@ -1474,6 +1476,8 @@ STR_CONFIG_SETTING_COMPANY_STARTING_COLOUR_SECONDARY_HELPTEXT :Choose starting
STR_CONFIG_SETTING_NEVER_EXPIRE_AIRPORTS :Airports never expire: {STRING2}
STR_CONFIG_SETTING_NEVER_EXPIRE_AIRPORTS_HELPTEXT :Enabling this setting makes each airport type stay available forever after its introduction
+STR_CONFIG_SETTING_ALLOW_CUSTOMIZED_AIRPORTS :Allow building customized airports: {STRING2}
+STR_CONFIG_SETTING_ALLOW_CUSTOMIZED_AIRPORTS_HELPTEXT :Enabling this setting allows each player being able to build customized airports.
STR_CONFIG_SETTING_WARN_LOST_VEHICLE :Warn if vehicle is lost: {STRING2}
STR_CONFIG_SETTING_WARN_LOST_VEHICLE_HELPTEXT :Trigger messages about vehicles unable to find a path to their ordered destination
@@ -1615,6 +1619,30 @@ STR_CONFIG_SETTING_SE_FLAT_WORLD_HEIGHT :The height leve
STR_CONFIG_SETTING_EDGES_NOT_EMPTY :{WHITE}One or more tiles at the northern edge are not empty
STR_CONFIG_SETTING_EDGES_NOT_WATER :{WHITE}One or more tiles at one of the edges is not water
+STR_CONFIG_SETTING_DISTANT_JOIN_DEPOTS :Allow to join depot parts not directly adjacent: {STRING2}
+STR_CONFIG_SETTING_DISTANT_JOIN_DEPOTS_HELPTEXT :Allow adding parts to a depot without directly touching the existing parts. Needs Ctrl+Click while placing the new parts
+STR_CONFIG_SETTING_DEPOT_SPREAD :Maximum depot spread: {STRING2}
+STR_CONFIG_SETTING_DEPOT_SPREAD_HELPTEXT :Maximum area the parts of a single depot may be spread out on
+
+STR_CONFIG_SETTING_RAIL_DEPOT_TYPES :Rail depot types: {STRING2}
+STR_CONFIG_SETTING_RAIL_DEPOT_TYPES_HELPTEXT :Available rail depot types for construction for human players
+STR_CONFIG_SETTING_ROAD_DEPOT_TYPES :Road depot types: {STRING2}
+STR_CONFIG_SETTING_ROAD_DEPOT_TYPES_HELPTEXT :Available road depot types for construction for human players
+STR_CONFIG_SETTING_WATER_DEPOT_TYPES :Water depot types: {STRING2}
+STR_CONFIG_SETTING_WATER_DEPOT_TYPES_HELPTEXT :Available water depot types for construction for human players
+STR_CONFIG_SETTING_HANGAR_TYPES :Hangar types: {STRING2}
+STR_CONFIG_SETTING_HANGAR_TYPES_HELPTEXT :Available hangar types for construction for human players
+###length 3
+STR_CONFIG_SETTING_ONLY_STANDARD_DEPOT :Standard depots
+STR_CONFIG_SETTING_ONLY_EXTENDED_DEPOT :Extended depots
+STR_CONFIG_SETTING_BOTH_DEPOT_TYPES :Both depot types
+###next-name-looks-similar
+
+STR_CONFIG_SETTING_REPLACE_INCOMPATIBLE_RAIL :Allow replacing rail vehicles with incompatible rail types: {STRING2}
+STR_CONFIG_SETTING_REPLACE_INCOMPATIBLE_RAIL_HELPTEXT :Allow replacing rail vehicles even if they are not compatible by rail type
+STR_CONFIG_SETTING_REPLACE_INCOMPATIBLE_ROAD :Allow replacing road vehicles with incompatible road types: {STRING2}
+STR_CONFIG_SETTING_REPLACE_INCOMPATIBLE_ROAD_HELPTEXT :Allow replacing road vehicles even if they are not compatible by road type
+
STR_CONFIG_SETTING_STATION_SPREAD :Maximum station spread: {STRING2}
STR_CONFIG_SETTING_STATION_SPREAD_HELPTEXT :Maximum area the parts of a single station may be spread out on. Note that high values will slow the game
@@ -1741,6 +1769,8 @@ STR_CONFIG_SETTING_QUICKGOTO_HELPTEXT :Pre-select the
STR_CONFIG_SETTING_DEFAULT_RAIL_TYPE :Default rail type (after new game/game load): {STRING2}
STR_CONFIG_SETTING_DEFAULT_RAIL_TYPE_HELPTEXT :Rail type to select after starting or loading a game. 'first available' selects the oldest type of tracks, 'last available' selects the newest type of tracks, and 'most used' selects the type which is currently most in use
+STR_CONFIG_SETTING_DEFAULT_AIR_TYPE :Default air type (after new game/game load): {STRING2}
+STR_CONFIG_SETTING_DEFAULT_AIR_TYPE_HELPTEXT :Air type to select after starting or loading a game. 'first available' selects the oldest type of airport, 'last available' selects the newest one, and 'most used' selects the type which is currently most in use
###length 3
STR_CONFIG_SETTING_DEFAULT_RAIL_TYPE_FIRST :First available
STR_CONFIG_SETTING_DEFAULT_RAIL_TYPE_LAST :Last available
@@ -2137,6 +2167,7 @@ STR_CONFIG_SETTING_ENVIRONMENT_TREES :Trees
STR_CONFIG_SETTING_AI :Competitors
STR_CONFIG_SETTING_AI_NPC :Computer players
STR_CONFIG_SETTING_NETWORK :Network
+STR_CONFIG_SETTING_DEPOTS :Depots
STR_CONFIG_SETTING_REVERSE_AT_SIGNALS :Automatic reversing at signals: {STRING2}
STR_CONFIG_SETTING_REVERSE_AT_SIGNALS_HELPTEXT :Allow trains to reverse on a signal, if they waited there a long time
@@ -2753,8 +2784,6 @@ STR_STATION_BUILD_COVERAGE_AREA_OFF_TOOLTIP :{BLACK}Don't hi
STR_STATION_BUILD_COVERAGE_AREA_ON_TOOLTIP :{BLACK}Highlight coverage area of proposed site
STR_STATION_BUILD_ACCEPTS_CARGO :{BLACK}Accepts: {GOLD}{CARGO_LIST}
STR_STATION_BUILD_SUPPLIES_CARGO :{BLACK}Supplies: {GOLD}{CARGO_LIST}
-STR_STATION_BUILD_INFRASTRUCTURE_COST_YEAR :{BLACK}Maintenance cost: {GOLD}{CURRENCY_SHORT}/year
-STR_STATION_BUILD_INFRASTRUCTURE_COST_PERIOD :{BLACK}Maintenance cost: {GOLD}{CURRENCY_SHORT}/period
# Join station window
STR_JOIN_STATION_CAPTION :{WHITE}Join station
@@ -2763,6 +2792,11 @@ STR_JOIN_STATION_CREATE_SPLITTED_STATION :{YELLOW}Build a
STR_JOIN_WAYPOINT_CAPTION :{WHITE}Join waypoint
STR_JOIN_WAYPOINT_CREATE_SPLITTED_WAYPOINT :{YELLOW}Build a separate waypoint
+# Join depot window
+STR_JOIN_DEPOT_CAPTION :{WHITE}Join depot
+STR_JOIN_DEPOT_CREATE_SPLITTED_DEPOT :{YELLOW}Build a separate depot
+STR_DEPOT_LIST_DEPOT :{YELLOW}{DEPOT}
+
# Generic toolbar
STR_TOOLBAR_DISABLED_NO_VEHICLE_AVAILABLE :{BLACK}Disabled as currently no vehicles are available for this infrastructure
@@ -2774,7 +2808,8 @@ STR_RAIL_TOOLBAR_MAGLEV_CONSTRUCTION_CAPTION :Maglev Construc
STR_RAIL_TOOLBAR_TOOLTIP_BUILD_RAILROAD_TRACK :{BLACK}Build railway track. Ctrl+Click to remove railway track. Also press Shift to show cost estimate only
STR_RAIL_TOOLBAR_TOOLTIP_BUILD_AUTORAIL :{BLACK}Build railway track using the Autorail mode. Ctrl+Click to remove railway track. Also press Shift to show cost estimate only
-STR_RAIL_TOOLBAR_TOOLTIP_BUILD_TRAIN_DEPOT_FOR_BUILDING :{BLACK}Build train depot (for buying and servicing trains). Also press Shift to show cost estimate only
+STR_RAIL_TOOLBAR_TOOLTIP_BUILD_TRAIN_DEPOT :{BLACK}Build standard train depot (for buying and servicing trains). Ctrl+Click to select another depot to join. Also press Shift to show cost estimate only
+STR_RAIL_TOOLBAR_TOOLTIP_BUILD_EXTENDED_TRAIN_DEPOT :{BLACK}Build extended train depot (for buying and servicing trains). Ctrl+Click to select another depot to join. Also press Shift to show cost estimate only
STR_RAIL_TOOLBAR_TOOLTIP_CONVERT_RAIL_TO_WAYPOINT :{BLACK}Build waypoint on railway. Ctrl+Click to select another waypoint to join. Also press Shift to show cost estimate only
STR_RAIL_TOOLBAR_TOOLTIP_BUILD_RAILROAD_STATION :{BLACK}Build railway station. Ctrl+Click to select another station to join. Also press Shift to show cost estimate only
STR_RAIL_TOOLBAR_TOOLTIP_BUILD_RAILROAD_SIGNALS :{BLACK}Build signal on railway. Ctrl+Click to build the alternate signal style{}Click+Drag to fill the selected section of rail with signals at the chosen spacing. Ctrl+Click+Drag to fill signals up to the next junction, station, or signal. Also press Shift to show cost estimate only
@@ -2885,8 +2920,10 @@ STR_ROAD_TOOLBAR_TOOLTIP_BUILD_ROAD_SECTION :{BLACK}Build ro
STR_ROAD_TOOLBAR_TOOLTIP_BUILD_TRAMWAY_SECTION :{BLACK}Build tramway section. Ctrl+Click to remove tramway section. Also press Shift to show cost estimate only
STR_ROAD_TOOLBAR_TOOLTIP_BUILD_AUTOROAD :{BLACK}Build road section using the Autoroad mode. Ctrl+Click to remove road section. Also press Shift to show cost estimate only
STR_ROAD_TOOLBAR_TOOLTIP_BUILD_AUTOTRAM :{BLACK}Build tramway section using the Autotram mode. Ctrl+Click to remove tramway section. Also press Shift to show cost estimate only
-STR_ROAD_TOOLBAR_TOOLTIP_BUILD_ROAD_VEHICLE_DEPOT :{BLACK}Build road vehicle depot (for buying and servicing vehicles). Also press Shift to show cost estimate only
-STR_ROAD_TOOLBAR_TOOLTIP_BUILD_TRAM_VEHICLE_DEPOT :{BLACK}Build tram vehicle depot (for buying and servicing vehicles). Also press Shift to show cost estimate only
+STR_ROAD_TOOLBAR_TOOLTIP_BUILD_ROAD_VEHICLE_DEPOT :{BLACK}Build standard road vehicle depot (for buying and servicing vehicles). Ctrl+Click to select another depot to join. Also press Shift to show cost estimate only
+STR_ROAD_TOOLBAR_TOOLTIP_BUILD_EXTENDED_ROAD_VEHICLE_DEPOT :{BLACK}Build extended road vehicle depot (for buying and servicing vehicles). Ctrl+Click to select another depot to join. Also press Shift to show cost estimate only
+STR_ROAD_TOOLBAR_TOOLTIP_BUILD_TRAM_VEHICLE_DEPOT :{BLACK}Build standard tram vehicle depot (for buying and servicing vehicles). Ctrl+Click to select another depot to join. Also press Shift to show cost estimate only
+STR_ROAD_TOOLBAR_TOOLTIP_BUILD_EXTENDED_TRAM_VEHICLE_DEPOT :{BLACK}Build extended tram vehicle depot (for buying and servicing vehicles). Ctrl+Click to select another depot to join. Also press Shift to show cost estimate only
STR_ROAD_TOOLBAR_TOOLTIP_CONVERT_ROAD_TO_WAYPOINT :{BLACK}Build waypoint on road. Ctrl+Click to select another waypoint to join. Also press Shift to show cost estimate only
STR_ROAD_TOOLBAR_TOOLTIP_CONVERT_TRAM_TO_WAYPOINT :{BLACK}Build waypoint on tramway. Ctrl+Click to select another waypoint to join. Also press Shift to show cost estimate only
STR_ROAD_TOOLBAR_TOOLTIP_BUILD_BUS_STATION :{BLACK}Build bus station. Ctrl+Click to select another station to join. Also press Shift to show cost estimate only
@@ -2927,7 +2964,8 @@ STR_WATERWAYS_TOOLBAR_CAPTION :{WHITE}Waterway
STR_WATERWAYS_TOOLBAR_CAPTION_SE :{WHITE}Waterways
STR_WATERWAYS_TOOLBAR_BUILD_CANALS_TOOLTIP :{BLACK}Build canals. Also press Shift to show cost estimate only
STR_WATERWAYS_TOOLBAR_BUILD_LOCKS_TOOLTIP :{BLACK}Build locks. Also press Shift to show cost estimate only
-STR_WATERWAYS_TOOLBAR_BUILD_DEPOT_TOOLTIP :{BLACK}Build ship depot (for buying and servicing ships). Also press Shift to show cost estimate only
+STR_WATERWAYS_TOOLBAR_BUILD_DEPOT_TOOLTIP :{BLACK}Build standard ship depot (for buying and servicing ships). Ctrl+Click to select another depot to join. Also press Shift to show cost estimate only
+STR_WATERWAYS_TOOLBAR_BUILD_EXTENDED_DEPOT_TOOLTIP :{BLACK}Build extended ship depot (for buying and servicing ships). Ctrl+Click to select another depot to join. Also press Shift to show cost estimate only
STR_WATERWAYS_TOOLBAR_BUILD_DOCK_TOOLTIP :{BLACK}Build ship dock. Ctrl+Click to select another station to join. Also press Shift to show cost estimate only
STR_WATERWAYS_TOOLBAR_BUOY_TOOLTIP :{BLACK}Place a buoy which can be used as a waypoint. Also press Shift to show cost estimate only
STR_WATERWAYS_TOOLBAR_BUILD_AQUEDUCT_TOOLTIP :{BLACK}Build aqueduct. Also press Shift to show cost estimate only
@@ -2942,8 +2980,67 @@ STR_DEPOT_BUILD_SHIP_ORIENTATION_TOOLTIP :{BLACK}Select s
STR_STATION_BUILD_DOCK_CAPTION :{WHITE}Dock
# Airport toolbar
-STR_TOOLBAR_AIRCRAFT_CAPTION :{WHITE}Airports
-STR_TOOLBAR_AIRCRAFT_BUILD_AIRPORT_TOOLTIP :{BLACK}Build airport. Ctrl+Click to select another station to join. Also press Shift to show cost estimate only
+STR_TOOLBAR_AIRPORT_GRAVEL_CONSTRUCTION_CAPTION :Gravel Airport Construction
+STR_TOOLBAR_AIRPORT_ASPHALT_CONSTRUCTION_CAPTION :Asphalt Airport Construction
+STR_TOOLBAR_AIRPORT_WATER_CONSTRUCTION_CAPTION :Water Airport Construction
+STR_TOOLBAR_AIRPORT_DARK_CONSTRUCTION_CAPTION :Darker Airport Construction
+STR_TOOLBAR_AIRPORT_YELLOW_CONSTRUCTION_CAPTION :Drawn Tracks Airport Construction
+
+# Airport menu text
+###length 5
+STR_TOOLBAR_AIRPORT_GRAVEL_MENU_TEXT :Gravel airport construction
+STR_TOOLBAR_AIRPORT_ASPHALT_MENU_TEXT :Asphalt airport construction
+STR_TOOLBAR_AIRPORT_WATER_MENU_TEXT :Water airport construction
+STR_TOOLBAR_AIRPORT_DARK_MENU_TEXT :Darker airport construction
+STR_TOOLBAR_AIRPORT_YELLOW_MENU_TEXT :Drawn tracks airport construction
+###next-name-looks-similar
+
+STR_TOOLBAR_AIRPORT_CAPTION :{WHITE}Airports
+STR_TOOLBAR_AIRPORT_BUILD_AIRPORT_TOOLTIP :{BLACK}Build airport. Ctrl+Click to select another station to join. Also press Shift to show cost estimate only
+
+STR_TOOLBAR_AIRPORT_ADD_TILES :{BLACK}Add or remove airport tiles to an airport
+STR_TOOLBAR_AIRPORT_INFRASTRUCTURE_CATCHMENT :{BLACK}Define as infrastructure with catchment
+STR_TOOLBAR_AIRPORT_INFRASTRUCTURE_NO_CATCHMENT :{BLACK}Define as infrastructure without catchment
+STR_TOOLBAR_AIRPORT_SET_TRACKS :{BLACK}Set tracks
+STR_TOOLBAR_AIRPORT_DEFINE_RUNWAY_LANDING :{BLACK}Define a runway allowing landing
+STR_TOOLBAR_AIRPORT_DEFINE_RUNWAY_NO_LANDING :{BLACK}Define a runway forbidding landing
+STR_TOOLBAR_AIRPORT_BUILD_APRON :{BLACK}Build an apron
+STR_TOOLBAR_AIRPORT_BUILD_HELIPAD :{BLACK}Build a helipad
+STR_TOOLBAR_AIRPORT_BUILD_HELIPORT :{BLACK}Build a heliport
+STR_TOOLBAR_AIRPORT_BUILD_HANGAR_STANDARD :{BLACK}Build a standard hangar
+STR_TOOLBAR_AIRPORT_BUILD_HANGAR_EXTENDED :{BLACK}Build an extended hangar
+STR_TOOLBAR_AIRPORT_TOGGLE_BUILD_REMOVE :{BLACK}Toggle build/remove for airport tiles, infrastructure, aprons and hangars
+STR_TOOLBAR_AIRPORT_BUILD_PRE_AIRPORT_TOOLTIP :{BLACK}Build a predefined airport layout. Ctrl enables joining stations. Shift toggles building/showing cost estimate
+STR_TOOLBAR_AIRPORT_CHANGE_AIRTYPE :{BLACK}Change the airtype of an airport
+STR_TOOLBAR_AIRPORT_ROTATE_GRAPHICS :{BLACK}Change track graphics
+STR_TOOLBAR_AIRPORT_ROTATE_GRAPHICS_TOOLTIP :{BLACK}Click over a simple airport tile to change its graphics (experimental; only works on the Drawn tracks airtype)
+STR_TOOLBAR_AIRPORT_TOGGLE_GROUND :{BLACK}Toggle ground graphics
+STR_TOOLBAR_AIRPORT_TOGGLE_GROUND_TOOLBAR :{BLACK}Click over a simple airport tile to show or hide the airtype ground sprite.
+
+STR_AIRTYPE_NAME_GRAVEL :Gravel airport
+STR_AIRTYPE_NAME_ASPHALT :Asphalt airport
+STR_AIRTYPE_NAME_WATER :Water airport
+STR_AIRTYPE_NAME_DARK :Darker airport
+STR_AIRTYPE_NAME_YELLOW :Drawn tracks airport
+
+# Airport infrastructure construction window
+STR_BUILD_AIRPORT_INFRA_NO_CATCH_CAPTION :{WHITE}Infrastructure without cargo catchment
+STR_BUILD_AIRPORT_INFRA_WITH_CATCH_CAPTION :{WHITE}Infrastructure with cargo catchment
+STR_BUILD_AIRPORT_INFRA_TOOLTIP :{BLACK}Select a piece of infrastructre and its orientation
+
+# Heliport construction window
+STR_BUILD_HELIPORT_CAPTION :{WHITE}Heliport Orientation
+STR_BUILD_HELIPORT_ORIENTATION_TOOLTIP :{BLACK}Select heliport orientation
+
+# Hangar construction window
+STR_BUILD_HANGAR_CAPTION :{WHITE}Hangar Orientation
+STR_BUILD_HANGAR_ORIENTATION_TOOLTIP :{BLACK}Select hangar orientation
+
+# Select gfx for airport track tiles
+STR_SELECT_GFX_AIRPORT_TRACKS_CAPTION :{WHITE}Selection of graphics for track tiles
+STR_SELECT_GFX_AIRPORT_TRACKS_TOOLTIP :{BLACK}Select the graphics for airport track tiles and click on airport track tiles.
+STR_SELECT_GFX_AIRPORT_TRACKS_DEFAULT :{BLACK}Default
+STR_SELECT_GFX_AIRPORT_TRACKS_DETECT :{BLACK}Autodetect sprites
# Airport construction window
STR_STATION_BUILD_AIRPORT_CAPTION :{WHITE}Airport Selection
@@ -2960,11 +3057,19 @@ STR_AIRPORT_INTERCONTINENTAL :Intercontinenta
STR_AIRPORT_HELIPORT :Heliport
STR_AIRPORT_HELIDEPOT :Helidepot
STR_AIRPORT_HELISTATION :Helistation
+STR_AIRPORT_CUSTOM :Customized
STR_AIRPORT_CLASS_SMALL :Small airports
STR_AIRPORT_CLASS_LARGE :Large airports
STR_AIRPORT_CLASS_HUB :Hub airports
STR_AIRPORT_CLASS_HELIPORTS :Helicopter airports
+STR_AIRPORT_CLASS_CUSTOMIZED :Customized airports
+
+###length 4
+STR_AIRPORT_ROTATION_0 :Default orientation
+STR_AIRPORT_ROTATION_90 :90º rotated
+STR_AIRPORT_ROTATION_180 :180º rotated
+STR_AIRPORT_ROTATION_270 :270º rotated
STR_STATION_BUILD_NOISE :{BLACK}Noise generated: {GOLD}{COMMA}
@@ -3096,6 +3201,7 @@ STR_LAND_AREA_INFORMATION_LANDINFO_INDEX :{BLACK}Tile ind
STR_LAND_AREA_INFORMATION_BUILD_DATE :{BLACK}Built/renovated: {LTBLUE}{DATE_LONG}
STR_LAND_AREA_INFORMATION_STATION_CLASS :{BLACK}Station class: {LTBLUE}{STRING}
STR_LAND_AREA_INFORMATION_STATION_TYPE :{BLACK}Station type: {LTBLUE}{STRING}
+STR_LANG_AREA_INFORMATION_AIR_TYPE :{BLACK}Air type: {LTBLUE}{STRING}
STR_LAND_AREA_INFORMATION_AIRPORT_CLASS :{BLACK}Airport class: {LTBLUE}{STRING}
STR_LAND_AREA_INFORMATION_AIRPORT_NAME :{BLACK}Airport name: {LTBLUE}{STRING}
STR_LAND_AREA_INFORMATION_AIRPORTTILE_NAME :{BLACK}Airport tile name: {LTBLUE}{STRING}
@@ -3140,12 +3246,14 @@ STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_EXIT_NOENTRYSIGNALS :Railway track w
STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_COMBO_PBSSIGNALS :Railway track with combo- and path signals
STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_COMBO_NOENTRYSIGNALS :Railway track with combo- and one-way path signals
STR_LAI_RAIL_DESCRIPTION_TRACK_WITH_PBS_NOENTRYSIGNALS :Railway track with path and one-way path signals
-STR_LAI_RAIL_DESCRIPTION_TRAIN_DEPOT :Railway train depot
+STR_LAI_RAIL_DESCRIPTION_TRAIN_DEPOT :Standard railway train depot
+STR_LAI_RAIL_DESCRIPTION_TRAIN_DEPOT_EXTENDED :Extended railway train depot
STR_LAI_ROAD_DESCRIPTION_ROAD :Road
STR_LAI_ROAD_DESCRIPTION_ROAD_WITH_STREETLIGHTS :Road with street lights
STR_LAI_ROAD_DESCRIPTION_TREE_LINED_ROAD :Tree-lined road
-STR_LAI_ROAD_DESCRIPTION_ROAD_VEHICLE_DEPOT :Road vehicle depot
+STR_LAI_ROAD_DESCRIPTION_ROAD_VEHICLE_DEPOT :Standard road vehicle depot
+STR_LAI_ROAD_DESCRIPTION_ROAD_VEHICLE_DEPOT_EXTENDED :Extended road vehicle depot
STR_LAI_ROAD_DESCRIPTION_ROAD_RAIL_LEVEL_CROSSING :Road/rail level crossing
STR_LAI_ROAD_DESCRIPTION_TRAMWAY :Tramway
@@ -3157,20 +3265,36 @@ STR_LAI_TREE_NAME_RAINFOREST :Rainforest
STR_LAI_TREE_NAME_CACTUS_PLANTS :Cactus plants
STR_LAI_STATION_DESCRIPTION_RAILROAD_STATION :Railway station
-STR_LAI_STATION_DESCRIPTION_AIRCRAFT_HANGAR :Aircraft hangar
-STR_LAI_STATION_DESCRIPTION_AIRPORT :Airport
STR_LAI_STATION_DESCRIPTION_TRUCK_LOADING_AREA :Lorry station
STR_LAI_STATION_DESCRIPTION_BUS_STATION :Bus station
STR_LAI_STATION_DESCRIPTION_SHIP_DOCK :Ship dock
STR_LAI_STATION_DESCRIPTION_BUOY :Buoy
STR_LAI_STATION_DESCRIPTION_WAYPOINT :Waypoint
+STR_LAI_STATION_DESCRIPTION_AIRPORT :Airport
+STR_LAI_STATION_DESCRIPTION_AIR_INFRASTRUCTURE_WITH_CATCHMENT :Airport infrastructure with cargo catchment
+STR_LAI_STATION_DESCRIPTION_AIR_INFRASTRUCTURE_WITHOUT_CATCHMENT:Airport infrastructure with no cargo catchment
+STR_LAI_STATION_DESCRIPTION_AIR_PLAIN :Airport tile for tracks
+STR_LAI_STATION_DESCRIPTION_AIR_WAITING_POINT :Airport waiting point
+###length 4
+STR_LAI_STATION_DESCRIPTION_AIR_TERMINAL :Terminal
+STR_LAI_STATION_DESCRIPTION_AIR_HELIPAD :Helipad
+STR_LAI_STATION_DESCRIPTION_AIR_HELIPORT :Heliport
+STR_LAI_STATION_DESCRIPTION_AIR_BUILTIN_HELIPORT :Built-in heliport
+###next-name-looks-similar
+STR_LAI_STATION_DESCRIPTION_AIRCRAFT_HANGAR :Standard aircraft hangar
+STR_LAI_STATION_DESCRIPTION_AIRCRAFT_EXTENDED_HANGAR :Extended aircraft hangar
+STR_LAI_STATION_DESCRIPTION_AIR_RUNWAY_MIDDLE :Middle runway tile
+STR_LAI_STATION_DESCRIPTION_AIR_RUNWAY_END :Runway end
+STR_LAI_STATION_DESCRIPTION_AIR_RUNWAY_START_NO_LANDING :Runway start only for takeoffs
+STR_LAI_STATION_DESCRIPTION_AIR_RUNWAY_START_LANDING :Runway start for landing
STR_LAI_WATER_DESCRIPTION_WATER :Water
STR_LAI_WATER_DESCRIPTION_CANAL :Canal
STR_LAI_WATER_DESCRIPTION_LOCK :Lock
STR_LAI_WATER_DESCRIPTION_RIVER :River
STR_LAI_WATER_DESCRIPTION_COAST_OR_RIVERBANK :Coast or riverbank
-STR_LAI_WATER_DESCRIPTION_SHIP_DEPOT :Ship depot
+STR_LAI_WATER_DESCRIPTION_SHIP_DEPOT :Standard ship depot
+STR_LAI_WATER_DESCRIPTION_SHIP_DEPOT_EXTENDED :Extended ship depot
# Industries come directly from their industry names
@@ -3497,6 +3621,7 @@ STR_NEWGRF_INSPECT_CAPTION_OBJECT_AT :{STRING1} at {H
STR_NEWGRF_INSPECT_CAPTION_OBJECT_AT_OBJECT :Object
STR_NEWGRF_INSPECT_CAPTION_OBJECT_AT_RAIL_TYPE :Rail type
STR_NEWGRF_INSPECT_CAPTION_OBJECT_AT_ROAD_TYPE :Road type
+STR_NEWGRF_INSPECT_CAPTION_OBJECT_AT_AIR_TYPE :Air type
STR_NEWGRF_INSPECT_QUERY_CAPTION :{WHITE}NewGRF variable 60+x parameter (hexadecimal)
@@ -3908,7 +4033,7 @@ STR_COMPANY_VIEW_INFRASTRUCTURE_RAIL :{WHITE}{COMMA}
STR_COMPANY_VIEW_INFRASTRUCTURE_ROAD :{WHITE}{COMMA} road piece{P "" s}
STR_COMPANY_VIEW_INFRASTRUCTURE_WATER :{WHITE}{COMMA} water tile{P "" s}
STR_COMPANY_VIEW_INFRASTRUCTURE_STATION :{WHITE}{COMMA} station tile{P "" s}
-STR_COMPANY_VIEW_INFRASTRUCTURE_AIRPORT :{WHITE}{COMMA} airport{P "" s}
+STR_COMPANY_VIEW_INFRASTRUCTURE_AIRPORT :{WHITE}{COMMA} airport tile{P "" s}
STR_COMPANY_VIEW_INFRASTRUCTURE_NONE :{WHITE}None
STR_COMPANY_VIEW_BUILD_HQ_BUTTON :{BLACK}Build HQ
@@ -3950,9 +4075,9 @@ STR_COMPANY_INFRASTRUCTURE_VIEW_WATER_SECT :{GOLD}Water til
STR_COMPANY_INFRASTRUCTURE_VIEW_CANALS :{WHITE}Canals
STR_COMPANY_INFRASTRUCTURE_VIEW_STATION_SECT :{GOLD}Stations:
STR_COMPANY_INFRASTRUCTURE_VIEW_STATIONS :{WHITE}Station tiles
-STR_COMPANY_INFRASTRUCTURE_VIEW_AIRPORTS :{WHITE}Airports
-STR_COMPANY_INFRASTRUCTURE_VIEW_TOTAL_YEAR :{WHITE}{CURRENCY_LONG}/year
-STR_COMPANY_INFRASTRUCTURE_VIEW_TOTAL_PERIOD :{WHITE}{CURRENCY_LONG}/period
+STR_COMPANY_INFRASTRUCTURE_VIEW_TOTAL_YEAR :{WHITE}{1:CURRENCY_LONG}/year ({0:COMMA})
+STR_COMPANY_INFRASTRUCTURE_VIEW_TOTAL_PERIOD :{WHITE}{1:CURRENCY_LONG}/period ({0:COMMA})
+STR_COMPANY_INFRASTRUCTURE_VIEW_AIRPORT_SECT :{GOLD}Airport pieces:
# Industry directory
STR_INDUSTRY_DIRECTORY_CAPTION :{WHITE}Industries ({COMMA} of {COMMA})
@@ -4328,6 +4453,7 @@ STR_REPLACE_ENGINES :Engines
STR_REPLACE_WAGONS :Wagons
STR_REPLACE_ALL_RAILTYPE :All rail vehicles
STR_REPLACE_ALL_ROADTYPE :All road vehicles
+STR_REPLACE_ALL_AIRTYPE :All aircraft vehicles
###length 2
STR_REPLACE_HELP_RAILTYPE :{BLACK}Choose the rail type you want to replace engines for
@@ -4343,6 +4469,12 @@ STR_REPLACE_MAGLEV_VEHICLES :Maglev Vehicles
STR_REPLACE_ROAD_VEHICLES :Road Vehicles
STR_REPLACE_TRAM_VEHICLES :Tramway Vehicles
+STR_REPLACE_AIRCRAFT_GRAVEL_VEHICLES :Gravel aircraft
+STR_REPLACE_AIRCRAFT_ASPHALT_VEHICLES :Asphalt aircraft
+STR_REPLACE_AIRCRAFT_WATER_VEHICLES :Water aircraft
+STR_REPLACE_AIRCRAFT_DARK_VEHICLES :Dark aircraft
+STR_REPLACE_AIRCRAFT_YELLOW_VEHICLES :Drawn tracks aircraft
+
STR_REPLACE_REMOVE_WAGON :{BLACK}Wagon removal ({STRING1}): {ORANGE}{STRING}
STR_REPLACE_REMOVE_WAGON_HELP :{BLACK}Make autoreplace keep the length of a train the same by removing wagons (starting at the front), if replacing the engine would make the train longer
STR_REPLACE_REMOVE_WAGON_GROUP_HELP :{STRING}. Ctrl+Click to also apply to sub-groups
@@ -4402,11 +4534,13 @@ STR_VEHICLE_STATUS_LOADING_UNLOADING :{LTBLUE}Loading
STR_VEHICLE_STATUS_LEAVING :{LTBLUE}Leaving
STR_VEHICLE_STATUS_WAITING_UNBUNCHING :{LTBLUE}Waiting to unbunch
STR_VEHICLE_STATUS_CRASHED :{RED}Crashed!
+STR_VEHICLE_STATUS_FALLING :{RED}Falling!
STR_VEHICLE_STATUS_BROKEN_DOWN :{RED}Broken down
STR_VEHICLE_STATUS_STOPPED :{RED}Stopped
STR_VEHICLE_STATUS_TRAIN_STOPPING_VEL :{RED}{VELOCITY} - Stopping
STR_VEHICLE_STATUS_TRAIN_NO_POWER :{RED}No power
STR_VEHICLE_STATUS_TRAIN_STUCK :{ORANGE}Waiting for free path
+STR_VEHICLE_STATUS_SERVICING :{LTBLUE}Servicing vehicle
STR_VEHICLE_STATUS_AIRCRAFT_TOO_FAR :{ORANGE}Too far to next destination
STR_VEHICLE_STATUS_HEADING_FOR_STATION_VEL :{LTBLUE}{1:VELOCITY} - Heading for {0:STATION}
@@ -4894,6 +5028,8 @@ STR_PERCENT_DOWN_SMALL :{TINY_FONT}{WHI
STR_PERCENT_DOWN :{WHITE}{NUM}%{DOWN_ARROW}
STR_PERCENT_UP_DOWN_SMALL :{TINY_FONT}{WHITE}{NUM}%{UP_ARROW}{DOWN_ARROW}
STR_PERCENT_UP_DOWN :{WHITE}{NUM}%{UP_ARROW}{DOWN_ARROW}
+STR_SERVICING_INDICATOR_SMALL :{TINY_FONT}{LTBLUE}Servicing ({NUM}{NBSP}%)
+STR_SERVICING_INDICATOR :{LTBLUE}Servicing ({NUM}{NBSP}%)
STR_PERCENT_NONE_SMALL :{TINY_FONT}{WHITE}{NUM}%
STR_PERCENT_NONE :{WHITE}{NUM}%
@@ -5074,6 +5210,28 @@ STR_ERROR_DRIVE_THROUGH_DIRECTION :{WHITE}... road
STR_ERROR_DRIVE_THROUGH_CORNER :{WHITE}... drive through stops can't have corners
STR_ERROR_DRIVE_THROUGH_JUNCTION :{WHITE}... drive through stops can't have junctions
+STR_ERROR_AIRPORT_DISABLED_BY_TILE :{WHITE}... setting about building by tile is disabled.
+STR_ERROR_AIRPORT_INCORRECT_AIRTYPE :{WHITE}... airport has a different air type.
+STR_ERROR_AIRPORT_ALREADY_AIRTYPE :{WHITE}... airport has this air type already.
+STR_ERROR_AIRPORT_CAN_T_CONVERT_WATER :{WHITE}... can't convert between water and non-water airtypes.
+STR_ERROR_AIRPORT_CAN_T_CONVERT_HELIPORT :{WHITE}... can't convert heliport to another airtype.
+STR_ERROR_AIRPORT_CAN_T_BUILD_HELIPORT :{WHITE}... can't build heliport with this airtype.
+STR_ERROR_AIRPORT_TOO_MUCH_RUNWAYS :{WHITE}... exceeding maximum number of runways for this air type.
+STR_ERROR_AIRPORT_NOT_SAME_LEVEL :{WHITE}... not at the same level.
+STR_ERROR_AIRPORT_PLAIN_WATER :{WHITE}... must be built on water and plain terrain.
+STR_ERROR_AIRPORT_INVALID_AIR_TYPE :{WHITE}... tile has a different air type.
+STR_ERROR_AIRPORT_CAN_T_HAVE_TRACKS :{WHITE}... tile can't have tracks.
+STR_ERROR_AIRPORT_NO_COMPATIBLE_NEIGHBOURS :{WHITE}... can't add track. New track must be compatible with neighbour tiles.
+STR_ERROR_AIRPORT_CAN_T_ADD_TRACK_HANGAR :{WHITE}... hangars can only have tracks on x or y axis. revise
+STR_ERROR_AIRPORT_RUNWAY_TOO_SHORT :{WHITE}... runway too short.
+STR_ERROR_AIRPORT_RUNWAY_INCOMPLETE :{WHITE}... not all runway is on the same airport station.
+STR_ERROR_AIRPORT_RUNWAY_CAN_T_BUILD_OVER :{WHITE}... it is not possible to build the runway over those tiles.
+STR_ERROR_AIRPORT_PRESENT_AIRCRAFT :{WHITE}... aircraft present in the airport.
+
+STR_ERROR_AIRPORT_RUNWAY_OVERLAP :{WHITE}... runway overlaps other runways.
+STR_ERROR_AIRPORT_RUNWAY_CAN_T_REMOVE :{WHITE}... remove runways correctly.
+STR_ERROR_AIRPORT_REMOVE_RUNWAYS_FIRST :{WHITE}... must remove runways first.
+
# Station destruction related errors
STR_ERROR_CAN_T_REMOVE_PART_OF_STATION :{WHITE}Can't remove part of station...
STR_ERROR_MUST_REMOVE_RAILWAY_STATION_FIRST :{WHITE}Must remove railway station first
@@ -5110,10 +5268,18 @@ STR_ERROR_BUOY_IS_IN_USE :{WHITE}... buoy
# Depot related errors
STR_ERROR_CAN_T_BUILD_TRAIN_DEPOT :{WHITE}Can't build train depot here...
+STR_ERROR_CAN_T_REMOVE_TRAIN_DEPOT :{WHITE}Can't remove train depot here...
STR_ERROR_CAN_T_BUILD_ROAD_DEPOT :{WHITE}Can't build road vehicle depot here...
STR_ERROR_CAN_T_BUILD_TRAM_DEPOT :{WHITE}Can't build tram vehicle depot here...
STR_ERROR_CAN_T_BUILD_SHIP_DEPOT :{WHITE}Can't build ship depot here...
+STR_ERROR_ADJOINS_MORE_THAN_ONE_EXISTING_DEPOT :{WHITE}... adjoins more than one existing depot
+STR_ERROR_DEPOT_TYPE_NOT_AVAILABLE :{WHITE}... depot type not available
+STR_ERROR_DEPOT_EXTENDING_PLATFORMS :{WHITE}Extending already reserved depot platforms
+STR_ERROR_DEPOT_EXTENDED_RAIL_DEPOT_IS_NOT_FREE :{WHITE}Extended rail depot has a reserved tile and can't be converted
+
+STR_ERROR_CAN_T_START_STOP_VEHICLES :{WHITE}Can't start at least one vehicle in the depot...
+STR_ERROR_CAN_T_REPLACE_VEHICLES :{WHITE}Can't replace vehicles in the depot...
STR_ERROR_CAN_T_RENAME_DEPOT :{WHITE}Can't rename depot...
STR_ERROR_TRAIN_MUST_BE_STOPPED_INSIDE_DEPOT :{WHITE}... must be stopped inside a depot
@@ -5123,6 +5289,7 @@ STR_ERROR_AIRCRAFT_MUST_BE_STOPPED_INSIDE_HANGAR :{WHITE}... must
STR_ERROR_TRAINS_CAN_ONLY_BE_ALTERED_INSIDE_A_DEPOT :{WHITE}Trains can only be altered when stopped inside a depot
STR_ERROR_TRAIN_TOO_LONG :{WHITE}Train too long
+STR_ERROR_INCOMPATIBLE_RAILTYPES_WITH_DEPOT :{WHITE}Train chain is incompatible with any tile of this depot
STR_ERROR_CAN_T_REVERSE_DIRECTION_RAIL_VEHICLE :{WHITE}Can't reverse direction of vehicle...
STR_ERROR_CAN_T_REVERSE_DIRECTION_RAIL_VEHICLE_MULTIPLE_UNITS :{WHITE}... consists of multiple units
STR_ERROR_INCOMPATIBLE_RAIL_TYPES :Incompatible rail types
@@ -5131,8 +5298,19 @@ STR_ERROR_CAN_T_MOVE_VEHICLE :{WHITE}Can't mo
STR_ERROR_REAR_ENGINE_FOLLOW_FRONT :{WHITE}The rear engine will always follow its front counterpart
STR_ERROR_UNABLE_TO_FIND_ROUTE_TO :{WHITE}Unable to find route to local depot
STR_ERROR_UNABLE_TO_FIND_LOCAL_DEPOT :{WHITE}Unable to find local depot
+STR_ERROR_UNABLE_TO_FIND_APPROPRIATE_DEPOT_TILE :{WHITE}Unable to find appropriate depot tile
+STR_ERROR_DEPOT_TOO_SPREAD_OUT :{WHITE}... depot too spread out
STR_ERROR_DEPOT_WRONG_DEPOT_TYPE :Wrong depot type
+STR_ERROR_CAN_T_START_PLATFORM_TYPE :{WHITE}{VEHICLE} can't be started because there is no compatible platform in the depot for this type of train
+STR_ERROR_CAN_T_START_PLATFORM_LONG :{WHITE}{VEHICLE} can't be started because compatible platforms are not long enough
+STR_ERROR_DEPOT_FULL_DEPOT :There is no free depot compatible with this type of vehicle
+###length 5
+STR_ADVICE_PLATFORM_TYPE :{WHITE}{VEHICLE} can't leave depot because there is no compatible platform for this type of train
+STR_ADVICE_PLATFORM_LONG :{WHITE}{VEHICLE} can't leave depot because compatible platforms are not long enough
+STR_ADVICE_VEHICLE_HAS_NO_POWER :{WHITE}{VEHICLE} can't leave depot because it has no power in any tile of the depot
+STR_ADVICE_PLATFORM_FREE_PLATFORM :{WHITE}{VEHICLE} can't leave depot because compatible platforms are occupied
+STR_ADVICE_PLATFORM_SIGNALS :{WHITE}{VEHICLE} can't leave depot because the segments of the compatible free platforms are occupied. Check the signaling of those segments.
# Depot unbunching related errors
STR_ERROR_UNBUNCHING_ONLY_ONE_ALLOWED :{WHITE}... can only have one unbunching order
@@ -5297,6 +5475,8 @@ STR_ERROR_TOO_MANY_VEHICLES_IN_GAME :{WHITE}Too many
STR_ERROR_CAN_T_CHANGE_SERVICING :{WHITE}Can't change servicing interval...
STR_ERROR_VEHICLE_IS_DESTROYED :{WHITE}... vehicle is destroyed
+STR_ERROR_NO_FREE_DEPOT :{WHITE}... there is no free depot
+STR_ERROR_NO_FREE_HANGAR :{WHITE}... there is no free hangar
STR_ERROR_CAN_T_CLONE_VEHICLE_LIST :{WHITE}... not all vehicles are identical
@@ -5314,6 +5494,7 @@ STR_ERROR_NO_TOWN_ROADTYPES_AVAILABLE_YET_EXPLANATION :{WHITE}Start a
STR_ERROR_CAN_T_MAKE_TRAIN_PASS_SIGNAL :{WHITE}Can't make train pass signal at danger...
STR_ERROR_CAN_T_REVERSE_DIRECTION_TRAIN :{WHITE}Can't reverse direction of train...
STR_ERROR_TRAIN_START_NO_POWER :Train has no power
+STR_ERROR_ROAD_VEHICLE_START_NO_POWER :Road vehicle has no power
STR_ERROR_CAN_T_MAKE_ROAD_VEHICLE_TURN :{WHITE}Can't make road vehicle turn around...
@@ -5825,6 +6006,9 @@ STR_VIEWPORT_STATION_TINY :{TINY_FONT}{STA
STR_VIEWPORT_WAYPOINT :{WAYPOINT}
STR_VIEWPORT_WAYPOINT_TINY :{TINY_FONT}{WAYPOINT}
+STR_VIEWPORT_DEPOT :{DEPOT}
+STR_VIEWPORT_DEPOT_TINY :{TINY_FONT}{DEPOT}
+
# Simple strings to get specific types of data
STR_COMPANY_NAME :{COMPANY}
STR_COMPANY_NAME_COMPANY_NUM :{COMPANY} {COMPANY_NUM}
diff --git a/src/misc_gui.cpp b/src/misc_gui.cpp
index cb97d7c8c3e15..00c9a77a95599 100644
--- a/src/misc_gui.cpp
+++ b/src/misc_gui.cpp
@@ -159,6 +159,7 @@ class LandInfoWindow : public Window {
td.station_class = STR_NULL;
td.station_name = STR_NULL;
+ td.airtype = STR_NULL;
td.airport_class = STR_NULL;
td.airport_name = STR_NULL;
td.airport_tile_name = STR_NULL;
@@ -246,6 +247,12 @@ class LandInfoWindow : public Window {
this->landinfo_data.push_back(GetString(STR_LAND_AREA_INFORMATION_STATION_TYPE));
}
+ /* Airport type */
+ if (td.airtype != STR_NULL) {
+ SetDParam(0, td.airtype);
+ this->landinfo_data.push_back(GetString(STR_LANG_AREA_INFORMATION_AIR_TYPE));
+ }
+
/* Airport class */
if (td.airport_class != STR_NULL) {
SetDParam(0, td.airport_class);
diff --git a/src/newgrf.cpp b/src/newgrf.cpp
index 3458535b18dbe..5446596e498ec 100644
--- a/src/newgrf.cpp
+++ b/src/newgrf.cpp
@@ -16,6 +16,7 @@
#include "engine_base.h"
#include "bridge.h"
#include "town.h"
+#include "air.h"
#include "newgrf_engine.h"
#include "newgrf_text.h"
#include "fontcache.h"
@@ -51,9 +52,12 @@
#include "road.h"
#include "newgrf_roadstop.h"
+#include "table/airport_defaults.h"
#include "table/strings.h"
#include "table/build_industry.h"
+#include
+
#include "safeguards.h"
/* TTDPatch extended GRF format codec
@@ -321,6 +325,7 @@ struct GRFTempEngineData {
uint16_t cargo_allowed;
uint16_t cargo_disallowed;
RailTypeLabel railtypelabel;
+ AirTypeLabel airtypelabel;
uint8_t roadtramtype;
const GRFFile *defaultcargo_grf; ///< GRF defining the cargo translation table to use if the default cargo is the 'first refittable'.
Refittability refittability; ///< Did the newgrf set any refittability property? If not, default refittability will be applied.
@@ -1766,6 +1771,25 @@ static ChangeInfoResult AircraftVehicleChangeInfo(uint engine, int numinfo, int
AircraftVehicleInfo *avi = &e->u.air;
switch (prop) {
+ case 0x05: { // Air type
+ uint8_t airtype = buf.ReadByte();
+
+ if (airtype < _cur.grffile->airtype_list.size()) {
+ _gted[e->index].airtypelabel = _cur.grffile->airtype_list[airtype];
+ break;
+ }
+
+ switch (airtype) {
+ case 0: _gted[e->index].airtypelabel = AIRTYPE_LABEL_GRAVEL; break;
+ case 1: _gted[e->index].airtypelabel = AIRTYPE_LABEL_ASPHALT; break;
+ case 2: _gted[e->index].airtypelabel = AIRTYPE_LABEL_WATER; break;
+ default:
+ GrfMsg(1, "AircraftVehicleChangeInfo: Invalid air type {} specified, ignoring", airtype);
+ break;
+ }
+ break;
+ }
+
case 0x08: { // Sprite ID
uint8_t spriteid = buf.ReadByte();
uint8_t orig_spriteid = spriteid;
@@ -2715,6 +2739,9 @@ static ChangeInfoResult GlobalVarChangeInfo(uint gvid, int numinfo, int prop, By
case 0x17: // Tram type translation table; loading during both reservation and activation stage (in case it is selected depending on defined railtypes)
return LoadTranslationTable(gvid, numinfo, buf, _cur.grffile->tramtype_list, "Tram type");
+ case 0x18: // Air type translation table; loading during both reservation and activation stage (in case it is selected depending on defined airtypes)
+ return LoadTranslationTable(gvid, numinfo, buf, _cur.grffile->airtype_list, "Air type");
+
default:
break;
}
@@ -2933,6 +2960,9 @@ static ChangeInfoResult GlobalVarReserveInfo(uint gvid, int numinfo, int prop, B
case 0x17: // Tram type translation table; loading during both reservation and activation stage (in case it is selected depending on defined tramtypes)
return LoadTranslationTable(gvid, numinfo, buf, _cur.grffile->tramtype_list, "Tram type");
+ case 0x18: // Air type translation table; loading during both reservation and activation stage (in case it is selected depending on defined airtypes)
+ return LoadTranslationTable(gvid, numinfo, buf, _cur.grffile->airtype_list, "Air type");
+
default:
break;
}
@@ -3872,6 +3902,49 @@ static ChangeInfoResult IndustriesChangeInfo(uint indid, int numinfo, int prop,
return ret;
}
+AirType GetConversionAirtype(uint airport)
+{
+ struct AirportTypesConversion {
+ AirportTypes airport_type;
+ AirType air_type;
+ };
+
+ switch (_cur.grffile->grfid) {
+ default:
+ Debug(misc, 0, "Trying to load airports of unknown airtype from grffile with id {}", _cur.grffile->grfid);
+ return AIRTYPE_GRAVEL;
+ case 16860225:
+ return AIRTYPE_WATER;
+ case 19680837: // North Korean Aviation Set: Small asphalt airports
+ return AIRTYPE_ASPHALT;
+ case 5259587: { // OpenGFX+ Airports
+ /* This table indicates how to convert the airports provided in OpenGFX+Airports,
+ * as long as it is the first NewGRF to be applied that modifies airports. */
+ /* The "S" shows which airports have a (close) equivalent in original airports. */
+ AirportTypesConversion opengfx_plus_airports[] = {
+ { AT_SMALL, AIRTYPE_GRAVEL }, // S NEW_AIRPORT_OFFSET + 0 Small gravel
+ { AT_SMALL, AIRTYPE_WATER }, // NEW_AIRPORT_OFFSET + 1 Small water
+ { AT_SMALL, AIRTYPE_ASPHALT }, // NEW_AIRPORT_OFFSET + 2 Small asphalt
+ { AT_COMMUTER, AIRTYPE_GRAVEL }, // NEW_AIRPORT_OFFSET + 3 Commuter gravel
+ { AT_COMMUTER, AIRTYPE_ASPHALT }, // S NEW_AIRPORT_OFFSET + 4 Commuter asphalt
+ { AT_LARGE, AIRTYPE_ASPHALT }, // S NEW_AIRPORT_OFFSET + 5 Large asphalt
+ { AT_METROPOLITAN, AIRTYPE_ASPHALT }, // S NEW_AIRPORT_OFFSET + 6 City asphalt
+ { AT_INTERNATIONAL, AIRTYPE_ASPHALT }, // S NEW_AIRPORT_OFFSET + 7 International asphalt
+ { AT_INTERCON, AIRTYPE_ASPHALT }, // S NEW_AIRPORT_OFFSET + 8 Intercontinental asphalt -- Uses a different and non-rectangular layout.
+ { AT_HELIPORT, AIRTYPE_ASPHALT }, // S NEW_AIRPORT_OFFSET + 9 Heliport
+ { AT_HELIDEPOT, AIRTYPE_GRAVEL }, // NEW_AIRPORT_OFFSET + 10 Helidepot gravel
+ { AT_HELIDEPOT, AIRTYPE_ASPHALT }, // S NEW_AIRPORT_OFFSET + 11 Helidepot asphalt
+ { AT_HELISTATION, AIRTYPE_GRAVEL }, // NEW_AIRPORT_OFFSET + 12 Helistation gravel
+ { AT_HELISTATION, AIRTYPE_ASPHALT }, // S NEW_AIRPORT_OFFSET + 13 Helistation asphalt
+ };
+ const uint num_opengfx_plus_airports = sizeof(opengfx_plus_airports)/sizeof(opengfx_plus_airports[0]);
+
+ if (airport >= num_opengfx_plus_airports) NOT_REACHED();
+ return opengfx_plus_airports[airport].air_type;
+ }
+ }
+}
+
/**
* Define properties for airports
* @param airport Local ID of the airport.
@@ -3889,9 +3962,11 @@ static ChangeInfoResult AirportChangeInfo(uint airport, int numinfo, int prop, B
return CIR_INVALID_ID;
}
- /* Allocate industry specs if they haven't been allocated already. */
+ /* Allocate airport specs if they haven't been allocated already. */
if (_cur.grffile->airportspec.size() < airport + numinfo) _cur.grffile->airportspec.resize(airport + numinfo);
+ AirType conversion_airtype = GetConversionAirtype(airport);
+
for (int i = 0; i < numinfo; i++) {
AirportSpec *as = _cur.grffile->airportspec[airport + i].get();
@@ -3925,6 +4000,7 @@ static ChangeInfoResult AirportChangeInfo(uint airport, int numinfo, int prop, B
as->grf_prop.local_id = airport + i;
as->grf_prop.subst_id = subs_id;
as->grf_prop.grffile = _cur.grffile;
+ as->airtype = conversion_airtype;
/* override the default airport */
_airport_mngr.Add(airport + i, _cur.grffile->grfid, subs_id);
}
@@ -3932,63 +4008,81 @@ static ChangeInfoResult AirportChangeInfo(uint airport, int numinfo, int prop, B
}
case 0x0A: { // Set airport layout
- uint8_t num_layouts = buf.ReadByte();
- buf.ReadDWord(); // Total size of definition, unneeded.
- uint8_t size_x = 0;
- uint8_t size_y = 0;
-
- std::vector layouts;
- layouts.reserve(num_layouts);
-
- for (uint8_t j = 0; j != num_layouts; ++j) {
- auto &layout = layouts.emplace_back();
- layout.rotation = static_cast(buf.ReadByte() & 6); // Rotation can only be DIR_NORTH, DIR_EAST, DIR_SOUTH or DIR_WEST.
+ if (_cur.grffile->grf_version <= 8) {
+ /* Deal with the only NewGRF that modified airport layouts. */
+ const uint max_airport_tiles = 4096; // 64 * 64, max station spread.
+ [[maybe_unused]] uint num_tiles = as->layouts[0].size_x * as->layouts[0].size_y;
+ assert(num_tiles <= max_airport_tiles);
+ uint8_t num_layouts = buf.ReadByte();
+ std::bitset defined_tiles;
+ buf.ReadDWord(); // Total size of the definition, unneeded.
+
+ as->layouts.resize(1);
+ auto &layout = as->layouts[0];
+ assert(layout.tiles.size() == num_tiles);
+
+ for (uint8_t j = 0; j != num_layouts; ++j) {
+ DiagDirection rotation = (DiagDirection)(buf.ReadByte() / 2); // rotation
+
+ for (;;) {
+ uint8_t x = buf.ReadByte(); // Offsets from northermost tile
+ uint8_t y = buf.ReadByte();
+
+ if (x == 0 && y == 0x80) break;
+
+ // Get the corresponding offset for the non-rotated version.
+ switch (rotation) {
+ case 0:
+ break;
+ case 1:
+ Swap(x, y);
+ x = as->layouts[0].size_x - 1 - x;
+ break;
+ case 2:
+ x = as->layouts[0].size_x - 1 - x;
+ y = as->layouts[0].size_y - 1 - y;
+ break;
+ case 3:
+ Swap(x, y);
+ y = as->layouts[0].size_y - 1 - y;
+ break;
+ default:
+ NOT_REACHED();
+ }
- for (;;) {
- auto &tile = layout.tiles.emplace_back();
- tile.ti.x = buf.ReadByte();
- tile.ti.y = buf.ReadByte();
- if (tile.ti.x == 0 && tile.ti.y == 0x80) {
- /* Convert terminator to our own. */
- tile.ti.x = -0x80;
- tile.ti.y = 0;
- tile.gfx = 0;
- break;
- }
+ uint16_t table_index = as->layouts[0].size_x * y + x;
+ assert(table_index < as->layouts[0].size_x * as->layouts[0].size_y);
- tile.gfx = buf.ReadByte();
+ // Only keep track of first layout.
+ if (j == 0) defined_tiles[table_index] = true;
+ auto &tile = layout.tiles[table_index];
- if (tile.gfx == 0xFE) {
- /* Use a new tile from this GRF */
- int local_tile_id = buf.ReadWord();
+ tile.gfx[rotation] = (AirportTiles)buf.ReadByte();
- /* Read the ID from the _airporttile_mngr. */
- uint16_t tempid = _airporttile_mngr.GetID(local_tile_id, _cur.grffile->grfid);
+ if (tile.gfx[rotation] == 0xFE) { // gfx
+ int local_tile_id = buf.ReadWord(); // use a new tile for this GRFC
+ /* Read the ID from the _airporttile_mngr. */
+ uint16_t tempid = _airporttile_mngr.GetID(local_tile_id, _cur.grffile->grfid);
- if (tempid == INVALID_AIRPORTTILE) {
- GrfMsg(2, "AirportChangeInfo: Attempt to use airport tile {} with airport id {}, not yet defined. Ignoring.", local_tile_id, airport + i);
- } else {
- /* Declared as been valid, can be used */
- tile.gfx = tempid;
+ if (tempid == INVALID_AIRPORTTILE) {
+ GrfMsg(2, "AirportChangeInfo: Attempt to use airport tile {} with airport id {}, not yet defined. Ignoring.", local_tile_id, airport + i);
+ } else {
+ /* Declared as been valid, can be used */
+ tile.gfx[rotation] = (AirportTiles)tempid;
+ }
}
- } else if (tile.gfx == 0xFF) {
- tile.ti.x = static_cast(GB(tile.ti.x, 0, 8));
- tile.ti.y = static_cast(GB(tile.ti.y, 0, 8));
}
+ }
- /* Determine largest size. */
- if (layout.rotation == DIR_E || layout.rotation == DIR_W) {
- size_x = std::max(size_x, tile.ti.y + 1);
- size_y = std::max(size_y, tile.ti.x + 1);
- } else {
- size_x = std::max(size_x, tile.ti.x + 1);
- size_y = std::max(size_y, tile.ti.y + 1);
- }
+ /* Set the empty tiles if any. */
+ for (int i = 0; i < as->layouts[0].size_x * as->layouts[0].size_y; i++) {
+ if (defined_tiles[i]) continue;
+ layout.tiles[i].type = ATT_INVALID;
}
+
+ } else {
+ ret = CIR_UNKNOWN;
}
- as->layouts = std::move(layouts);
- as->size_x = size_x;
- as->size_y = size_y;
break;
}
@@ -4003,11 +4097,11 @@ static ChangeInfoResult AirportChangeInfo(uint airport, int numinfo, int prop, B
break;
case 0x0E:
- as->catchment = Clamp(buf.ReadByte(), 1, MAX_CATCHMENT);
+ buf.ReadByte(); // Old airport catchment
break;
case 0x0F:
- as->noise_level = buf.ReadByte();
+ buf.ReadByte(); // Old airport noise
break;
case 0x10:
@@ -4015,9 +4109,17 @@ static ChangeInfoResult AirportChangeInfo(uint airport, int numinfo, int prop, B
break;
case 0x11: // Maintenance cost factor
- as->maintenance_cost = buf.ReadWord();
+ buf.ReadWord();
break;
+ /*
+ as->num_runways = buf->ReadByte();
+ as->num_aprons = buf->ReadByte();
+ as->num_helipads = buf->ReadByte();
+ as->num_heliports = buf->ReadByte();
+ as->min_runway_length = buf->ReadByte();
+ */
+
default:
ret = CIR_UNKNOWN;
break;
@@ -4203,6 +4305,262 @@ static ChangeInfoResult ObjectChangeInfo(uint id, int numinfo, int prop, ByteRea
return ret;
}
+/**
+ * Define properties for airtypes
+ * @param id ID of the airtype.
+ * @param numinfo Number of subsequent IDs to change the property for.
+ * @param prop The property to change.
+ * @param buf The property value.
+ * @return ChangeInfoResult.
+ */
+static ChangeInfoResult AirTypeChangeInfo(uint id, int numinfo, int prop, ByteReader &buf)
+{
+ ChangeInfoResult ret = CIR_SUCCESS;
+
+ extern AirTypeInfo _airtypes[AIRTYPE_END];
+
+ if (id + numinfo > AIRTYPE_END) {
+ GrfMsg(1, "AirTypeChangeInfo: Rail type {} is invalid, max {}, ignoring", id + numinfo, AIRTYPE_END);
+ return CIR_INVALID_ID;
+ }
+
+ for (int i = 0; i < numinfo; i++) {
+ AirType at = _cur.grffile->airtype_map[id + i];
+ if (at == INVALID_AIRTYPE) return CIR_INVALID_ID;
+
+ AirTypeInfo *ati = &_airtypes[at];
+
+ switch (prop) {
+ case 0x08: // Label of air type
+ /* Skipped here as this is loaded during reservation stage. */
+ buf.ReadDWord();
+ break;
+
+ case 0x09: { // Toolbar caption of airtype
+ uint16_t str = buf.ReadWord();
+ AddStringForMapping(str, &ati->strings.toolbar_caption);
+ break;
+ }
+
+ case 0x0A: // Menu text of airtype
+ AddStringForMapping(buf.ReadWord(), &ati->strings.menu_text);
+ break;
+
+ /*case 0x0B: // Build window caption
+ AddStringForMapping(buf->ReadWord(), &ati->strings.build_caption);
+ break;*/
+
+ case 0x0C: // Autoreplace text
+ AddStringForMapping(buf.ReadWord(), &ati->strings.replace_text);
+ break;
+
+ /*case 0x0D: // New locomotive text
+ AddStringForMapping(buf->ReadWord(), &rti->strings.new_loco);
+ break;*/
+
+ case 0x0E: // Compatible airtype list
+ case 0x18: // AirType list required for date introduction
+ case 0x19: // Introduced airtype list
+ {
+ /* Air type compatibility bits are added to the existing bits
+ * to allow multiple GRFs to modify compatibility with the
+ * default air types. */
+ int n = buf.ReadByte();
+ for (int j = 0; j != n; j++) {
+ AirTypeLabel label = buf.ReadDWord();
+ AirType resolved_at = GetAirTypeByLabel(BSWAP32(label), false);
+ if (resolved_at != INVALID_AIRTYPE) {
+ switch (prop) {
+ case 0x0E: SetBit(ati->compatible_airtypes, resolved_at); break;
+ case 0x18: SetBit(ati->introduction_required_airtypes, resolved_at); break;
+ case 0x19: SetBit(ati->introduces_airtypes, resolved_at); break;
+ }
+ }
+ }
+ break;
+ }
+
+ /*case 0x10: // Rail Type flags
+ rti->flags = (RailTypeFlags)buf->ReadByte();
+ break;*/
+
+ /*case 0x11: // Curve speed advantage
+ rti->curve_speed = buf->ReadByte();
+ break;*/
+
+ case 0x12: // Station graphic
+ ati->fallback_airtype = Clamp(buf.ReadByte(), 0, 2);
+ break;
+
+ case 0x13: // Construction cost factor
+ ati->cost_multiplier = buf.ReadWord();
+ break;
+
+ case 0x14: // Speed limit
+ ati->max_speed = buf.ReadWord();
+ break;
+
+ /*case 0x15: // Acceleration model
+ rti->acceleration_type = Clamp(buf->ReadByte(), 0, 2);
+ break;*/
+
+ case 0x16: // Map colour
+ ati->map_colour = buf.ReadByte();
+ break;
+
+ case 0x17: // Introduction date
+ ati->introduction_date = buf.ReadDWord();
+ break;
+
+ case 0x1A: // Sort order
+ ati->sorting_order = buf.ReadByte();
+ break;
+
+ case 0x1B: // Name of airtype
+ AddStringForMapping(buf.ReadWord(), &ati->strings.name);
+ break;
+
+ case 0x1C: // Maintenance cost factor
+ ati->maintenance_multiplier = buf.ReadWord();
+ break;
+
+ case 0x1D: // Alternate air type label list
+ /* Skipped here as this is loaded during reservation stage. */
+ for (int j = buf.ReadByte(); j != 0; j--) buf.ReadDWord();
+ break;
+
+ case 0x1E: // Catchment radius
+ ati->catchment_radius = buf.ReadByte();
+ break;
+
+ case 0x1F: // Max. runways
+ ati->max_num_runways = buf.ReadByte();
+ break;
+
+ case 0x20: // Min. runway length
+ ati->min_runway_length = buf.ReadByte();
+ break;
+
+ case 0x21: // Base noise level
+ ati->base_noise_level = buf.ReadByte();
+ break;
+
+ case 0x22: // Runway noise level
+ ati->runway_noise_level = buf.ReadByte();
+ break;
+
+ case 0x23: { // Heliport availability
+ uint8_t availability = buf.ReadByte();
+ ati->heliport_availability = HasBit(availability, 0);
+ break;
+ }
+
+ case 0x24: { // Build this airport type on water
+ uint8_t on_water = buf.ReadByte();
+ ati->build_on_water = HasBit(on_water, 0);
+ break;
+ }
+
+ default:
+ ret = CIR_UNKNOWN;
+ break;
+ }
+ }
+
+ return ret;
+}
+
+static ChangeInfoResult AirTypeReserveInfo(uint id, int numinfo, int prop, ByteReader &buf)
+{
+ ChangeInfoResult ret = CIR_SUCCESS;
+
+ extern AirTypeInfo _airtypes[AIRTYPE_END];
+
+ if (id + numinfo > AIRTYPE_END) {
+ GrfMsg(1, "AirTypeReserveInfo: Air type {} is invalid, max {}, ignoring", id + numinfo, AIRTYPE_END);
+ return CIR_INVALID_ID;
+ }
+
+ for (int i = 0; i < numinfo; i++) {
+ switch (prop) {
+ case 0x08: // Label of air type
+ {
+ AirTypeLabel atl = buf.ReadDWord();
+ atl = BSWAP32(atl);
+
+ AirType at = GetAirTypeByLabel(atl, false);
+ if (at == INVALID_AIRTYPE) {
+ /* Set up new air type */
+ at = AllocateAirType(atl);
+ }
+
+ _cur.grffile->airtype_map[id + i] = at;
+ break;
+ }
+
+ case 0x09: // Toolbar caption of airtype
+ case 0x0A: // Menu text
+ //case 0x0B: // Build window caption
+ case 0x0C: // Autoreplace text
+ //case 0x0D: // New loco
+ case 0x13: // Construction cost
+ case 0x14: // Speed limit
+ case 0x1B: // Name of airtype
+ case 0x1C: // Maintenance cost factor
+ buf.ReadWord();
+ break;
+
+ case 0x1D: // Alternate air type label list
+ if (_cur.grffile->airtype_map[id + i] != INVALID_AIRTYPE) {
+ int n = buf.ReadByte();
+ for (int j = 0; j != n; j++) {
+ _airtypes[_cur.grffile->airtype_map[id + i]].alternate_labels.push_back(BSWAP32(buf.ReadDWord()));
+ }
+ break;
+ }
+ GrfMsg(1, "AirTypeReserveInfo: Ignoring property 1D for air type {} because no label was set", id + i);
+ [[fallthrough]];
+
+
+ case 0x0E: // Compatible railtype list
+ //case 0x0F: // Powered railtype list
+ case 0x18: // Railtype list required for date introduction
+ case 0x19: // Introduced railtype list
+ for (int j = buf.ReadByte(); j != 0; j--) buf.ReadDWord();
+ break;
+
+ //case 0x10: // Rail Type flags
+ //case 0x11: // Curve speed advantage
+ case 0x12: // Station graphic
+ //case 0x15: // Acceleration model
+ case 0x16: // Map colour
+ case 0x1A: // Sort order
+ buf.ReadByte();
+ break;
+
+ case 0x17: // Introduction date
+ buf.ReadDWord();
+ break;
+
+ case 0x1E: // Catchment radius
+ case 0x1F: // Max. runways
+ case 0x20: // Min. runway length
+ case 0x21: // Base noise level
+ case 0x22: // Runway noise level
+ case 0x23: // Heliport availability
+ case 0x24: // Build this airport type on water
+ buf.ReadByte();
+ break;
+
+ default:
+ ret = CIR_UNKNOWN;
+ break;
+ }
+ }
+
+ return ret;
+}
+
/**
* Define properties for railtypes
* @param id ID of the railtype.
@@ -4678,7 +5036,7 @@ static ChangeInfoResult AirportTilesChangeInfo(uint airtid, int numinfo, int pro
/* Allocate space for this airport tile. */
if (tsp == nullptr) {
- _cur.grffile->airtspec[airtid + i] = std::make_unique(*AirportTileSpec::Get(subs_id));
+ _cur.grffile->airtspec[airtid + i] = std::make_unique(*AirportTileSpec::GetAirportTileSpec(subs_id));
tsp = _cur.grffile->airtspec[airtid + i].get();
tsp->enabled = true;
@@ -4702,6 +5060,7 @@ static ChangeInfoResult AirportTilesChangeInfo(uint airtid, int numinfo, int pro
continue;
}
+ Debug(misc, 0, "Overriding airport tile {} {}", airtid + i, override);
_airporttile_mngr.Add(airtid + i, _cur.grffile->grfid, override);
break;
}
@@ -4925,6 +5284,7 @@ static void FeatureChangeInfo(ByteReader &buf)
/* GSF_ROADTYPES */ RoadTypeChangeInfo,
/* GSF_TRAMTYPES */ TramTypeChangeInfo,
/* GSF_ROADSTOPS */ RoadStopChangeInfo,
+ /* GSF_AIRTYPES */ AirTypeChangeInfo,
};
static_assert(GSF_END == lengthof(handler));
@@ -4999,7 +5359,17 @@ static void ReserveChangeInfo(ByteReader &buf)
{
uint8_t feature = buf.ReadByte();
- if (feature != GSF_CARGOES && feature != GSF_GLOBALVAR && feature != GSF_RAILTYPES && feature != GSF_ROADTYPES && feature != GSF_TRAMTYPES) return;
+ switch (feature) {
+ case GSF_CARGOES:
+ case GSF_GLOBALVAR:
+ case GSF_RAILTYPES:
+ case GSF_ROADTYPES:
+ case GSF_TRAMTYPES:
+ case GSF_AIRTYPES:
+ break;
+ default:
+ return;
+ }
uint8_t numprops = buf.ReadByte();
uint8_t numinfo = buf.ReadByte();
@@ -5030,6 +5400,10 @@ static void ReserveChangeInfo(ByteReader &buf)
case GSF_TRAMTYPES:
cir = TramTypeReserveInfo(index, numinfo, prop, buf);
break;
+
+ case GSF_AIRTYPES:
+ cir = AirTypeReserveInfo(index, numinfo, prop, buf);
+ break;
}
if (HandleChangeInfoResult("ReserveChangeInfo", cir, feature, prop)) return;
@@ -5334,6 +5708,7 @@ static void NewSpriteGroup(ByteReader &buf)
case GSF_CARGOES:
case GSF_AIRPORTS:
case GSF_RAILTYPES:
+ case GSF_AIRTYPES:
case GSF_ROADTYPES:
case GSF_TRAMTYPES:
{
@@ -5949,6 +6324,38 @@ static void RailTypeMapSpriteGroup(ByteReader &buf, uint8_t idcount)
buf.ReadWord();
}
+static void AirTypeMapSpriteGroup(ByteReader &buf, uint8_t idcount)
+{
+ std::vector airtypes;
+ airtypes.reserve(idcount);
+ for (uint i = 0; i < idcount; i++) {
+ uint16_t id = buf.ReadExtendedByte();
+ airtypes.push_back(id < AIRTYPE_END ? _cur.grffile->airtype_map[id] : INVALID_AIRTYPE);
+ }
+
+ uint8_t cidcount = buf.ReadByte();
+ for (uint c = 0; c < cidcount; c++) {
+ uint8_t ctype = buf.ReadByte();
+ uint16_t groupid = buf.ReadWord();
+ if (!IsValidGroupID(groupid, "AirTypeMapSpriteGroup")) continue;
+
+ if (ctype >= RTSG_END) continue;
+
+ extern AirTypeInfo _airtypes[AIRTYPE_END];
+ for (auto &airtype : airtypes) {
+ if (airtype != INVALID_AIRTYPE) {
+ AirTypeInfo *ati = &_airtypes[airtype];
+
+ ati->grffile[ctype] = _cur.grffile;
+ ati->group[ctype] = _cur.spritegroups[groupid];
+ }
+ }
+ }
+
+ /* AirTypes do not use the default group. */
+ buf.ReadWord();
+}
+
static void RoadTypeMapSpriteGroup(ByteReader &buf, uint8_t idcount, RoadTramType rtt)
{
RoadType *type_map = (rtt == RTT_TRAM) ? _cur.grffile->tramtype_map : _cur.grffile->roadtype_map;
@@ -6199,6 +6606,10 @@ static void FeatureMapSpriteGroup(ByteReader &buf)
RoadTypeMapSpriteGroup(buf, idcount, RTT_TRAM);
break;
+ case GSF_AIRTYPES:
+ AirTypeMapSpriteGroup(buf, idcount);
+ break;
+
case GSF_AIRPORTTILES:
AirportTileMapSpriteGroup(buf, idcount);
return;
@@ -6385,6 +6796,7 @@ static constexpr auto _action5_types = std::to_array({
/* 0x17 */ { A5BLOCK_ALLOW_OFFSET, SPR_RAILTYPE_TUNNEL_BASE, 1, RAILTYPE_TUNNEL_BASE_COUNT, "Railtype tunnel base" },
/* 0x18 */ { A5BLOCK_ALLOW_OFFSET, SPR_PALETTE_BASE, 1, PALETTE_SPRITE_COUNT, "Palette" },
/* 0x19 */ { A5BLOCK_ALLOW_OFFSET, SPR_ROAD_WAYPOINTS_BASE, 1, ROAD_WAYPOINTS_SPRITE_COUNT, "Road waypoints" },
+ /* 0x1A */ { A5BLOCK_ALLOW_OFFSET, SPR_AIRTYPE_BASE, 1, AIRTYPE_SPRITE_TOTAL_COUNT, "Airtype graphics" },
});
/**
@@ -6865,6 +7277,10 @@ static void SkipIf(ByteReader &buf)
result = rt != INVALID_ROADTYPE && RoadTypeIsTram(rt);
break;
}
+ case 0x13: result = GetAirTypeByLabel(BSWAP32(cond_val)) == INVALID_AIRTYPE;
+ break;
+ case 0x14: result = GetAirTypeByLabel(BSWAP32(cond_val)) != INVALID_AIRTYPE;
+ break;
default: GrfMsg(1, "SkipIf: Unsupported condition type {:02X}. Ignoring", condtype); return;
}
} else if (param == 0x88) {
@@ -8767,6 +9183,9 @@ void ResetNewGRFData()
/* Copy/reset original road type info data */
ResetRoadTypes();
+ /* Reset air type information */
+ ResetAirTypes();
+
/* Allocate temporary refit/cargo class data */
_gted.resize(Engine::GetPoolSize());
@@ -8775,6 +9194,12 @@ void ResetNewGRFData()
_gted[e->index].railtypelabel = GetRailTypeInfo(e->u.rail.railtype)->label;
}
+ /* Fill air type label temporary data for default aircraft */
+ for (const Engine *e : Engine::IterateType(VEH_AIRCRAFT)) {
+ assert(e->u.air.airtype < AIRTYPE_END);
+ _gted[e->index].airtypelabel = GetAirTypeInfo(e->u.air.airtype)->label;
+ }
+
/* Reset GRM reservations */
memset(&_grm_engines, 0, sizeof(_grm_engines));
memset(&_grm_cargoes, 0, sizeof(_grm_cargoes));
@@ -8931,6 +9356,12 @@ GRFFile::GRFFile(const GRFConfig *config)
std::fill(std::begin(this->tramtype_map), std::end(this->tramtype_map), INVALID_ROADTYPE);
this->tramtype_map[0] = ROADTYPE_TRAM;
+ /* Initialise air type map with default air types */
+ std::fill(std::begin(this->airtype_map), std::end(this->airtype_map), INVALID_AIRTYPE);
+ this->airtype_map[0] = AIRTYPE_GRAVEL;
+ this->airtype_map[1] = AIRTYPE_ASPHALT;
+ this->airtype_map[2] = AIRTYPE_WATER;
+
/* Copy the initial parameter list
* 'Uninitialised' parameters are zeroed as that is their default value when dynamically creating them. */
this->param = config->param;
@@ -9505,6 +9936,7 @@ static void FinaliseAirportsArray()
for (auto &as : file->airportspec) {
if (as != nullptr && as->enabled) {
_airport_mngr.SetEntitySpec(as.get());
+ /* Fill some missing data if not provided. */
}
}
@@ -9956,9 +10388,10 @@ static void AfterLoadGRFs()
/* Load old tram depot sprites in new position, if no new ones are present */
ActivateOldTramDepot();
- /* Set up custom rail types */
+ /* Set up custom rail, road and air types */
InitRailTypes();
InitRoadTypes();
+ InitAirTypes();
for (Engine *e : Engine::IterateType(VEH_ROAD)) {
if (_gted[e->index].rv_max_speed != 0) {
@@ -10003,6 +10436,16 @@ static void AfterLoadGRFs()
}
}
+ for (Engine *e : Engine::IterateType(VEH_AIRCRAFT)) {
+ AirType airtype = GetAirTypeByLabel(_gted[e->index].airtypelabel);
+ if (airtype == INVALID_AIRTYPE) {
+ /* Air type is not available, so disable this engine */
+ e->info.climates = 0;
+ } else {
+ e->u.air.airtype = airtype;
+ }
+ }
+
SetYearEngineAgingStops();
FinalisePriceBaseMultipliers();
diff --git a/src/newgrf.h b/src/newgrf.h
index e49c22ee5c590..f6e326c47d5b2 100644
--- a/src/newgrf.h
+++ b/src/newgrf.h
@@ -13,6 +13,7 @@
#include "cargotype.h"
#include "rail_type.h"
#include "road_type.h"
+#include "air_type.h"
#include "fileio_type.h"
#include "newgrf_text_type.h"
#include "core/bitmath_func.hpp"
@@ -86,6 +87,7 @@ enum GrfSpecFeature {
GSF_ROADTYPES,
GSF_TRAMTYPES,
GSF_ROADSTOPS,
+ GSF_AIRTYPES,
GSF_END,
GSF_FAKE_TOWNS = GSF_END, ///< Fake town GrfSpecFeature for NewGRF debugging (parent scope)
@@ -139,6 +141,9 @@ struct GRFFile : ZeroedMemoryAllocator {
std::vector tramtype_list; ///< Roadtype translation table (tram)
RoadType tramtype_map[ROADTYPE_END];
+ std::vector airtype_list; ///< AirType translation table
+ AirType airtype_map[AIRTYPE_END];
+
CanalProperties canal_local_properties[CF_END]; ///< Canal properties as set by this NewGRF
std::unordered_map language_map; ///< Mappings related to the languages.
diff --git a/src/newgrf_airport.cpp b/src/newgrf_airport.cpp
index 0d90c0adad8f9..0d04b40604e6a 100644
--- a/src/newgrf_airport.cpp
+++ b/src/newgrf_airport.cpp
@@ -15,9 +15,17 @@
#include "station_base.h"
#include "newgrf_class_func.h"
#include "town.h"
+#include "air.h"
+#include "table/airport_defaults.h"
#include "safeguards.h"
+uint8_t AirportSpec::GetAirportNoise(AirType airtype) const
+{
+ const AirTypeInfo *ati = GetAirTypeInfo(airtype);
+ return this->num_aprons + this->num_helipads + this->num_heliports + this->num_runways * ati->runway_noise_level + ati->base_noise_level;
+}
+
/**
* Reset airport classes to their default state.
* This includes initialising the defaults classes with an empty
@@ -30,6 +38,7 @@ template <>
AirportClass::Get(AirportClass::Allocate('LARG'))->name = STR_AIRPORT_CLASS_LARGE;
AirportClass::Get(AirportClass::Allocate('HUB_'))->name = STR_AIRPORT_CLASS_HUB;
AirportClass::Get(AirportClass::Allocate('HELI'))->name = STR_AIRPORT_CLASS_HELIPORTS;
+ AirportClass::Get(AirportClass::Allocate('CUST'))->name = STR_AIRPORT_CLASS_CUSTOMIZED;
}
template <>
@@ -78,28 +87,54 @@ AirportSpec AirportSpec::specs[NUM_AIRPORTS]; ///< Airport specifications.
return &AirportSpec::specs[type];
}
-/** Check whether this airport is available to build. */
-bool AirportSpec::IsAvailable() const
+/**
+ * Check whether this airport is available to build.
+ * @param airtype the airtype to check for, or INVALID_AIRTYPE
+ * to check against default airtype for this airport spec
+ * @return whether this airport spec is available.
+ */
+bool AirportSpec::IsAvailable(AirType air_type) const
{
if (!this->enabled) return false;
if (TimerGameCalendar::year < this->min_year) return false;
+
+ if (air_type != INVALID_AIRTYPE) {
+ const AirTypeInfo *ati = GetAirTypeInfo(air_type);
+ assert(ati != nullptr);
+ if (ati->max_num_runways < this->num_runways) return false;
+ if (this->num_runways > 0 && ati->min_runway_length > this->min_runway_length) return false;
+
+ if (!GetAirTypeInfo(air_type)->heliport_availability) {
+ /* Check at least one layout doesn't have any heliport. */
+ bool all_have_heliport = true;
+ for (uint layout_num = 0; all_have_heliport && layout_num < this->layouts.size(); layout_num++) {
+ bool has_heliport = false;
+ uint num_tiles = this->layouts[layout_num].size_x * this->layouts[layout_num].size_y;
+ for (uint tile_num = 0; (tile_num < num_tiles) && !has_heliport; tile_num++) {
+ if (this->layouts[layout_num].tiles[tile_num].type == ATT_APRON_HELIPORT) has_heliport = true;
+ }
+ if (!has_heliport) all_have_heliport = false;
+ }
+ if (all_have_heliport) return false;
+ }
+ }
+
if (_settings_game.station.never_expire_airports) return true;
return TimerGameCalendar::year <= this->max_year;
}
/**
* Check if the airport would be within the map bounds at the given tile.
- * @param table Selected layout table. This affects airport rotation, and therefore dimensions.
+ * @param rotation Selected rotation. This affects airport rotation, and therefore dimensions.
* @param tile Top corner of the airport.
* @return true iff the airport would be within the map bounds at the given tile.
*/
-bool AirportSpec::IsWithinMapBounds(uint8_t table, TileIndex tile) const
+bool AirportSpec::IsWithinMapBounds(uint8_t rotation, TileIndex tile, uint8_t layout) const
{
- if (table >= this->layouts.size()) return false;
+ uint8_t w = this->layouts[layout].size_x;
+ uint8_t h = this->layouts[layout].size_y;
- uint8_t w = this->size_x;
- uint8_t h = this->size_y;
- if (this->layouts[table].rotation == DIR_E || this->layouts[table].rotation == DIR_W) Swap(w, h);
+ if (rotation % 2 != 0) Swap(w, h);
return TileX(tile) + w < Map::SizeX() &&
TileY(tile) + h < Map::SizeY();
@@ -276,6 +311,9 @@ StringID GetAirportTextCallback(const AirportSpec *as, uint8_t layout, uint16_t
AirportResolverObject object(INVALID_TILE, nullptr, as, layout, (CallbackID)callback);
uint16_t cb_res = object.ResolveCallback();
if (cb_res == CALLBACK_FAILED || cb_res == 0x400) return STR_UNDEFINED;
+
+ // Old GRF files that provided airport layouts, provided now unneeded rotated layouts.
+ if (callback == CBID_AIRPORT_LAYOUT_NAME && as->grf_prop.grffile->grf_version <= 8) return STR_UNDEFINED;
if (cb_res > 0x400) {
ErrorUnknownCallbackResult(as->grf_prop.grffile->grfid, callback, cb_res);
return STR_UNDEFINED;
diff --git a/src/newgrf_airport.h b/src/newgrf_airport.h
index 3242e482c5842..b58bc66f67e6c 100644
--- a/src/newgrf_airport.h
+++ b/src/newgrf_airport.h
@@ -18,54 +18,11 @@
#include "newgrf_town.h"
#include "tilearea_type.h"
+#include "table/airporttile_ids.h"
+
/** Copy from station_map.h */
typedef uint8_t StationGfx;
-/** Tile-offset / AirportTileID pair. */
-struct AirportTileTable {
- TileIndexDiffC ti; ///< Tile offset from the top-most airport tile.
- StationGfx gfx; ///< AirportTile to use for this tile.
-};
-
-/** Iterator to iterate over all tiles belonging to an airport spec. */
-class AirportTileTableIterator : public TileIterator {
-private:
- const AirportTileTable *att; ///< The offsets.
- TileIndex base_tile; ///< The tile we base the offsets off.
-
-public:
- /**
- * Construct the iterator.
- * @param att The TileTable we want to iterate over.
- * @param base_tile The basetile for all offsets.
- */
- AirportTileTableIterator(const AirportTileTable *att, TileIndex base_tile) : TileIterator(base_tile + ToTileIndexDiff(att->ti)), att(att), base_tile(base_tile)
- {
- }
-
- inline TileIterator& operator ++() override
- {
- this->att++;
- if (this->att->ti.x == -0x80) {
- this->tile = INVALID_TILE;
- } else {
- this->tile = this->base_tile + ToTileIndexDiff(this->att->ti);
- }
- return *this;
- }
-
- /** Get the StationGfx for the current tile. */
- StationGfx GetStationGfx() const
- {
- return this->att->gfx;
- }
-
- std::unique_ptr Clone() const override
- {
- return std::make_unique(*this);
- }
-};
-
/** List of default airport classes. */
enum AirportClassID {
APC_BEGIN = 0, ///< Lowest valid airport class id
@@ -73,6 +30,7 @@ enum AirportClassID {
APC_LARGE, ///< id for large airports class
APC_HUB, ///< id for hub airports class
APC_HELIPORT, ///< id for heliports
+ APC_CUSTOM, ///< customized airport class
APC_MAX = 16, ///< maximum number of airport classes
};
@@ -87,44 +45,117 @@ enum TTDPAirportType {
ATP_TTDP_OILRIG, ///< Same as AT_OILRIG
};
-/** A list of all hangar tiles in an airport */
-struct HangarTileTable {
- TileIndexDiffC ti; ///< Tile offset from the top-most airport tile.
- Direction dir; ///< Direction of the exit.
- uint8_t hangar_num; ///< The hangar to which this tile belongs.
+struct AirportTileTable {
+ AirportTileType type; // Use this tile will have (apron, tracks,...).
+ ApronType apron_type{APRON_INVALID}; // Subtype of apron.
+ DiagDirection dir{INVALID_DIAGDIR}; // Direction of runway or exit direction of hangars.
+ TrackBits trackbits{TRACK_BIT_NONE}; // Tracks for this tile.
+ Direction runway_directions{INVALID_DIR}; // Directions of the runways present on this tile.
+ // Maps a direction into the diagonal directions of the runways.
+ AirportTiles at_gfx{ATTG_DEFAULT_GFX}; // Sprite for this tile as provided by an airtype.
+ AirportTiles gfx[DIAGDIR_END] { // Sprites for this tile.
+ INVALID_AIRPORTTILE,
+ INVALID_AIRPORTTILE,
+ INVALID_AIRPORTTILE,
+ INVALID_AIRPORTTILE
+ };
+
+ void SetGfx(AirportTiles gfx) {
+ this->gfx[DIAGDIR_BEGIN] = gfx;
+ }
+
+ /* Description for simple track tiles. */
+ AirportTileTable(AirportTileType att, TrackBits trackbits, AirportTiles gfx = INVALID_AIRPORTTILE) :
+ type(att),
+ trackbits(trackbits)
+ {
+ assert(att == ATT_SIMPLE_TRACK);
+ SetGfx(gfx);
+ }
+
+ /* Description for aprons, helipads and heliports. */
+ AirportTileTable(AirportTileType att, TrackBits trackbits, ApronType type,
+ AirportTiles gfx = INVALID_AIRPORTTILE) :
+ type(att),
+ apron_type(type),
+ trackbits(trackbits)
+ {
+ assert(att >= ATT_APRON_NORMAL && att <= ATT_APRON_BUILTIN_HELIPORT);
+ SetGfx(gfx);
+ }
+
+ /* Description for hangars and runway end and start. */
+ AirportTileTable(AirportTileType att, TrackBits trackbits, DiagDirection dir, AirportTiles gfx = INVALID_AIRPORTTILE) :
+ type(att),
+ dir(dir),
+ trackbits(trackbits)
+ {
+ assert(att == ATT_HANGAR_STANDARD ||
+ att == ATT_HANGAR_EXTENDED ||
+ att == ATT_RUNWAY_END ||
+ att == ATT_RUNWAY_START_ALLOW_LANDING ||
+ att == ATT_RUNWAY_START_NO_LANDING);
+ SetGfx(gfx);
+ }
+
+ /* Description for middle parts of runways. */
+ AirportTileTable(AirportTileType att, TrackBits trackbits, Direction runway_directions, AirportTiles gfx = INVALID_AIRPORTTILE) :
+ type(att),
+ trackbits(trackbits),
+ runway_directions(runway_directions)
+ {
+ assert(att == ATT_RUNWAY_MIDDLE);
+ assert(IsValidDirection(runway_directions));
+ SetGfx(gfx);
+ }
+
+ /* Description for infrastructure. */
+ AirportTileTable(AirportTileType att, AirportTiles at_gfx, DiagDirection rotation = DIAGDIR_NE,
+ AirportTiles gfx = INVALID_AIRPORTTILE) :
+ type(att),
+ dir(rotation),
+ at_gfx(at_gfx)
+ {
+ assert(att == ATT_INFRASTRUCTURE_WITH_CATCH || att == ATT_INFRASTRUCTURE_NO_CATCH);
+ SetGfx(gfx);
+ }
+
+ /* Description for a non-airport tile, for non-rectangular airports. */
+ AirportTileTable() : type(ATT_INVALID) {}
};
struct AirportTileLayout {
std::vector tiles; ///< List of all tiles in this layout.
- Direction rotation; ///< The rotation of this layout.
+ uint8_t size_x; ///< size of airport in x direction
+ uint8_t size_y; ///< size of airport in y direction
};
/**
* Defines the data structure for an airport.
*/
struct AirportSpec : NewGRFSpecBase {
- const struct AirportFTAClass *fsm; ///< the finite statemachine for the default airports
- std::vector layouts; ///< List of layouts composing the airport.
- std::span depots; ///< Position of the depots on the airports.
- uint8_t size_x; ///< size of airport in x direction
- uint8_t size_y; ///< size of airport in y direction
- uint8_t noise_level; ///< noise that this airport generates
- uint8_t catchment; ///< catchment area of this airport
+ std::vector layouts;///< list of the different layouts
+ AirType airtype; ///< the airtype for this set of layouts
+ uint8_t num_runways; ///< number of runways
+ uint8_t num_aprons; ///< number of aprons
+ uint8_t num_helipads; ///< number of helipads
+ uint8_t num_heliports; ///< number of heliports
+ uint8_t min_runway_length; ///< length of the shortest runway
TimerGameCalendar::Year min_year; ///< first year the airport is available
TimerGameCalendar::Year max_year; ///< last year the airport is available
StringID name; ///< name of this airport
TTDPAirportType ttd_airport_type; ///< ttdpatch airport type (Small/Large/Helipad/Oilrig)
SpriteID preview_sprite; ///< preview sprite for this airport
- uint16_t maintenance_cost; ///< maintenance cost multiplier
- /* Newgrf data */
bool enabled; ///< Entity still available (by default true). Newgrf can disable it, though.
+ bool has_hangar;
+ bool has_heliport;
struct GRFFileProps grf_prop; ///< Properties related to the grf file.
static const AirportSpec *Get(uint8_t type);
static AirportSpec *GetWithoutOverride(uint8_t type);
- bool IsAvailable() const;
- bool IsWithinMapBounds(uint8_t table, TileIndex index) const;
+ bool IsAvailable(AirType air_type = INVALID_AIRTYPE) const;
+ bool IsWithinMapBounds(uint8_t table, TileIndex index, uint8_t layout) const;
static void ResetAirports();
@@ -135,7 +166,10 @@ struct AirportSpec : NewGRFSpecBase {
return static_cast(std::distance(std::cbegin(specs), this));
}
- static const AirportSpec dummy; ///< The dummy airport.
+ uint8_t GetAirportNoise(AirType airtype) const;
+
+ static const AirportSpec custom; ///< The customized airports specs.
+ static const AirportSpec dummy; ///< The dummy airport.
private:
static AirportSpec specs[NUM_AIRPORTS]; ///< Specs of the airports.
diff --git a/src/newgrf_airporttiles.cpp b/src/newgrf_airporttiles.cpp
index 8cd612318212e..1b218c1a40753 100644
--- a/src/newgrf_airporttiles.cpp
+++ b/src/newgrf_airporttiles.cpp
@@ -16,6 +16,7 @@
#include "water.h"
#include "landscape.h"
#include "company_base.h"
+#include "air_map.h"
#include "town.h"
#include "table/strings.h"
#include "table/airporttiles.h"
@@ -26,6 +27,7 @@
extern uint32_t GetRelativePosition(TileIndex tile, TileIndex ind_tile);
AirportTileSpec AirportTileSpec::tiles[NUM_AIRPORTTILES];
+AirportTileSpec AirportTileSpec::airtype_tiles[NUM_AIRTYPE_INFRATILES];
AirportTileOverrideManager _airporttile_mngr(NEW_AIRPORTTILE_OFFSET, NUM_AIRPORTTILES, INVALID_AIRPORTTILE);
@@ -34,7 +36,7 @@ AirportTileOverrideManager _airporttile_mngr(NEW_AIRPORTTILE_OFFSET, NUM_AIRPORT
* @param gfx index of airport tile
* @return A pointer to the corresponding AirportTileSpec
*/
-/* static */ const AirportTileSpec *AirportTileSpec::Get(StationGfx gfx)
+/* static */ const AirportTileSpec *AirportTileSpec::GetAirportTileSpec(StationGfx gfx)
{
/* should be assert(gfx < lengthof(tiles)), but that gives compiler warnings
* since it's always true if the following holds: */
@@ -42,6 +44,17 @@ AirportTileOverrideManager _airporttile_mngr(NEW_AIRPORTTILE_OFFSET, NUM_AIRPORT
return &AirportTileSpec::tiles[gfx];
}
+/**
+ * Retrieve airtype tile spec for the given airport tile
+ * @param gfx index of airport tile
+ * @return A pointer to the corresponding AirportTileSpec
+ */
+/* static */ const AirportTileSpec *AirportTileSpec::GetAirtypeTileSpec(StationGfx gfx)
+{
+ assert(gfx < ATTG_END);
+ return &AirportTileSpec::airtype_tiles[gfx];
+}
+
/**
* Retrieve airport tile spec for the given airport tile.
* @param tile The airport tile.
@@ -49,7 +62,11 @@ AirportTileOverrideManager _airporttile_mngr(NEW_AIRPORTTILE_OFFSET, NUM_AIRPORT
*/
/* static */ const AirportTileSpec *AirportTileSpec::GetByTile(TileIndex tile)
{
- return AirportTileSpec::Get(GetAirportGfx(tile));
+ assert(IsTileType(tile, MP_STATION) && IsAirport(tile));
+
+ if (HasAirtypeGfx(tile)) return AirportTileSpec::GetAirtypeTileSpec(GetAirportGfx(tile));
+
+ return AirportTileSpec::GetAirportTileSpec(GetAirportGfx(tile));
}
/**
@@ -60,6 +77,9 @@ void AirportTileSpec::ResetAirportTiles()
auto insert = std::copy(std::begin(_origin_airporttile_specs), std::end(_origin_airporttile_specs), std::begin(AirportTileSpec::tiles));
std::fill(insert, std::end(AirportTileSpec::tiles), AirportTileSpec{});
+ auto insert_airtype = std::copy(std::begin(_origin_airtype_specs), std::end(_origin_airtype_specs), std::begin(AirportTileSpec::airtype_tiles));
+ std::fill(insert_airtype, std::end(AirportTileSpec::airtype_tiles), AirportTileSpec{});
+
/* Reset any overrides that have been set. */
_airporttile_mngr.ResetOverride();
}
@@ -95,8 +115,8 @@ void AirportTileOverrideManager::SetEntitySpec(const AirportTileSpec *airpts)
*/
StationGfx GetTranslatedAirportTileID(StationGfx gfx)
{
- const AirportTileSpec *it = AirportTileSpec::Get(gfx);
- return it->grf_prop.override == INVALID_AIRPORTTILE ? gfx : it->grf_prop.override;
+ const AirportTileSpec *it = AirportTileSpec::GetAirportTileSpec(gfx);
+ return it->grf_prop.override == (StationGfx)INVALID_AIRPORTTILE ? gfx : it->grf_prop.override;
}
/**
@@ -131,7 +151,7 @@ static uint32_t GetAirportTileIDAtOffset(TileIndex tile, const Station *st, uint
}
StationGfx gfx = GetAirportGfx(tile);
- const AirportTileSpec *ats = AirportTileSpec::Get(gfx);
+ const AirportTileSpec *ats = AirportTileSpec::GetAirportTileSpec(gfx);
if (gfx < NEW_AIRPORTTILE_OFFSET) { // Does it belongs to an old type?
/* It is an old tile. We have to see if it's been overridden */
@@ -139,7 +159,7 @@ static uint32_t GetAirportTileIDAtOffset(TileIndex tile, const Station *st, uint
return 0xFF << 8 | gfx; // no. Tag FF + the gfx id of that tile
}
/* Overridden */
- const AirportTileSpec *tile_ovr = AirportTileSpec::Get(ats->grf_prop.override);
+ const AirportTileSpec *tile_ovr = AirportTileSpec::GetAirportTileSpec(ats->grf_prop.override);
if (tile_ovr->grf_prop.grffile->grfid == cur_grfid) {
return tile_ovr->grf_prop.local_id; // same grf file
diff --git a/src/newgrf_airporttiles.h b/src/newgrf_airporttiles.h
index 34b36691286bf..2eeb97dcd18fc 100644
--- a/src/newgrf_airporttiles.h
+++ b/src/newgrf_airporttiles.h
@@ -73,13 +73,15 @@ struct AirportTileSpec {
bool enabled; ///< entity still available (by default true). newgrf can disable it, though
GRFFileProps grf_prop; ///< properties related the the grf file
- static const AirportTileSpec *Get(StationGfx gfx);
+ static const AirportTileSpec *GetAirportTileSpec(StationGfx gfx);
+ static const AirportTileSpec *GetAirtypeTileSpec(StationGfx gfx);
static const AirportTileSpec *GetByTile(TileIndex tile);
static void ResetAirportTiles();
private:
static AirportTileSpec tiles[NUM_AIRPORTTILES];
+ static AirportTileSpec airtype_tiles[NUM_AIRTYPE_INFRATILES];
friend void AirportTileOverrideManager::SetEntitySpec(const AirportTileSpec *airpts);
};
diff --git a/src/newgrf_airtype.cpp b/src/newgrf_airtype.cpp
new file mode 100644
index 0000000000000..87f049399cb6a
--- /dev/null
+++ b/src/newgrf_airtype.cpp
@@ -0,0 +1,148 @@
+/*
+ * 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 newgrf_airtype.cpp NewGRF handling of air types. */
+
+#include "stdafx.h"
+#include "core/container_func.hpp"
+#include "debug.h"
+#include "newgrf_airtype.h"
+#include "station_base.h"
+#include "depot_base.h"
+#include "town.h"
+
+#include "safeguards.h"
+
+/* virtual */ uint32_t AirTypeScopeResolver::GetRandomBits() const
+{
+ uint tmp = CountBits(this->tile.base() + (TileX(this->tile) + TileY(this->tile)) * TILE_SIZE);
+ return GB(tmp, 0, 2);
+}
+
+/* virtual */ uint32_t AirTypeScopeResolver::GetVariable(uint8_t variable, [[maybe_unused]] uint32_t parameter, bool &available) const
+{
+ if (this->tile == INVALID_TILE) {
+ switch (variable) {
+ case 0x40: return 0;
+ case 0x41: return 0;
+ case 0x42: return 0;
+ case 0x43: return TimerGameCalendar::date.base();
+ case 0x44: return HZB_TOWN_EDGE;
+ }
+ }
+
+ switch (variable) {
+ case 0x40: return GetTerrainType(this->tile, this->context);
+ case 0x41: return 0;
+ case 0x42: return 0;
+ case 0x43:
+ if (IsHangarTile(this->tile)) return Depot::GetByTile(this->tile)->build_date.base();
+ return TimerGameCalendar::date.base();
+ case 0x44: {
+ const Town *t = nullptr;
+ t = Station::GetByTile(this->tile)->town;
+ return t != nullptr ? GetTownRadiusGroup(t, this->tile) : HZB_TOWN_EDGE;
+ }
+ }
+
+ Debug(grf, 1, "Unhandled air type tile variable 0x{:X}", variable);
+
+ available = false;
+ return UINT_MAX;
+}
+
+GrfSpecFeature AirTypeResolverObject::GetFeature() const
+{
+ return GSF_AIRTYPES;
+}
+
+uint32_t AirTypeResolverObject::GetDebugID() const
+{
+ return this->airtype_scope.ati->label;
+}
+
+/**
+ * Resolver object for air types.
+ * @param ati AirType. nullptr in NewGRF Inspect window.
+ * @param tile %Tile containing the track. For track on a bridge this is the southern bridgehead.
+ * @param context Are we resolving sprites for the upper halftile, or on a bridge?
+ * @param rtsg Airpart of interest
+ * @param param1 Extra parameter (first parameter of the callback, except airtypes do not have callbacks).
+ * @param param2 Extra parameter (second parameter of the callback, except airtypes do not have callbacks).
+ */
+AirTypeResolverObject::AirTypeResolverObject(const AirTypeInfo *ati, TileIndex tile, TileContext context, AirTypeSpriteGroup rtsg, uint32_t param1, uint32_t param2)
+ : ResolverObject(ati != nullptr ? ati->grffile[rtsg] : nullptr, CBID_NO_CALLBACK, param1, param2), airtype_scope(*this, ati, tile, context)
+{
+ this->root_spritegroup = ati != nullptr ? ati->group[rtsg] : nullptr;
+}
+
+/**
+ * Get the sprite to draw for the given tile.
+ * @param ati The air type data (spec).
+ * @param tile The tile to get the sprite for.
+ * @param rtsg The type of sprite to draw.
+ * @param content Where are we drawing the tile?
+ * @param [out] num_results If not nullptr, return the number of sprites in the spriteset.
+ * @return The sprite to draw.
+ */
+SpriteID GetCustomAirSprite(const AirTypeInfo *ati, TileIndex tile, AirTypeSpriteGroup atsg, TileContext context, uint *num_results)
+{
+ assert(atsg < ATSG_END);
+
+ if (ati->group[atsg] == nullptr) return 0;
+
+ AirTypeResolverObject object(ati, tile, context, atsg);
+ const SpriteGroup *group = object.Resolve();
+ if (group == nullptr || group->GetNumResults() == 0) return 0;
+
+ if (num_results != nullptr) *num_results = group->GetNumResults();
+
+ return group->GetResult();
+}
+
+/**
+ * Translate an index to the GRF-local airtype-translation table into an AirType.
+ * @param airtype Index into GRF-local translation table.
+ * @param grffile Originating GRF file.
+ * @return AirType or INVALID_AIRTYPE if the airtype is unknown.
+ */
+AirType GetAirTypeTranslation(uint8_t airtype, const GRFFile *grffile)
+{
+ if (grffile == nullptr || grffile->airtype_list.size() == 0) {
+ /* No airtype table present. Return airtype as-is (if valid), so it works for original airtypes. */
+ if (airtype >= AIRTYPE_END || GetAirTypeInfo(static_cast(airtype))->label == 0) return INVALID_AIRTYPE;
+
+ return static_cast(airtype);
+ } else {
+ /* AirType table present, but invalid index, return invalid type. */
+ if (airtype >= grffile->airtype_list.size()) return INVALID_AIRTYPE;
+
+ /* Look up airtype including alternate labels. */
+ return GetAirTypeByLabel(grffile->airtype_list[airtype]);
+ }
+}
+
+/**
+ * Perform a reverse airtype lookup to get the GRF internal ID.
+ * @param airtype The global (OpenTTD) airtype.
+ * @param grffile The GRF to do the lookup for.
+ * @return the GRF internal ID.
+ */
+uint8_t GetReverseAirTypeTranslation(AirType airtype, const GRFFile *grffile)
+{
+ /* No air type table present, return air type as-is */
+ if (grffile == nullptr || grffile->airtype_list.size() == 0) return airtype;
+
+ /* Look for a matching air type label in the table */
+ AirTypeLabel label = GetAirTypeInfo(airtype)->label;
+
+ int idx = find_index(grffile->airtype_list, label);
+ if (idx >= 0) return idx;
+
+ /* If not found, return invalid. */
+ return 0xFF;
+}
diff --git a/src/newgrf_airtype.h b/src/newgrf_airtype.h
new file mode 100644
index 0000000000000..d5f54c9b5514d
--- /dev/null
+++ b/src/newgrf_airtype.h
@@ -0,0 +1,61 @@
+/*
+ * 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 newgrf_airtype.h NewGRF handling of air types. */
+
+#ifndef NEWGRF_AIRTYPE_H
+#define NEWGRF_AIRTYPE_H
+
+#include "air.h"
+#include "newgrf_commons.h"
+#include "newgrf_spritegroup.h"
+
+/** Resolver for the railtype scope. */
+struct AirTypeScopeResolver : public ScopeResolver {
+ TileIndex tile; ///< Tracktile. For track on a bridge this is the southern bridgehead.
+ TileContext context; ///< Are we resolving sprites for the upper halftile, or on a bridge?
+ const AirTypeInfo *ati; ///< The corresponding airtype info.
+
+ /**
+ * Constructor of the airtype scope resolvers.
+ * @param ro Surrounding resolver.
+ * @param ati AirTypeInfo
+ * @param tile %Tile containing the track. For track on a bridge this is the southern bridgehead.
+ * @param context Are we resolving sprites for the upper halftile, or on a bridge?
+ */
+ AirTypeScopeResolver(ResolverObject &ro, const AirTypeInfo *ati, TileIndex tile, TileContext context) :
+ ScopeResolver(ro), tile(tile), context(context), ati(ati)
+ {
+ }
+
+ uint32_t GetRandomBits() const override;
+ uint32_t GetVariable(uint8_t variable, uint32_t parameter, bool &available) const override;
+};
+
+/** Resolver object for air types. */
+struct AirTypeResolverObject : public ResolverObject {
+ AirTypeScopeResolver airtype_scope; ///< Resolver for the airtype scope.
+
+ AirTypeResolverObject(const AirTypeInfo *ati, TileIndex tile, TileContext context, AirTypeSpriteGroup rtsg, uint32_t param1 = 0, uint32_t param2 = 0);
+
+ ScopeResolver *GetScope(VarSpriteGroupScope scope = VSG_SCOPE_SELF, uint8_t relative = 0) override
+ {
+ if (scope == VSG_SCOPE_SELF) return &this->airtype_scope;
+
+ return ResolverObject::GetScope(scope, relative);
+ }
+
+ GrfSpecFeature GetFeature() const override;
+ uint32_t GetDebugID() const override;
+};
+
+SpriteID GetCustomAirSprite(const AirTypeInfo *ati, TileIndex tile, AirTypeSpriteGroup atsg, TileContext context = TCX_NORMAL, uint *num_results = nullptr);
+
+AirType GetAirTypeTranslation(uint8_t airtype, const GRFFile *grffile);
+uint8_t GetReverseAirTypeTranslation(AirType railtype, const GRFFile *grffile);
+
+#endif /* NEWGRF_AIRTYPE_H */
diff --git a/src/newgrf_debug_gui.cpp b/src/newgrf_debug_gui.cpp
index 1b6c36752020d..1149f42891951 100644
--- a/src/newgrf_debug_gui.cpp
+++ b/src/newgrf_debug_gui.cpp
@@ -773,7 +773,7 @@ GrfSpecFeature GetGrfSpecFeature(TileIndex tile)
case MP_STATION:
switch (GetStationType(tile)) {
case STATION_RAIL: return GSF_STATIONS;
- case STATION_AIRPORT: return GSF_AIRPORTTILES;
+ case STATION_AIRPORT: return HasAirtypeGfx(tile) ? GSF_AIRTYPES : GSF_AIRPORTTILES;
case STATION_BUS: return GSF_ROADSTOPS;
case STATION_TRUCK: return GSF_ROADSTOPS;
default: return GSF_INVALID;
diff --git a/src/newgrf_engine.cpp b/src/newgrf_engine.cpp
index b5659581bdfd9..1a19cfa87fd29 100644
--- a/src/newgrf_engine.cpp
+++ b/src/newgrf_engine.cpp
@@ -21,9 +21,11 @@
#include "aircraft.h"
#include "station_base.h"
#include "company_base.h"
+#include "newgrf_airtype.h"
#include "newgrf_railtype.h"
#include "newgrf_roadtype.h"
#include "ship.h"
+#include "air_map.h"
#include "safeguards.h"
@@ -94,35 +96,35 @@ static int MapOldSubType(const Vehicle *v)
/* TTDP style aircraft movement states for GRF Action 2 Var 0xE2 */
enum TTDPAircraftMovementStates {
- AMS_TTDP_HANGAR,
- AMS_TTDP_TO_HANGAR,
- AMS_TTDP_TO_PAD1,
- AMS_TTDP_TO_PAD2,
- AMS_TTDP_TO_PAD3,
- AMS_TTDP_TO_ENTRY_2_AND_3,
- AMS_TTDP_TO_ENTRY_2_AND_3_AND_H,
- AMS_TTDP_TO_JUNCTION,
- AMS_TTDP_LEAVE_RUNWAY,
- AMS_TTDP_TO_INWAY,
- AMS_TTDP_TO_RUNWAY,
- AMS_TTDP_TO_OUTWAY,
- AMS_TTDP_WAITING,
- AMS_TTDP_TAKEOFF,
- AMS_TTDP_TO_TAKEOFF,
- AMS_TTDP_CLIMBING,
- AMS_TTDP_FLIGHT_APPROACH,
- AMS_TTDP_UNUSED_0x11,
- AMS_TTDP_FLIGHT_TO_TOWER,
- AMS_TTDP_UNUSED_0x13,
- AMS_TTDP_FLIGHT_FINAL,
- AMS_TTDP_FLIGHT_DESCENT,
- AMS_TTDP_BRAKING,
- AMS_TTDP_HELI_TAKEOFF_AIRPORT,
- AMS_TTDP_HELI_TO_TAKEOFF_AIRPORT,
- AMS_TTDP_HELI_LAND_AIRPORT,
- AMS_TTDP_HELI_TAKEOFF_HELIPORT,
- AMS_TTDP_HELI_TO_TAKEOFF_HELIPORT,
- AMS_TTDP_HELI_LAND_HELIPORT,
+ AMS_TTDP_HANGAR, // used (in hangar)
+ AMS_TTDP_TO_HANGAR, // used (moving towards hangar)
+ AMS_TTDP_TO_PAD1, //
+ AMS_TTDP_TO_PAD2, //
+ AMS_TTDP_TO_PAD3, //
+ AMS_TTDP_TO_ENTRY_2_AND_3, //
+ AMS_TTDP_TO_ENTRY_2_AND_3_AND_H, //
+ AMS_TTDP_TO_JUNCTION, // used (moving towards terminal or helipad)
+ AMS_TTDP_LEAVE_RUNWAY, //
+ AMS_TTDP_TO_INWAY, //
+ AMS_TTDP_TO_RUNWAY, // used (moving towards a runway)
+ AMS_TTDP_TO_OUTWAY, // used (moving towards the end of the runway; middle section: aircraft has already started accelerating)
+ AMS_TTDP_WAITING, //
+ AMS_TTDP_TAKEOFF, // used (moving towards the end of the runway; start section: aircraft is stopped and starts to accelerate)
+ AMS_TTDP_TO_TAKEOFF, //
+ AMS_TTDP_CLIMBING, //
+ AMS_TTDP_FLIGHT_APPROACH, // used (aircraft flying or on hold)
+ AMS_TTDP_UNUSED_0x11, //
+ AMS_TTDP_FLIGHT_TO_TOWER, // used (helicopter flying, on hold or approaching destination)
+ AMS_TTDP_UNUSED_0x13, //
+ AMS_TTDP_FLIGHT_FINAL, //
+ AMS_TTDP_FLIGHT_DESCENT, // used (aircraft approaching landing)
+ AMS_TTDP_BRAKING, // used (aircraft finished descending and touches the runway)
+ AMS_TTDP_HELI_TAKEOFF_AIRPORT, // used
+ AMS_TTDP_HELI_TO_TAKEOFF_AIRPORT, //
+ AMS_TTDP_HELI_LAND_AIRPORT, // used
+ AMS_TTDP_HELI_TAKEOFF_HELIPORT, // used
+ AMS_TTDP_HELI_TO_TAKEOFF_HELIPORT, //
+ AMS_TTDP_HELI_LAND_HELIPORT, // used
};
@@ -135,96 +137,70 @@ static uint8_t MapAircraftMovementState(const Aircraft *v)
const Station *st = GetTargetAirportIfValid(v);
if (st == nullptr) return AMS_TTDP_FLIGHT_TO_TOWER;
- const AirportFTAClass *afc = st->airport.GetFTA();
- uint16_t amdflag = afc->MovingData(v->pos)->flag;
-
switch (v->state) {
- case HANGAR:
- /* The international airport is a special case as helicopters can land in
- * front of the hangar. Helicopters also change their air.state to
- * AMED_HELI_LOWER some time before actually descending. */
+ case AS_HANGAR:
+ return AMS_TTDP_HANGAR;
- /* This condition only occurs for helicopters, during descent,
- * to a landing by the hangar of an international airport. */
- if (amdflag & AMED_HELI_LOWER) return AMS_TTDP_HELI_LAND_AIRPORT;
+ case AS_APRON:
+ case AS_HELIPAD:
+ case AS_HELIPORT:
+ case AS_BUILTIN_HELIPORT:
+ return AMS_TTDP_TO_JUNCTION;
- /* This condition only occurs for helicopters, before starting descent,
- * to a landing by the hangar of an international airport. */
- if (amdflag & AMED_SLOWTURN) return AMS_TTDP_FLIGHT_TO_TOWER;
+ case AS_RUNNING:
+ switch (v->Next()->state) {
+ case AS_HANGAR:
+ return AMS_TTDP_TO_HANGAR;
+ case AS_START_TAKEOFF:
+ return AMS_TTDP_TO_RUNWAY;
+ default:
+ break;
+ }
+ return AMS_TTDP_TO_JUNCTION;
- /* The final two conditions apply to helicopters or aircraft.
- * Has reached hangar? */
- if (amdflag & AMED_EXACTPOS) return AMS_TTDP_HANGAR;
+ case AS_START_TAKEOFF:
+ if (!v->IsHelicopter()) return AMS_TTDP_TO_OUTWAY;
- /* Still moving towards hangar. */
- return AMS_TTDP_TO_HANGAR;
+ assert(IsAirportTile(v->tile) && IsApron(v->tile));
+ return IsHeliport(v->tile) ? AMS_TTDP_HELI_TAKEOFF_HELIPORT : AMS_TTDP_HELI_TAKEOFF_AIRPORT;
- case TERM1:
- if (amdflag & AMED_EXACTPOS) return AMS_TTDP_TO_PAD1;
- return AMS_TTDP_TO_JUNCTION;
+ case AS_LANDED:
+ if (!v->IsHelicopter()) return AMS_TTDP_BRAKING;
- case TERM2:
- if (amdflag & AMED_EXACTPOS) return AMS_TTDP_TO_PAD2;
- return AMS_TTDP_TO_ENTRY_2_AND_3_AND_H;
-
- case TERM3:
- case TERM4:
- case TERM5:
- case TERM6:
- case TERM7:
- case TERM8:
- /* TTDPatch only has 3 terminals, so treat these states the same */
- if (amdflag & AMED_EXACTPOS) return AMS_TTDP_TO_PAD3;
- return AMS_TTDP_TO_ENTRY_2_AND_3_AND_H;
-
- case HELIPAD1:
- case HELIPAD2:
- case HELIPAD3:
- /* Will only occur for helicopters.*/
- if (amdflag & AMED_HELI_LOWER) return AMS_TTDP_HELI_LAND_AIRPORT; // Descending.
- if (amdflag & AMED_SLOWTURN) return AMS_TTDP_FLIGHT_TO_TOWER; // Still hasn't started descent.
- return AMS_TTDP_TO_JUNCTION; // On the ground.
-
- case TAKEOFF: // Moving to takeoff position.
- return AMS_TTDP_TO_OUTWAY;
-
- case STARTTAKEOFF: // Accelerating down runway.
- return AMS_TTDP_TAKEOFF;
+ assert(IsAirportTile(v->tile) && IsApron(v->tile));
+ return IsHeliport(v->tile) ? AMS_TTDP_HELI_LAND_HELIPORT : AMS_TTDP_HELI_LAND_AIRPORT;
- case ENDTAKEOFF: // Ascent
+ case AS_FLYING_TAKEOFF:
+ case AS_FLYING_LEAVING_AIRPORT:
return AMS_TTDP_CLIMBING;
- case HELITAKEOFF: // Helicopter is moving to take off position.
- if (afc->delta_z == 0) {
- return amdflag & AMED_HELI_RAISE ?
- AMS_TTDP_HELI_TAKEOFF_AIRPORT : AMS_TTDP_TO_JUNCTION;
- } else {
- return AMS_TTDP_HELI_TAKEOFF_HELIPORT;
- }
+ case AS_TAKEOFF_BEFORE_FLYING:
+ return AMS_TTDP_TAKEOFF;
- case FLYING:
- return amdflag & AMED_HOLD ? AMS_TTDP_FLIGHT_APPROACH : AMS_TTDP_FLIGHT_TO_TOWER;
+ case AS_FLYING_LANDING:
+ return AMS_TTDP_BRAKING;
- case LANDING: // Descent
- return AMS_TTDP_FLIGHT_DESCENT;
+ case AS_FLYING_HELICOPTER_TAKEOFF:
+ case AS_FLYING_HELICOPTER_LANDING:
+ return IsHelipadTile(v->tile) ? AMS_TTDP_HELI_LAND_AIRPORT : AMS_TTDP_HELI_LAND_HELIPORT;
- case ENDLANDING: // On the runway braking
- if (amdflag & AMED_BRAKE) return AMS_TTDP_BRAKING;
- /* Landed - moving off runway */
- return AMS_TTDP_TO_INWAY;
+ case AS_DESCENDING:
+ return v->IsHelicopter() ? AMS_TTDP_FLIGHT_TO_TOWER : AMS_TTDP_FLIGHT_DESCENT;
- case HELILANDING:
- case HELIENDLANDING: // Helicoptor is descending.
- if (amdflag & AMED_HELI_LOWER) {
- return afc->delta_z == 0 ?
- AMS_TTDP_HELI_LAND_AIRPORT : AMS_TTDP_HELI_LAND_HELIPORT;
- } else {
- return AMS_TTDP_FLIGHT_TO_TOWER;
- }
+ case AS_ON_HOLD_APPROACHING:
+ case AS_FLYING:
+ case AS_FLYING_NO_DEST:
+ case AS_ON_HOLD_WAITING:
+ return v->IsHelicopter() ? AMS_TTDP_FLIGHT_TO_TOWER : AMS_TTDP_FLIGHT_APPROACH;
+
+ case AS_IDLE:
+ break;
default:
- return AMS_TTDP_HANGAR;
+ NOT_REACHED();
}
+
+ return AMS_TTDP_HANGAR;
}
@@ -260,40 +236,31 @@ enum TTDPAircraftMovementActions {
static uint8_t MapAircraftMovementAction(const Aircraft *v)
{
switch (v->state) {
- case HANGAR:
+ case AS_HANGAR:
return (v->cur_speed > 0) ? AMA_TTDP_LANDING_TO_HANGAR : AMA_TTDP_IN_HANGAR;
- case TERM1:
- case HELIPAD1:
+ case AS_APRON:
+ case AS_HELIPAD:
return (v->current_order.IsType(OT_LOADING)) ? AMA_TTDP_ON_PAD1 : AMA_TTDP_LANDING_TO_PAD1;
- case TERM2:
- case HELIPAD2:
- return (v->current_order.IsType(OT_LOADING)) ? AMA_TTDP_ON_PAD2 : AMA_TTDP_LANDING_TO_PAD2;
-
- case TERM3:
- case TERM4:
- case TERM5:
- case TERM6:
- case TERM7:
- case TERM8:
- case HELIPAD3:
- return (v->current_order.IsType(OT_LOADING)) ? AMA_TTDP_ON_PAD3 : AMA_TTDP_LANDING_TO_PAD3;
-
- case TAKEOFF: // Moving to takeoff position
- case STARTTAKEOFF: // Accelerating down runway
- case ENDTAKEOFF: // Ascent
- case HELITAKEOFF:
+ case AS_START_TAKEOFF: // Moving to takeoff position
+ case AS_TAKEOFF_BEFORE_FLYING:
+ case AS_FLYING_TAKEOFF: // Accelerating down runway
+ case AS_FLYING_LEAVING_AIRPORT: // Ascent
+ case AS_FLYING_HELICOPTER_TAKEOFF:
/* @todo Need to find which terminal (or hangar) we've come from. How? */
return AMA_TTDP_PAD1_TO_TAKEOFF;
- case FLYING:
+ case AS_ON_HOLD_WAITING:
+ case AS_FLYING:
+ case AS_FLYING_NO_DEST:
return AMA_TTDP_IN_FLIGHT;
- case LANDING: // Descent
- case ENDLANDING: // On the runway braking
- case HELILANDING:
- case HELIENDLANDING:
+ case AS_ON_HOLD_APPROACHING:
+ case AS_DESCENDING:
+ case AS_FLYING_LANDING: // On the runway braking
+ case AS_LANDED:
+ case AS_FLYING_HELICOPTER_LANDING:
/* @todo Need to check terminal we're landing to. Is it known yet? */
return (v->current_order.IsType(OT_GOTO_DEPOT)) ?
AMA_TTDP_LANDING_TO_HANGAR : AMA_TTDP_LANDING_TO_PAD1;
@@ -689,6 +656,13 @@ static uint32_t VehicleGetVariable(Vehicle *v, const VehicleScopeResolver *objec
return (HasPowerOnRoad(param_type, tile_type) ? 0x06 : 0x00) |
0x01;
}
+ case VEH_AIRCRAFT: {
+ AirType param_type = GetAirTypeTranslation(parameter, object->ro.grffile);
+ if (param_type == INVALID_AIRTYPE) return 0x00;
+ AirType tile_type = GetAirType(v->tile);
+ if (tile_type == param_type) return 0x0F;
+ return 0x01;
+ }
default: return 0x00;
}
diff --git a/src/newgrf_station.cpp b/src/newgrf_station.cpp
index af67fbf648bca..86462ff4b9da0 100644
--- a/src/newgrf_station.cpp
+++ b/src/newgrf_station.cpp
@@ -24,6 +24,7 @@
#include "newgrf_animation_base.h"
#include "newgrf_class_func.h"
#include "timer/timer_game_calendar.h"
+#include "platform_func.h"
#include "safeguards.h"
diff --git a/src/openttd.cpp b/src/openttd.cpp
index 25082be2c5b29..b5e2e72ea0209 100644
--- a/src/openttd.cpp
+++ b/src/openttd.cpp
@@ -28,6 +28,7 @@
#include "news_func.h"
#include "fios.h"
#include "aircraft.h"
+#include "airport_gui.h"
#include "roadveh.h"
#include "train.h"
#include "ship.h"
@@ -891,6 +892,7 @@ static void MakeNewGameDone()
InitializeRailGUI();
InitializeRoadGUI();
+ InitializeAirportGui();
if (_settings_client.gui.pause_on_newgame) Command::Post(PM_PAUSED_NORMAL, true);
diff --git a/src/order_backup.cpp b/src/order_backup.cpp
index f696d8435dea5..053c407c8c7aa 100644
--- a/src/order_backup.cpp
+++ b/src/order_backup.cpp
@@ -19,6 +19,8 @@
#include "order_cmd.h"
#include "group_cmd.h"
#include "vehicle_func.h"
+#include "depot_map.h"
+#include "depot_base.h"
#include "safeguards.h"
@@ -45,8 +47,9 @@ OrderBackup::~OrderBackup()
*/
OrderBackup::OrderBackup(const Vehicle *v, uint32_t user)
{
+ assert(IsDepotTile(v->tile));
this->user = user;
- this->tile = v->tile;
+ this->depot_id = GetDepotIndex(v->tile);
this->group = v->group_id;
this->CopyConsistPropertiesFrom(v);
@@ -123,8 +126,10 @@ void OrderBackup::DoRestore(Vehicle *v)
*/
/* static */ void OrderBackup::Restore(Vehicle *v, uint32_t user)
{
+ assert(IsDepotTile(v->tile));
+ DepotID depot_id_veh = GetDepotIndex(v->tile);
for (OrderBackup *ob : OrderBackup::Iterate()) {
- if (v->tile != ob->tile || ob->user != user) continue;
+ if (depot_id_veh != ob->depot_id || ob->user != user) continue;
ob->DoRestore(v);
delete ob;
@@ -133,28 +138,28 @@ void OrderBackup::DoRestore(Vehicle *v)
/**
* Reset an OrderBackup given a tile and user.
- * @param tile The tile associated with the OrderBackup.
+ * @param depot_id The depot associated with the OrderBackup.
* @param user The user associated with the OrderBackup.
* @note Must not be used from the GUI!
*/
-/* static */ void OrderBackup::ResetOfUser(TileIndex tile, uint32_t user)
+/* static */ void OrderBackup::ResetOfUser(DepotID depot_id, uint32_t user)
{
for (OrderBackup *ob : OrderBackup::Iterate()) {
- if (ob->user == user && (ob->tile == tile || tile == INVALID_TILE)) delete ob;
+ if (ob->user == user && (ob->depot_id == depot_id || depot_id == INVALID_DEPOT)) delete ob;
}
}
/**
* Clear an OrderBackup
* @param flags For command.
- * @param tile Tile related to the to-be-cleared OrderBackup.
+ * @param depot_id Tile related to the to-be-cleared OrderBackup.
* @param user_id User that had the OrderBackup.
* @return The cost of this operation or an error.
*/
-CommandCost CmdClearOrderBackup(DoCommandFlag flags, TileIndex tile, ClientID user_id)
+CommandCost CmdClearOrderBackup(DoCommandFlag flags, DepotID depot_id, ClientID user_id)
{
- /* No need to check anything. If the tile or user don't exist we just ignore it. */
- if (flags & DC_EXEC) OrderBackup::ResetOfUser(tile == 0 ? INVALID_TILE : tile, user_id);
+ assert(Depot::IsValidID(depot_id));
+ if (flags & DC_EXEC) OrderBackup::ResetOfUser(depot_id, user_id);
return CommandCost();
}
@@ -173,18 +178,18 @@ CommandCost CmdClearOrderBackup(DoCommandFlag flags, TileIndex tile, ClientID us
/* If it's not a backup of us, ignore it. */
if (ob->user != user) continue;
- Command::Post(0, static_cast(user));
+ Command::Post(ob->depot_id, static_cast(user));
return;
}
}
/**
* Reset the OrderBackups from GUI/game logic.
- * @param t The tile of the order backup.
+ * @param depot_id The index of the depot associated to the order backups.
* @param from_gui Whether the call came from the GUI, i.e. whether
* it must be synced over the network.
*/
-/* static */ void OrderBackup::Reset(TileIndex t, bool from_gui)
+/* static */ void OrderBackup::Reset(DepotID depot_id, bool from_gui)
{
/* The user has CLIENT_ID_SERVER as default when network play is not active,
* but compiled it. A network client has its own variable for the unique
@@ -195,16 +200,16 @@ CommandCost CmdClearOrderBackup(DoCommandFlag flags, TileIndex tile, ClientID us
for (OrderBackup *ob : OrderBackup::Iterate()) {
/* If this is a GUI action, and it's not a backup of us, ignore it. */
if (from_gui && ob->user != user) continue;
- /* If it's not for our chosen tile either, ignore it. */
- if (t != INVALID_TILE && t != ob->tile) continue;
+ /* If it's not for our chosen depot either, ignore it. */
+ if (depot_id != INVALID_DEPOT && depot_id != ob->depot_id) continue;
if (from_gui) {
/* We need to circumvent the "prevention" from this command being executed
* while the game is paused, so use the internal method. Nor do we want
* this command to get its cost estimated when shift is pressed. */
- Command::Unsafe(STR_NULL, nullptr, true, false, ob->tile, CommandTraits::Args{ ob->tile, static_cast(user) });
+ Command::Unsafe(STR_NULL, nullptr, true, false, ob->depot_id, CommandTraits::Args{ ob->depot_id, static_cast(user) });
} else {
- /* The command came from the game logic, i.e. the clearing of a tile.
+ /* The command came from the game logic, i.e. the clearing of a depot.
* In that case we have no need to actually sync this, just do it. */
delete ob;
}
@@ -246,18 +251,14 @@ CommandCost CmdClearOrderBackup(DoCommandFlag flags, TileIndex tile, ClientID us
* Removes an order from all vehicles. Triggers when, say, a station is removed.
* @param type The type of the order (OT_GOTO_[STATION|DEPOT|WAYPOINT]).
* @param destination The destination. Can be a StationID, DepotID or WaypointID.
- * @param hangar Only used for airports in the destination.
- * When false, remove airport and hangar orders.
- * When true, remove either airport or hangar order.
*/
-/* static */ void OrderBackup::RemoveOrder(OrderType type, DestinationID destination, bool hangar)
+/* static */ void OrderBackup::RemoveOrder(OrderType type, DestinationID destination)
{
for (OrderBackup *ob : OrderBackup::Iterate()) {
for (Order *order = ob->orders; order != nullptr; order = order->next) {
OrderType ot = order->GetType();
if (ot == OT_GOTO_DEPOT && (order->GetDepotActionType() & ODATFB_NEAREST_DEPOT) != 0) continue;
- if (ot == OT_GOTO_DEPOT && hangar && !IsHangarTile(ob->tile)) continue; // Not an aircraft? Can't have a hangar order.
- if (ot == OT_IMPLICIT || (IsHangarTile(ob->tile) && ot == OT_GOTO_DEPOT && !hangar)) ot = OT_GOTO_STATION;
+ if (ot == OT_IMPLICIT) ot = OT_GOTO_STATION;
if (ot == type && order->GetDestination() == destination) {
/* Remove the order backup! If a station/depot gets removed, we can't/shouldn't restore those broken orders. */
delete ob;
diff --git a/src/order_backup.h b/src/order_backup.h
index 8616c564eed24..823e74b2f397f 100644
--- a/src/order_backup.h
+++ b/src/order_backup.h
@@ -16,6 +16,7 @@
#include "vehicle_type.h"
#include "base_consist.h"
#include "saveload/saveload.h"
+#include "depot_type.h"
/** Unique identifier for an order backup. */
typedef uint8_t OrderBackupID;
@@ -34,8 +35,8 @@ struct OrderBackup : OrderBackupPool::PoolItem<&_order_backup_pool>, BaseConsist
private:
friend SaveLoadTable GetOrderBackupDescription(); ///< Saving and loading of order backups.
friend struct BKORChunkHandler; ///< Creating empty orders upon savegame loading.
- uint32_t user; ///< The user that requested the backup.
- TileIndex tile; ///< Tile of the depot where the order was changed.
+ uint32_t user; ///< The user that requested the backup.
+ DepotID depot_id; ///< Depot where the order was changed.
GroupID group; ///< The group the vehicle was part of.
const Vehicle *clone; ///< Vehicle this vehicle was a clone of.
@@ -53,13 +54,13 @@ struct OrderBackup : OrderBackupPool::PoolItem<&_order_backup_pool>, BaseConsist
static void Backup(const Vehicle *v, uint32_t user);
static void Restore(Vehicle *v, uint32_t user);
- static void ResetOfUser(TileIndex tile, uint32_t user);
+ static void ResetOfUser(DepotID depot_id, uint32_t user);
static void ResetUser(uint32_t user);
- static void Reset(TileIndex tile = INVALID_TILE, bool from_gui = true);
+ static void Reset(DepotID depot = INVALID_DEPOT, bool from_gui = true);
static void ClearGroup(GroupID group);
static void ClearVehicle(const Vehicle *v);
- static void RemoveOrder(OrderType type, DestinationID destination, bool hangar);
+ static void RemoveOrder(OrderType type, DestinationID destination);
};
#endif /* ORDER_BACKUP_H */
diff --git a/src/order_base.h b/src/order_base.h
index 8f2f10c252aa9..b223440c591fe 100644
--- a/src/order_base.h
+++ b/src/order_base.h
@@ -225,6 +225,7 @@ struct Order : OrderPool::PoolItem<&_order_pool> {
inline void SetMaxSpeed(uint16_t speed) { this->max_speed = speed; }
bool ShouldStopAtStation(const Vehicle *v, StationID station) const;
+ bool ShouldStopAtDepot(DepotID depot) const;
bool CanLoadOrUnload() const;
bool CanLeaveWithCargo(bool has_cargo) const;
diff --git a/src/order_cmd.cpp b/src/order_cmd.cpp
index 1d6743106efe2..b83ba11f52659 100644
--- a/src/order_cmd.cpp
+++ b/src/order_cmd.cpp
@@ -16,6 +16,7 @@
#include "timetable.h"
#include "vehicle_func.h"
#include "depot_base.h"
+#include "depot_func.h"
#include "core/pool_func.hpp"
#include "core/random_func.hpp"
#include "aircraft.h"
@@ -630,7 +631,6 @@ static void DeleteOrderWarnings(const Vehicle *v)
DeleteVehicleNews(v->index, STR_NEWS_VEHICLE_HAS_VOID_ORDER);
DeleteVehicleNews(v->index, STR_NEWS_VEHICLE_HAS_DUPLICATE_ENTRY);
DeleteVehicleNews(v->index, STR_NEWS_VEHICLE_HAS_INVALID_ENTRY);
- DeleteVehicleNews(v->index, STR_NEWS_PLANE_USES_TOO_SHORT_RUNWAY);
}
/**
@@ -650,7 +650,7 @@ TileIndex Order::GetLocation(const Vehicle *v, bool airport) const
case OT_GOTO_DEPOT:
if (this->GetDestination() == INVALID_DEPOT) return INVALID_TILE;
- return (v->type == VEH_AIRCRAFT) ? Station::Get(this->GetDestination())->xy : Depot::Get(this->GetDestination())->xy;
+ return Depot::Get(this->GetDestination())->xy;
default:
return INVALID_TILE;
@@ -684,6 +684,32 @@ uint GetOrderDistance(const Order *prev, const Order *cur, const Vehicle *v, int
return v->type == VEH_AIRCRAFT ? DistanceSquare(prev_tile, cur_tile) : DistanceManhattan(prev_tile, cur_tile);
}
+/**
+ * Get the station or depot index associated to an order of a vehicle.
+ * For aircraft, it will return the index of the associated station, even for go to hangar orders.
+ * @param o Order to check.
+ * @param is_aircraft Whether the order is of an aircraft vehicle.
+ * @return index associated to a station or depot, or INVALID_STATION.
+ */
+DestinationID GetTargetDestination(const Order &o, bool is_aircraft)
+{
+ DestinationID destination_id = o.GetDestination();
+ switch (o.GetType()) {
+ case OT_GOTO_STATION:
+ return destination_id;
+ case OT_GOTO_DEPOT:
+ assert(Depot::IsValidID(destination_id));
+ if (is_aircraft) {
+ destination_id = Depot::Get(destination_id)->station->index;
+ assert(Station::IsValidID(destination_id));
+ }
+ return destination_id;
+ default:
+ return INVALID_STATION;
+ }
+}
+
+
/**
* Add an order to the orderlist of a vehicle.
* @param flags operation to perform
@@ -763,41 +789,15 @@ CommandCost CmdInsertOrder(DoCommandFlag flags, VehicleID veh, VehicleOrderID se
case OT_GOTO_DEPOT: {
if ((new_order.GetDepotActionType() & ODATFB_NEAREST_DEPOT) == 0) {
- if (v->type == VEH_AIRCRAFT) {
- const Station *st = Station::GetIfValid(new_order.GetDestination());
-
- if (st == nullptr) return CMD_ERROR;
-
- ret = CheckOwnership(st->owner);
- if (ret.Failed()) return ret;
-
- if (!CanVehicleUseStation(v, st) || !st->airport.HasHangar()) {
- return CMD_ERROR;
- }
- } else {
- const Depot *dp = Depot::GetIfValid(new_order.GetDestination());
-
- if (dp == nullptr) return CMD_ERROR;
-
- ret = CheckOwnership(GetTileOwner(dp->xy));
- if (ret.Failed()) return ret;
+ const Depot *dp = Depot::GetIfValid(new_order.GetDestination());
- switch (v->type) {
- case VEH_TRAIN:
- if (!IsRailDepotTile(dp->xy)) return CMD_ERROR;
- break;
+ if (dp == nullptr || !dp->IsInUse()) return CMD_ERROR;
- case VEH_ROAD:
- if (!IsRoadDepotTile(dp->xy)) return CMD_ERROR;
- break;
-
- case VEH_SHIP:
- if (!IsShipDepotTile(dp->xy)) return CMD_ERROR;
- break;
+ ret = CheckOwnership(dp->owner);
+ if (ret.Failed()) return ret;
- default: return CMD_ERROR;
- }
- }
+ if (v->type != dp->veh_type) return CMD_ERROR;
+ if (v->type == VEH_AIRCRAFT && !CanVehicleUseStation(v, Station::GetByTile(dp->xy))) return CMD_ERROR;
}
if (new_order.GetNonStopType() != ONSF_STOP_EVERYWHERE && !v->IsGroundVehicle()) return CMD_ERROR;
@@ -1742,12 +1742,6 @@ void CheckOrders(const Vehicle *v)
n_st++;
if (!CanVehicleUseStation(v, st)) {
message = STR_NEWS_VEHICLE_HAS_INVALID_ENTRY;
- } else if (v->type == VEH_AIRCRAFT &&
- (AircraftVehInfo(v->engine_type)->subtype & AIR_FAST) &&
- (st->airport.GetFTA()->flags & AirportFTAClass::SHORT_STRIP) &&
- !_cheats.no_jetcrash.value &&
- message == INVALID_STRING_ID) {
- message = STR_NEWS_PLANE_USES_TOO_SHORT_RUNWAY;
}
}
}
@@ -1780,24 +1774,11 @@ void CheckOrders(const Vehicle *v)
* Removes an order from all vehicles. Triggers when, say, a station is removed.
* @param type The type of the order (OT_GOTO_[STATION|DEPOT|WAYPOINT]).
* @param destination The destination. Can be a StationID, DepotID or WaypointID.
- * @param hangar Only used for airports in the destination.
- * When false, remove airport and hangar orders.
- * When true, remove either airport or hangar order.
*/
-void RemoveOrderFromAllVehicles(OrderType type, DestinationID destination, bool hangar)
+void RemoveOrderFromAllVehicles(OrderType type, DestinationID destination)
{
- /* Aircraft have StationIDs for depot orders and never use DepotIDs
- * This fact is handled specially below
- */
-
/* Go through all vehicles */
for (Vehicle *v : Vehicle::Iterate()) {
- if ((v->type == VEH_AIRCRAFT && v->current_order.IsType(OT_GOTO_DEPOT) && !hangar ? OT_GOTO_STATION : v->current_order.GetType()) == type &&
- (!hangar || v->type == VEH_AIRCRAFT) && v->current_order.GetDestination() == destination) {
- v->current_order.MakeDummy();
- SetWindowDirty(WC_VEHICLE_VIEW, v->index);
- }
-
/* Clear the order from the order-list */
int id = -1;
for (Order *order : v->Orders()) {
@@ -1806,8 +1787,7 @@ void RemoveOrderFromAllVehicles(OrderType type, DestinationID destination, bool
OrderType ot = order->GetType();
if (ot == OT_GOTO_DEPOT && (order->GetDepotActionType() & ODATFB_NEAREST_DEPOT) != 0) continue;
- if (ot == OT_GOTO_DEPOT && hangar && v->type != VEH_AIRCRAFT) continue; // Not an aircraft? Can't have a hangar order.
- if (ot == OT_IMPLICIT || (v->type == VEH_AIRCRAFT && ot == OT_GOTO_DEPOT && !hangar)) ot = OT_GOTO_STATION;
+ if (ot == OT_IMPLICIT) ot = OT_GOTO_STATION;
if (ot == type && order->GetDestination() == destination) {
/* We want to clear implicit orders, but we don't want to make them
* dummy orders. They should just vanish. Also check the actual order
@@ -1841,7 +1821,7 @@ void RemoveOrderFromAllVehicles(OrderType type, DestinationID destination, bool
}
}
- OrderBackup::RemoveOrder(type, destination, hangar);
+ OrderBackup::RemoveOrder(type, destination);
}
/**
@@ -2028,19 +2008,12 @@ bool UpdateOrderDest(Vehicle *v, const Order *order, int conditional_depth, bool
/* PBS reservations cannot reverse */
if (pbs_look_ahead && closestDepot.reverse) return false;
- v->SetDestTile(closestDepot.location);
v->current_order.SetDestination(closestDepot.destination);
+ v->SetDestTile(closestDepot.location);
/* If there is no depot in front, reverse automatically (trains only) */
if (v->type == VEH_TRAIN && closestDepot.reverse) Command::Do(DC_EXEC, v->index, false);
- if (v->type == VEH_AIRCRAFT) {
- Aircraft *a = Aircraft::From(v);
- if (a->state == FLYING && a->targetairport != closestDepot.destination) {
- /* The aircraft is now heading for a different hangar than the next in the orders */
- AircraftNextAirportPos_and_Order(a);
- }
- }
return true;
}
@@ -2051,13 +2024,14 @@ bool UpdateOrderDest(Vehicle *v, const Order *order, int conditional_depth, bool
v->IncrementRealOrderIndex();
} else {
if (v->type != VEH_AIRCRAFT) {
- v->SetDestTile(Depot::Get(order->GetDestination())->xy);
+ v->SetDestTile(Depot::Get(order->GetDestination())->GetBestDepotTile(v));
} else {
Aircraft *a = Aircraft::From(v);
- DestinationID destination = a->current_order.GetDestination();
- if (a->targetairport != destination) {
+ Depot *dep = Depot::Get(a->current_order.GetDestination());
+ assert(dep->station != nullptr);
+ if (a->targetairport != dep->station->index) {
/* The aircraft is now heading for a different hangar than the next in the orders */
- a->SetDestTile(a->GetOrderStationLocation(destination));
+ a->SetDestTile(a->GetOrderStationLocation(dep->station->index));
}
}
return true;
@@ -2133,11 +2107,8 @@ bool ProcessOrders(Vehicle *v)
break;
case OT_LOADING:
- return false;
-
case OT_LEAVESTATION:
- if (v->type != VEH_AIRCRAFT) return false;
- break;
+ return false;
default: break;
}
@@ -2189,7 +2160,7 @@ bool ProcessOrders(Vehicle *v)
}
/* If it is unchanged, keep it. */
- if (order->Equals(v->current_order) && (v->type == VEH_AIRCRAFT || v->dest_tile != 0) &&
+ if (order->Equals(v->current_order) && v->dest_tile != 0 &&
(v->type != VEH_SHIP || !order->IsType(OT_GOTO_STATION) || Station::Get(order->GetDestination())->ship_station.tile != INVALID_TILE)) {
return false;
}
@@ -2232,6 +2203,17 @@ bool Order::ShouldStopAtStation(const Vehicle *v, StationID station) const
!(this->GetNonStopType() & (is_dest_station ? ONSF_NO_STOP_AT_DESTINATION_STATION : ONSF_NO_STOP_AT_INTERMEDIATE_STATIONS));
}
+/**
+ * Check whether the given vehicle should stop at the given depot.
+ * @param v the vehicle that might be stopping.
+ * @param depot the depot to stop at.
+ * @return true if the vehicle should stop.
+ */
+bool Order::ShouldStopAtDepot(DepotID depot) const
+{
+ return this->IsType(OT_GOTO_DEPOT) && this->dest == depot;
+}
+
bool Order::CanLoadOrUnload() const
{
return (this->IsType(OT_GOTO_STATION) || this->IsType(OT_IMPLICIT)) &&
diff --git a/src/order_cmd.h b/src/order_cmd.h
index b0ab888d9421a..d59ca58adf857 100644
--- a/src/order_cmd.h
+++ b/src/order_cmd.h
@@ -21,7 +21,7 @@ CommandCost CmdInsertOrder(DoCommandFlag flags, VehicleID veh, VehicleOrderID se
CommandCost CmdOrderRefit(DoCommandFlag flags, VehicleID veh, VehicleOrderID order_number, CargoID cargo);
CommandCost CmdCloneOrder(DoCommandFlag flags, CloneOptions action, VehicleID veh_dst, VehicleID veh_src);
CommandCost CmdMoveOrder(DoCommandFlag flags, VehicleID veh, VehicleOrderID moving_order, VehicleOrderID target_order);
-CommandCost CmdClearOrderBackup(DoCommandFlag flags, TileIndex tile, ClientID user_id);
+CommandCost CmdClearOrderBackup(DoCommandFlag flags, DepotID depot_id, ClientID user_id);
DEF_CMD_TRAIT(CMD_MODIFY_ORDER, CmdModifyOrder, CMD_LOCATION, CMDT_ROUTE_MANAGEMENT)
DEF_CMD_TRAIT(CMD_SKIP_TO_ORDER, CmdSkipToOrder, CMD_LOCATION, CMDT_ROUTE_MANAGEMENT)
diff --git a/src/order_func.h b/src/order_func.h
index 12f7d4684a9b9..797066e1eb57a 100644
--- a/src/order_func.h
+++ b/src/order_func.h
@@ -15,7 +15,7 @@
#include "company_type.h"
/* Functions */
-void RemoveOrderFromAllVehicles(OrderType type, DestinationID destination, bool hangar = false);
+void RemoveOrderFromAllVehicles(OrderType type, DestinationID destination);
void InvalidateVehicleOrder(const Vehicle *v, int data);
void CheckOrders(const Vehicle*);
void DeleteVehicleOrders(Vehicle *v, bool keep_orderlist = false, bool reset_order_indices = true);
@@ -23,6 +23,7 @@ bool ProcessOrders(Vehicle *v);
bool UpdateOrderDest(Vehicle *v, const Order *order, int conditional_depth = 0, bool pbs_look_ahead = false);
VehicleOrderID ProcessConditionalOrder(const Order *order, const Vehicle *v);
uint GetOrderDistance(const Order *prev, const Order *cur, const Vehicle *v, int conditional_depth = 0);
+DestinationID GetTargetDestination(const Order &o, bool is_aircraft);
void DrawOrderString(const Vehicle *v, const Order *order, int order_index, int y, bool selected, bool timetable, int left, int middle, int right);
diff --git a/src/order_gui.cpp b/src/order_gui.cpp
index 77b68d9479b2f..3bcfda6c4904e 100644
--- a/src/order_gui.cpp
+++ b/src/order_gui.cpp
@@ -34,6 +34,7 @@
#include "error.h"
#include "order_cmd.h"
#include "company_cmd.h"
+#include "depot_base.h"
#include "widgets/order_widget.h"
@@ -309,7 +310,7 @@ void DrawOrderString(const Vehicle *v, const Order *order, int order_index, int
/* Going to a specific depot. */
SetDParam(0, STR_ORDER_GO_TO_DEPOT_FORMAT);
SetDParam(2, v->type);
- SetDParam(3, order->GetDestination());
+ SetDParam(3, GetTargetDestination(*order, v->type == VEH_AIRCRAFT));
}
if (order->GetDepotOrderType() & ODTFB_SERVICE) {
@@ -384,8 +385,7 @@ static Order GetOrderCmdFromTile(const Vehicle *v, TileIndex tile)
/* check depot first */
if (IsDepotTypeTile(tile, (TransportType)(uint)v->type) && IsTileOwner(tile, _local_company)) {
- order.MakeGoToDepot(v->type == VEH_AIRCRAFT ? GetStationIndex(tile) : GetDepotIndex(tile),
- ODTFB_PART_OF_ORDERS,
+ order.MakeGoToDepot(GetDepotIndex(tile), ODTFB_PART_OF_ORDERS,
(_settings_client.gui.new_nonstop && v->IsGroundVehicle()) ? ONSF_NO_STOP_AT_INTERMEDIATE_STATIONS : ONSF_STOP_EVERYWHERE);
if (_ctrl_pressed) {
diff --git a/src/pathfinder/follow_track.hpp b/src/pathfinder/follow_track.hpp
index d384568b62f74..fcb4bcd72927f 100644
--- a/src/pathfinder/follow_track.hpp
+++ b/src/pathfinder/follow_track.hpp
@@ -18,6 +18,9 @@
#include "../tunnelbridge_map.h"
#include "../depot_map.h"
#include "pathfinder_func.h"
+#include "../platform_func.h"
+#include "../air_map.h"
+#include "../air.h"
/**
* Track follower helper template class (can serve pathfinders and vehicle
@@ -46,7 +49,8 @@ struct CFollowTrackT
bool m_is_tunnel; ///< last turn passed tunnel
bool m_is_bridge; ///< last turn passed bridge ramp
bool m_is_station; ///< last turn passed station
- int m_tiles_skipped; ///< number of skipped tunnel or station tiles
+ bool m_is_extended_depot; ///< last turn passed depot
+ int m_tiles_skipped; ///< number of skipped tunnel, depot or station tiles
ErrorCode m_err;
RailTypes m_railtypes;
@@ -57,7 +61,7 @@ struct CFollowTrackT
inline CFollowTrackT(Owner o, RailTypes railtype_override = INVALID_RAILTYPES)
{
- assert(IsRailTT());
+ assert(IsRailTT() || IsAirTT());
m_veh = nullptr;
Init(o, railtype_override);
}
@@ -80,7 +84,7 @@ struct CFollowTrackT
m_new_tile = INVALID_TILE;
m_new_td_bits = TRACKDIR_BIT_NONE;
m_exitdir = INVALID_DIAGDIR;
- m_is_station = m_is_bridge = m_is_tunnel = false;
+ m_is_station = m_is_bridge = m_is_tunnel = m_is_extended_depot = false;
m_tiles_skipped = 0;
m_err = EC_NONE;
m_railtypes = railtype_override;
@@ -89,6 +93,7 @@ struct CFollowTrackT
debug_inline static TransportType TT() { return Ttr_type_; }
debug_inline static bool IsWaterTT() { return TT() == TRANSPORT_WATER; }
debug_inline static bool IsRailTT() { return TT() == TRANSPORT_RAIL; }
+ debug_inline static bool IsAirTT() { return TT() == TRANSPORT_AIR; }
inline bool IsTram() { return IsRoadTT() && RoadTypeIsTram(RoadVehicle::From(m_veh)->roadtype); }
debug_inline static bool IsRoadTT() { return TT() == TRANSPORT_ROAD; }
inline static bool Allow90degTurns() { return T90deg_turns_allowed_; }
@@ -99,7 +104,7 @@ struct CFollowTrackT
{
assert(IsTram()); // this function shouldn't be called in other cases
- if (IsNormalRoadTile(tile)) {
+ if (IsNormalRoadTile(tile) || IsExtendedRoadDepotTile(tile)) {
RoadBits rb = GetRoadBits(tile, RTT_TRAM);
switch (rb) {
case ROAD_NW: return DIAGDIR_NW;
@@ -123,17 +128,21 @@ struct CFollowTrackT
m_err = EC_NONE;
assert([&]() {
+ if (IsAirTT()) return true;
if (IsTram() && GetSingleTramBit(m_old_tile) != INVALID_DIAGDIR) return true; // Skip the check for single tram bits
const uint sub_mode = (IsRoadTT() && m_veh != nullptr) ? (this->IsTram() ? RTT_TRAM : RTT_ROAD) : 0;
const TrackdirBits old_tile_valid_dirs = TrackStatusToTrackdirBits(GetTileTrackStatus(m_old_tile, TT(), sub_mode));
return (old_tile_valid_dirs & TrackdirToTrackdirBits(m_old_td)) != TRACKDIR_BIT_NONE;
}());
+ assert(!IsAirTT() || (GetTileTrackStatus(m_old_tile, TT(), 0) != 0));
+
m_exitdir = TrackdirToExitdir(m_old_td);
if (ForcedReverse()) return true;
if (!CanExitOldTile()) return false;
FollowTileExit();
if (!QueryNewTileTrackStatus()) return TryReverse();
+ if (IsAirTT() && !CanEnterNewTile()) return false;
m_new_td_bits &= DiagdirReachesTrackdirs(m_exitdir);
if (m_new_td_bits == TRACKDIR_BIT_NONE || !CanEnterNewTile()) {
/* In case we can't enter the next tile, but are
@@ -170,11 +179,11 @@ struct CFollowTrackT
{
if (!DoTrackMasking()) return true;
- if (m_is_station) {
- /* Check skipped station tiles as well. */
+ if (m_is_station || m_is_extended_depot) {
+ /* Check skipped station and depot tiles as well. */
TileIndexDiff diff = TileOffsByDiagDir(m_exitdir);
for (TileIndex tile = m_new_tile - diff * m_tiles_skipped; tile != m_new_tile; tile += diff) {
- if (HasStationReservation(tile)) {
+ if ((m_is_station && HasStationReservation(tile)) || (m_is_extended_depot && HasDepotReservation(tile))) {
m_new_td_bits = TRACKDIR_BIT_NONE;
m_err = EC_RESERVED;
return false;
@@ -200,7 +209,7 @@ struct CFollowTrackT
/** Follow the m_exitdir from m_old_tile and fill m_new_tile and m_tiles_skipped */
inline void FollowTileExit()
{
- m_is_station = m_is_bridge = m_is_tunnel = false;
+ m_is_station = m_is_bridge = m_is_tunnel = m_is_extended_depot = false;
m_tiles_skipped = 0;
/* extra handling for tunnels and bridges in our direction */
@@ -224,9 +233,13 @@ struct CFollowTrackT
/* normal or station tile, do one step */
m_new_tile = TileAddByDiagDir(m_old_tile, m_exitdir);
- /* special handling for stations */
- if (IsRailTT() && HasStationTileRail(m_new_tile)) {
- m_is_station = true;
+ /* special handling for stations and multi-tile depots */
+ if (IsRailTT()) {
+ if (HasStationTileRail(m_new_tile)) {
+ m_is_station = true;
+ } else if (IsExtendedRailDepotTile(m_new_tile)) {
+ m_is_extended_depot = true;
+ }
} else if (IsRoadTT() && IsStationRoadStopTile(m_new_tile)) {
m_is_station = true;
}
@@ -235,8 +248,14 @@ struct CFollowTrackT
/** stores track status (available trackdirs) for the new tile into m_new_td_bits */
inline bool QueryNewTileTrackStatus()
{
+ if (IsAirTT()) {
+ if (!IsAirportTile(m_new_tile) ||
+ !MayHaveAirTracks(m_new_tile)) return false;
+ m_new_td_bits = TrackBitsToTrackdirBits(GetAirportTileTracks(m_new_tile));
+ return m_new_td_bits != TRACKDIR_BIT_NONE;
+ }
if (IsRailTT() && IsPlainRailTile(m_new_tile)) {
- m_new_td_bits = (TrackdirBits)(GetTrackBits(m_new_tile) * 0x101);
+ m_new_td_bits = TrackBitsToTrackdirBits(GetTrackBits(m_new_tile));
} else if (IsRoadTT()) {
m_new_td_bits = GetTrackdirBitsForRoad(m_new_tile, this->IsTram() ? RTT_TRAM : RTT_ROAD);
} else {
@@ -248,6 +267,20 @@ struct CFollowTrackT
/** return true if we can leave m_old_tile in m_exitdir */
inline bool CanExitOldTile()
{
+ /* hangar can be left at one direction */
+ if (IsAirTT()) {
+ if (IsHangarTile(m_old_tile)) {
+ DiagDirection exitdir = GetHangarDirection(m_old_tile);
+ if (exitdir != m_exitdir) {
+ m_err = EC_NO_WAY;
+ return false;
+ }
+ } else if (IsHeliportTile(m_old_tile)) {
+ m_err = EC_NO_WAY;
+ return false;
+ }
+ }
+
/* road stop can be left at one direction only unless it's a drive-through stop */
if (IsRoadTT() && IsBayRoadStopTile(m_old_tile)) {
DiagDirection exitdir = GetRoadStopDir(m_old_tile);
@@ -266,20 +299,36 @@ struct CFollowTrackT
}
}
- /* road depots can be also left in one direction only */
+ /* road depots can be also left in one direction sometimes */
if (IsRoadTT() && IsDepotTypeTile(m_old_tile, TT())) {
- DiagDirection exitdir = GetRoadDepotDirection(m_old_tile);
- if (exitdir != m_exitdir) {
+ RoadTramType rtt = IsTram() ? RTT_TRAM : RTT_ROAD;
+ RoadBits rb = GetRoadBits(m_old_tile, rtt);
+ if ((rb & DiagDirToRoadBits(m_exitdir)) == ROAD_NONE) {
m_err = EC_NO_WAY;
return false;
}
}
+
return true;
}
/** return true if we can enter m_new_tile from m_exitdir */
inline bool CanEnterNewTile()
{
+ if (IsAirTT()) {
+ if (GetStationIndex(m_old_tile) != GetStationIndex(m_new_tile)) return false;
+ if (IsHangarTile(m_new_tile)) {
+ DiagDirection exitdir = GetHangarDirection(m_new_tile);
+ if (ReverseDiagDir(exitdir) != m_exitdir) {
+ m_err = EC_NO_WAY;
+ return false;
+ }
+ } else if (IsHeliportTile(m_new_tile)) {
+ m_err = EC_NO_WAY;
+ return false;
+ }
+ }
+
if (IsRoadTT() && IsBayRoadStopTile(m_new_tile)) {
/* road stop can be entered from one direction only unless it's a drive-through stop */
DiagDirection exitdir = GetRoadStopDir(m_new_tile);
@@ -300,18 +349,19 @@ struct CFollowTrackT
/* road and rail depots can also be entered from one direction only */
if (IsRoadTT() && IsDepotTypeTile(m_new_tile, TT())) {
- DiagDirection exitdir = GetRoadDepotDirection(m_new_tile);
- if (ReverseDiagDir(exitdir) != m_exitdir) {
- m_err = EC_NO_WAY;
- return false;
- }
/* don't try to enter other company's depots */
if (GetTileOwner(m_new_tile) != m_veh_owner) {
m_err = EC_OWNER;
return false;
}
+ RoadTramType rtt = IsTram() ? RTT_TRAM : RTT_ROAD;
+ RoadBits rb = GetRoadBits(m_new_tile, rtt);
+ if ((rb & DiagDirToRoadBits(ReverseDiagDir(m_exitdir))) == ROAD_NONE) {
+ m_err = EC_NO_WAY;
+ return false;
+ }
}
- if (IsRailTT() && IsDepotTypeTile(m_new_tile, TT())) {
+ if (IsRailTT() && IsStandardRailDepotTile(m_new_tile)) {
DiagDirection exitdir = GetRailDepotDirection(m_new_tile);
if (ReverseDiagDir(exitdir) != m_exitdir) {
m_err = EC_NO_WAY;
@@ -368,14 +418,14 @@ struct CFollowTrackT
}
}
- /* special handling for rail stations - get to the end of platform */
- if (IsRailTT() && m_is_station) {
- /* entered railway station
- * get platform length */
- uint length = BaseStation::GetByTile(m_new_tile)->GetPlatformLength(m_new_tile, TrackdirToExitdir(m_old_td));
- /* how big step we must do to get to the last platform tile? */
- m_tiles_skipped = length - 1;
- /* move to the platform end */
+ /* special handling for rail platforms - get to the end of platform */
+ if (IsRailTT() && (m_is_station || m_is_extended_depot)) {
+ /* Entered a platform. */
+ assert(HasStationTileRail(m_new_tile) || IsExtendedRailDepotTile(m_new_tile));
+ /* How big step we must do to get to the last platform tile? */
+ m_tiles_skipped = GetPlatformLength(m_new_tile, TrackdirToExitdir(m_old_td)) - 1;
+ /* Move to the platform end. */
+
TileIndexDiff diff = TileOffsByDiagDir(m_exitdir);
diff *= m_tiles_skipped;
m_new_tile = TileAdd(m_new_tile, diff);
@@ -390,14 +440,29 @@ struct CFollowTrackT
{
/* rail and road depots cause reversing */
if (!IsWaterTT() && IsDepotTypeTile(m_old_tile, TT())) {
- DiagDirection exitdir = IsRailTT() ? GetRailDepotDirection(m_old_tile) : GetRoadDepotDirection(m_old_tile);
+ DiagDirection exitdir;
+ switch (TT()) {
+ case TRANSPORT_AIR:
+ return false;
+ case TRANSPORT_RAIL:
+ if (IsExtendedRailDepot(m_old_tile)) return false;
+ exitdir = GetRailDepotDirection(m_old_tile);
+ break;
+ case TRANSPORT_ROAD: {
+ if (GetRoadBits(m_old_tile, IsTram() ? RTT_TRAM : RTT_ROAD) != DiagDirToRoadBits(m_exitdir)) return false;
+ exitdir = ReverseDiagDir(m_exitdir);
+ break;
+ }
+ default: NOT_REACHED();
+ }
+
if (exitdir != m_exitdir) {
/* reverse */
m_new_tile = m_old_tile;
m_new_td_bits = TrackdirToTrackdirBits(ReverseTrackdir(m_old_td));
- m_exitdir = exitdir;
+ m_exitdir = IsAirTT() ? ReverseDiagDir(m_exitdir) : exitdir;
m_tiles_skipped = 0;
- m_is_tunnel = m_is_bridge = m_is_station = false;
+ m_is_tunnel = m_is_bridge = m_is_station = m_is_extended_depot = false;
return true;
}
}
@@ -460,6 +525,10 @@ struct CFollowTrackT
uint16_t road_speed = GetRoadTypeInfo(GetRoadType(m_old_tile, GetRoadTramType(RoadVehicle::From(m_veh)->roadtype)))->max_speed;
if (road_speed > 0) max_speed = std::min(max_speed, road_speed);
}
+ if (IsAirTT()) {
+ uint16_t air_speed = GetAirTypeInfo(GetAirType(m_old_tile))->max_speed;
+ if (air_speed > 0) max_speed = std::min(max_speed, (int)air_speed);
+ }
/* if min speed was requested, return it */
if (pmin_speed != nullptr) *pmin_speed = min_speed;
@@ -470,6 +539,7 @@ struct CFollowTrackT
typedef CFollowTrackT CFollowTrackWater;
typedef CFollowTrackT CFollowTrackRoad;
typedef CFollowTrackT CFollowTrackRail;
+typedef CFollowTrackT CFollowTrackAirport;
typedef CFollowTrackT CFollowTrackRailNo90;
diff --git a/src/pathfinder/pathfinder_func.h b/src/pathfinder/pathfinder_func.h
index 444b100ce7b0a..410776511b5b0 100644
--- a/src/pathfinder/pathfinder_func.h
+++ b/src/pathfinder/pathfinder_func.h
@@ -12,6 +12,7 @@
#include "../tile_cmd.h"
#include "../waypoint_base.h"
+#include "../depot_base.h"
/**
* Calculates the tile of given station that is closest to a given tile
@@ -47,6 +48,46 @@ inline TileIndex CalcClosestStationTile(StationID station, TileIndex tile, Stati
return TileXY(x, y);
}
+/**
+ * Calculates the tile of a depot that is closest to a given tile.
+ * @param depot_id The depot to calculate the distance to.
+ * @param tile The tile from where to calculate the distance.
+ * @return The closest depot tile to the given tile.
+ */
+static inline TileIndex CalcClosestDepotTile(DepotID depot_id, TileIndex tile)
+{
+ assert(Depot::IsValidID(depot_id));
+ const Depot *dep = Depot::Get(depot_id);
+
+ /* If tile area is empty, use the xy tile. */
+ if (dep->ta.tile == INVALID_TILE) {
+ assert(dep->xy != INVALID_TILE);
+ return dep->xy;
+ }
+
+ TileIndex best_tile = INVALID_TILE;
+ DepotReservation best_found_type = dep->veh_type == VEH_SHIP ? DEPOT_RESERVATION_END : DEPOT_RESERVATION_EMPTY;
+ uint best_distance = UINT_MAX;
+
+ for (auto const &depot_tile : dep->depot_tiles) {
+ uint new_distance = DistanceManhattan(depot_tile, tile);
+ bool check_south_direction = dep->veh_type == VEH_ROAD;
+again:
+ DepotReservation depot_reservation = GetDepotReservation(depot_tile, check_south_direction);
+ if (((best_found_type == depot_reservation) && new_distance < best_distance) || (depot_reservation < best_found_type)) {
+ best_tile = depot_tile;
+ best_distance = new_distance;
+ best_found_type = depot_reservation;
+ }
+ if (check_south_direction) {
+ check_south_direction = false;
+ goto again;
+ }
+ }
+
+ return best_tile;
+}
+
/**
* Wrapper around GetTileTrackStatus() and TrackStatusToTrackdirBits(), as for
* single tram bits GetTileTrackStatus() returns 0. The reason for this is
diff --git a/src/pathfinder/yapf/CMakeLists.txt b/src/pathfinder/yapf/CMakeLists.txt
index 6717233352b60..fdd1dd744a3e1 100644
--- a/src/pathfinder/yapf/CMakeLists.txt
+++ b/src/pathfinder/yapf/CMakeLists.txt
@@ -10,9 +10,11 @@ add_files(
yapf_costrail.hpp
yapf_destrail.hpp
yapf_node.hpp
+ yapf_node_air.hpp
yapf_node_rail.hpp
yapf_node_road.hpp
yapf_node_ship.hpp
+ yapf_air.cpp
yapf_rail.cpp
yapf_road.cpp
yapf_ship.cpp
diff --git a/src/pathfinder/yapf/yapf.h b/src/pathfinder/yapf/yapf.h
index 186986ce57e57..80ab9e9c7fead 100644
--- a/src/pathfinder/yapf/yapf.h
+++ b/src/pathfinder/yapf/yapf.h
@@ -15,14 +15,26 @@
#include "../../vehicle_type.h"
#include "../../ship.h"
#include "../../roadveh.h"
+#include "../../aircraft.h"
#include "../pathfinder_type.h"
+/**
+ * Finds the best path for given aircraft using YAPF.
+ * @param v the aircraft that is looking for a path
+ * @param best_dest [out] struct containing best dest tile and best trackdir
+ * @param path_found [out] whether a path has been found (true) or has been guessed (false)
+ * @param dest_state the state that the aircraft is trying to get
+ * @param path_cache [out] cache for the newly found path
+ * @return first trackdir on starting tile of the best path found, or INVALID_TRACKDIR if none found.
+ */
+Trackdir YapfAircraftFindPath(const Aircraft *v, struct PBSTileInfo &best_dest, bool &path_found, AircraftState dest_state, AircraftPathChoice &path_cache);
+
/**
* Finds the best path for given ship using YAPF.
- * @param v the ship that needs to find a path
- * @param tile the tile to find the path from (should be next tile the ship is about to enter)
- * @param path_found [out] Whether a path has been found (true) or has been guessed (false)
- * @return the best trackdir for next turn or INVALID_TRACK if the path could not be found
+ * @param v the ship that needs to find a path
+ * @param tile the tile to find the path from (should be next tile the ship is about to enter)
+ * @param path_found [out] whether a path has been found (true) or has been guessed (false)
+ * @return the best trackdir for next turn or INVALID_TRACK if the path could not be found
*/
Track YapfShipChooseTrack(const Ship *v, TileIndex tile, bool &path_found, ShipPathCache &path_cache);
@@ -36,26 +48,25 @@ bool YapfShipCheckReverse(const Ship *v, Trackdir *trackdir);
/**
* Finds the best path for given road vehicle using YAPF.
- * @param v the RV that needs to find a path
- * @param tile the tile to find the path from (should be next tile the RV is about to enter)
- * @param enterdir diagonal direction which the RV will enter this new tile from
- * @param trackdirs available trackdirs on the new tile (to choose from)
- * @param path_found [out] Whether a path has been found (true) or has been guessed (false)
- * @return the best trackdir for next turn or INVALID_TRACKDIR if the path could not be found
+ * @param v the RV that needs to find a path
+ * @param tile the tile to find the path from (should be next tile the RV is about to enter)
+ * @param enterdir diagonal direction which the RV will enter this new tile from
+ * @param trackdirs available trackdirs on the new tile (to choose from)
+ * @param path_found [out] whether a path has been found (true) or has been guessed (false)
+ * @return the best trackdir for next turn or INVALID_TRACKDIR if the path could not be found
*/
Trackdir YapfRoadVehicleChooseTrack(const RoadVehicle *v, TileIndex tile, DiagDirection enterdir, TrackdirBits trackdirs, bool &path_found, RoadVehPathCache &path_cache);
/**
* Finds the best path for given train using YAPF.
- * @param v the train that needs to find a path
- * @param tile the tile to find the path from (should be next tile the train is about to enter)
- * @param enterdir diagonal direction which the RV will enter this new tile from
- * @param tracks available trackdirs on the new tile (to choose from)
- * @param path_found [out] Whether a path has been found (true) or has been guessed (false)
+ * @param v the train that needs to find a path
+ * @param tile the tile to find the path from (should be next tile the train is about to enter)
+ * @param enterdir diagonal direction which the RV will enter this new tile from
+ * @param tracks available trackdirs on the new tile (to choose from)
+ * @param path_found [out] whether a path has been found (true) or has been guessed (false)
* @param reserve_track indicates whether YAPF should try to reserve the found path
- * @param target [out] the target tile of the reservation, free is set to true if path was reserved
- * @param dest [out] the final tile of the best path found
- * @return the best track for next turn
+ * @param target [out] the target tile of the reservation, free is set to true if path was reserved
+ * @param dest the best track for next turn
*/
Track YapfTrainChooseTrack(const Train *v, TileIndex tile, DiagDirection enterdir, TrackBits tracks, bool &path_found, bool reserve_track, struct PBSTileInfo *target, TileIndex *dest);
diff --git a/src/pathfinder/yapf/yapf_air.cpp b/src/pathfinder/yapf/yapf_air.cpp
new file mode 100644
index 0000000000000..2ab7a044bd3c8
--- /dev/null
+++ b/src/pathfinder/yapf/yapf_air.cpp
@@ -0,0 +1,443 @@
+/*
+ * 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 yapf_aircraft.cpp Implementation of YAPF for aircraft. */
+
+#include "../../stdafx.h"
+#include "../../aircraft.h"
+#include "../../pbs_air.h"
+#include "../../pbs_air.h"
+
+#include "track_func.h"
+#include "yapf.hpp"
+#include "yapf_node_air.hpp"
+
+#include "../../safeguards.h"
+
+/**
+ * When looking for aircraft paths, crossing a non-diagonal track
+ * may put the aircraft too close to another aircraft crossing the
+ * equivalent track on a neighbour tile (or getting too close to a hangar).
+ * Check whether the associated tile is available
+ * and its corresponding track is not reserved.
+ * @param tile Tile to check.
+ * @param track Involved track on tile.
+ * @return Whether the associated tile can be crossed and it is of the same station and is not reserved.
+ * @pre IsAirportTile
+ */
+bool IsAssociatedAirportTileFree(TileIndex tile, Track track)
+{
+ assert(IsAirportTile(tile));
+ assert(IsValidTrack(track));
+
+ if (IsDiagonalTrack(track)) return true;
+
+ static const Direction track_dir_table[TRACK_END] = {
+ INVALID_DIR,
+ INVALID_DIR,
+ DIR_N,
+ DIR_S,
+ DIR_W,
+ DIR_E,
+ };
+
+ TileIndex neighbour = TileAddByDir(tile, track_dir_table[track]);
+
+ return IsValidTile(neighbour)
+ && IsAirportTileOfStation(neighbour, GetStationIndex(tile))
+ && MayHaveAirTracks(neighbour)
+ && !IsHangar(neighbour)
+ && !(IsRunway(neighbour) && GetReservationAsRunway(neighbour))
+ && !HasAirportTrackReserved(neighbour, TrackToOppositeTrack(track));
+}
+
+/**
+ * Check if a tile can be reserved and does not collide with another reserved path.
+ * @param tile The tile.
+ * @param trackdir The trackdir to check.
+ * @return True if reserving \a trackdir on tile \a tile doesn't collide with other paths.
+ */
+bool IsAirportTileFree(TileIndex tile, Trackdir trackdir)
+{
+ assert(IsAirportTile(tile));
+ assert(MayHaveAirTracks(tile));
+ assert(IsValidTrackdir(trackdir));
+
+ /* Reserved tiles are not free. */
+ if (HasAirportTileAnyReservation(tile)) return false;
+
+ /* With non-diagonal tracks, if there is a close reserved non-diagonal track,
+ * it is not a free tile. */
+ Track track = TrackdirToTrack(trackdir);
+ return IsAssociatedAirportTileFree(tile, track);
+}
+
+/** Node Follower module of YAPF for aircraft. */
+template
+class CYapfFollowAircraftT
+{
+public:
+ typedef typename Types::Tpf Tpf; ///< the pathfinder class (derived from THIS class)
+ typedef typename Types::TrackFollower TrackFollower;
+ typedef typename Types::NodeList::Titem Node; ///< this will be our node type
+ typedef typename Node::Key Key; ///< key to hash tables
+
+protected:
+ /** to access inherited path finder */
+ inline Tpf& Yapf()
+ {
+ return *static_cast(this);
+ }
+
+public:
+
+ /**
+ * Called by YAPF to move from the given node to the next tile. For each
+ * reachable trackdir on the new tile creates new node, initializes it
+ * and adds it to the open list by calling Yapf().AddNewNode(n)
+ */
+ inline void PfFollowNode(Node &old_node)
+ {
+ TrackFollower F(Yapf().GetVehicle());
+ bool try_reverse = true; // Whether the vehicle should try to reverse at the end of this tile.
+ bool reverse_reservable; // Whether if reservation while reversing on the edge of this tile is possible.
+
+ /* Add nodes for rotation in middle of the tile if possible. */
+ if (IsDiagonalTrackdir(old_node.m_key.m_td) &&
+ (old_node.m_parent == nullptr || old_node.m_key.m_tile != old_node.m_parent->m_key.m_tile) &&
+ (GetAirportTileTracks(old_node.m_key.m_tile) & TRACK_BIT_CROSS) == TRACK_BIT_CROSS) {
+ assert(IsValidTrackdir(old_node.m_key.m_td));
+ try_reverse = false;
+ TrackBits tracks = TRACK_BIT_CROSS & ~TrackToTrackBits(TrackdirToTrack(old_node.m_key.m_td));
+ for (TrackdirBits rtds = TrackBitsToTrackdirBits(tracks);
+ rtds != TRACKDIR_BIT_NONE; rtds = KillFirstBit(rtds)) {
+ Trackdir td = (Trackdir)FindFirstBit(rtds);
+ Node &n = Yapf().CreateNewNode();
+ // Here there is a choice.
+ n.Set(&old_node, old_node.GetTile(), td, true, true);
+ Yapf().AddNewNode(n, F);
+ }
+ }
+
+ if (F.Follow(old_node.m_key.m_tile, old_node.m_key.m_td)) {
+ for (TrackdirBits rtds = F.m_new_td_bits; rtds != TRACKDIR_BIT_NONE; rtds = KillFirstBit(rtds)) {
+ Trackdir td = (Trackdir)FindFirstBit(rtds);
+ Node &n = Yapf().CreateNewNode();
+ n.Set(&old_node, F.m_new_tile, td, false, true);
+ Yapf().AddNewNode(n, F);
+ }
+
+ if (!try_reverse) return;
+
+ /* If next tile can have air tracks and the old tile has no track bit cross,
+ * then allow rotation at the end of this tile. */
+ reverse_reservable = (GetReservedAirportTracks(F.m_new_tile) & TrackdirBitsToTrackBits(F.m_new_td_bits)) == TRACK_BIT_NONE;
+
+ /* Add nodes for rotation at the end of the tile if possible. */
+ DiagDirection reentry_dir = ReverseDiagDir(TrackdirToExitdir(old_node.m_key.m_td));
+ TrackdirBits rtds = DiagdirReachesTrackdirs(reentry_dir) &
+ TrackBitsToTrackdirBits(GetAirportTileTracks(old_node.m_key.m_tile)) &
+ ~TrackdirToTrackdirBits(old_node.m_key.m_td);
+ for ( ; rtds != TRACKDIR_BIT_NONE; rtds = KillFirstBit(rtds)) {
+ Trackdir td = (Trackdir)FindFirstBit(rtds);
+ Node &n = Yapf().CreateNewNode();
+ // reverse_reservable indicates whether there would be
+ // a path collision with another path on the next tile.
+ n.Set(&old_node, old_node.GetTile(), td, false, reverse_reservable);
+ Yapf().AddNewNode(n, F);
+ }
+ }
+ }
+
+ /** return debug report character to identify the transportation type */
+ inline char TransportTypeChar() const
+ {
+ return 'a';
+ }
+
+ static Trackdir ChooseAircraftPath(const Aircraft *v, PBSTileInfo *best_dest, bool &path_found, AircraftState dest_state, AircraftPathChoice &path_cache)
+ {
+ if (!path_cache.empty()) path_cache.clear();
+
+ /* Handle special case: when next tile is destination tile. */
+ if (v->tile == v->GetNextTile()) {
+ path_found = true;
+ best_dest->okay = true;
+ best_dest->tile = v->tile;
+ best_dest->trackdir = v->trackdir;
+ return v->trackdir;
+ }
+
+ assert(IsValidTrackdir(v->trackdir));
+ Track track= TrackdirToTrack(v->trackdir);
+ TrackdirBits trackdirs;
+ if ((GetAirportTileTracks(v->tile) & TRACK_BIT_CROSS) == TRACK_BIT_CROSS && (v->x_pos & 0xF) == 8 && (v->y_pos & 0xF) == 8) {
+ trackdirs = TrackBitsToTrackdirBits(TRACK_BIT_CROSS);
+ } else {
+ trackdirs = TrackBitsToTrackdirBits(TrackToTrackBits(track));
+ }
+
+ /* Create pathfinder instance. */
+ Tpf pf;
+
+ /* Set origin and destination nodes. */
+ pf.SetOrigin(v->tile, trackdirs);
+ pf.SetDestination(v->IsHelicopter(), dest_state);
+
+ /* Find best path. */
+ path_found = pf.FindPath(v);
+ bool do_track_reservation = path_found;
+ Node *pNode = pf.GetBestNode();
+
+ Trackdir next_trackdir = INVALID_TRACKDIR;
+
+ if (pNode != nullptr) {
+ /* walk through the path back to the origin */
+ while (pNode->m_parent != nullptr) {
+ do_track_reservation &= pNode->m_reservable;
+ pNode = pNode->m_parent;
+ }
+ assert(pNode->GetTile() == v->tile);
+ /* return trackdir from the best next node (origin) */
+ next_trackdir = pNode->GetTrackdir();
+ /* Reserve path from origin till last safe waiting tile */
+ if (do_track_reservation) {
+ for (const Node *cur = pf.GetBestNode(); cur != nullptr; cur = cur->m_parent) {
+ assert(IsValidTrackdir(cur->GetTrackdir()));
+ SetAirportTrackReservation(cur->GetTile(), TrackdirToTrack(cur->GetTrackdir()));
+ if (cur->m_parent != nullptr && cur->GetIsChoice()) {
+ assert(cur->GetTile() == cur->m_parent->GetTile());
+ path_cache.td.push_front(cur->GetTrackdir());
+ path_cache.tile.push_front(cur->GetTile());
+ }
+ }
+ }
+
+ best_dest->tile = pf.GetBestNode()->GetTile();
+ best_dest->trackdir = pf.GetBestNode()->GetTrackdir();
+ }
+
+ assert(!path_found || next_trackdir != INVALID_TRACKDIR);
+ best_dest->okay = do_track_reservation;
+
+ return next_trackdir;
+ }
+};
+
+/** YAPF origin provider class for air. */
+template
+class CYapfAirportOriginTileT
+{
+public:
+ typedef typename Types::Tpf Tpf; ///< the pathfinder class (derived from THIS class)
+ typedef typename Types::NodeList::Titem Node; ///< this will be our node type
+ typedef typename Node::Key Key; ///< key to hash tables
+
+protected:
+ TileIndex m_orgTile; ///< origin tile
+ TrackdirBits m_orgTrackdirs; ///< origin trackdir mask
+
+ /** to access inherited path finder */
+ inline Tpf& Yapf()
+ {
+ return *static_cast(this);
+ }
+
+public:
+ /** Set origin tile / trackdir mask */
+ void SetOrigin(TileIndex tile, TrackdirBits trackdirs)
+ {
+ m_orgTile = tile;
+ m_orgTrackdirs = trackdirs;
+ }
+
+ /** Called when YAPF needs to place origin nodes into open list */
+ void PfSetStartupNodes()
+ {
+ bool is_choice = (KillFirstBit(m_orgTrackdirs) != TRACKDIR_BIT_NONE);
+ for (TrackdirBits tdb = m_orgTrackdirs; tdb != TRACKDIR_BIT_NONE; tdb = KillFirstBit(tdb)) {
+ Trackdir td = (Trackdir)FindFirstBit(tdb);
+ Node &n1 = Yapf().CreateNewNode();
+ n1.Set(nullptr, m_orgTile, td, is_choice, true);
+ DirDiff difference = NonOrientedDirDifference(TrackdirToDir(Yapf().GetVehicle()->trackdir), TrackdirToDir(td));
+ assert(difference % 2 == 0);
+ assert(difference <= 4);
+ n1.m_cost = (difference / 2) * YAPF_TILE_LENGTH;
+ Yapf().AddStartupNode(n1);
+ }
+ }
+};
+
+/** Destination module of YAPF for aircraft. */
+template
+class CYapfDestinationAircraftBase {
+protected:
+ AircraftState dest_state;
+ bool is_helicopter;
+
+public:
+ typedef typename Types::Tpf Tpf; ///< the pathfinder class (derived from THIS class)
+ typedef typename Types::NodeList::Titem Node; ///< this will be our node type
+ typedef typename Node::Key Key; ///< key to hash tables
+
+ /** to access inherited path finder */
+ Tpf& Yapf()
+ {
+ return *static_cast(this);
+ }
+
+ void SetDestination(bool is_helicopter, AircraftState dest_state)
+ {
+ this->dest_state = dest_state;
+ this->is_helicopter = is_helicopter;
+ }
+
+ /** Called by YAPF to detect if node ends in the desired destination */
+ inline bool PfDetectDestination(Node &n)
+ {
+ return PfDetectDestination(n.m_segment_last_tile, n.m_segment_last_td);
+ }
+
+ /** Called by YAPF to detect if node ends in the desired destination */
+ inline bool PfDetectDestination(TileIndex tile, Trackdir td)
+ {
+ if (!IsDiagonalTrackdir(td)) return false;
+
+ switch (this->dest_state) {
+ case AS_HANGAR:
+ /* Reserved extended hangars may be occupied for a long time,
+ so better try finding another hangar tile. */
+ return IsHangar(tile) && (IsStandardHangar(tile) || !HasAirportTileAnyReservation(tile));
+ case AS_HELIPAD:
+ return IsHelipadTile(tile) || IsPlaneApronTile(tile);
+ case AS_APRON:
+ return IsPlaneApronTile(tile);
+ case AS_ON_HOLD_APPROACHING:
+ case AS_DESCENDING:
+ return IsRunwayStart(tile) && IsLandingTypeTile(tile);
+ case AS_START_TAKEOFF:
+ return this->is_helicopter ? IsApron(tile) : IsRunwayStart(tile);
+ case AS_IDLE:
+ return false;
+ default:
+ NOT_REACHED();
+ }
+ }
+
+ /**
+ * Called by YAPF to calculate cost estimate. Calculates distance to the destination
+ * adds it to the actual cost from origin and stores the sum to the Node::m_estimate
+ */
+ inline bool PfCalcEstimate(Node &n)
+ {
+ n.m_estimate = n.m_cost;
+ return true;
+ }
+
+};
+
+/** Cost Provider module of YAPF for aircraft. */
+template
+class CYapfCostAircraftT
+{
+public:
+ typedef typename Types::Tpf Tpf; ///< the pathfinder class (derived from THIS class)
+ typedef typename Types::TrackFollower TrackFollower;
+ typedef typename Types::NodeList::Titem Node; ///< this will be our node type
+ typedef typename Node::Key Key; ///< key to hash tables
+
+protected:
+ /** to access inherited path finder */
+ Tpf& Yapf()
+ {
+ return *static_cast(this);
+ }
+
+public:
+ /**
+ * Called by YAPF to calculate the cost from the origin to the given node.
+ * Calculates only the cost of given node, adds it to the parent node cost
+ * and stores the result into Node::m_cost member
+ */
+ inline bool PfCalcCost(Node &n, const TrackFollower *)
+ {
+ int c = 0;
+
+ /* Is current trackdir a diagonal one? */
+ c += IsDiagonalTrackdir(n.GetTrackdir()) ? YAPF_TILE_LENGTH : YAPF_TILE_CORNER_LENGTH;
+
+ /* Reserved tiles. */
+ if (!IsAirportTileFree(n.GetTile(), n.GetTrackdir())) {
+ n.m_reservable = false;
+ c *= 4;
+ /* Prefer other possible paths where the destination is not occupied. */
+ if (Yapf().PfDetectDestination(n.GetTile(), n.GetTrackdir())) {
+ c *= 4;
+ }
+ }
+
+ /* Add cost to avoid vehicles going over aprons and other special tiles as much as possible. */
+ if (!IsSimpleTrack(n.GetTile())) c += 8 * YAPF_TILE_LENGTH;
+
+ if (n.GetTile() == n.m_parent->GetTile()) {
+ /* Penalty for rotating in a tile (middle or edge position). */
+ c += YAPF_TILE_LENGTH;
+
+ if (!IsDiagonalTrack(TrackdirToTrack(n.GetTrackdir())) ||
+ !IsDiagonalTrack(TrackdirToTrack(n.m_parent->GetTrackdir()))) {
+ /* Extra penalty for rotating at the edge of a tile. */
+ c += YAPF_TILE_LENGTH;
+ }
+ }
+
+ /* Penalty for curves. */
+ if (n.GetTrackdir() != NextTrackdir(n.m_parent->GetTrackdir())) {
+ /* New trackdir does not match the next one when going straight. */
+ c += YAPF_TILE_LENGTH;
+ }
+
+ /* Apply it. */
+ n.m_cost = n.m_parent->m_cost + c;
+ return true;
+ }
+};
+
+/**
+ * Config struct of YAPF for aircraft.
+ * Defines all 6 base YAPF modules as classes providing services for CYapfBaseT.
+ */
+template class TCYapfCostAircraftT>
+struct CYapfAircraft_TypesT
+{
+ /** Types - shortcut for this struct type */
+ typedef CYapfAircraft_TypesT Types;
+
+ /** Tpf - pathfinder type */
+ typedef Tpf_ Tpf;
+ /** track follower helper class */
+ typedef Ttrack_follower TrackFollower;
+ /** node list type */
+ typedef CAircraftNodeListTrackDir NodeList;
+ typedef Aircraft VehicleType;
+ /** pathfinder components (modules) */
+ typedef CYapfBaseT PfBase; // base pathfinder class
+ typedef CYapfFollowAircraftT PfFollow; // node follower
+ typedef CYapfAirportOriginTileT PfOrigin; // origin provider
+ typedef CYapfDestinationAircraftBase PfDestination; // destination/distance provider
+ typedef CYapfSegmentCostCacheNoneT PfCache; // segment cost cache provider
+ typedef TCYapfCostAircraftT PfCost; // cost provider
+};
+
+/* YAPF with reservation - uses TileIndex/Trackdir as Node key, allows 90-deg turns */
+struct CYapfAircraft : CYapfT > {};
+
+/** Aircraft controller helper - path finder invoker */
+Trackdir YapfAircraftFindPath(const Aircraft *v, PBSTileInfo &best_dest, bool &path_found, AircraftState dest_state, AircraftPathChoice &path_cache)
+{
+ return CYapfAircraft::ChooseAircraftPath(v, &best_dest, path_found, dest_state, path_cache);
+}
+
diff --git a/src/pathfinder/yapf/yapf_costrail.hpp b/src/pathfinder/yapf/yapf_costrail.hpp
index f6217d2b245ee..196403aa97ff4 100644
--- a/src/pathfinder/yapf/yapf_costrail.hpp
+++ b/src/pathfinder/yapf/yapf_costrail.hpp
@@ -150,13 +150,24 @@ class CYapfCostRailT : public CYapfCostBase {
return false;
}
+ /** Check for a reserved depot platform. */
+ inline bool IsAnyDepotTileReserved(TileIndex tile, Trackdir trackdir, int skipped)
+ {
+ TileIndexDiff diff = TileOffsByDiagDir(TrackdirToExitdir(ReverseTrackdir(trackdir)));
+ for (; skipped >= 0; skipped--, tile += diff) {
+ if (HasDepotReservation(tile)) return true;
+ }
+ return false;
+ }
+
/** The cost for reserved tiles, including skipped ones. */
inline int ReservationCost(Node &n, TileIndex tile, Trackdir trackdir, int skipped)
{
if (n.m_num_signals_passed >= m_sig_look_ahead_costs.size() / 2) return 0;
if (!IsPbsSignal(n.m_last_signal_type)) return 0;
- if (IsRailStationTile(tile) && IsAnyStationTileReserved(tile, trackdir, skipped)) {
+ if ((IsRailStationTile(tile) && IsAnyStationTileReserved(tile, trackdir, skipped)) ||
+ (IsExtendedRailDepotTile(tile) && IsAnyDepotTileReserved(tile, trackdir, skipped))) {
return Yapf().PfGetSettings().rail_pbs_station_penalty * (skipped + 1);
} else if (TrackOverlapsTracks(GetReservedTrackbits(tile), TrackdirToTrack(trackdir))) {
int cost = Yapf().PfGetSettings().rail_pbs_cross_penalty;
@@ -389,13 +400,12 @@ class CYapfCostRailT : public CYapfCostBase {
/* Tests for 'potential target' reasons to close the segment. */
if (cur.tile == prev.tile) {
/* Penalty for reversing in a depot. */
- assert(IsRailDepot(cur.tile));
+ assert(IsStandardRailDepot(cur.tile));
segment_cost += Yapf().PfGetSettings().rail_depot_reverse_penalty;
- } else if (IsRailDepotTile(cur.tile)) {
+ } else if (IsStandardRailDepotTile(cur.tile)) {
/* We will end in this pass (depot is possible target) */
end_segment_reason |= ESRB_DEPOT;
-
} else if (cur.tile_type == MP_STATION && IsRailWaypoint(cur.tile)) {
if (v->current_order.IsType(OT_GOTO_WAYPOINT) &&
GetStationIndex(cur.tile) == v->current_order.GetDestination() &&
@@ -440,14 +450,14 @@ class CYapfCostRailT : public CYapfCostBase {
/* Waypoint is also a good reason to finish. */
end_segment_reason |= ESRB_WAYPOINT;
- } else if (tf->m_is_station) {
+ } else if (tf->m_is_station || tf->m_is_extended_depot) {
/* Station penalties. */
uint platform_length = tf->m_tiles_skipped + 1;
/* We don't know yet if the station is our target or not. Act like
* if it is pass-through station (not our destination). */
segment_cost += Yapf().PfGetSettings().rail_station_penalty * platform_length;
/* We will end in this pass (station is possible target) */
- end_segment_reason |= ESRB_STATION;
+ end_segment_reason |= ESRB_PLATFORM;
} else if (TrackFollower::DoTrackMasking() && cur.tile_type == MP_RAILWAY) {
/* Searching for a safe tile? */
@@ -591,13 +601,21 @@ class CYapfCostRailT : public CYapfCostBase {
}
}
- /* Station platform-length penalty. */
- if ((end_segment_reason & ESRB_STATION) != ESRB_NONE) {
- const BaseStation *st = BaseStation::GetByTile(n.GetLastTile());
- assert(st != nullptr);
- uint platform_length = st->GetPlatformLength(n.GetLastTile(), ReverseDiagDir(TrackdirToExitdir(n.GetLastTrackdir())));
- /* Reduce the extra cost caused by passing-station penalty (each station receives it in the segment cost). */
+ /* Platform-length penalty. */
+ if ((end_segment_reason & ESRB_PLATFORM) != ESRB_NONE) {
+ assert(HasStationTileRail(n.GetLastTile()) || IsExtendedRailDepotTile(n.GetLastTile()));
+ uint platform_length = GetPlatformLength(n.GetLastTile(), ReverseDiagDir(TrackdirToExitdir(n.GetLastTrackdir())));
+ /* Reduce the extra cost caused by passing-platform penalty (each platform receives it in the segment cost). */
extra_cost -= Yapf().PfGetSettings().rail_station_penalty * platform_length;
+ if (tf->m_is_extended_depot) {
+ DepotReservation depot_reservation = GetDepotReservation(n.GetLastTile());
+ if (depot_reservation == DEPOT_RESERVATION_FULL_STOPPED_VEH) {
+ extra_cost += YAPF_INFINITE_PENALTY;
+ } else {
+ extra_cost += (HasDepotReservation(n.GetLastTile()) ? 2 : 1) * platform_length * Yapf().PfGetSettings().rail_station_penalty;
+ }
+ }
+
/* Add penalty for the inappropriate platform length. */
extra_cost += PlatformLengthPenalty(platform_length);
}
diff --git a/src/pathfinder/yapf/yapf_destrail.hpp b/src/pathfinder/yapf/yapf_destrail.hpp
index f39a8a2c4b40e..f6b871ea2c656 100644
--- a/src/pathfinder/yapf/yapf_destrail.hpp
+++ b/src/pathfinder/yapf/yapf_destrail.hpp
@@ -118,6 +118,7 @@ class CYapfDestinationTileOrStationRailT : public CYapfDestinationRailBase {
TileIndex m_destTile;
TrackdirBits m_destTrackdirs;
StationID m_dest_station_id;
+ DepotID m_dest_depot_id;
bool m_any_depot;
/** to access inherited path finder */
@@ -149,14 +150,25 @@ class CYapfDestinationTileOrStationRailT : public CYapfDestinationRailBase {
break;
case OT_GOTO_DEPOT:
+ m_dest_station_id = INVALID_STATION;
+
if (v->current_order.GetDepotActionType() & ODATFB_NEAREST_DEPOT) {
m_any_depot = true;
+ m_dest_depot_id = INVALID_DEPOT;
+ m_destTile = v->dest_tile;
+ m_destTrackdirs = TrackStatusToTrackdirBits(GetTileTrackStatus(v->dest_tile, TRANSPORT_RAIL, 0));
+ } else {
+ m_dest_depot_id = v->current_order.GetDestination();
+ assert(Depot::IsValidID(m_dest_depot_id));
+ m_destTile = CalcClosestDepotTile(m_dest_depot_id, v->tile);
+ m_destTrackdirs = INVALID_TRACKDIR_BIT;
}
- [[fallthrough]];
+ break;
default:
m_destTile = v->dest_tile;
m_dest_station_id = INVALID_STATION;
+ m_dest_depot_id = INVALID_DEPOT;
m_destTrackdirs = TrackStatusToTrackdirBits(GetTileTrackStatus(v->dest_tile, TRANSPORT_RAIL, 0));
break;
}
@@ -176,6 +188,10 @@ class CYapfDestinationTileOrStationRailT : public CYapfDestinationRailBase {
return HasStationTileRail(tile)
&& (GetStationIndex(tile) == m_dest_station_id)
&& (GetRailStationTrack(tile) == TrackdirToTrack(td));
+ } else if (m_dest_depot_id != INVALID_DEPOT) {
+ return IsRailDepotTile(tile)
+ && (GetDepotIndex(tile) == m_dest_depot_id)
+ && (GetRailDepotTrack(tile) == TrackdirToTrack(td));
}
if (m_any_depot) {
diff --git a/src/pathfinder/yapf/yapf_node_air.hpp b/src/pathfinder/yapf/yapf_node_air.hpp
new file mode 100644
index 0000000000000..1abc2ed1b8323
--- /dev/null
+++ b/src/pathfinder/yapf/yapf_node_air.hpp
@@ -0,0 +1,34 @@
+/*
+ * 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 yapf_node_aircraft.hpp Node tailored for aircraft pathfinding. */
+
+#ifndef YAPF_NODE_AIRCRAFT_HPP
+#define YAPF_NODE_AIRCRAFT_HPP
+
+/** Yapf Node for aircraft YAPF */
+template
+struct CYapfAircraftNodeT : CYapfNodeT > {
+ typedef CYapfNodeT > base;
+
+ TileIndex m_segment_last_tile;
+ Trackdir m_segment_last_td;
+ bool m_reservable;
+
+ void Set(CYapfAircraftNodeT *parent, TileIndex tile, Trackdir td, bool is_choice, bool reservable)
+ {
+ base::Set(parent, tile, td, is_choice);
+ m_segment_last_tile = tile;
+ m_segment_last_td = td;
+ m_reservable = reservable;
+ }
+};
+
+typedef CYapfAircraftNodeT CYapfAircraftNodeTrackDir;
+typedef CNodeList_HashTableT CAircraftNodeListTrackDir;
+
+#endif /* YAPF_NODE_AIRCRAFT_HPP */
diff --git a/src/pathfinder/yapf/yapf_rail.cpp b/src/pathfinder/yapf/yapf_rail.cpp
index 95800867991dc..d88e88110fb14 100644
--- a/src/pathfinder/yapf/yapf_rail.cpp
+++ b/src/pathfinder/yapf/yapf_rail.cpp
@@ -16,6 +16,7 @@
#include "yapf_destrail.hpp"
#include "../../viewport_func.h"
#include "../../newgrf_station.h"
+#include "../../platform_func.h"
#include "../../safeguards.h"
@@ -85,6 +86,23 @@ class CYapfReserveTrack
return true;
}
+ /** Reserve a railway platform. Tile contains the failed tile on abort. */
+ bool ReserveRailDepotPlatform(TileIndex &tile, DiagDirection dir)
+ {
+ assert(IsExtendedRailDepotTile(tile));
+ TileIndex start = tile;
+ TileIndexDiff diff = TileOffsByDiagDir(dir);
+
+ do {
+ if (HasDepotReservation(tile)) return false;
+ SetDepotReservation(tile, true);
+ MarkTileDirtyByTile(tile);
+ tile = TileAdd(tile, diff);
+ } while (IsCompatibleTrainDepotTile(tile, start) && tile != m_origin_tile);
+
+ return true;
+ }
+
/** Try to reserve a single track/platform. */
bool ReserveSingleTrack(TileIndex tile, Trackdir td)
{
@@ -94,6 +112,12 @@ class CYapfReserveTrack
m_res_fail_tile = tile;
m_res_fail_td = td;
}
+ } else if (IsExtendedRailDepotTile(tile)) {
+ if (!ReserveRailDepotPlatform(tile, TrackdirToExitdir(ReverseTrackdir(td)))) {
+ /* Platform could not be reserved, undo. */
+ m_res_fail_tile = tile;
+ m_res_fail_td = td;
+ }
} else {
if (!TryReserveRailTrack(tile, TrackdirToTrack(td))) {
/* Tile couldn't be reserved, undo. */
@@ -116,6 +140,13 @@ class CYapfReserveTrack
SetRailStationReservation(tile, false);
tile = TileAdd(tile, diff);
}
+ } else if (IsExtendedRailDepotTile(tile)) {
+ TileIndex start = tile;
+ TileIndexDiff diff = TileOffsByDiagDir(TrackdirToExitdir(ReverseTrackdir(td)));
+ while ((tile != m_res_fail_tile || td != m_res_fail_td) && IsCompatibleTrainDepotTile(tile, start)) {
+ SetDepotReservation(tile, false);
+ tile = TileAdd(tile, diff);
+ }
} else if (tile != m_res_fail_tile || td != m_res_fail_td) {
UnreserveRailTrack(tile, TrackdirToTrack(td));
}
@@ -646,7 +677,9 @@ bool YapfTrainFindNearestSafeTile(const Train *v, TileIndex tile, Trackdir td, b
/** if any track changes, this counter is incremented - that will invalidate segment cost cache */
int CSegmentCostCacheBase::s_rail_change_counter = 0;
+extern void FixBigRailDepotSprites(Tile tile);
void YapfNotifyTrackLayoutChange(TileIndex tile, Track track)
{
+ FixBigRailDepotSprites(tile);
CSegmentCostCacheBase::NotifyTrackLayoutChange(tile, track);
}
diff --git a/src/pathfinder/yapf/yapf_road.cpp b/src/pathfinder/yapf/yapf_road.cpp
index 209b64b52afe4..c1876dad13fa2 100644
--- a/src/pathfinder/yapf/yapf_road.cpp
+++ b/src/pathfinder/yapf/yapf_road.cpp
@@ -66,6 +66,19 @@ class CYapfCostRoadT
/* Increase the cost for level crossings */
if (IsLevelCrossing(tile)) {
cost += Yapf().PfGetSettings().road_crossing_penalty;
+ } else if (IsRoadDepot(tile) && IsExtendedRoadDepot(tile)) {
+ switch (GetDepotReservation(tile, IsDiagDirFacingSouth(TrackdirToExitdir(trackdir)))) {
+ case DEPOT_RESERVATION_FULL_STOPPED_VEH:
+ cost += 16 * YAPF_TILE_LENGTH;
+ break;
+ case DEPOT_RESERVATION_IN_USE:
+ cost += 8 * YAPF_TILE_LENGTH;
+ break;
+ case DEPOT_RESERVATION_EMPTY:
+ cost += YAPF_TILE_LENGTH;
+ break;
+ default: NOT_REACHED();
+ }
}
break;
@@ -135,7 +148,7 @@ class CYapfCostRoadT
}
/* stop if we have just entered the depot */
- if (IsRoadDepotTile(tile) && trackdir == DiagDirToDiagTrackdir(ReverseDiagDir(GetRoadDepotDirection(tile)))) {
+ if (IsRoadDepotTile(tile) && !IsExtendedRoadDepotTile(tile) && trackdir == DiagDirToDiagTrackdir(ReverseDiagDir(GetRoadDepotDirection(tile)))) {
/* next time we will reverse and leave the depot */
break;
}
@@ -201,7 +214,7 @@ class CYapfDestinationAnyDepotRoadT
/** Called by YAPF to detect if node ends in the desired destination */
inline bool PfDetectDestination(Node &n)
{
- return IsRoadDepotTile(n.m_segment_last_tile);
+ return PfDetectDestinationTile(n.m_segment_last_tile, n.m_segment_last_td);
}
inline bool PfDetectDestinationTile(TileIndex tile, Trackdir)
@@ -234,7 +247,9 @@ class CYapfDestinationTileRoadT
TileIndex m_destTile;
TrackdirBits m_destTrackdirs;
StationID m_dest_station;
+ DepotID m_dest_depot;
StationType m_station_type;
+ bool m_bus;
bool m_non_artic;
public:
@@ -252,8 +267,14 @@ class CYapfDestinationTileRoadT
m_destTile = CalcClosestStationTile(m_dest_station, v->tile, m_station_type);
m_non_artic = !v->HasArticulatedPart();
m_destTrackdirs = INVALID_TRACKDIR_BIT;
+ } else if (v->current_order.IsType(OT_GOTO_DEPOT)) {
+ m_dest_station = INVALID_STATION;
+ m_dest_depot = v->current_order.GetDestination();
+ m_destTile = CalcClosestDepotTile(m_dest_depot, v->tile);
+ m_destTrackdirs = INVALID_TRACKDIR_BIT;
} else {
m_dest_station = INVALID_STATION;
+ m_dest_depot = INVALID_DEPOT;
m_destTile = v->dest_tile;
m_destTrackdirs = TrackStatusToTrackdirBits(GetTileTrackStatus(v->dest_tile, TRANSPORT_ROAD, GetRoadTramType(v->roadtype)));
}
@@ -287,6 +308,11 @@ class CYapfDestinationTileRoadT
(m_non_artic || IsDriveThroughStopTile(tile));
}
+ if (m_dest_depot != INVALID_DEPOT) {
+ return IsRoadDepotTile(tile) &&
+ GetDepotIndex(tile) == m_dest_depot;
+ }
+
return tile == m_destTile && HasTrackdir(m_destTrackdirs, trackdir);
}
diff --git a/src/pathfinder/yapf/yapf_ship.cpp b/src/pathfinder/yapf/yapf_ship.cpp
index 57d5e9d87742d..b2b998ba8b4e0 100644
--- a/src/pathfinder/yapf/yapf_ship.cpp
+++ b/src/pathfinder/yapf/yapf_ship.cpp
@@ -382,6 +382,10 @@ class CYapfCostShipT
c += count * 3 * YAPF_TILE_LENGTH;
}
+ if (IsShipDepotTile(n.GetTile())) {
+ if (IsExtendedDepot(n.GetTile()) && IsDepotFullWithStoppedVehicles(n.GetTile())) c += YAPF_INFINITE_PENALTY;
+ }
+
/* Skipped tile cost for aqueducts. */
c += YAPF_TILE_LENGTH * tf->m_tiles_skipped;
diff --git a/src/pathfinder/yapf/yapf_type.hpp b/src/pathfinder/yapf/yapf_type.hpp
index 4f301b0fb719b..511f89be83460 100644
--- a/src/pathfinder/yapf/yapf_type.hpp
+++ b/src/pathfinder/yapf/yapf_type.hpp
@@ -23,7 +23,7 @@ enum EndSegmentReason {
ESR_CHOICE_FOLLOWS, ///< the next tile contains a choice (the track splits to more than one segments)
ESR_DEPOT, ///< stop in the depot (could be a target next time)
ESR_WAYPOINT, ///< waypoint encountered (could be a target next time)
- ESR_STATION, ///< station encountered (could be a target next time)
+ ESR_PLATFORM, ///< platform (station/extended depot) encountered (could be a target next time)
ESR_SAFE_TILE, ///< safe waiting position found (could be a target)
/* The following reasons are used only internally by PfCalcCost().
@@ -47,7 +47,7 @@ enum EndSegmentReasonBits {
ESRB_CHOICE_FOLLOWS = 1 << ESR_CHOICE_FOLLOWS,
ESRB_DEPOT = 1 << ESR_DEPOT,
ESRB_WAYPOINT = 1 << ESR_WAYPOINT,
- ESRB_STATION = 1 << ESR_STATION,
+ ESRB_PLATFORM = 1 << ESR_PLATFORM,
ESRB_SAFE_TILE = 1 << ESR_SAFE_TILE,
ESRB_PATH_TOO_LONG = 1 << ESR_PATH_TOO_LONG,
@@ -58,10 +58,10 @@ enum EndSegmentReasonBits {
/* Additional (composite) values. */
/* What reasons mean that the target can be found and needs to be detected. */
- ESRB_POSSIBLE_TARGET = ESRB_DEPOT | ESRB_WAYPOINT | ESRB_STATION | ESRB_SAFE_TILE,
+ ESRB_POSSIBLE_TARGET = ESRB_DEPOT | ESRB_WAYPOINT | ESRB_PLATFORM | ESRB_SAFE_TILE,
/* What reasons can be stored back into cached segment. */
- ESRB_CACHED_MASK = ESRB_DEAD_END | ESRB_RAIL_TYPE | ESRB_INFINITE_LOOP | ESRB_SEGMENT_TOO_LONG | ESRB_CHOICE_FOLLOWS | ESRB_DEPOT | ESRB_WAYPOINT | ESRB_STATION | ESRB_SAFE_TILE,
+ ESRB_CACHED_MASK = ESRB_DEAD_END | ESRB_RAIL_TYPE | ESRB_INFINITE_LOOP | ESRB_SEGMENT_TOO_LONG | ESRB_CHOICE_FOLLOWS | ESRB_DEPOT | ESRB_WAYPOINT | ESRB_PLATFORM | ESRB_SAFE_TILE,
/* Reasons to abort pathfinding in this direction. */
ESRB_ABORT_PF_MASK = ESRB_DEAD_END | ESRB_PATH_TOO_LONG | ESRB_INFINITE_LOOP | ESRB_FIRST_TWO_WAY_RED,
diff --git a/src/pbs.cpp b/src/pbs.cpp
index 363404330c5fc..27a366ff919e0 100644
--- a/src/pbs.cpp
+++ b/src/pbs.cpp
@@ -12,6 +12,8 @@
#include "vehicle_func.h"
#include "newgrf_station.h"
#include "pathfinder/follow_track.hpp"
+#include "platform_func.h"
+#include "depot_map.h"
#include "safeguards.h"
@@ -47,28 +49,6 @@ TrackBits GetReservedTrackbits(TileIndex t)
return TRACK_BIT_NONE;
}
-/**
- * Set the reservation for a complete station platform.
- * @pre IsRailStationTile(start)
- * @param start starting tile of the platform
- * @param dir the direction in which to follow the platform
- * @param b the state the reservation should be set to
- */
-void SetRailStationPlatformReservation(TileIndex start, DiagDirection dir, bool b)
-{
- TileIndex tile = start;
- TileIndexDiff diff = TileOffsByDiagDir(dir);
-
- assert(IsRailStationTile(start));
- assert(GetRailStationAxis(start) == DiagDirToAxis(dir));
-
- do {
- SetRailStationReservation(tile, b);
- MarkTileDirtyByTile(tile);
- tile = TileAdd(tile, diff);
- } while (IsCompatibleTrainStationTile(tile, start));
-}
-
/**
* Try to reserve a specific track on a tile
* @param tile the tile
@@ -202,12 +182,12 @@ static PBSTileInfo FollowReservation(Owner o, RailTypes rts, TileIndex tile, Tra
/* No reservation --> path end found */
if (reserved == TRACKDIR_BIT_NONE) {
- if (ft.m_is_station) {
+ if (ft.m_is_station || ft.m_is_extended_depot) {
/* Check skipped station tiles as well, maybe our reservation ends inside the station. */
TileIndexDiff diff = TileOffsByDiagDir(ft.m_exitdir);
while (ft.m_tiles_skipped-- > 0) {
ft.m_new_tile -= diff;
- if (HasStationReservation(ft.m_new_tile)) {
+ if ((ft.m_is_station && HasStationReservation(ft.m_new_tile)) || (ft.m_is_extended_depot && HasDepotReservation(ft.m_new_tile))) {
tile = ft.m_new_tile;
trackdir = DiagDirToDiagTrackdir(ft.m_exitdir);
break;
@@ -240,7 +220,7 @@ static PBSTileInfo FollowReservation(Owner o, RailTypes rts, TileIndex tile, Tra
if (tile == start_tile && trackdir == start_trackdir) break;
}
/* Depot tile? Can't continue. */
- if (IsRailDepotTile(tile)) break;
+ if (IsStandardRailDepotTile(tile)) break;
/* Non-pbs signal? Reservation can't continue. */
if (IsTileType(tile, MP_RAILWAY) && HasSignalOnTrackdir(tile, trackdir) && !IsPbsSignal(GetSignalType(tile, TrackdirToTrack(trackdir)))) break;
}
@@ -292,7 +272,7 @@ PBSTileInfo FollowTrainReservation(const Train *v, Vehicle **train_on_res)
TileIndex tile = v->tile;
Trackdir trackdir = v->GetVehicleTrackdir();
- if (IsRailDepotTile(tile) && !GetDepotReservationTrackBits(tile)) return PBSTileInfo(tile, trackdir, false);
+ if (IsStandardRailDepotTile(tile) && !GetDepotReservationTrackBits(tile)) return PBSTileInfo(tile, trackdir, false);
FindTrainOnTrackInfo ftoti;
ftoti.res = FollowReservation(v->owner, GetRailTypeInfo(v->railtype)->compatible_railtypes, tile, trackdir);
@@ -300,14 +280,14 @@ PBSTileInfo FollowTrainReservation(const Train *v, Vehicle **train_on_res)
if (train_on_res != nullptr) {
FindVehicleOnPos(ftoti.res.tile, &ftoti, FindTrainOnTrackEnum);
if (ftoti.best != nullptr) *train_on_res = ftoti.best->First();
- if (*train_on_res == nullptr && IsRailStationTile(ftoti.res.tile)) {
- /* The target tile is a rail station. The track follower
+ if (*train_on_res == nullptr && (IsRailStationTile(ftoti.res.tile) || IsExtendedRailDepotTile(ftoti.res.tile))) {
+ /* The target tile is a rail station or extended depot. The track follower
* has stopped on the last platform tile where we haven't
* found a train. Also check all previous platform tiles
* for a possible train. */
TileIndexDiff diff = TileOffsByDiagDir(TrackdirToExitdir(ReverseTrackdir(ftoti.res.trackdir)));
- for (TileIndex st_tile = ftoti.res.tile + diff; *train_on_res == nullptr && IsCompatibleTrainStationTile(st_tile, ftoti.res.tile); st_tile += diff) {
- FindVehicleOnPos(st_tile, &ftoti, FindTrainOnTrackEnum);
+ for (TileIndex pt_tile = ftoti.res.tile + diff; *train_on_res == nullptr && IsCompatiblePlatformTile(pt_tile, ftoti.res.tile); pt_tile += diff) {
+ FindVehicleOnPos(pt_tile, &ftoti, FindTrainOnTrackEnum);
if (ftoti.best != nullptr) *train_on_res = ftoti.best->First();
}
}
@@ -348,11 +328,11 @@ Train *GetTrainForReservation(TileIndex tile, Track track)
FindVehicleOnPos(ftoti.res.tile, &ftoti, FindTrainOnTrackEnum);
if (ftoti.best != nullptr) return ftoti.best;
- /* Special case for stations: check the whole platform for a vehicle. */
- if (IsRailStationTile(ftoti.res.tile)) {
+ /* Special case for stations and extended depots: check the whole platform for a vehicle. */
+ if (IsRailStationTile(ftoti.res.tile) || IsExtendedRailDepotTile(ftoti.res.tile)) {
TileIndexDiff diff = TileOffsByDiagDir(TrackdirToExitdir(ReverseTrackdir(ftoti.res.trackdir)));
- for (TileIndex st_tile = ftoti.res.tile + diff; IsCompatibleTrainStationTile(st_tile, ftoti.res.tile); st_tile += diff) {
- FindVehicleOnPos(st_tile, &ftoti, FindTrainOnTrackEnum);
+ for (TileIndex pt_tile = ftoti.res.tile + diff; IsCompatiblePlatformTile(pt_tile, ftoti.res.tile); pt_tile += diff) {
+ FindVehicleOnPos(pt_tile, &ftoti, FindTrainOnTrackEnum);
if (ftoti.best != nullptr) return ftoti.best;
}
}
@@ -379,7 +359,7 @@ Train *GetTrainForReservation(TileIndex tile, Track track)
*/
bool IsSafeWaitingPosition(const Train *v, TileIndex tile, Trackdir trackdir, bool include_line_end, bool forbid_90deg)
{
- if (IsRailDepotTile(tile)) return true;
+ if (IsStandardRailDepotTile(tile)) return true;
if (IsTileType(tile, MP_RAILWAY)) {
/* For non-pbs signals, stop on the signal tile. */
@@ -432,7 +412,7 @@ bool IsWaitingPositionFree(const Train *v, TileIndex tile, Trackdir trackdir, bo
if (TrackOverlapsTracks(reserved, track)) return false;
/* Not reserved and depot or not a pbs signal -> free. */
- if (IsRailDepotTile(tile)) return true;
+ if (IsStandardRailDepotTile(tile)) return true;
if (IsTileType(tile, MP_RAILWAY) && HasSignalOnTrackdir(tile, trackdir) && !IsPbsSignal(GetSignalType(tile, track))) return true;
/* Check the next tile, if it's a PBS signal, it has to be free as well. */
@@ -446,3 +426,26 @@ bool IsWaitingPositionFree(const Train *v, TileIndex tile, Trackdir trackdir, bo
return !HasReservedTracks(ft.m_new_tile, TrackdirBitsToTrackBits(ft.m_new_td_bits));
}
+
+/**
+ * Fix the sprites of depots to show it opened or closed depending on its neighbours.
+ * @param t Tile that has changed.
+ */
+void FixBigRailDepotSprites(Tile t)
+{
+ if (t == INVALID_TILE) return;
+
+ /* Expand tile area to check. */
+ TileArea ta = TileArea(t).Expand(1);
+
+ for (Tile tile : ta) {
+ if (!IsExtendedRailDepotTile(tile)) continue;
+ CFollowTrackRail ft(GetTileOwner(tile), GetRailTypeInfo(GetTileRailType(tile))->compatible_railtypes);
+ Track track = GetRailDepotTrack(tile);
+ Trackdir trackdir = TrackToTrackdir(track);
+ if (track == TRACK_X) trackdir = ReverseTrackdir(trackdir);
+ bool opened = ft.Follow(tile, trackdir);
+ if (track == TRACK_Y) opened = !opened;
+ SB(tile.m5(), 1, 1, opened);
+ }
+}
diff --git a/src/pbs_air.cpp b/src/pbs_air.cpp
new file mode 100644
index 0000000000000..e5423af928fa0
--- /dev/null
+++ b/src/pbs_air.cpp
@@ -0,0 +1,134 @@
+/*
+ * 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 pbs_air.cpp Path based system routines for air vehicles. */
+
+#include "stdafx.h"
+#include "air_map.h"
+#include "aircraft.h"
+#include "pathfinder/follow_track.hpp"
+
+#include "safeguards.h"
+
+/**
+ * When arriving at the end of a landing runway, choose an appropriate trackdir.
+ * Or when an helicopter lands in an apron, choose an appropriate one.
+ * @param tile
+ * @param preferred_trackdir
+ * @return a valid free trackdir (\a preferred_trackdir if possible).
+ */
+Trackdir GetFreeAirportTrackdir(TileIndex tile, Trackdir preferred_trackdir)
+{
+ if (tile == INVALID_TILE) return INVALID_TRACKDIR;
+ assert(IsValidTrackdir(preferred_trackdir));
+ assert(IsDiagonalTrackdir(preferred_trackdir));
+ assert(IsAirportTile(tile));
+ assert(MayHaveAirTracks(tile));
+ assert(IsApron(tile) || (IsRunwayExtreme(tile) && IsRunwayEnd(tile)));
+ if (HasAirportTrackReserved(tile)) return INVALID_TRACKDIR;
+
+ TrackBits tracks = GetAirportTileTracks(tile) & TRACK_BIT_CROSS;
+ if (tracks == TRACK_BIT_NONE) return INVALID_TRACKDIR;
+
+ Track preferred_track = TrackdirToTrack(preferred_trackdir);
+ if (HasTrack(tracks, preferred_track)) return preferred_trackdir;
+
+ tracks &= ~TrackToTrackBits(preferred_track);
+ if (tracks == TRACK_BIT_NONE) return INVALID_TRACKDIR;
+
+ preferred_track = RemoveFirstTrack(&tracks);
+
+ /* Get one trackdir of the two available trackdirs, better if the trackdir reaches tracks on next tile. */
+ CFollowTrackAirport fs(INVALID_COMPANY);
+ Trackdir trackdir = TrackToTrackdir(preferred_track);
+ if (fs.Follow(tile, trackdir)) return trackdir;
+ return ReverseTrackdir(trackdir);
+}
+
+/**
+ * Remove reservation of given aircraft.
+ * @param v vehicle that frees some reservations of tracks.
+ * @param skip_first_track whether skip the first reserved vehicle track.
+ */
+void LiftAirportPathReservation(Aircraft *v, bool skip_first_track)
+{
+ if ((v->vehstatus & VS_HIDDEN) != 0) return;
+
+ if (IsHeliportTile(v->tile)) {
+ /* Special case for heliports. */
+ assert(IsValidTrackdir(v->trackdir));
+ assert(IsDiagonalTrackdir(v->trackdir));
+ if (!skip_first_track) RemoveAirportTrackReservation(v->tile, TrackdirToTrack(v->trackdir));
+ return;
+ }
+
+ /* If not rotating, v->trackdir is the first trackdir.
+ * If rotating, v->next_trackdir contains the first trackdir (once it has rotated). */
+ Trackdir trackdir = v->next_trackdir == INVALID_TRACKDIR ? v->trackdir : v->next_trackdir;
+ assert(IsValidTrackdir(trackdir));
+ TileIndex tile = v->tile;
+ assert(IsAirportTile(tile));
+ assert(MayHaveAirTracks(tile));
+
+ CFollowTrackAirport fs(INVALID_COMPANY);
+ for (;;) {
+ assert(IsAirportTile(tile));
+ assert(MayHaveAirTracks(tile));
+ Track track = TrackdirToTrack(trackdir);
+ assert(HasAirportTrackReserved(tile, track));
+ RemoveAirportTrackReservation(tile, track);
+ TrackBits reserved = GetReservedAirportTracks(tile);
+
+ /* Find next part of the path. */
+ if ((reserved | TrackToTrackBits(track)) == TRACK_BIT_CROSS) {
+ /* Path continues in the same tile (middle tile rotation). */
+ assert(!v->path.empty());
+ assert(v->path.tile.front() == tile);
+ trackdir = v->path.td.front();
+ v->path.pop_front();
+ assert(IsValidTrackdir(trackdir));
+ continue;
+ }
+
+ DiagDirection exit_dir = TrackdirToExitdir(trackdir);
+ TrackdirBits edge_trackdirs = DiagdirReachesTrackdirs(ReverseDiagDir(exit_dir)) &
+ TrackBitsToTrackdirBits(reserved);
+ if (edge_trackdirs != TRACKDIR_BIT_NONE) {
+ assert(CountBits(edge_trackdirs) == 1);
+ trackdir = FindFirstTrackdir(edge_trackdirs);
+ /* Path continues in the same tile (rotation at the edge of the tile). */
+ continue;
+ }
+
+ if (!fs.Follow(tile, trackdir)) {
+ /* Can't follow path. Path end. */
+ assert(IsDiagonalTrackdir(trackdir));
+ break;
+ }
+
+ /* Path may continue ahead. Get the corresponding tile and trackdir, if any. */
+ fs.m_new_td_bits &= TrackBitsToTrackdirBits(GetReservedAirportTracks(fs.m_new_tile));
+ assert(CountBits(fs.m_new_td_bits) < 2);
+ if (fs.m_new_td_bits == TRACKDIR_BIT_NONE) {
+ /* Path reservation ended. */
+ assert(IsDiagonalTrackdir(trackdir));
+ break;
+ }
+
+ tile = fs.m_new_tile;
+ trackdir = FindFirstTrackdir(fs.m_new_td_bits);
+ }
+
+ if (skip_first_track) {
+ /* Full path unreserved, but must keep the first reserved track.
+ * Reserve it again. */
+ trackdir = v->next_trackdir == INVALID_TRACKDIR ? v->trackdir : v->next_trackdir;
+ SetAirportTrackReservation(v->tile, TrackdirToTrack(trackdir));
+ }
+
+ assert(v->path.empty());
+}
diff --git a/src/pbs_air.h b/src/pbs_air.h
new file mode 100644
index 0000000000000..7801c18563be9
--- /dev/null
+++ b/src/pbs_air.h
@@ -0,0 +1,20 @@
+/*
+ * 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 pbs_air.h PBS support routines for aircraft crossing tiles of an airport or just flying. */
+
+#ifndef PBS_AIR_H
+#define PBS_AIR_H
+
+#include "track_type.h"
+
+struct Aircraft;
+
+Trackdir GetFreeAirportTrackdir(TileIndex tile, Trackdir preferred_trackdir);
+void LiftAirportPathReservation(Aircraft *v, bool skip_first_track);
+
+#endif /* PBS_AIR_H */
diff --git a/src/pbs_water.cpp b/src/pbs_water.cpp
new file mode 100644
index 0000000000000..ce02e21f4c944
--- /dev/null
+++ b/src/pbs_water.cpp
@@ -0,0 +1,589 @@
+/*
+ * 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 pbs_water.cpp Path based system routines for water. */
+
+#include "stdafx.h"
+#include "viewport_func.h"
+#include "ship.h"
+#include "vehicle_func.h"
+#include "pathfinder/follow_track.hpp"
+#include "pbs_water.h"
+
+/**
+ * Return the reserved water track bits of the tile.
+ * @param t Tile to query.
+ * @return Reserved trackbits.
+ * @pre WaterTrackMayExist
+ */
+TrackBits GetReservedWaterTracks(TileIndex t)
+{
+ assert(WaterTrackMayExist(t));
+ if (IsTileType(t, MP_TUNNELBRIDGE)) {
+ if (HasTunnelBridgeReservation(t)) {
+ return (GetTunnelBridgeDirection(t) % 2) == 0 ? TRACK_BIT_X : TRACK_BIT_Y;
+ } else {
+ return TRACK_BIT_NONE;
+ }
+ }
+
+ byte track_b = GB(t.m8(), 12, 3);
+ if (track_b == 0) return TRACK_BIT_NONE;
+ Track track = (Track)(track_b - 1); // map array saves Track+1
+ return TrackToTrackBits(track) | (HasBit(t.m8(), 15) ? TrackToTrackBits(TrackToOppositeTrack(track)) : TRACK_BIT_NONE);
+}
+
+/**
+ * When trying to enter a tile with possible trackdirs,
+ * return only the tracks that don't collide with another path.
+ * @param t Tile
+ * @param trackdirs Track directions that are free.
+ * @return Track directions that, if taken, don't collide with other paths.
+ */
+TrackBits GetFreeWaterTrackReservation(TileIndex t, TrackdirBits trackdirs)
+{
+ TrackBits trackbits = TRACK_BIT_NONE;
+ for (Trackdir trackdir = RemoveFirstTrackdir(&trackdirs); trackdir != INVALID_TRACKDIR; trackdir = RemoveFirstTrackdir(&trackdirs)) {
+ if (IsWaterPositionFree(t, trackdir)) trackbits |= TrackToTrackBits(TrackdirToTrack(trackdir));
+ }
+ return trackbits;
+}
+
+/**
+ * Set the reserved tracks of a tile.
+ * @param t Tile to set.
+ * @param tracks Tracks to reserve on the tile \a t.
+ * @pre WaterTrackMayExist
+ */
+void SetWaterTrackReservation(TileIndex t, TrackBits tracks)
+{
+ if (!_settings_game.pf.ship_path_reservation) return;
+
+ Track track = RemoveFirstTrack(&tracks);
+ SB(t.m8(), 12, 3, track == INVALID_TRACK ? 0 : track + 1);
+ SB(t.m8(), 15, 1, tracks != TRACK_BIT_NONE);
+}
+
+/**
+ * Return true if a ship can try to cross the tile.
+ * @tile Tile to check.
+ * @return True if the tile may contain water tracks, false otherwise.
+ */
+bool WaterTrackMayExist(TileIndex t)
+{
+ return IsValidTile(t) && (
+ IsTileType(t, MP_WATER) ||
+ IsBuoyTile(t) || IsDockTile(t) ||
+ (IsTileType(t, MP_RAILWAY) && GetRailGroundType(t) == RAIL_GROUND_WATER) ||
+ (IsTileType(t, MP_TUNNELBRIDGE) && GetTunnelBridgeTransportType(t) == TRANSPORT_WATER)
+ );
+}
+
+/**
+ * Reserve a track of a tile.
+ * @param t Tile where to reserve the (water) track.
+ * @param b Track to reserve.
+ * @param value true if adding a reservation, false if removing it.
+ * @return True if the track has been reserved.
+ */
+bool SetWaterTrackReservation(TileIndex t, Track track, bool value)
+{
+ assert(WaterTrackMayExist(t));
+ assert(track != INVALID_TRACK);
+
+ if (!_settings_game.pf.ship_path_reservation && value) return true;
+
+ if (IsTileType(t, MP_TUNNELBRIDGE)) {
+ assert(IsDiagonalTrack(track));
+ if (value == HasTunnelBridgeReservation(t)) return false;
+
+ SetTunnelBridgeReservation(t, value);
+ TileArea ta(t);
+ t = GetOtherTunnelBridgeEnd(t);
+ SetTunnelBridgeReservation(t, value);
+ ta.Add(t);
+
+ for (TileIndex t : ta) MarkTileDirtyByTile(t);
+
+ return true;
+ }
+
+ TrackBits trackbits = GetReservedWaterTracks(t);
+
+ if (value) {
+ if (TrackOverlapsTracks(trackbits, track)) return false;
+ trackbits |= TrackToTrackBits(track);
+
+ SB(t.m8(), 12, 3, RemoveFirstTrack(&trackbits) + 1);
+ SB(t.m8(), 15, 1, trackbits != TRACK_BIT_NONE);
+ } else {
+ TrackBits removing_track = TrackToTrackBits(track);
+ if (!(trackbits & removing_track)) return false;
+ trackbits &= ~removing_track;
+ track = RemoveFirstTrack(&trackbits);
+ assert(trackbits == TRACK_BIT_NONE);
+ SB(t.m8(), 12, 3, track == INVALID_TRACK ? 0 : track + 1);
+ SB(t.m8(), 15, 1, 0);
+ }
+
+ MarkTileDirtyByTile(t);
+ return true;
+}
+
+/**
+ * There is a track already reserved?
+ * @param tile Tile to check.
+ * @return True if there is a track reserved on \a tile, false if none is reserved.
+ * @pre WaterTrackMayExist
+ */
+bool HasWaterTrackReservation(TileIndex t)
+{
+ assert(WaterTrackMayExist(t));
+ return GetReservedWaterTracks(t) != TRACK_BIT_NONE;
+}
+
+/**
+ * Are some of this tracks reserved?
+ * @param t Tile to check.
+ * @param tracks Tracks to check.
+ * @return True if any of the given tracks \a tracks is reserved on tile \a t.
+ */
+bool HasWaterTracksReserved(TileIndex t, TrackBits tracks)
+{
+ assert(WaterTrackMayExist(t));
+ return (GetReservedWaterTracks(t) & tracks) != TRACK_BIT_NONE;
+}
+
+/**
+ * Check if a track collides with the water tracks reserved on a tile.
+ * @param t Tile to check.
+ * @param track Track to check.
+ * @return True if the track can be reserved on tile \a t without colliding other reserved paths.
+ * @note It doesn't check next tile.
+ */
+bool TrackCollidesTrackReservation(TileIndex t, Track track)
+{
+ assert(WaterTrackMayExist(t));
+ return TrackOverlapsTracks(GetReservedWaterTracks(t), track);
+}
+
+/**
+ * Check if a tile can be reserved and does not collide with another path on next tile.
+ * @param tile The tile.
+ * @param trackdir The trackdir to check.
+ * @return True if reserving track direction \a trackdir on tile \a tile
+ * doesn't collide with other paths.
+ */
+bool IsWaterPositionFree(TileIndex tile, Trackdir trackdir)
+{
+ if (!_settings_game.pf.ship_path_reservation) return true;
+
+ /* Check the next tile, if a path collides, then it isn't a waiting position at all. */
+ CFollowTrackWater ft(INVALID_COMPANY);
+
+ /* Skip tiles of a lock. */
+ if (IsLockTile(tile)) {
+ while (ft.Follow(tile, trackdir) && CheckSameLock(tile, ft.m_new_tile)) {
+ tile = ft.m_new_tile;
+ }
+ }
+
+ Track track = TrackdirToTrack(trackdir);
+
+ /* Tile reserved? Can never be a free waiting position. */
+ if (TrackCollidesTrackReservation(tile, track)) return false;
+
+ if (!ft.Follow(tile, trackdir)) {
+ if (IsTileType(ft.m_new_tile, MP_INDUSTRY)) {
+ /* Let ships approach oil rigs. */
+ return true;
+ } else if (!(IsDockTile(tile) && IsDockTile(ft.m_new_tile))) {
+ /* Let ships cross docks when next tile is a dock. */
+ /* We can reverse on docks if needed, but only when next tile is a dock as well. */
+ return false;
+ }
+ }
+
+ /* On tunnels and bridges we must check the other bridge end. */
+ if (IsTileType(tile, MP_TUNNELBRIDGE) && IsTileType(ft.m_new_tile, MP_TUNNELBRIDGE) &&
+ GetOtherTunnelBridgeEnd(tile) == ft.m_new_tile) {
+ tile = ft.m_new_tile;
+ if (!ft.Follow(ft.m_new_tile, trackdir)) return false;
+ }
+
+ /* Check for reachable tracks.
+ * Don't discard 90deg turns as we don't want two paths to collide
+ * even if they cannot really collide because of a 90deg turn */
+ ft.m_new_td_bits &= DiagdirReachesTrackdirs(ft.m_exitdir);
+
+ return !HasWaterTracksReserved(ft.m_new_tile, TrackdirBitsToTrackBits(ft.m_new_td_bits));
+}
+
+/**
+ * Follow a reservation starting from a specific tile to its end.
+ * @param o Owner (unused: as ships can cross tiles of other owners).
+ * @param rts Former railtypes (unused: can be converted to water_types? sea, river, canal).
+ * @param tile Start tile.
+ * @param trackdir Track direction to look for.
+ * @return Last tile and info of the reserved path.
+ */
+static PBSTileInfo FollowShipReservation(Owner o, RailTypes rts, TileIndex tile, Trackdir trackdir)
+{
+ /* Start track not reserved? */
+ assert(HasWaterTracksReserved(tile, TrackToTrackBits(TrackdirToTrack(trackdir))));
+
+ /* Do not disallow 90 deg turns as the setting might have changed between reserving and now. */
+ CFollowTrackWater fs(o, rts);
+ while (fs.Follow(tile, trackdir)) {
+ TrackdirBits reserved = fs.m_new_td_bits & TrackBitsToTrackdirBits(GetReservedWaterTracks(fs.m_new_tile));
+ if (reserved == TRACKDIR_BIT_NONE) break;
+
+ /* Can't have more than one reserved trackdir */
+ trackdir = FindFirstTrackdir(reserved);
+ tile = fs.m_new_tile;
+ }
+
+ return PBSTileInfo(tile, trackdir, false);
+}
+
+/**
+ * Helper struct for finding the best matching vehicle on a specific track.
+ */
+struct FindShipOnTrackInfo {
+ PBSTileInfo res; ///< Information about the track.
+ Ship *best; ///< The currently "best" vehicle we have found.
+
+ /** Init the best ship to nullptr always! */
+ FindShipOnTrackInfo() : best(nullptr) {}
+};
+
+/**
+ * Callback for Has/FindVehicleOnPos to find a train on a specific track.
+ * @param v Vehicle to test.
+ * @param data FindshipOnTrackInfo.
+ * @return The vehicle on track or nullptr if none found.
+ */
+static Vehicle *FindShipOnTrackEnum(Vehicle *v, void *data)
+{
+ FindShipOnTrackInfo *info = (FindShipOnTrackInfo *)data;
+
+ if (v->type != VEH_SHIP) return nullptr;
+
+ Ship *s = Ship::From(v);
+ TrackBits tracks = s->state;
+
+ if (tracks == TRACK_BIT_DEPOT) return nullptr;
+
+ if (tracks == TRACK_BIT_WORMHOLE || HasBit(tracks, TrackdirToTrack(info->res.trackdir))) {
+ /* ALWAYS return the lowest ID (anti-desync!) */
+ if (info->best == nullptr || s->index < info->best->index) info->best = s;
+ return s;
+ }
+
+ return nullptr;
+}
+
+/**
+ * Follow the reserved path of a ship to its end.
+ * @param v The vehicle.
+ * @return The last tile of the reservation or the current ship tile if no reservation is present.
+ */
+PBSTileInfo FollowShipReservation(const Ship *v)
+{
+ assert(v->type == VEH_SHIP);
+
+ return FollowShipReservation(v->owner, (RailTypes)0, v->tile, v->GetVehicleTrackdir());
+}
+
+/**
+ * Find the ship which has reserved a specific path.
+ * @param tile A tile on the path.
+ * @param track A reserved track on the tile.
+ * @return The vehicle holding the reservation or nullptr if the path is stray.
+ */
+Ship *GetShipForReservation(TileIndex tile, Track track)
+{
+ assert(HasWaterTracksReserved(tile, TrackToTrackBits(track)));
+ Trackdir trackdir = TrackToTrackdir(track);
+
+ /* Follow the path from tile to both ends.
+ * One of the end tiles should have a ship on it. */
+ for (int i = 0; i < 2; ++i, trackdir = ReverseTrackdir(trackdir)) {
+ FindShipOnTrackInfo fsoti;
+ fsoti.res = FollowShipReservation(GetTileOwner(tile), (RailTypes)0, tile, trackdir);
+
+ FindVehicleOnPos(fsoti.res.tile, &fsoti, FindShipOnTrackEnum);
+ if (fsoti.best != nullptr) return fsoti.best;
+
+ /* Special case for bridges/tunnels: check the other end as well. */
+ if (IsTileType(fsoti.res.tile, MP_TUNNELBRIDGE)) {
+ FindVehicleOnPos(GetOtherTunnelBridgeEnd(fsoti.res.tile), &fsoti, FindShipOnTrackEnum);
+ if (fsoti.best != nullptr) return fsoti.best;
+ }
+
+ /* Special case for locks: check all lock tiles. */
+ if (IsLockTile(fsoti.res.tile)) {
+ TileIndex t = GetLockMiddleTile(fsoti.res.tile);
+ TileIndexDiff tilediff_to_lower = GetLockTileIndexDiffToLastLowerTile(t);
+ TileArea ta(t + tilediff_to_lower, t - tilediff_to_lower);
+
+ for (TileIndex t : ta) {
+ FindVehicleOnPos(t, &fsoti, FindShipOnTrackEnum);
+ if (fsoti.best != nullptr) return fsoti.best;
+ }
+ }
+ }
+
+ /* Ship that reserved a given path not found. */
+ return nullptr;
+}
+
+/**
+ * Remove a reservation starting on given tile with a given trackdir.
+ * @param tile Starting tile.
+ * @param trackdir Starting trackdir.
+ */
+void LiftShipPathReservation(TileIndex tile, Trackdir trackdir)
+{
+ if (!SetWaterTrackReservation(tile, TrackdirToTrack(trackdir), false)) NOT_REACHED();
+
+ CFollowTrackWater fs(INVALID_COMPANY);
+
+ while (fs.Follow(tile, trackdir)) {
+ /* Skip 2nd tile of an aqueduct. */
+ if (IsBridgeTile(tile) && IsBridgeTile(fs.m_new_tile) &&
+ GetOtherTunnelBridgeEnd(fs.m_new_tile) == tile) {
+ tile = fs.m_new_tile;
+ continue;
+ }
+
+ fs.m_new_td_bits &= TrackBitsToTrackdirBits(GetReservedWaterTracks(fs.m_new_tile));
+
+ /* Can't have more than one reserved trackdir */
+ trackdir = FindFirstTrackdir(fs.m_new_td_bits);
+ if (trackdir == INVALID_TRACKDIR) break;
+ tile = fs.m_new_tile;
+
+ if (!SetWaterTrackReservation(tile, TrackdirToTrack(trackdir), false)) NOT_REACHED();
+ }
+}
+
+/**
+ * Unreserve the path of a ship, keeping of course the current tile and track reserved.
+ * @param ship The ship we want to free the path of.
+ * @param tile The tile that asks the path to be freed (see note 2).
+ * @param track The track that asks to be freed (see note 2).
+ * @param keep_pref_water_trackdirs Whether to keep initial reservation for consistence
+ * with preferred water trackdirs.
+ * @note 1.- The path will not be freed if the ship tile and track is
+ * the same as the tile and track that ask to free the path.
+ * @note 2.- If @param tile is INVALID_TILE, then the algorithm removes the full path
+ * the ship, keeping a consistent path with preferred trackdirs
+ * if @param keep_pref_water_trackdirs is true.
+ */
+void LiftShipPathReservation(Ship *v, TileIndex tile, Track track, bool keep_pref_water_trackdirs)
+{
+ assert(v != nullptr);
+ if (v->tile == tile && TrackdirToTrack(v->GetVehicleTrackdir()) == track) return;
+
+ /* Do not disallow 90 deg turns as the setting might have changed between reserving and now. */
+ CFollowTrackWater fs(v->owner);
+
+ tile = v->tile;
+ Trackdir trackdir = v->GetVehicleTrackdir();
+
+ /* Skip first tile of a tunnel. */
+ if (IsTileType(tile, MP_TUNNELBRIDGE) && GetTunnelBridgeDirection(tile) == TrackdirToExitdir(trackdir)) {
+ fs.Follow(tile, trackdir);
+ tile = fs.m_new_tile;
+ trackdir = FindFirstTrackdir(fs.m_new_td_bits & TrackBitsToTrackdirBits(GetReservedWaterTracks(tile)));
+ }
+
+ bool check_first = true;
+ keep_pref_water_trackdirs &= HasPreferredWaterTrackdirs(v->tile) &&
+ !HasTrackdir(GetPreferredWaterTrackdirs(v->tile), trackdir);
+ TileIndex keep_lock_reserved = v->tile;
+
+ while (fs.Follow(tile, trackdir)) {
+ /* Skip 2nd tile of an aqueduct. */
+ if (IsBridgeTile(tile) && IsBridgeTile(fs.m_new_tile) &&
+ GetOtherTunnelBridgeEnd(fs.m_new_tile) == tile) {
+ tile = fs.m_new_tile;
+ continue;
+ }
+
+ fs.m_new_td_bits &= TrackBitsToTrackdirBits(GetReservedWaterTracks(fs.m_new_tile));
+
+ /* Can't have more than one reserved trackdir */
+ trackdir = FindFirstTrackdir(fs.m_new_td_bits);
+ if (trackdir == INVALID_TRACKDIR) break;
+
+ tile = fs.m_new_tile;
+
+ if (check_first) {
+ if (tile == v->dest_tile) return;
+ check_first = false;
+ }
+
+ /* Skip tiles of the same lock. */
+ if (CheckSameLock(keep_lock_reserved, tile)) continue;
+
+ if (keep_pref_water_trackdirs) {
+ if (HasPreferredWaterTrackdirs(tile) &&
+ !HasTrackdir(GetPreferredWaterTrackdirs(tile), trackdir)) continue;
+
+ /* A path must keep all lock tiles reserved. */
+ if (IsLockTile(tile)) keep_lock_reserved = tile;
+
+ keep_pref_water_trackdirs = false;
+ continue;
+ }
+
+ if (!SetWaterTrackReservation(tile, TrackdirToTrack(trackdir), false)) NOT_REACHED();
+ }
+}
+
+/**
+ * Free ship paths on a tile.
+ * @param tile Tile we want to free.
+ * @param keep_pref_water_trackdirs Whether keep preferred water trackdir paths if possible.
+ * @return True if tile has no reservation after the paths have been freed.
+ */
+bool LiftShipPathsReservations(TileIndex tile, bool keep_pref_water_trackdirs)
+{
+ if (!HasWaterTrackReservation(tile)) return true;
+
+ Track track;
+ for (Track track : SetTrackBitIterator(GetReservedWaterTracks(tile))) {
+ Ship *s = GetShipForReservation(tile, track);
+ LiftShipPathReservation(s, tile, track, keep_pref_water_trackdirs);
+ }
+
+ return !HasWaterTrackReservation(tile);
+}
+
+/**
+ * Check whether a tile has some preference for water trackdirs.
+ * @param tile Tile to check.
+ * @return true if there is some preferred trackdir.
+ */
+bool HasPreferredWaterTrackdirs(TileIndex tile)
+{
+ assert(WaterTrackMayExist(tile));
+
+ switch (GetTileType(tile)) {
+ case MP_STATION: return GB(t.m7(), 7, 1);
+ case MP_TUNNELBRIDGE: return GB(_m[tile].m3, 7, 1);
+ default: return GB(_m[tile].m2, 15, 1);
+ }
+}
+
+/**
+ * Get the preferred trackdirs for a water tile, if any is set.
+ * @param tile Tile to check.
+ * @return trackdir bits preferred on tile.
+ */
+TrackdirBits GetPreferredWaterTrackdirs(TileIndex tile)
+{
+ assert(WaterTrackMayExist(tile));
+
+ switch (GetTileType(tile)) {
+ case MP_RAILWAY:
+ return (HasBit(_m[tile].m2, 12) ? TRACKDIR_BIT_MASK_ES : TRACKDIR_BIT_NONE) |
+ (HasBit(_m[tile].m2, 13) ? TRACKDIR_BIT_MASK_WN : TRACKDIR_BIT_NONE);
+ case MP_WATER:
+ return (TrackdirBits)((GB(t.m6(), 2, 6) << 8) |
+ (GB(t.m6(), 0, 2) << 4) | GB(t.m7(), 4, 4));
+ case MP_STATION:
+ if (IsBuoy(tile)) {
+ return (TrackdirBits)(GB(tile.m5(), 2, 6) << 8 | GB(tile.m5(), 0, 2) << 4 |
+ GB(t.m6(), 0, 3) << 1 | GB(t.m7(), 6, 1));
+ } else {
+ assert(IsDock(tile));
+ return (TrackdirBits)(GB(tile.m4, 2, 2) << 8 | GB(tile.m4, 0, 2));
+ }
+ case MP_TUNNELBRIDGE: {
+ TrackdirBits trackdirs = TrackBitsToTrackdirBits(DiagDirToDiagTrackBits(GetTunnelBridgeDirection(tile)));
+ if (GB(_m[tile].m3, 5, 1) == 0) trackdirs &= TRACKDIR_BIT_MASK_WN;
+ if (GB(_m[tile].m3, 6, 1) == 0) trackdirs &= TRACKDIR_BIT_MASK_ES;
+ return trackdirs;
+ }
+ default:
+ NOT_REACHED();
+ }
+}
+
+/**
+ * Set some trackdir bits to a given value (1 preferred, 0 not preferred).
+ * @param tile Tile to modify.
+ * @param change_trackdirs TrackdirBits to modify.
+ * @param preference Value to set (1 preferred, 0 not preferred).
+ */
+void SetPreferredWaterTrackdirs(TileIndex tile, TrackdirBits change_trackdirs, bool preference)
+{
+ assert(WaterTrackMayExist(tile));
+ TrackdirBits present_trackdirs = GetPreferredWaterTrackdirs(tile);
+
+ if (preference == true) {
+ present_trackdirs |= change_trackdirs;
+ } else {
+ present_trackdirs &= ~change_trackdirs;
+ }
+
+ // Save updated trackdirs.
+ switch (GetTileType(tile)) {
+ case MP_RAILWAY:
+ SB(_m[tile].m2, 12, 1, ((present_trackdirs & TRACKDIR_BIT_MASK_ES) != TRACKDIR_BIT_NONE));
+ SB(_m[tile].m2, 13, 1, ((present_trackdirs & TRACKDIR_BIT_MASK_WN) != TRACKDIR_BIT_NONE));
+ break;
+ case MP_WATER:
+ SB(t.m6(), 0, 2, present_trackdirs >> 4);
+ SB(t.m6(), 2, 6, present_trackdirs >> 8);
+ SB(t.m7(), 4, 4, present_trackdirs);
+ break;
+ case MP_STATION:
+ if (IsBuoy(tile)) {
+ SB(tile.m5(), 0, 2, present_trackdirs >> 4);
+ SB(tile.m5(), 2, 6, present_trackdirs >> 8);
+ SB(t.m6(), 0, 3, present_trackdirs >> 1);
+ SB(t.m7(), 6, 1, present_trackdirs);
+ } else {
+ assert(IsDock(tile));
+ SB(tile.m4, 0, 2, present_trackdirs);
+ SB(tile.m4, 2, 2, present_trackdirs >> 8);
+ }
+ break;
+ case MP_TUNNELBRIDGE:
+ SB(_m[tile].m3, 5, 1, (present_trackdirs & TRACKDIR_BIT_MASK_ES) != TRACKDIR_BIT_NONE);
+ SB(_m[tile].m3, 6, 1, (present_trackdirs & TRACKDIR_BIT_MASK_WN) != TRACKDIR_BIT_NONE);
+ break;
+ default:
+ NOT_REACHED();
+ }
+
+ // Update whether tile has preferred water trackdirs.
+ switch (GetTileType(tile)) {
+ case MP_STATION:
+ SB(t.m7(), 7, 1, present_trackdirs != TRACKDIR_BIT_NONE);
+ return;
+ case MP_TUNNELBRIDGE:
+ SB(_m[tile].m3, 7, 1, present_trackdirs != TRACKDIR_BIT_NONE);
+ return;
+ default:
+ SB(_m[tile].m2, 15, 1, present_trackdirs != TRACKDIR_BIT_NONE);
+ return;
+ }
+}
+
+/**
+ * It clears all preferences on water trackdirs and then establishes the ones
+ * specified in trackdirs. */
+void ClearAndSetPreferredWaterTrackdirs(TileIndex tile, TrackdirBits trackdirs)
+{
+ SetPreferredWaterTrackdirs(tile, TRACKDIR_BIT_MASK, false);
+ if (trackdirs != TRACKDIR_BIT_NONE) SetPreferredWaterTrackdirs(tile, trackdirs, true);
+}
diff --git a/src/platform.cpp b/src/platform.cpp
new file mode 100644
index 0000000000000..55e0871d62e1b
--- /dev/null
+++ b/src/platform.cpp
@@ -0,0 +1,406 @@
+/*
+ * 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 platform.cpp Implementation of platform functions. */
+
+#include "stdafx.h"
+#include "station_map.h"
+#include "platform_func.h"
+#include "viewport_func.h"
+#include "depot_base.h"
+#include "vehicle_base.h"
+#include "engine_base.h"
+
+/**
+ * Set the reservation for a complete station platform.
+ * @pre IsRailStationTile(start)
+ * @param start starting tile of the platform
+ * @param dir the direction in which to follow the platform
+ * @param b the state the reservation should be set to
+ */
+void SetRailStationPlatformReservation(TileIndex start, DiagDirection dir, bool b)
+{
+ TileIndex tile = start;
+ TileIndexDiff diff = TileOffsByDiagDir(dir);
+
+ assert(IsRailStationTile(start));
+ assert(GetRailStationAxis(start) == DiagDirToAxis(dir));
+
+ do {
+ SetRailStationReservation(tile, b);
+ MarkTileDirtyByTile(tile);
+ tile = TileAdd(tile, diff);
+ } while (IsCompatibleTrainStationTile(tile, start));
+}
+
+
+/**
+ * Set the reservation for a complete depot platform.
+ * @pre IsExtendedRailDepotTile(start)
+ * @param start starting tile of the platform
+ * @param dir the direction in which to follow the platform
+ * @param b the state the reservation should be set to
+ */
+void SetRailDepotPlatformReservation(TileIndex start, DiagDirection dir, bool b)
+{
+ TileIndex tile = start;
+ TileIndexDiff diff = TileOffsByDiagDir(dir);
+
+ assert(IsExtendedRailDepotTile(start));
+ assert(GetRailDepotTrack(start) == DiagDirToDiagTrack(dir));
+
+ do {
+ SetDepotReservation(tile, b);
+ MarkTileDirtyByTile(tile);
+ tile = TileAdd(tile, diff);
+ } while (IsCompatibleTrainDepotTile(tile, start));
+}
+
+/**
+ * Set the reservation for a complete platform in a given direction.
+ * @param start starting tile of the platform
+ * @param dir the direction in which to follow the platform
+ * @param b the state the reservation should be set to
+ */
+void SetPlatformReservation(TileIndex start, DiagDirection dir, bool b)
+{
+ switch (GetPlatformType(start)) {
+ case PT_RAIL_STATION:
+ SetRailStationPlatformReservation(start, dir, b);
+ return;
+ case PT_RAIL_WAYPOINT:
+ SetRailStationReservation(start, b);
+ return;
+ case PT_RAIL_DEPOT:
+ SetRailDepotPlatformReservation(start, dir, b);
+ return;
+ default: NOT_REACHED();
+ }
+}
+
+/**
+ * Set the reservation for a complete platform.
+ * @param start A tile of the platform
+ * @param b the state the reservation should be set to
+ */
+void SetPlatformReservation(TileIndex start, bool b)
+{
+ DiagDirection dir;
+ switch (GetPlatformType(start)) {
+ case PT_RAIL_STATION:
+ NOT_REACHED();
+ case PT_RAIL_WAYPOINT:
+ NOT_REACHED();
+ case PT_RAIL_DEPOT:
+ assert(IsExtendedRailDepotTile(start));
+ dir = GetRailDepotDirection(start);
+ SetRailDepotPlatformReservation(start, dir, b);
+ SetRailDepotPlatformReservation(start, ReverseDiagDir(dir), b);
+ return;
+ default: NOT_REACHED();
+ }
+}
+
+/**
+ * Get the length of a rail station platform.
+ * @pre IsRailStationTile(tile)
+ * @param tile Tile to check
+ * @return The length of the platform in tile length.
+ */
+uint GetRailStationPlatformLength(TileIndex tile)
+{
+ assert(IsRailStationTile(tile));
+
+ TileIndexDiff delta = (GetRailStationAxis(tile) == AXIS_X ? TileDiffXY(1, 0) : TileDiffXY(0, 1));
+
+ TileIndex t = tile;
+ uint len = 0;
+ do {
+ t -= delta;
+ len++;
+ } while (IsCompatibleTrainStationTile(t, tile));
+
+ t = tile;
+ do {
+ t += delta;
+ len++;
+ } while (IsCompatibleTrainStationTile(t, tile));
+
+ return len - 1;
+}
+
+/**
+ * Get the length of a rail station platform in a given direction.
+ * @pre IsRailStationTile(tile)
+ * @param tile Tile to check
+ * @param dir Direction to check
+ * @return The length of the platform in tile length in the given direction.
+ */
+uint GetRailStationPlatformLength(TileIndex tile, DiagDirection dir)
+{
+ TileIndex start_tile = tile;
+ uint length = 0;
+ assert(IsRailStationTile(tile));
+ assert(dir < DIAGDIR_END);
+
+ do {
+ length++;
+ tile += TileOffsByDiagDir(dir);
+ } while (IsCompatibleTrainStationTile(tile, start_tile));
+
+ return length;
+}
+
+/**
+ * Get the length of a rail depot platform.
+ * @pre IsDepotTypeTile(tile, TRANSPORT_RAIL)
+ * @param tile Tile to check
+ * @return The length of the platform in tile length.
+ */
+uint GetRailDepotPlatformLength(TileIndex tile)
+{
+ assert(IsExtendedRailDepotTile(tile));
+
+ TileIndexDiff delta = (GetRailDepotTrack(tile) == TRACK_X ? TileDiffXY(1, 0) : TileDiffXY(0, 1));
+
+ TileIndex t = tile;
+ uint len = 0;
+ do {
+ t -= delta;
+ len++;
+ } while (IsCompatibleTrainDepotTile(t, tile));
+
+ t = tile;
+ do {
+ t += delta;
+ len++;
+ } while (IsCompatibleTrainDepotTile(t, tile));
+
+ return len - 1;
+}
+
+/**
+ * Get the length of a road depot platform.
+ * @pre IsDepotTypeTile(tile, TRANSPORT_ROAD)
+ * @param tile Tile to check
+ * @param rtt Whether to check for road or tram type.
+ * @return The length of the platform in tile length.
+ */
+uint GetRoadDepotPlatformLength(TileIndex tile, RoadTramType rtt)
+{
+ assert(IsExtendedRoadDepotTile(tile));
+
+ DiagDirection dir = GetRoadDepotDirection(tile);
+ TileIndexDiff delta = TileOffsByDiagDir(dir);
+
+ TileIndex t = tile;
+ uint len = 0;
+ do {
+ len++;
+ if ((GetRoadBits(t, rtt) & DiagDirToRoadBits(dir)) == ROAD_NONE) break;
+ t -= delta;
+ } while (IsCompatibleRoadDepotTile(t, tile, rtt));
+
+ t = tile;
+ dir = ReverseDiagDir(dir);
+ do {
+ len++;
+ if ((GetRoadBits(t, rtt) & DiagDirToRoadBits(dir)) == ROAD_NONE) break;
+ t += delta;
+ } while (IsCompatibleRoadDepotTile(t, tile, rtt));
+
+ return len - 1;
+}
+
+
+/**
+ * Get the length of a rail depot platform in a given direction.
+ * @pre IsRailDepotTile(tile)
+ * @param tile Tile to check
+ * @param dir Direction to check
+ * @return The length of the platform in tile length in the given direction.
+ */
+uint GetRailDepotPlatformLength(TileIndex tile, DiagDirection dir)
+{
+ TileIndex start_tile = tile;
+ uint length = 0;
+ assert(IsExtendedRailDepotTile(tile));
+ assert(dir < DIAGDIR_END);
+
+ do {
+ length++;
+ tile += TileOffsByDiagDir(dir);
+ } while (IsCompatibleTrainDepotTile(tile, start_tile));
+
+ return length;
+}
+
+/**
+ * Get the length of a road depot platform in a given direction.
+ * @pre IsRoadDepotTile(tile)
+ * @param tile Tile to check
+ * @param dir Direction to check
+ * @param rtt Whether to check for road or tram type.
+ * @return The length of the platform in tile length in the given direction.
+ */
+uint GetRoadDepotPlatformLength(TileIndex tile, DiagDirection dir, RoadTramType rtt)
+{
+ TileIndex start_tile = tile;
+ uint length = 0;
+ assert(IsExtendedRoadDepotTile(tile));
+ assert(dir < DIAGDIR_END);
+
+ do {
+ length++;
+ if ((GetRoadBits(tile, rtt) & DiagDirToRoadBits(dir)) == ROAD_NONE) break;
+ tile += TileOffsByDiagDir(dir);
+ } while (IsCompatibleRoadDepotTile(tile, start_tile, rtt));
+
+ return length;
+}
+
+/**
+ * Get the length of a platform.
+ * @param tile Tile to check
+ * @param rtt Whether to check for road or tram type (only for road transport).
+ * @return The length of the platform in tile length.
+ */
+uint GetPlatformLength(TileIndex tile, RoadTramType rtt)
+{
+ switch (GetPlatformType(tile)) {
+ case PT_RAIL_STATION:
+ return GetRailStationPlatformLength(tile);
+ case PT_RAIL_WAYPOINT:
+ return 1;
+ case PT_RAIL_DEPOT:
+ return GetRailDepotPlatformLength(tile);
+ case PT_ROAD_DEPOT:
+ return GetRoadDepotPlatformLength(tile, rtt);
+ default: NOT_REACHED();
+ }
+}
+
+/**
+ * Get the length of a rail depot platform in a given direction.
+ * @pre IsRailDepotTile(tile)
+ * @param tile Tile to check
+ * @param dir Direction to check
+ * @param rtt Whether to check for road or tram type (only for road transport).
+ * @return The length of the platform in tile length in the given direction.
+ */
+uint GetPlatformLength(TileIndex tile, DiagDirection dir, RoadTramType rtt)
+{
+ switch (GetPlatformType(tile)) {
+ case PT_RAIL_STATION:
+ return GetRailStationPlatformLength(tile, dir);
+ case PT_RAIL_WAYPOINT:
+ return 1;
+ case PT_RAIL_DEPOT:
+ return GetRailDepotPlatformLength(tile, dir);
+ case PT_ROAD_DEPOT:
+ return GetRoadDepotPlatformLength(tile, dir, rtt);
+ default: NOT_REACHED();
+ }
+}
+
+/**
+ * Get a tile where a rail station platform begins or ends.
+ * @pre IsRailStationTile(tile)
+ * @param tile Tile to check
+ * @param dir The diagonal direction to check
+ * @return The last tile of the platform seen from tile with direction dir.
+ */
+TileIndex GetRailStationExtreme(TileIndex tile, DiagDirection dir)
+{
+ assert(IsRailStationTile(tile));
+ assert(GetRailStationAxis(tile) == DiagDirToAxis(dir));
+ TileIndexDiff delta = TileOffsByDiagDir(dir);
+
+ TileIndex t = tile;
+ do {
+ t -= delta;
+ } while (IsCompatibleTrainStationTile(t, tile));
+
+ return t + delta;
+}
+
+/**
+ * Get a tile where a depot platform begins or ends.
+ * @pre IsExtendedDepotTile(tile)
+ * @param tile Tile to check
+ * @param dir The diagonal direction to check
+ * @return The last tile of the platform seen from tile with direction dir.
+ */
+TileIndex GetRailDepotExtreme(TileIndex tile, DiagDirection dir)
+{
+ assert(IsExtendedDepotTile(tile));
+ assert(GetRailDepotTrack(tile) == DiagDirToDiagTrack(dir));
+ TileIndexDiff delta = TileOffsByDiagDir(dir);
+
+ TileIndex t = tile;
+ do {
+ t -= delta;
+ } while (IsCompatibleTrainDepotTile(t, tile));
+
+ return t + delta;
+}
+
+/**
+ * Get a tile where a platform begins or ends.
+ * @param tile Tile to check
+ * @param dir Direction to check
+ * @return The last tile of the platform seen from tile with direction dir.
+ */
+TileIndex GetPlatformExtremeTile(TileIndex tile, DiagDirection dir)
+{
+ switch (GetPlatformType(tile)) {
+ case PT_RAIL_STATION:
+ return GetRailStationExtreme(tile, dir);
+ case PT_RAIL_WAYPOINT:
+ return tile;
+ case PT_RAIL_DEPOT:
+ return GetRailDepotExtreme(tile, dir);
+ default: NOT_REACHED();
+ }
+}
+
+/**
+ * Get the tiles belonging to a platform.
+ * @param tile Tile of a platform
+ * @return the tile area of the platform
+ */
+TileArea GetPlatformTileArea(TileIndex tile)
+{
+ switch (GetPlatformType(tile)) {
+ case PT_RAIL_STATION: {
+ assert(IsRailStationTile(tile));
+ DiagDirection dir = AxisToDiagDir(GetRailStationAxis(tile));
+ return TileArea(GetRailStationExtreme(tile, dir), GetRailStationExtreme(tile, ReverseDiagDir(dir)));
+ }
+ case PT_RAIL_WAYPOINT:
+ return TileArea(tile);
+ case PT_RAIL_DEPOT: {
+ assert(IsExtendedRailDepotTile(tile));
+ DiagDirection dir = GetRailDepotDirection(tile);
+ return TileArea(GetRailDepotExtreme(tile, dir), GetRailDepotExtreme(tile, ReverseDiagDir(dir)));
+ }
+ default: NOT_REACHED();
+ }
+}
+
+
+/**
+ * Check whether this tile is an extreme of a platform.
+ * @param tile Tile to check
+ * @return Whether the tile is the extreme of a platform.
+ */
+bool IsAnyStartPlatformTile(TileIndex tile)
+{
+ assert(IsExtendedRailDepotTile(tile));
+ DiagDirection dir = GetRailDepotDirection(tile);
+ return tile == GetPlatformExtremeTile(tile, dir) || tile == GetPlatformExtremeTile(tile, ReverseDiagDir(dir));
+}
diff --git a/src/platform_func.h b/src/platform_func.h
new file mode 100644
index 0000000000000..baa59128741b8
--- /dev/null
+++ b/src/platform_func.h
@@ -0,0 +1,171 @@
+/*
+ * 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 platform_func.h Functions related with platforms (tiles in a row that are connected somehow). */
+
+#ifndef PLATFORM_FUNC_H
+#define PLATFORM_FUNC_H
+
+#include "station_map.h"
+#include "depot_map.h"
+#include "platform_type.h"
+#include "road_map.h"
+
+/**
+ * Check if a tile is a valid continuation to a railstation tile.
+ * The tile \a test_tile is a valid continuation to \a station_tile, if all of the following are true:
+ * \li \a test_tile is a rail station tile
+ * \li the railtype of \a test_tile is compatible with the railtype of \a station_tile
+ * \li the tracks on \a test_tile and \a station_tile are in the same direction
+ * \li both tiles belong to the same station
+ * \li \a test_tile is not blocked (@see IsStationTileBlocked)
+ * @param test_tile Tile to test
+ * @param station_tile Station tile to compare with
+ * @pre IsRailStationTile(station_tile)
+ * @return true if the two tiles are compatible
+ */
+static inline bool IsCompatibleTrainStationTile(TileIndex test_tile, TileIndex station_tile)
+{
+ assert(IsRailStationTile(station_tile));
+ return IsRailStationTile(test_tile) && !IsStationTileBlocked(test_tile) &&
+ IsCompatibleRail(GetRailType(test_tile), GetRailType(station_tile)) &&
+ GetRailStationAxis(test_tile) == GetRailStationAxis(station_tile) &&
+ GetStationIndex(test_tile) == GetStationIndex(station_tile);
+}
+
+/**
+ * Check if a tile is a valid continuation to an extended rail depot tile.
+ * The tile \a test_tile is a valid continuation to \a depot_tile, if all of the following are true:
+ * \li \a test_tile is an extended depot tile
+ * \li \a test_tile and \a depot_tile have the same rail type
+ * \li the tracks on \a test_tile and \a depot_tile are in the same direction
+ * \li both tiles belong to the same depot
+ * @param test_tile Tile to test
+ * @param depot_tile Depot tile to compare with
+ * @pre IsExtendedRailDepotTile(depot_tile)
+ * @return true if the two tiles are compatible
+ */
+static inline bool IsCompatibleTrainDepotTile(TileIndex test_tile, TileIndex depot_tile)
+{
+ assert(IsExtendedRailDepotTile(depot_tile));
+ return IsExtendedRailDepotTile(test_tile) &&
+ GetRailType(test_tile) == GetRailType(depot_tile) &&
+ GetRailDepotTrack(test_tile) == GetRailDepotTrack(depot_tile) &&
+ GetDepotIndex(test_tile) == GetDepotIndex(depot_tile);
+}
+
+/**
+ * Check if a tile is a valid continuation to an extended road depot tile.
+ * The tile \a test_tile is a valid continuation to \a depot_tile, if all of the following are true:
+ * \li \a test_tile is an extended depot tile
+ * \li \a test_tile and \a depot_tile have the same road type and appropriate road bits
+ * \li the tracks on \a test_tile and \a depot_tile are in the same direction
+ * \li both tiles belong to the same depot
+ * @param test_tile Tile to test
+ * @param depot_tile Depot tile to compare with
+ * @param rtt Whether road or tram type.
+ * @pre IsExtendedRoadDepotTile(depot_tile)
+ * @return true if the two tiles are compatible
+ */
+static inline bool IsCompatibleRoadDepotTile(TileIndex test_tile, TileIndex depot_tile, RoadTramType rtt)
+{
+ assert(IsExtendedRoadDepotTile(depot_tile));
+ if (!IsExtendedRoadDepotTile(test_tile)) return false;
+ if (GetDepotIndex(test_tile) != GetDepotIndex(depot_tile)) return false;
+ if (GetRoadType(depot_tile, rtt) != GetRoadType(test_tile, rtt)) return false;
+
+ DiagDirection dir = DiagdirBetweenTiles(test_tile, depot_tile);
+ assert(dir != INVALID_DIAGDIR);
+ return (GetRoadBits(test_tile, rtt) & DiagDirToRoadBits(dir)) != ROAD_NONE;
+}
+
+/**
+ * Returns the type of platform of a given tile.
+ * @param tile Tile to check
+ * @return the type of platform (rail station, rail waypoint...)
+ */
+static inline PlatformType GetPlatformType(TileIndex tile)
+{
+ switch (GetTileType(tile)) {
+ case MP_STATION:
+ if (IsRailStation(tile)) return PT_RAIL_STATION;
+ if (IsRailWaypoint(tile)) return PT_RAIL_WAYPOINT;
+ break;
+ case MP_RAILWAY:
+ if (IsExtendedRailDepotTile(tile)) return PT_RAIL_DEPOT;
+ break;
+ case MP_ROAD:
+ if (IsExtendedRoadDepotTile(tile)) return PT_ROAD_DEPOT;
+ break;
+ default: break;
+ }
+
+ return INVALID_PLATFORM_TYPE;
+}
+
+/**
+ * Check whether a tile is a known platform type.
+ * @param tile to check
+ * @return whether the tile is a known platform type.
+ */
+static inline bool IsPlatformTile(TileIndex tile)
+{
+ return GetPlatformType(tile) != INVALID_PLATFORM_TYPE;
+}
+
+/**
+ * Check whether a platform tile is reserved.
+ * @param tile to check
+ * @return whether the platform tile is reserved
+ */
+static inline bool HasPlatformReservation(TileIndex tile)
+{
+ switch(GetPlatformType(tile)) {
+ case PT_RAIL_STATION:
+ case PT_RAIL_WAYPOINT:
+ return HasStationReservation(tile);
+ case PT_RAIL_DEPOT:
+ return HasDepotReservation(tile);
+ default: NOT_REACHED();
+ }
+}
+
+/**
+ * Check whether two tiles are compatible platform tiles: they must have the same
+ * platform type and (depending on the platform type) its railtype or other specs.
+ * @param test_tile the tile to check
+ * @param orig_tile the tile with the platform type we are interested in
+ * @param rtt Whether to check road or tram types (only for road transport);
+ * @return whether the two tiles are compatible tiles for defining a platform
+ */
+static inline bool IsCompatiblePlatformTile(TileIndex test_tile, TileIndex orig_tile, RoadTramType rtt = RTT_ROAD)
+{
+ switch (GetPlatformType(orig_tile)) {
+ case PT_RAIL_STATION:
+ return IsCompatibleTrainStationTile(test_tile, orig_tile);
+ case PT_RAIL_WAYPOINT:
+ return test_tile == orig_tile;
+ case PT_RAIL_DEPOT:
+ return IsCompatibleTrainDepotTile(test_tile, orig_tile);
+ case PT_ROAD_DEPOT:
+ return IsCompatibleRoadDepotTile(test_tile, orig_tile, rtt);
+ default: NOT_REACHED();
+ }
+}
+
+void SetPlatformReservation(TileIndex start, DiagDirection dir, bool b);
+void SetPlatformReservation(TileIndex start, bool b);
+
+uint GetPlatformLength(TileIndex tile, RoadTramType rtt = RTT_ROAD);
+uint GetPlatformLength(TileIndex tile, DiagDirection dir, RoadTramType rtt = RTT_ROAD);
+
+TileIndex GetPlatformExtremeTile(TileIndex tile, DiagDirection dir);
+TileArea GetPlatformTileArea(TileIndex tile);
+
+bool IsAnyStartPlatformTile(TileIndex tile);
+
+#endif /* PLATFORM_FUNC_H */
diff --git a/src/platform_type.h b/src/platform_type.h
new file mode 100644
index 0000000000000..e6c543465a91d
--- /dev/null
+++ b/src/platform_type.h
@@ -0,0 +1,24 @@
+/*
+ * 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 platform_type.h Types related to platforms. */
+
+#ifndef PLATFORM_TYPE_H
+#define PLATFORM_TYPE_H
+
+#include "core/enum_type.hpp"
+
+enum PlatformType {
+ PT_RAIL_STATION,
+ PT_RAIL_WAYPOINT,
+ PT_RAIL_DEPOT,
+ PT_ROAD_DEPOT,
+ PT_END,
+ INVALID_PLATFORM_TYPE = PT_END,
+};
+
+#endif /* PLATFORM_TYPE_H */
diff --git a/src/rail.cpp b/src/rail.cpp
index 290248ac88212..ec89178e8cdfb 100644
--- a/src/rail.cpp
+++ b/src/rail.cpp
@@ -73,6 +73,13 @@ extern const TrackBits _track_crosses_tracks[] = {
TRACK_BIT_HORZ // TRACK_RIGHT
};
+/* Maps a trackdir to the (4-way) direction the tile was entered from to follow
+ * that trackdir */
+extern const DiagDirection _trackdir_to_entrydir[TRACKDIR_END] = {
+ DIAGDIR_NE, DIAGDIR_SE, DIAGDIR_SE, DIAGDIR_NE, DIAGDIR_SE, DIAGDIR_SW, INVALID_DIAGDIR, INVALID_DIAGDIR,
+ DIAGDIR_SW, DIAGDIR_NW, DIAGDIR_SW, DIAGDIR_NW, DIAGDIR_NE, DIAGDIR_NW,
+};
+
/* Maps a trackdir to the (4-way) direction the tile is exited when following
* that trackdir */
extern const DiagDirection _trackdir_to_exitdir[TRACKDIR_END] = {
@@ -80,6 +87,12 @@ extern const DiagDirection _trackdir_to_exitdir[TRACKDIR_END] = {
DIAGDIR_SW, DIAGDIR_NW, DIAGDIR_NW, DIAGDIR_SW, DIAGDIR_NW, DIAGDIR_NE,
};
+/* Maps a trackdir to the corresponding 8-way direction */
+extern const Direction _trackdir_to_direction[TRACKDIR_END] = {
+ DIR_NE, DIR_SE, DIR_E, DIR_E, DIR_S, DIR_S, INVALID_DIR, INVALID_DIR,
+ DIR_SW, DIR_NW, DIR_W, DIR_W, DIR_N, DIR_N, INVALID_DIR, INVALID_DIR,
+};
+
extern const Trackdir _track_exitdir_to_trackdir[][DIAGDIR_END] = {
{TRACKDIR_X_NE, INVALID_TRACKDIR, TRACKDIR_X_SW, INVALID_TRACKDIR},
{INVALID_TRACKDIR, TRACKDIR_Y_SE, INVALID_TRACKDIR, TRACKDIR_Y_NW},
diff --git a/src/rail.h b/src/rail.h
index 149996aae0cdc..f15b9d67039ba 100644
--- a/src/rail.h
+++ b/src/rail.h
@@ -337,6 +337,19 @@ inline bool HasPowerOnRail(RailType enginetype, RailType tiletype)
return HasBit(GetRailTypeInfo(enginetype)->powered_railtypes, tiletype);
}
+/**
+ * Checks if an engine with a given \a enginetype is powered on \a rail_types.
+ * This would normally just be an equality check,
+ * but for electric rails (which also support non-electric vehicles).
+ * @param enginetype The RailType of the engine we are considering.
+ * @param rail_types The RailTypes we are considering.
+ * @return Whether the engine got power on this tile.
+ */
+static inline bool HasPowerOnRails(RailType enginetype, RailTypes rail_types)
+{
+ return (GetRailTypeInfo(enginetype)->powered_railtypes & rail_types) != 0;
+}
+
/**
* Test if a RailType disallows build of level crossings.
* @param rt The RailType to check.
diff --git a/src/rail_cmd.cpp b/src/rail_cmd.cpp
index 0429661eda40c..a6e6949b74bfa 100644
--- a/src/rail_cmd.cpp
+++ b/src/rail_cmd.cpp
@@ -33,6 +33,7 @@
#include "object_map.h"
#include "rail_cmd.h"
#include "landscape_cmd.h"
+#include "platform_func.h"
#include "table/strings.h"
#include "table/railtypes.h"
@@ -814,7 +815,7 @@ static const TileIndexDiffC _trackdelta[] = {
};
-static CommandCost ValidateAutoDrag(Trackdir *trackdir, TileIndex start, TileIndex end)
+CommandCost ValidateAutoDrag(Trackdir *trackdir, TileIndex start, TileIndex end)
{
int x = TileX(start);
int y = TileY(start);
@@ -952,81 +953,163 @@ CommandCost CmdRemoveRailroadTrack(DoCommandFlag flags, TileIndex end_tile, Tile
/**
* Build a train depot
* @param flags operation to perform
- * @param tile position of the train depot
+ * @param tile first position of the train depot
* @param railtype rail type
* @param dir entrance direction
+ * @param adjacent allow adjacent depots
+ * @param extended build extended depots
+ * @param join_to depot to join to
+ * @param end_tile end tile of the area to be built
* @return the cost of this operation or an error
*
* @todo When checking for the tile slope,
* distinguish between "Flat land required" and "land sloped in wrong direction"
*/
-CommandCost CmdBuildTrainDepot(DoCommandFlag flags, TileIndex tile, RailType railtype, DiagDirection dir)
+CommandCost CmdBuildTrainDepot(DoCommandFlag flags, TileIndex tile, RailType railtype, DiagDirection dir, bool adjacent, bool extended, DepotID join_to, TileIndex end_tile)
{
/* check railtype and valid direction for depot (0 through 3), 4 in total */
if (!ValParamRailType(railtype) || !IsValidDiagDirection(dir)) return CMD_ERROR;
- Slope tileh = GetTileSlope(tile);
+ if (Company::IsValidHumanID(_current_company) && !HasBit(_settings_game.depot.rail_depot_types, extended)) return_cmd_error(STR_ERROR_DEPOT_TYPE_NOT_AVAILABLE);
CommandCost cost(EXPENSES_CONSTRUCTION);
+ TileArea ta(tile, end_tile);
+ Depot *depot = nullptr;
+
+ /* Create a new depot or find a depot to join to. */
+ CommandCost ret = FindJoiningDepot(ta, VEH_TRAIN, join_to, depot, adjacent, flags);
+ if (ret.Failed()) return ret;
+
+ Axis axis = DiagDirToAxis(dir);
+ /* Do not allow extending already occupied platforms. */
+ if (extended && join_to != NEW_DEPOT) {
+ TileArea ta_ext = TileArea(ta.tile, ta.w, ta.h).Expand(1);
+
+ uint max_coord;
+ uint min_coord;
+ if (axis == AXIS_X) {
+ min_coord = TileY(ta.tile);
+ max_coord = min_coord + ta.h;
+ } else {
+ min_coord = TileX(ta.tile);
+ max_coord = min_coord + ta.w;
+ }
+
+ for (Tile t : ta_ext) {
+ if (!IsExtendedRailDepotTile(t)) continue;
+ if (GetDepotIndex(t) != depot->index) continue;
+ if (GetRailType(t) != railtype) continue;
+ if (!HasDepotReservation(t)) continue;
+ if (DiagDirToAxis(GetRailDepotDirection(t)) != axis) continue;
+ uint current = (axis == AXIS_X) ? TileY(t) : TileX(t);
+ if (!IsInsideMM(current, min_coord, max_coord)) continue;
+ return_cmd_error(STR_ERROR_DEPOT_EXTENDING_PLATFORMS);
+ }
+ }
+
+ uint8_t num_new_depot_tiles = 0;
+ uint8_t num_overbuilt_depot_tiles = 0;
/* Prohibit construction if
* The tile is non-flat AND
* 1) build-on-slopes is disabled
* 2) the tile is steep i.e. spans two height levels
* 3) the exit points in the wrong direction
+ * 4) the tile is not an already built depot (or it is a compatible single rail tile for building extended depots)
*/
+ for (Tile t : ta) {
+ if (IsBridgeAbove(t)) return_cmd_error(STR_ERROR_MUST_DEMOLISH_BRIDGE_FIRST);
+
+ Slope tileh = GetTileSlope(t);
+ if (tileh != SLOPE_FLAT) {
+ if (!_settings_game.construction.build_on_slopes ||
+ !CanBuildDepotByTileh(dir, tileh)) {
+ return_cmd_error(STR_ERROR_FLAT_LAND_REQUIRED);
+ }
+ if (extended && !CanBuildDepotByTileh(ReverseDiagDir(dir), tileh)) {
+ return_cmd_error(STR_ERROR_FLAT_LAND_REQUIRED);
+ }
+ cost.AddCost(_price[PR_BUILD_FOUNDATION]);
+ }
+
+ if (extended) {
+ if (IsPlainRailTile(t) && !HasSignals(t) && GetRailType(t) == railtype) {
+ /* Allow overbuilding if the tile:
+ * - has rail, but no signals
+ * - it has exactly one track
+ * - the track is in line with the depot
+ * - the current rail type is the same as the to-be-built
+ */
+ TrackBits tracks = GetTrackBits(t);
+ Track track = RemoveFirstTrack(&tracks);
+ uint invalid_dirs = 5 << DiagDirToAxis(dir);
+ Track expected_track = HasBit(invalid_dirs, DIAGDIR_NE) ? TRACK_X : TRACK_Y;
+
+ if (tracks == TRACK_BIT_NONE && track == expected_track) {
+ cost.AddCost(Command::Do(flags, t, track).GetCost());
+ /* With flags & ~DC_EXEC CmdLandscapeClear would fail since the rail still exists */
+ if (cost.Failed()) return cost;
+ goto new_depot_tile;
+ }
+ }
- if (tileh != SLOPE_FLAT) {
- if (!_settings_game.construction.build_on_slopes || !CanBuildDepotByTileh(dir, tileh)) {
- return_cmd_error(STR_ERROR_FLAT_LAND_REQUIRED);
- }
- cost.AddCost(_price[PR_BUILD_FOUNDATION]);
- }
-
- /* Allow the user to rotate the depot instead of having to destroy it and build it again */
- bool rotate_existing_depot = false;
- if (IsRailDepotTile(tile) && railtype == GetRailType(tile)) {
- CommandCost ret = CheckTileOwnership(tile);
- if (ret.Failed()) return ret;
-
- if (dir == GetRailDepotDirection(tile)) return CommandCost();
+ /* Skip already existing and compatible extended depots. */
+ if (IsRailDepotTile(t) && IsExtendedRailDepotTile(t) &&
+ GetDepotIndex(t) == join_to && railtype == GetRailType(t)) {
+ if (axis == DiagDirToAxis(GetRailDepotDirection(t))) continue;
+ }
+ } else {
+ /* Check whether this is a standard depot tile and it needs to be rotated. */
+ if (IsRailDepotTile(t) && IsStandardRailDepotTile(t) &&
+ GetDepotIndex(t) == join_to && railtype == GetRailType(t)) {
+ if (dir == GetRailDepotDirection(t)) continue;
- ret = EnsureNoVehicleOnGround(tile);
- if (ret.Failed()) return ret;
+ ret = EnsureNoVehicleOnGround(t);
+ if (ret.Failed()) return ret;
- rotate_existing_depot = true;
- }
+ num_overbuilt_depot_tiles++;
+ if (flags & DC_EXEC) {
+ SetRailDepotExitDirection(t, dir);
+ AddSideToSignalBuffer(t, INVALID_DIAGDIR, _current_company);
+ YapfNotifyTrackLayoutChange(t, DiagDirToDiagTrack(dir));
+ MarkTileDirtyByTile(t);
+ }
+ continue;
+ }
+ }
- if (!rotate_existing_depot) {
- cost.AddCost(Command::Do(flags, tile));
+ cost.AddCost(Command::Do(flags, t));
if (cost.Failed()) return cost;
- if (IsBridgeAbove(tile)) return_cmd_error(STR_ERROR_MUST_DEMOLISH_BRIDGE_FIRST);
+new_depot_tile:
+ num_new_depot_tiles++;
- if (!Depot::CanAllocateItem()) return CMD_ERROR;
- }
+ if (flags & DC_EXEC) {
+ MakeRailDepot(t, _current_company, depot->index, dir, railtype);
+ SB(t.m5(), 5, 1, extended);
- if (flags & DC_EXEC) {
- if (rotate_existing_depot) {
- SetRailDepotExitDirection(tile, dir);
- } else {
- Depot *d = new Depot(tile);
- d->build_date = TimerGameCalendar::date;
+ if (extended) {
+ AddTrackToSignalBuffer(t, DiagDirToDiagTrack(dir), _current_company);
+ } else {
+ AddSideToSignalBuffer(t, INVALID_DIAGDIR, _current_company);
+ }
+ YapfNotifyTrackLayoutChange(t, DiagDirToDiagTrack(dir));
+ MarkTileDirtyByTile(t);
+ }
+ }
- MakeRailDepot(tile, _current_company, d->index, dir, railtype);
- MakeDefaultName(d);
+ if (num_new_depot_tiles + num_overbuilt_depot_tiles == 0) return CommandCost();
- Company::Get(_current_company)->infrastructure.rail[railtype]++;
- DirtyCompanyInfrastructureWindows(_current_company);
- }
+ cost.AddCost(_price[PR_BUILD_DEPOT_TRAIN] * (num_new_depot_tiles + num_overbuilt_depot_tiles));
+ cost.AddCost(RailBuildCost(railtype) * (num_new_depot_tiles + num_overbuilt_depot_tiles));
- MarkTileDirtyByTile(tile);
- AddSideToSignalBuffer(tile, INVALID_DIAGDIR, _current_company);
- YapfNotifyTrackLayoutChange(tile, DiagDirToDiagTrack(dir));
+ if (flags & DC_EXEC) {
+ Company::Get(_current_company)->infrastructure.rail[railtype] += num_new_depot_tiles;
+ DirtyCompanyInfrastructureWindows(_current_company);
+ depot->AfterAddRemove(ta, true);
+ if (join_to == NEW_DEPOT) MakeDefaultName(depot);
}
- cost.AddCost(_price[PR_BUILD_DEPOT_TRAIN]);
- cost.AddCost(RailBuildCost(railtype));
return cost;
}
@@ -1540,6 +1623,56 @@ static Vehicle *UpdateTrainPowerProc(Vehicle *v, void *data)
return nullptr;
}
+/**
+ * Returns whether a depot has an extended depot
+ * tile which is reserved.
+ * @param Depot pointer to a depot
+ * @return true iff \a dep has an extended depot tile reserved.
+ */
+bool HasAnyExtendedDepotReservedTile(Depot *dep)
+{
+ assert(dep != nullptr);
+ for (TileIndex tile : dep->ta) {
+ if (!IsExtendedDepotTile(tile)) continue;
+ if (GetDepotIndex(tile) != dep->index) continue;
+ if (HasDepotReservation(tile)) return true;
+ }
+
+ return false;
+}
+
+CommandCost ConvertExtendedDepot(DoCommandFlag flags, Depot *dep, RailType rail_type)
+{
+ CommandCost cost(EXPENSES_CONSTRUCTION);
+ assert(dep->owner == _current_company);
+ Company *c = Company::Get(dep->owner);
+
+ for (TileIndex tile : dep->ta) {
+ if (!IsDepotTile(tile)) continue;
+ if (GetDepotIndex(tile) != dep->index) continue;
+ assert(!HasDepotReservation(tile));
+ assert(dep->owner == GetTileOwner(tile));
+
+ /* Original railtype we are converting from */
+ RailType type = GetRailType(tile);
+
+ if (type == rail_type || (_settings_game.vehicle.disable_elrails && rail_type == RAILTYPE_RAIL && type == RAILTYPE_ELECTRIC)) continue;
+
+ cost.AddCost(RailConvertCost(type, rail_type));
+
+ if (flags & DC_EXEC) {
+ c->infrastructure.rail[type]--;
+ c->infrastructure.rail[rail_type]++;
+ SetRailType(tile, rail_type);
+ MarkTileDirtyByTile(tile);
+ YapfNotifyTrackLayoutChange(tile, GetRailDepotTrack(tile));
+ DirtyCompanyInfrastructureWindows(c->index);
+ }
+ }
+
+ return cost;
+}
+
/**
* Convert one rail type to the other. You can convert normal rail to
* monorail/maglev easily or vice-versa.
@@ -1558,6 +1691,7 @@ CommandCost CmdConvertRail(DoCommandFlag flags, TileIndex tile, TileIndex area_s
if (area_start >= Map::Size()) return CMD_ERROR;
TrainList affected_trains;
+ std::vector affected_depots;
CommandCost cost(EXPENSES_CONSTRUCTION);
CommandCost error = CommandCost(STR_ERROR_NO_SUITABLE_RAILROAD_TRACK); // by default, there is no track to convert.
@@ -1647,31 +1781,27 @@ CommandCost CmdConvertRail(DoCommandFlag flags, TileIndex tile, TileIndex area_s
switch (tt) {
case MP_RAILWAY:
- switch (GetRailTileType(tile)) {
- case RAIL_TILE_DEPOT:
- if (flags & DC_EXEC) {
- /* notify YAPF about the track layout change */
- YapfNotifyTrackLayoutChange(tile, GetRailDepotTrack(tile));
-
- /* Update build vehicle window related to this depot */
- InvalidateWindowData(WC_VEHICLE_DEPOT, tile);
- InvalidateWindowData(WC_BUILD_VEHICLE, tile);
- }
- found_convertible_track = true;
- cost.AddCost(RailConvertCost(type, totype));
- break;
+ found_convertible_track = true;
+ if (GetRailTileType(tile) == RAIL_TILE_DEPOT) {
+ if (flags & DC_EXEC) {
+ /* notify YAPF about the track layout change */
+ YapfNotifyTrackLayoutChange(tile, GetRailDepotTrack(tile));
+ }
+
+ if (find(affected_depots.begin(), affected_depots.end(), (tile)) == affected_depots.end()) {
+ affected_depots.push_back(GetDepotIndex(tile));
+ }
- default: // RAIL_TILE_NORMAL, RAIL_TILE_SIGNALS
- if (flags & DC_EXEC) {
- /* notify YAPF about the track layout change */
- TrackBits tracks = GetTrackBits(tile);
- while (tracks != TRACK_BIT_NONE) {
- YapfNotifyTrackLayoutChange(tile, RemoveFirstTrack(&tracks));
- }
+ cost.AddCost(RailConvertCost(type, totype));
+ } else { // RAIL_TILE_NORMAL, RAIL_TILE_SIGNALS
+ if (flags & DC_EXEC) {
+ /* notify YAPF about the track layout change */
+ TrackBits tracks = GetTrackBits(tile);
+ while (tracks != TRACK_BIT_NONE) {
+ YapfNotifyTrackLayoutChange(tile, RemoveFirstTrack(&tracks));
}
- found_convertible_track = true;
- cost.AddCost(RailConvertCost(type, totype) * CountBits(GetTrackBits(tile)));
- break;
+ }
+ cost.AddCost(RailConvertCost(type, totype) * CountBits(GetTrackBits(tile)));
}
break;
@@ -1753,6 +1883,17 @@ CommandCost CmdConvertRail(DoCommandFlag flags, TileIndex tile, TileIndex area_s
}
}
+ /* Update affected depots. */
+ for (auto &depot_tile : affected_depots) {
+ Depot *dep = Depot::Get(depot_tile);
+ if (HasAnyExtendedDepotReservedTile(dep)) cost.MakeError(STR_ERROR_DEPOT_EXTENDED_RAIL_DEPOT_IS_NOT_FREE);
+
+ if (flags & DC_EXEC) {
+ dep->RescanDepotTiles();
+ InvalidateWindowData(WC_VEHICLE_DEPOT, dep->index);
+ }
+ }
+
if (flags & DC_EXEC) {
/* Railtype changed, update trains as when entering different track */
for (Train *v : affected_trains) {
@@ -1763,8 +1904,10 @@ CommandCost CmdConvertRail(DoCommandFlag flags, TileIndex tile, TileIndex area_s
return found_convertible_track ? cost : error;
}
-static CommandCost RemoveTrainDepot(TileIndex tile, DoCommandFlag flags)
+static CommandCost RemoveTrainDepot(TileIndex tile, DoCommandFlag flags, bool keep_rail)
{
+ assert(IsRailDepotTile(tile));
+
if (_current_company != OWNER_WATER) {
CommandCost ret = CheckTileOwnership(tile);
if (ret.Failed()) return ret;
@@ -1773,10 +1916,21 @@ static CommandCost RemoveTrainDepot(TileIndex tile, DoCommandFlag flags)
CommandCost ret = EnsureNoVehicleOnGround(tile);
if (ret.Failed()) return ret;
+ if (HasDepotReservation(tile)) return CMD_ERROR;
+
+ CommandCost total_cost(EXPENSES_CONSTRUCTION);
+
+ if (keep_rail) {
+ /* Don't refund the 'steel' of the track when we keep the rail. */
+ total_cost.AddCost(-_price[PR_CLEAR_RAIL]);
+ }
+
if (flags & DC_EXEC) {
- /* read variables before the depot is removed */
+ Depot *depot = Depot::GetByTile(tile);
+ Company *c = Company::GetIfValid(depot->owner);
+ assert(c != nullptr);
+
DiagDirection dir = GetRailDepotDirection(tile);
- Owner owner = GetTileOwner(tile);
Train *v = nullptr;
if (HasDepotReservation(tile)) {
@@ -1784,17 +1938,57 @@ static CommandCost RemoveTrainDepot(TileIndex tile, DoCommandFlag flags)
if (v != nullptr) FreeTrainTrackReservation(v);
}
- Company::Get(owner)->infrastructure.rail[GetRailType(tile)]--;
- DirtyCompanyInfrastructureWindows(owner);
+ Track track = GetRailDepotTrack(tile);
+ RailType rt = GetRailType(tile);
+ bool is_extended_depot = IsExtendedDepot(tile);
- delete Depot::GetByTile(tile);
DoClearSquare(tile);
- AddSideToSignalBuffer(tile, dir, owner);
+
+ if (keep_rail) {
+ MakeRailNormal(tile, depot->owner, TrackToTrackBits(track), rt);
+ } else {
+ c->infrastructure.rail[GetRailType(tile)]--;
+ DirtyCompanyInfrastructureWindows(c->index);
+ }
+
+ if (is_extended_depot) {
+ AddTrackToSignalBuffer(tile, DiagDirToDiagTrack(dir), c->index);
+ } else {
+ AddSideToSignalBuffer(tile, dir, c->index);
+ }
+
YapfNotifyTrackLayoutChange(tile, DiagDirToDiagTrack(dir));
if (v != nullptr) TryPathReserve(v, true);
+
+ depot->AfterAddRemove(TileArea(tile), false);
}
- return CommandCost(EXPENSES_CONSTRUCTION, _price[PR_CLEAR_DEPOT_TRAIN]);
+ total_cost.AddCost(_price[PR_CLEAR_DEPOT_TRAIN]);
+ return total_cost;
+}
+
+/**
+ * Remove train depots from an area
+ * @param flags operation to perform
+ * @param start_tile start tile of the area
+ * @param end_tile end tile of the area
+ * @return the cost of this operation or an error
+ */
+CommandCost CmdRemoveTrainDepot(DoCommandFlag flags, TileIndex start_tile, TileIndex end_tile)
+{
+ assert(IsValidTile(start_tile));
+ assert(IsValidTile(end_tile));
+
+ CommandCost cost(EXPENSES_CONSTRUCTION);
+ TileArea ta(start_tile, end_tile);
+ for (TileIndex t : ta) {
+ if (!IsRailDepotTile(t)) continue;
+ CommandCost ret = RemoveTrainDepot(t, flags, IsExtendedDepot(t));
+ if (ret.Failed()) return ret;
+ cost.AddCost(ret);
+ }
+
+ return cost;
}
static CommandCost ClearTile_Track(TileIndex tile, DoCommandFlag flags)
@@ -1845,7 +2039,7 @@ static CommandCost ClearTile_Track(TileIndex tile, DoCommandFlag flags)
}
case RAIL_TILE_DEPOT:
- return RemoveTrainDepot(tile, flags);
+ return RemoveTrainDepot(tile, flags, false);
default:
return CMD_ERROR;
@@ -2072,7 +2266,12 @@ static inline void DrawTrackSprite(SpriteID sprite, PaletteID pal, const TileInf
static void DrawTrackBitsOverlay(TileInfo *ti, TrackBits track, const RailTypeInfo *rti)
{
RailGroundType rgt = GetRailGroundType(ti->tile);
- Foundation f = GetRailFoundation(ti->tileh, track);
+ Foundation f = FOUNDATION_NONE;
+ if (IsRailDepot(ti->tile)) {
+ if (ti->tileh != SLOPE_FLAT) f = FOUNDATION_LEVELED;
+ } else {
+ f = GetRailFoundation(ti->tileh, track);
+ }
Corner halftile_corner = CORNER_INVALID;
if (IsNonContinuousFoundation(f)) {
@@ -2112,7 +2311,18 @@ static void DrawTrackBitsOverlay(TileInfo *ti, TrackBits track, const RailTypeIn
bool no_combine = ti->tileh == SLOPE_FLAT && HasBit(rti->flags, RTF_NO_SPRITE_COMBINE);
SpriteID overlay = GetCustomRailSprite(rti, ti->tile, RTSG_OVERLAY);
SpriteID ground = GetCustomRailSprite(rti, ti->tile, no_combine ? RTSG_GROUND_COMPLETE : RTSG_GROUND);
- TrackBits pbs = _settings_client.gui.show_track_reservation ? GetRailReservationTrackBits(ti->tile) : TRACK_BIT_NONE;
+
+ TrackBits pbs = TRACK_BIT_NONE;
+ if (_settings_client.gui.show_track_reservation) {
+ if (IsPlainRail(ti->tile)) {
+ pbs = GetRailReservationTrackBits(ti->tile);
+ } else {
+ assert(IsRailDepot(ti->tile));
+ if (HasDepotReservation(ti->tile)) {
+ pbs = track;
+ }
+ }
+ }
if (track == TRACK_BIT_NONE) {
/* Half-tile foundation, no track here? */
@@ -2342,7 +2552,14 @@ static void DrawTrackBits(TileInfo *ti, TrackBits track)
/* PBS debugging, draw reserved tracks darker */
if (_game_mode != GM_MENU && _settings_client.gui.show_track_reservation) {
/* Get reservation, but mask track on halftile slope */
- TrackBits pbs = GetRailReservationTrackBits(ti->tile) & track;
+ TrackBits pbs = TRACK_BIT_NONE;
+ if (IsPlainRail(ti->tile)) {
+ pbs = GetRailReservationTrackBits(ti->tile) & track;
+ } else {
+ assert(IsRailDepot(ti->tile));
+ if (HasDepotReservation(ti->tile)) pbs = track;
+ }
+
if (pbs & TRACK_BIT_X) {
if (ti->tileh == SLOPE_FLAT || ti->tileh == SLOPE_ELEVATED) {
DrawGroundSprite(rti->base_sprites.single_x, PALETTE_CRASH);
@@ -2425,124 +2642,40 @@ static void DrawTile_Track(TileInfo *ti)
_drawtile_track_palette = COMPANY_SPRITE_COLOUR(GetTileOwner(ti->tile));
+ TrackBits rails = TRACK_BIT_NONE;
if (IsPlainRail(ti->tile)) {
- TrackBits rails = GetTrackBits(ti->tile);
-
- DrawTrackBits(ti, rails);
-
- if (HasBit(_display_opt, DO_FULL_DETAIL)) DrawTrackDetails(ti, rti);
-
- if (HasRailCatenaryDrawn(GetRailType(ti->tile))) DrawRailCatenary(ti);
-
- if (HasSignals(ti->tile)) DrawSignals(ti->tile, rails, rti);
+ rails = GetTrackBits(ti->tile);
} else {
- /* draw depot */
- const DrawTileSprites *dts;
- PaletteID pal = PAL_NONE;
- SpriteID relocation;
-
- if (ti->tileh != SLOPE_FLAT) DrawFoundation(ti, FOUNDATION_LEVELED);
-
- if (IsInvisibilitySet(TO_BUILDINGS)) {
- /* Draw rail instead of depot */
- dts = &_depot_invisible_gfx_table[GetRailDepotDirection(ti->tile)];
- } else {
- dts = &_depot_gfx_table[GetRailDepotDirection(ti->tile)];
- }
-
- SpriteID image;
- if (rti->UsesOverlay()) {
- image = SPR_FLAT_GRASS_TILE;
- } else {
- image = dts->ground.sprite;
- if (image != SPR_FLAT_GRASS_TILE) image += rti->GetRailtypeSpriteOffset();
- }
-
- /* Adjust ground tile for desert and snow. */
- if (IsSnowRailGround(ti->tile)) {
- if (image != SPR_FLAT_GRASS_TILE) {
- image += rti->snow_offset; // tile with tracks
- } else {
- image = SPR_FLAT_SNOW_DESERT_TILE; // flat ground
- }
+ assert(IsRailDepot(ti->tile));
+ DiagDirection dir = GetRailDepotDirection(ti->tile);
+ if (IsDiagDirFacingSouth(dir) || IsTransparencySet(TO_BUILDINGS)) {
+ rails = TrackToTrackBits(GetRailDepotTrack(ti->tile));
}
+ }
- DrawGroundSprite(image, GroundSpritePaletteTransform(image, pal, _drawtile_track_palette));
-
- if (rti->UsesOverlay()) {
- SpriteID ground = GetCustomRailSprite(rti, ti->tile, RTSG_GROUND);
+ DrawTrackBits(ti, rails);
- switch (GetRailDepotDirection(ti->tile)) {
- case DIAGDIR_NE:
- if (!IsInvisibilitySet(TO_BUILDINGS)) break;
- [[fallthrough]];
- case DIAGDIR_SW:
- DrawGroundSprite(ground + RTO_X, PAL_NONE);
- break;
- case DIAGDIR_NW:
- if (!IsInvisibilitySet(TO_BUILDINGS)) break;
- [[fallthrough]];
- case DIAGDIR_SE:
- DrawGroundSprite(ground + RTO_Y, PAL_NONE);
- break;
- default:
- break;
- }
+ if (IsPlainRail(ti->tile) && HasBit(_display_opt, DO_FULL_DETAIL)) DrawTrackDetails(ti, rti);
- if (_settings_client.gui.show_track_reservation && HasDepotReservation(ti->tile)) {
- SpriteID overlay = GetCustomRailSprite(rti, ti->tile, RTSG_OVERLAY);
+ if (HasRailCatenaryDrawn(GetRailType(ti->tile))) DrawRailCatenary(ti);
- switch (GetRailDepotDirection(ti->tile)) {
- case DIAGDIR_NE:
- if (!IsInvisibilitySet(TO_BUILDINGS)) break;
- [[fallthrough]];
- case DIAGDIR_SW:
- DrawGroundSprite(overlay + RTO_X, PALETTE_CRASH);
- break;
- case DIAGDIR_NW:
- if (!IsInvisibilitySet(TO_BUILDINGS)) break;
- [[fallthrough]];
- case DIAGDIR_SE:
- DrawGroundSprite(overlay + RTO_Y, PALETTE_CRASH);
- break;
- default:
- break;
- }
- }
- } else {
- /* PBS debugging, draw reserved tracks darker */
- if (_game_mode != GM_MENU && _settings_client.gui.show_track_reservation && HasDepotReservation(ti->tile)) {
- switch (GetRailDepotDirection(ti->tile)) {
- case DIAGDIR_NE:
- if (!IsInvisibilitySet(TO_BUILDINGS)) break;
- [[fallthrough]];
- case DIAGDIR_SW:
- DrawGroundSprite(rti->base_sprites.single_x, PALETTE_CRASH);
- break;
- case DIAGDIR_NW:
- if (!IsInvisibilitySet(TO_BUILDINGS)) break;
- [[fallthrough]];
- case DIAGDIR_SE:
- DrawGroundSprite(rti->base_sprites.single_y, PALETTE_CRASH);
- break;
- default:
- break;
- }
- }
- }
+ if (IsRailDepot(ti->tile) && !IsInvisibilitySet(TO_BUILDINGS)) {
+ /* draw depot */
+ const DrawTileSprites *dts = &_depot_gfx_table[GetRailDepotDirection(ti->tile)];
int depot_sprite = GetCustomRailSprite(rti, ti->tile, RTSG_DEPOT);
- relocation = depot_sprite != 0 ? depot_sprite - SPR_RAIL_DEPOT_SE_1 : rti->GetRailtypeSpriteOffset();
-
- if (HasRailCatenaryDrawn(GetRailType(ti->tile))) DrawRailCatenary(ti);
+ SpriteID relocation = depot_sprite != 0 ? depot_sprite - SPR_RAIL_DEPOT_SE_1 : rti->GetRailtypeSpriteOffset();
DrawRailTileSeq(ti, dts, TO_BUILDINGS, relocation, 0, _drawtile_track_palette);
}
+
+ if (HasSignals(ti->tile)) DrawSignals(ti->tile, rails, rti);
+
DrawBridgeMiddle(ti);
}
void DrawTrainDepotSprite(int x, int y, int dir, RailType railtype)
{
- const DrawTileSprites *dts = &_depot_gfx_table[dir];
+ const DrawTileSprites *dts = &_depot_gfx_gui_table[dir];
const RailTypeInfo *rti = GetRailTypeInfo(railtype);
SpriteID image = rti->UsesOverlay() ? SPR_FLAT_GRASS_TILE : dts->ground.sprite;
uint32_t offset = rti->GetRailtypeSpriteOffset();
@@ -2758,6 +2891,13 @@ static TrackStatus GetTileTrackStatus_Track(TileIndex tile, TransportType mode,
}
case RAIL_TILE_DEPOT: {
+ if (IsExtendedRailDepot(tile)) {
+ Track track = GetRailDepotTrack(tile);
+ trackbits = TrackToTrackBits(track);
+ break;
+ }
+
+ /* Small depot. */
DiagDirection dir = GetRailDepotDirection(tile);
if (side != INVALID_DIAGDIR && side != dir) break;
@@ -2774,7 +2914,7 @@ static bool ClickTile_Track(TileIndex tile)
{
if (!IsRailDepot(tile)) return false;
- ShowDepotWindow(tile, VEH_TRAIN);
+ ShowDepotWindow(GetDepotIndex(tile));
return true;
}
@@ -2855,7 +2995,7 @@ static void GetTileDesc_Track(TileIndex tile, TileDesc *td)
}
case RAIL_TILE_DEPOT:
- td->str = STR_LAI_RAIL_DESCRIPTION_TRAIN_DEPOT;
+ td->str = IsExtendedDepot(tile) ? STR_LAI_RAIL_DESCRIPTION_TRAIN_DEPOT_EXTENDED : STR_LAI_RAIL_DESCRIPTION_TRAIN_DEPOT;
if (_settings_game.vehicle.train_acceleration_model != AM_ORIGINAL) {
if (td->rail_speed > 0) {
td->rail_speed = std::min(td->rail_speed, 61);
@@ -2915,6 +3055,7 @@ static const int8_t _deltacoord_leaveoffset[8] = {
*/
int TicksToLeaveDepot(const Train *v)
{
+ assert(IsStandardRailDepotTile(v->tile));
DiagDirection dir = GetRailDepotDirection(v->tile);
int length = v->CalcNextVehicleOffset();
@@ -2936,6 +3077,38 @@ static VehicleEnterTileStatus VehicleEnter_Track(Vehicle *u, TileIndex tile, int
/* This routine applies only to trains in depot tiles. */
if (u->type != VEH_TRAIN || !IsRailDepotTile(tile)) return VETSB_CONTINUE;
+ Train *v = Train::From(u);
+
+ if (IsExtendedRailDepot(tile)) {
+ DepotID depot_id = GetDepotIndex(tile);
+ if (!v->current_order.ShouldStopAtDepot(depot_id)) return VETSB_CONTINUE;
+
+ /* Stop position on platform is half the front vehicle length of the train. */
+ int stop_pos = v->gcache.cached_veh_length / 2;
+
+ int depot_ahead = (GetPlatformLength(tile, DirToDiagDir(v->direction)) - 1) * TILE_SIZE;
+ if (depot_ahead > stop_pos) return VETSB_CONTINUE;
+
+ DiagDirection dir = DirToDiagDir(v->direction);
+
+ x &= 0xF;
+ y &= 0xF;
+
+ if (DiagDirToAxis(dir) != AXIS_X) Swap(x, y);
+ if (y == TILE_SIZE / 2) {
+ if (dir == DIAGDIR_SE || dir == DIAGDIR_SW) x = TILE_SIZE - 1 - x;
+
+ if (stop_pos == x) {
+ return VETSB_ENTERED_DEPOT_PLATFORM;
+ } else if (stop_pos < x) {
+ v->vehstatus |= VS_TRAIN_SLOWING;
+ uint16_t spd = std::max(0, stop_pos * 20 - 15);
+ if (spd < v->cur_speed) v->cur_speed = spd;
+ }
+ }
+ return VETSB_CONTINUE;
+ }
+
/* Depot direction. */
DiagDirection dir = GetRailDepotDirection(tile);
@@ -2944,8 +3117,6 @@ static VehicleEnterTileStatus VehicleEnter_Track(Vehicle *u, TileIndex tile, int
/* Make sure a train is not entering the tile from behind. */
if (_fractcoords_behind[dir] == fract_coord) return VETSB_CANNOT_ENTER;
- Train *v = Train::From(u);
-
/* Leaving depot? */
if (v->direction == DiagDirToDir(dir)) {
/* Calculate the point where the following wagon should be activated. */
@@ -2970,10 +3141,10 @@ static VehicleEnterTileStatus VehicleEnter_Track(Vehicle *u, TileIndex tile, int
v->track = TRACK_BIT_DEPOT,
v->vehstatus |= VS_HIDDEN;
v->direction = ReverseDir(v->direction);
- if (v->Next() == nullptr) VehicleEnterDepot(v->First());
+ if (v->Next() == nullptr) HandleTrainEnterDepot(v->First());
v->tile = tile;
- InvalidateWindowData(WC_VEHICLE_DEPOT, v->tile);
+ InvalidateWindowData(WC_VEHICLE_DEPOT, GetDepotIndex(v->tile));
return VETSB_ENTERED_WORMHOLE;
}
@@ -3080,6 +3251,14 @@ static CommandCost TerraformTile_Track(TileIndex tile, DoCommandFlag flags, int
return CommandCost(EXPENSES_CONSTRUCTION, was_water ? _price[PR_CLEAR_WATER] : (Money)0);
} else if (_settings_game.construction.build_on_slopes && AutoslopeEnabled() &&
AutoslopeCheckForEntranceEdge(tile, z_new, tileh_new, GetRailDepotDirection(tile))) {
+ if (IsExtendedRailDepotTile(tile) && GetTileMaxZ(tile) == z_new + GetSlopeMaxZ(tileh_new)) {
+ DiagDirection direction = GetRailDepotDirection(tile);
+ if (!AutoslopeCheckForEntranceEdge(tile, z_new, tileh_new, direction) ||
+ !AutoslopeCheckForEntranceEdge(tile, z_new, tileh_new, ReverseDiagDir(direction))) {
+ return Command::Do(flags, tile);
+ }
+ }
+
return CommandCost(EXPENSES_CONSTRUCTION, _price[PR_BUILD_FOUNDATION]);
}
return Command::Do(flags, tile);
diff --git a/src/rail_cmd.h b/src/rail_cmd.h
index b9f6c0cc034a2..bcd28fa2e3f2d 100644
--- a/src/rail_cmd.h
+++ b/src/rail_cmd.h
@@ -19,7 +19,8 @@ CommandCost CmdBuildRailroadTrack(DoCommandFlag flags, TileIndex end_tile, TileI
CommandCost CmdRemoveRailroadTrack(DoCommandFlag flags, TileIndex end_tile, TileIndex start_tile, Track track);
CommandCost CmdBuildSingleRail(DoCommandFlag flags, TileIndex tile, RailType railtype, Track track, bool auto_remove_signals);
CommandCost CmdRemoveSingleRail(DoCommandFlag flags, TileIndex tile, Track track);
-CommandCost CmdBuildTrainDepot(DoCommandFlag flags, TileIndex tile, RailType railtype, DiagDirection dir);
+CommandCost CmdBuildTrainDepot(DoCommandFlag flags, TileIndex tile, RailType railtype, DiagDirection dir, bool adjacent, bool extended, DepotID depot_id, TileIndex end_tile);
+CommandCost CmdRemoveTrainDepot(DoCommandFlag flags, TileIndex start_tile, TileIndex end_tile);
CommandCost CmdBuildSingleSignal(DoCommandFlag flags, TileIndex tile, Track track, SignalType sigtype, SignalVariant sigvar, bool convert_signal, bool skip_existing_signals, bool ctrl_pressed, SignalType cycle_start, SignalType cycle_stop, uint8_t num_dir_cycle, uint8_t signals_copy);
CommandCost CmdRemoveSingleSignal(DoCommandFlag flags, TileIndex tile, Track track);
CommandCost CmdConvertRail(DoCommandFlag flags, TileIndex tile, TileIndex area_start, RailType totype, bool diagonal);
@@ -31,6 +32,7 @@ DEF_CMD_TRAIT(CMD_REMOVE_RAILROAD_TRACK, CmdRemoveRailroadTrack, CMD_AUTO,
DEF_CMD_TRAIT(CMD_BUILD_SINGLE_RAIL, CmdBuildSingleRail, CMD_AUTO | CMD_NO_WATER, CMDT_LANDSCAPE_CONSTRUCTION)
DEF_CMD_TRAIT(CMD_REMOVE_SINGLE_RAIL, CmdRemoveSingleRail, CMD_AUTO, CMDT_LANDSCAPE_CONSTRUCTION)
DEF_CMD_TRAIT(CMD_BUILD_TRAIN_DEPOT, CmdBuildTrainDepot, CMD_AUTO | CMD_NO_WATER, CMDT_LANDSCAPE_CONSTRUCTION)
+DEF_CMD_TRAIT(CMD_REMOVE_TRAIN_DEPOT, CmdRemoveTrainDepot, CMD_AUTO | CMD_NO_WATER, CMDT_LANDSCAPE_CONSTRUCTION)
DEF_CMD_TRAIT(CMD_BUILD_SINGLE_SIGNAL, CmdBuildSingleSignal, CMD_AUTO, CMDT_LANDSCAPE_CONSTRUCTION)
DEF_CMD_TRAIT(CMD_REMOVE_SINGLE_SIGNAL, CmdRemoveSingleSignal, CMD_AUTO, CMDT_LANDSCAPE_CONSTRUCTION)
DEF_CMD_TRAIT(CMD_CONVERT_RAIL, CmdConvertRail, 0, CMDT_LANDSCAPE_CONSTRUCTION)
@@ -40,6 +42,6 @@ DEF_CMD_TRAIT(CMD_REMOVE_SIGNAL_TRACK, CmdRemoveSignalTrack, CMD_AUTO,
CommandCallback CcPlaySound_CONSTRUCTION_RAIL;
CommandCallback CcStation;
CommandCallback CcBuildRailTunnel;
-void CcRailDepot(Commands cmd, const CommandCost &result, TileIndex tile, RailType rt, DiagDirection dir);
+void CcRailDepot(Commands cmd, const CommandCost &result, TileIndex tile, RailType rt, DiagDirection dir, bool adjacent, bool extended, DepotID join_to, TileIndex end_tile);
#endif /* RAIL_CMD_H */
diff --git a/src/rail_gui.cpp b/src/rail_gui.cpp
index 06819e4c3dd9b..94d67ab5ce330 100644
--- a/src/rail_gui.cpp
+++ b/src/rail_gui.cpp
@@ -41,6 +41,7 @@
#include "timer/timer.h"
#include "timer/timer_game_calendar.h"
#include "picker_gui.h"
+#include "depot_func.h"
#include "station_map.h"
#include "tunnelbridge_map.h"
@@ -72,7 +73,7 @@ static StationPickerSelection _station_gui; ///< Settings of the station picker.
static void HandleStationPlacement(TileIndex start, TileIndex end);
-static void ShowBuildTrainDepotPicker(Window *parent);
+static void ShowBuildTrainDepotPicker(Window *parent, bool extended_depot);
static void ShowBuildWaypointPicker(Window *parent);
static Window *ShowStationBuilder(Window *parent);
static void ShowSignalBuilder(Window *parent);
@@ -116,7 +117,7 @@ static void GenericPlaceRail(TileIndex tile, Track track)
*/
static void PlaceExtraDepotRail(TileIndex tile, DiagDirection dir, Track track)
{
- if (GetRailTileType(tile) == RAIL_TILE_DEPOT) return;
+ if (IsRailDepot(tile)) return;
if (GetRailTileType(tile) == RAIL_TILE_SIGNALS && !_settings_client.gui.auto_remove_signals) return;
if ((GetTrackBits(tile) & DiagdirReachesTracks(dir)) == 0) return;
@@ -137,24 +138,27 @@ static const DiagDirection _place_depot_extra_dir[12] = {
DIAGDIR_NW, DIAGDIR_NE, DIAGDIR_NW, DIAGDIR_NE,
};
-void CcRailDepot(Commands, const CommandCost &result, TileIndex tile, RailType, DiagDirection dir)
+void CcRailDepot(Commands, const CommandCost &result, TileIndex start_tile, RailType, DiagDirection dir, bool, bool extended, DepotID, TileIndex end_tile)
{
if (result.Failed()) return;
+ if (extended) return;
- if (_settings_client.sound.confirm) SndPlayTileFx(SND_20_CONSTRUCTION_RAIL, tile);
+ if (_settings_client.sound.confirm) SndPlayTileFx(SND_20_CONSTRUCTION_RAIL, start_tile);
if (!_settings_client.gui.persistent_buildingtools) ResetObjectToPlace();
- tile += TileOffsByDiagDir(dir);
+ TileArea ta(start_tile, end_tile);
+ for (TileIndex t : ta) {
+ TileIndex tile = t + TileOffsByDiagDir(dir);
- if (IsTileType(tile, MP_RAILWAY)) {
- PlaceExtraDepotRail(tile, _place_depot_extra_dir[dir], _place_depot_extra_track[dir]);
+ if (IsTileType(tile, MP_RAILWAY)) {
+ PlaceExtraDepotRail(tile, _place_depot_extra_dir[dir], _place_depot_extra_track[dir]);
- /* Don't place the rail straight out of the depot of there is another depot across from it. */
- Tile double_depot_tile = tile + TileOffsByDiagDir(dir);
- bool is_double_depot = IsValidTile(double_depot_tile) && IsRailDepotTile(double_depot_tile);
- if (!is_double_depot) PlaceExtraDepotRail(tile, _place_depot_extra_dir[dir + 4], _place_depot_extra_track[dir + 4]);
+ Tile double_depot_tile = tile + TileOffsByDiagDir(dir);
+ bool is_double_depot = IsValidTile(double_depot_tile) && IsRailDepotTile(double_depot_tile);
+ if (!is_double_depot) PlaceExtraDepotRail(tile, _place_depot_extra_dir[dir + 4], _place_depot_extra_track[dir + 4]);
- PlaceExtraDepotRail(tile, _place_depot_extra_dir[dir + 8], _place_depot_extra_track[dir + 8]);
+ PlaceExtraDepotRail(tile, _place_depot_extra_dir[dir + 8], _place_depot_extra_track[dir + 8]);
+ }
}
}
@@ -317,6 +321,7 @@ void CcBuildRailTunnel(Commands, const CommandCost &result, TileIndex tile)
static void ToggleRailButton_Remove(Window *w)
{
CloseWindowById(WC_SELECT_STATION, 0);
+ CloseWindowById(WC_SELECT_DEPOT, VEH_TRAIN);
w->ToggleWidgetLoweredState(WID_RAT_REMOVE);
w->SetWidgetDirty(WID_RAT_REMOVE);
_remove_button_clicked = w->IsWidgetLowered(WID_RAT_REMOVE);
@@ -334,7 +339,7 @@ static bool RailToolbar_CtrlChanged(Window *w)
/* allow ctrl to switch remove mode only for these widgets */
for (WidgetID i = WID_RAT_BUILD_NS; i <= WID_RAT_BUILD_STATION; i++) {
- if ((i <= WID_RAT_AUTORAIL || i >= WID_RAT_BUILD_WAYPOINT) && w->IsWidgetLowered(i)) {
+ if ((i <= WID_RAT_AUTORAIL || i >= WID_RAT_BUILD_DEPOT) && w->HasWidget(i) && w->IsWidgetLowered(i)) {
ToggleRailButton_Remove(w);
return true;
}
@@ -454,6 +459,11 @@ struct BuildRailToolbarWindow : Window {
if (this->IsWidgetLowered(WID_RAT_BUILD_WAYPOINT)) SetViewportCatchmentWaypoint(nullptr, true);
if (_settings_client.gui.link_terraform_toolbar) CloseWindowById(WC_SCEN_LAND_GEN, 0, false);
CloseWindowById(WC_SELECT_STATION, 0);
+ if ((this->HasWidget(WID_RAT_BUILD_DEPOT) && this->IsWidgetLowered(WID_RAT_BUILD_DEPOT)) ||
+ (this->HasWidget(WID_RAT_BUILD_EXTENDED_DEPOT) && this->IsWidgetLowered(WID_RAT_BUILD_EXTENDED_DEPOT))) {
+ SetViewportHighlightDepot(INVALID_DEPOT, true);
+ }
+
this->Window::Close();
}
@@ -505,7 +515,8 @@ struct BuildRailToolbarWindow : Window {
this->GetWidget(WID_RAT_BUILD_EW)->widget_data = rti->gui_sprites.build_ew_rail;
this->GetWidget(WID_RAT_BUILD_Y)->widget_data = rti->gui_sprites.build_y_rail;
this->GetWidget(WID_RAT_AUTORAIL)->widget_data = rti->gui_sprites.auto_rail;
- this->GetWidget(WID_RAT_BUILD_DEPOT)->widget_data = rti->gui_sprites.build_depot;
+ if (this->HasWidget(WID_RAT_BUILD_DEPOT)) this->GetWidget(WID_RAT_BUILD_DEPOT)->widget_data = rti->gui_sprites.build_depot;
+ if (this->HasWidget(WID_RAT_BUILD_EXTENDED_DEPOT)) this->GetWidget(WID_RAT_BUILD_EXTENDED_DEPOT)->widget_data = rti->gui_sprites.build_depot;
this->GetWidget(WID_RAT_CONVERT_RAIL)->widget_data = rti->gui_sprites.convert_rail;
this->GetWidget(WID_RAT_BUILD_TUNNEL)->widget_data = rti->gui_sprites.build_tunnel;
}
@@ -533,6 +544,8 @@ struct BuildRailToolbarWindow : Window {
case WID_RAT_BUILD_EW:
case WID_RAT_BUILD_Y:
case WID_RAT_AUTORAIL:
+ case WID_RAT_BUILD_DEPOT:
+ case WID_RAT_BUILD_EXTENDED_DEPOT:
case WID_RAT_BUILD_WAYPOINT:
case WID_RAT_BUILD_STATION:
case WID_RAT_BUILD_SIGNALS:
@@ -601,8 +614,9 @@ struct BuildRailToolbarWindow : Window {
break;
case WID_RAT_BUILD_DEPOT:
- if (HandlePlacePushButton(this, WID_RAT_BUILD_DEPOT, GetRailTypeInfo(_cur_railtype)->cursor.depot, HT_RECT)) {
- ShowBuildTrainDepotPicker(this);
+ case WID_RAT_BUILD_EXTENDED_DEPOT:
+ if (HandlePlacePushButton(this, widget, GetRailTypeInfo(_cur_railtype)->cursor.depot, HT_RECT)) {
+ ShowBuildTrainDepotPicker(this, widget == WID_RAT_BUILD_EXTENDED_DEPOT);
this->last_user_action = widget;
}
break;
@@ -689,8 +703,17 @@ struct BuildRailToolbarWindow : Window {
break;
case WID_RAT_BUILD_DEPOT:
- Command::Post(STR_ERROR_CAN_T_BUILD_TRAIN_DEPOT, CcRailDepot, tile, _cur_railtype, _build_depot_direction);
+ case WID_RAT_BUILD_EXTENDED_DEPOT: {
+ CloseWindowById(WC_SELECT_DEPOT, VEH_TRAIN);
+
+ ViewportPlaceMethod vpm = VPM_X_AND_Y_LIMITED;
+ if (this->last_user_action == WID_RAT_BUILD_DEPOT) {
+ vpm = (DiagDirToAxis(_build_depot_direction) == 0) ? VPM_X_LIMITED : VPM_Y_LIMITED;
+ }
+ VpStartPlaceSizing(tile, vpm, _remove_button_clicked ? DDSP_REMOVE_DEPOT : DDSP_BUILD_DEPOT);
+ VpSetPlaceSizingLimit(_settings_game.depot.depot_spread);
break;
+ }
case WID_RAT_BUILD_WAYPOINT:
PlaceRail_Waypoint(tile);
@@ -786,6 +809,22 @@ struct BuildRailToolbarWindow : Window {
}
}
break;
+
+ case DDSP_BUILD_DEPOT:
+ case DDSP_REMOVE_DEPOT:
+ if (_remove_button_clicked) {
+ Command::Post(STR_ERROR_CAN_T_REMOVE_TRAIN_DEPOT, CcPlaySound_CONSTRUCTION_RAIL, start_tile, end_tile);
+ } else {
+ bool adjacent = _ctrl_pressed;
+ bool extended = this->last_user_action == WID_RAT_BUILD_EXTENDED_DEPOT;
+
+ auto proc = [=](DepotID join_to) -> bool {
+ return Command::Post(STR_ERROR_CAN_T_BUILD_TRAIN_DEPOT, CcRailDepot, start_tile, _cur_railtype, _build_depot_direction, adjacent, extended, join_to, end_tile);
+ };
+
+ ShowSelectDepotIfNeeded(TileArea(start_tile, end_tile), proc, VEH_TRAIN);
+ }
+ break;
}
}
}
@@ -795,6 +834,11 @@ struct BuildRailToolbarWindow : Window {
if (this->IsWidgetLowered(WID_RAT_BUILD_STATION)) SetViewportCatchmentStation(nullptr, true);
if (this->IsWidgetLowered(WID_RAT_BUILD_WAYPOINT)) SetViewportCatchmentWaypoint(nullptr, true);
+ if ((this->HasWidget(WID_RAT_BUILD_DEPOT) && this->IsWidgetLowered(WID_RAT_BUILD_DEPOT)) ||
+ (this->HasWidget(WID_RAT_BUILD_EXTENDED_DEPOT) && this->IsWidgetLowered(WID_RAT_BUILD_EXTENDED_DEPOT))) {
+ SetViewportHighlightDepot(INVALID_DEPOT, true);
+ }
+
this->RaiseButtons();
this->DisableWidget(WID_RAT_REMOVE);
this->SetWidgetDirty(WID_RAT_REMOVE);
@@ -804,6 +848,7 @@ struct BuildRailToolbarWindow : Window {
CloseWindowById(WC_BUILD_DEPOT, TRANSPORT_RAIL);
CloseWindowById(WC_BUILD_WAYPOINT, TRANSPORT_RAIL);
CloseWindowById(WC_SELECT_STATION, 0);
+ CloseWindowById(WC_SELECT_DEPOT, VEH_TRAIN);
CloseWindowByClass(WC_BUILD_BRIDGE);
}
@@ -815,8 +860,12 @@ struct BuildRailToolbarWindow : Window {
EventState OnCTRLStateChange() override
{
- /* do not toggle Remove button by Ctrl when placing station */
- if (!this->IsWidgetLowered(WID_RAT_BUILD_STATION) && !this->IsWidgetLowered(WID_RAT_BUILD_WAYPOINT) && RailToolbar_CtrlChanged(this)) return ES_HANDLED;
+ /* do not toggle Remove button by Ctrl when placing station or depot */
+ if (!this->IsWidgetLowered(WID_RAT_BUILD_STATION) &&
+ !this->IsWidgetLowered(WID_RAT_BUILD_WAYPOINT) &&
+ !(this->HasWidget(WID_RAT_BUILD_DEPOT) && this->IsWidgetLowered(WID_RAT_BUILD_DEPOT)) &&
+ !(this->HasWidget(WID_RAT_BUILD_EXTENDED_DEPOT) && this->IsWidgetLowered(WID_RAT_BUILD_EXTENDED_DEPOT)) &&
+ RailToolbar_CtrlChanged(this)) return ES_HANDLED;
return ES_NOT_HANDLED;
}
@@ -857,6 +906,27 @@ struct BuildRailToolbarWindow : Window {
}, RailToolbarGlobalHotkeys};
};
+/**
+ * Add the depot icons depending on availability of construction.
+ * @return Panel with rail depot buttons.
+ */
+static std::unique_ptr MakeNWidgetRailDepot()
+{
+ auto hor = std::make_unique();
+
+ if (HasBit(_settings_game.depot.rail_depot_types, 0)) {
+ /* Add the widget for building standard rail depot. */
+ hor->Add(std::make_unique(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_RAT_BUILD_DEPOT, SPR_IMG_DEPOT_RAIL, STR_RAIL_TOOLBAR_TOOLTIP_BUILD_TRAIN_DEPOT));
+ }
+
+ if (HasBit(_settings_game.depot.rail_depot_types, 1)) {
+ /* Add the widget for building extended rail depot. */
+ hor->Add(std::make_unique(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_RAT_BUILD_EXTENDED_DEPOT, SPR_IMG_DEPOT_RAIL, STR_RAIL_TOOLBAR_TOOLTIP_BUILD_EXTENDED_TRAIN_DEPOT));
+ }
+
+ return hor;
+}
+
static constexpr NWidgetPart _nested_build_rail_widgets[] = {
NWidget(NWID_HORIZONTAL),
NWidget(WWT_CLOSEBOX, COLOUR_DARK_GREEN),
@@ -879,8 +949,7 @@ static constexpr NWidgetPart _nested_build_rail_widgets[] = {
NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_RAT_DEMOLISH),
SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_DYNAMITE, STR_TOOLTIP_DEMOLISH_BUILDINGS_ETC),
- NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_RAT_BUILD_DEPOT),
- SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_DEPOT_RAIL, STR_RAIL_TOOLBAR_TOOLTIP_BUILD_TRAIN_DEPOT_FOR_BUILDING),
+ NWidgetFunction(MakeNWidgetRailDepot),
NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_RAT_BUILD_WAYPOINT),
SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_WAYPOINT, STR_RAIL_TOOLBAR_TOOLTIP_CONVERT_RAIL_TO_WAYPOINT),
NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_RAT_BUILD_STATION),
@@ -1699,12 +1768,33 @@ static void ShowSignalBuilder(Window *parent)
}
struct BuildRailDepotWindow : public PickerWindowBase {
- BuildRailDepotWindow(WindowDesc &desc, Window *parent) : PickerWindowBase(desc, parent)
+
+ BuildRailDepotWindow(WindowDesc &desc, Window *parent, bool extended_depot) : PickerWindowBase(desc, parent)
{
this->InitNested(TRANSPORT_RAIL);
+
+ /* Fix direction for extended depots. */
+ if (extended_depot) {
+ switch ((BuildRailDepotWidgets)_build_depot_direction) {
+ case WID_BRAD_DEPOT_NE:
+ _build_depot_direction++;
+ break;
+ case WID_BRAD_DEPOT_NW:
+ _build_depot_direction--;
+ break;
+ default: break;
+ }
+ }
+
this->LowerWidget(WID_BRAD_DEPOT_NE + _build_depot_direction);
}
+ void Close([[maybe_unused]] int data = 0) override
+ {
+ CloseWindowById(WC_SELECT_DEPOT, VEH_TRAIN);
+ this->PickerWindowBase::Close();
+ }
+
void UpdateWidgetSize(WidgetID widget, Dimension &size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension &fill, [[maybe_unused]] Dimension &resize) override
{
if (!IsInsideMM(widget, WID_BRAD_DEPOT_NE, WID_BRAD_DEPOT_NW + 1)) return;
@@ -1734,6 +1824,7 @@ struct BuildRailDepotWindow : public PickerWindowBase {
case WID_BRAD_DEPOT_SE:
case WID_BRAD_DEPOT_SW:
case WID_BRAD_DEPOT_NW:
+ CloseWindowById(WC_SELECT_DEPOT, VEH_TRAIN);
this->RaiseWidget(WID_BRAD_DEPOT_NE + _build_depot_direction);
_build_depot_direction = (DiagDirection)(widget - WID_BRAD_DEPOT_NE);
this->LowerWidget(WID_BRAD_DEPOT_NE + _build_depot_direction);
@@ -1742,6 +1833,11 @@ struct BuildRailDepotWindow : public PickerWindowBase {
break;
}
}
+
+ void OnRealtimeTick([[maybe_unused]] uint delta_ms) override
+ {
+ CheckRedrawDepotHighlight(this, VEH_TRAIN);
+ }
};
/** Nested widget definition of the build rail depot window */
@@ -1771,9 +1867,30 @@ static WindowDesc _build_depot_desc(
_nested_build_depot_widgets
);
-static void ShowBuildTrainDepotPicker(Window *parent)
+/** Nested widget definition of the build extended rail depot window */
+static const NWidgetPart _nested_build_extended_depot_widgets[] = {
+ NWidget(NWID_HORIZONTAL),
+ NWidget(WWT_CLOSEBOX, COLOUR_DARK_GREEN),
+ NWidget(WWT_CAPTION, COLOUR_DARK_GREEN), SetDataTip(STR_BUILD_DEPOT_TRAIN_ORIENTATION_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
+ EndContainer(),
+ NWidget(WWT_PANEL, COLOUR_DARK_GREEN),
+ NWidget(NWID_HORIZONTAL_LTR), SetPIP(0, WidgetDimensions::unscaled.hsep_normal, 0), SetPIPRatio(1, 0, 1), SetPadding(WidgetDimensions::unscaled.picker),
+ NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BRAD_DEPOT_SW), SetMinimalSize(66, 50), SetDataTip(0x0, STR_BUILD_DEPOT_TRAIN_ORIENTATION_TOOLTIP),
+ NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BRAD_DEPOT_SE), SetMinimalSize(66, 50), SetDataTip(0x0, STR_BUILD_DEPOT_TRAIN_ORIENTATION_TOOLTIP),
+ EndContainer(),
+ EndContainer(),
+};
+
+static WindowDesc _build_extended_depot_desc(
+ WDP_AUTO, nullptr, 0, 0,
+ WC_BUILD_DEPOT, WC_BUILD_TOOLBAR,
+ WDF_CONSTRUCTION,
+ _nested_build_extended_depot_widgets
+);
+
+static void ShowBuildTrainDepotPicker(Window *parent, bool extended_depot)
{
- new BuildRailDepotWindow(_build_depot_desc, parent);
+ new BuildRailDepotWindow(extended_depot ? _build_extended_depot_desc : _build_depot_desc, parent, extended_depot);
}
class WaypointPickerCallbacks : public PickerCallbacksNewGRFClass