diff --git a/src/script/api/script_road.cpp b/src/script/api/script_road.cpp index 677d89586a..06879a4e57 100644 --- a/src/script/api/script_road.cpp +++ b/src/script/api/script_road.cpp @@ -136,6 +136,32 @@ /* Helper functions for ScriptRoad::CanBuildConnectedRoadParts(). */ +/** + * Enumeration or the orientations of road parts. + * + * Technically DiagDirection could be used, but that allows simple conversions to/from integer. In this case that is not wanted. + */ +enum class RoadPartOrientation { + NW, NE, SW, SE +}; + +/** + * Check whether the two given orientations combined are a straight road. + * @param start The part that should be build first. + * @param end The part that will be build second. + * @return True iff start and end combined describe a straight road (ROAD_X or ROAD_Y). + */ +static bool IsStraight(RoadPartOrientation start, RoadPartOrientation end) +{ + switch (start) { + case RoadPartOrientation::NW: return end == RoadPartOrientation::SE; + case RoadPartOrientation::NE: return end == RoadPartOrientation::SW; + case RoadPartOrientation::SW: return end == RoadPartOrientation::NE; + case RoadPartOrientation::SE: return end == RoadPartOrientation::NW; + default: NOT_REACHED(); + } +} + /** * Check whether the given existing bits the start and end part can be build. * As the function assumes the bits being build on a slope that does not @@ -150,9 +176,9 @@ * @param end The part that will be build second. * @return True if and only if the road bits can be build. */ -static bool CheckAutoExpandedRoadBits(const Array<> &existing, int32_t start, int32_t end) +static bool CheckAutoExpandedRoadBits(const Array &existing, RoadPartOrientation start, RoadPartOrientation end) { - return (start + end == 0) && (existing.empty() || existing[0] == start || existing[0] == end); + return IsStraight(start, end) && (existing.empty() || existing[0] == start || existing[0] == end); } /** @@ -165,7 +191,7 @@ static bool CheckAutoExpandedRoadBits(const Array<> &existing, int32_t start, in * they are build or 2 when building the first part automatically * builds the second part. */ -static int32_t LookupWithoutBuildOnSlopes(::Slope slope, const Array<> &existing, int32_t start, int32_t end) +static int32_t LookupWithoutBuildOnSlopes(::Slope slope, const Array &existing, RoadPartOrientation start, RoadPartOrientation end) { switch (slope) { /* Flat slopes can always be build. */ @@ -177,9 +203,9 @@ static int32_t LookupWithoutBuildOnSlopes(::Slope slope, const Array<> &existing * in the game have been changed. */ case SLOPE_NE: case SLOPE_SW: - return (CheckAutoExpandedRoadBits(existing, start, end) && (start == 1 || end == 1)) ? (existing.empty() ? 2 : 1) : 0; + return (CheckAutoExpandedRoadBits(existing, start, end) && (start == RoadPartOrientation::SW || end == RoadPartOrientation::SW)) ? (existing.empty() ? 2 : 1) : 0; case SLOPE_SE: case SLOPE_NW: - return (CheckAutoExpandedRoadBits(existing, start, end) && (start != 1 && end != 1)) ? (existing.empty() ? 2 : 1) : 0; + return (CheckAutoExpandedRoadBits(existing, start, end) && (start != RoadPartOrientation::SW && end != RoadPartOrientation::SW)) ? (existing.empty() ? 2 : 1) : 0; /* Any other tile cannot be built on. */ default: @@ -192,13 +218,13 @@ static int32_t LookupWithoutBuildOnSlopes(::Slope slope, const Array<> &existing * @param neighbour The neighbour. * @return The rotate neighbour data. */ -static int32_t RotateNeighbour(int32_t neighbour) +static RoadPartOrientation RotateNeighbour(RoadPartOrientation neighbour) { switch (neighbour) { - case -2: return -1; - case -1: return 2; - case 1: return -2; - case 2: return 1; + case RoadPartOrientation::NW: return RoadPartOrientation::NE; + case RoadPartOrientation::NE: return RoadPartOrientation::SE; + case RoadPartOrientation::SE: return RoadPartOrientation::SW; + case RoadPartOrientation::SW: return RoadPartOrientation::NW; default: NOT_REACHED(); } } @@ -208,13 +234,13 @@ static int32_t RotateNeighbour(int32_t neighbour) * @param neighbour The neighbour. * @return The bits representing the direction. */ -static RoadBits NeighbourToRoadBits(int32_t neighbour) +static RoadBits NeighbourToRoadBits(RoadPartOrientation neighbour) { switch (neighbour) { - case -2: return ROAD_NW; - case -1: return ROAD_NE; - case 2: return ROAD_SE; - case 1: return ROAD_SW; + case RoadPartOrientation::NW: return ROAD_NW; + case RoadPartOrientation::NE: return ROAD_NE; + case RoadPartOrientation::SE: return ROAD_SE; + case RoadPartOrientation::SW: return ROAD_SW; default: NOT_REACHED(); } } @@ -229,7 +255,7 @@ static RoadBits NeighbourToRoadBits(int32_t neighbour) * they are build or 2 when building the first part automatically * builds the second part. */ -static int32_t LookupWithBuildOnSlopes(::Slope slope, const Array<> &existing, int32_t start, int32_t end) +static int32_t LookupWithBuildOnSlopes(::Slope slope, const Array &existing, RoadPartOrientation start, RoadPartOrientation end) { /* Steep slopes behave the same as slopes with one corner raised. */ if (IsSteepSlope(slope)) { @@ -287,7 +313,7 @@ static int32_t LookupWithBuildOnSlopes(::Slope slope, const Array<> &existing, i RoadBits start_roadbits = NeighbourToRoadBits(start); RoadBits new_roadbits = start_roadbits | NeighbourToRoadBits(end); RoadBits existing_roadbits = ROAD_NONE; - for (int32_t neighbour : existing) { + for (RoadPartOrientation neighbour : existing) { for (int j = 0; j < base_rotate; j++) { neighbour = RotateNeighbour(neighbour); } @@ -356,55 +382,44 @@ static int32_t LookupWithBuildOnSlopes(::Slope slope, const Array<> &existing, i } /** - * Normalise all input data so we can easily handle it without needing - * to call the API lots of times or create large if-elseif-elseif-else - * constructs. - * In this case it means that a TileXY(0, -1) becomes -2 and TileXY(0, 1) - * becomes 2. TileXY(-1, 0) and TileXY(1, 0) stay respectively -1 and 1. - * Any other value means that it is an invalid tile offset. - * @param tile The tile to normalise. - * @return True if and only if the tile offset is valid. + * Normalise all input data (tile indices) so we can easily handle it without needing + * to call the API lots of times or create large if-elseif-elseif-else constructs. + * @param tile The tile to get the orientation from. + * @return The orientation or an empty optional when the input is invalid.. */ -static bool NormaliseTileOffset(int32_t *tile) +static std::optional ToRoadPartOrientation(const TileIndex &tile) { - if (*tile == ScriptMap::GetTileIndex(-1, 0)) { - *tile = -1; - return true; - } - if (*tile == ScriptMap::GetTileIndex(1, 0)) { - *tile = 1; - return true; - } - if (*tile == ScriptMap::GetTileIndex(0, -1)) { - *tile = -2; - return true; - } - if (*tile == ScriptMap::GetTileIndex(0, 1)) { - *tile = 2; - return true; - } - return false; + if (tile == ScriptMap::GetTileIndex(0, -1)) return RoadPartOrientation::NW; + if (tile == ScriptMap::GetTileIndex(1, 0)) return RoadPartOrientation::SW; + if (tile == ScriptMap::GetTileIndex(0, 1)) return RoadPartOrientation::SE; + if (tile == ScriptMap::GetTileIndex(-1, 0)) return RoadPartOrientation::NE; + return std::nullopt; } -/* static */ SQInteger ScriptRoad::CanBuildConnectedRoadParts(ScriptTile::Slope slope_, Array<> &&existing, TileIndex start_, TileIndex end_) +/* static */ SQInteger ScriptRoad::CanBuildConnectedRoadParts(ScriptTile::Slope slope_, Array &&existing, TileIndex start, TileIndex end) { ::Slope slope = (::Slope)slope_; - int32_t start = start_.base(); - int32_t end = end_.base(); /* The start tile and end tile cannot be the same tile either. */ if (start == end) return -1; - for (size_t i = 0; i < existing.size(); i++) { - if (!NormaliseTileOffset(&existing[i])) return -1; + std::vector existing_orientations; + existing_orientations.reserve(existing.size()); + for (const auto &t : existing) { + auto orientation = ToRoadPartOrientation(t); + if (!orientation) return -1; + existing_orientations.push_back(*orientation); } - if (!NormaliseTileOffset(&start)) return -1; - if (!NormaliseTileOffset(&end)) return -1; + auto start_orientation = ToRoadPartOrientation(start); + auto end_orientation = ToRoadPartOrientation(end); + if (!start_orientation || !end_orientation) return -1; /* Without build on slopes the characteristics are vastly different, so use * a different helper function (one that is much simpler). */ - return _settings_game.construction.build_on_slopes ? LookupWithBuildOnSlopes(slope, existing, start, end) : LookupWithoutBuildOnSlopes(slope, existing, start, end); + return _settings_game.construction.build_on_slopes ? + LookupWithBuildOnSlopes(slope, existing_orientations, *start_orientation, *end_orientation) : + LookupWithoutBuildOnSlopes(slope, existing_orientations, *start_orientation, *end_orientation); } /* static */ SQInteger ScriptRoad::CanBuildConnectedRoadPartsHere(TileIndex tile, TileIndex start, TileIndex end) @@ -426,9 +441,9 @@ static bool NormaliseTileOffset(int32_t *tile) rb = ::GetAnyRoadBits(tile, RTT_ROAD) | ::GetAnyRoadBits(tile, RTT_TRAM); } - Array<> existing; + Array existing; for (uint i = 0; i < lengthof(neighbours); i++) { - if (HasBit(rb, i)) existing.emplace_back(neighbours[i].base()); + if (HasBit(rb, i)) existing.emplace_back(neighbours[i]); } return ScriptRoad::CanBuildConnectedRoadParts(ScriptTile::GetSlope(tile), std::move(existing), start - tile, end - tile); diff --git a/src/script/api/script_road.hpp b/src/script/api/script_road.hpp index 46f75ae2f9..a73bc7a758 100644 --- a/src/script/api/script_road.hpp +++ b/src/script/api/script_road.hpp @@ -248,7 +248,7 @@ public: * they are build or 2 when building the first part automatically * builds the second part. -1 means the preconditions are not met. */ - static SQInteger CanBuildConnectedRoadParts(ScriptTile::Slope slope, Array<> &&existing, TileIndex start, TileIndex end); + static SQInteger CanBuildConnectedRoadParts(ScriptTile::Slope slope, Array &&existing, TileIndex start, TileIndex end); /** * Lookup function for building road parts independent of whether the