1
0
mirror of https://github.com/OpenRCT2/OpenRCT2 synced 2026-01-21 14:02:59 +01:00

MapCanConstructAt refactor (#11977)

* Start using MapCanConstructAt

* Fix #11675. Pass the full error message arguments on peep pickup

This was causing a crash as the error message arguments were being cleared which would mean eventually a nullptr dereference would happen.
This commit is contained in:
Duncan
2020-06-17 22:35:13 +01:00
committed by GitHub
parent 2013e1a36f
commit bb34213b93
9 changed files with 51 additions and 40 deletions

View File

@@ -140,6 +140,12 @@ public:
std::string GetErrorMessage() const;
};
class ConstructClearResult final : public GameActionResult
{
public:
uint8_t GroundFlags{ 0 };
};
/**
*
*/

View File

@@ -141,21 +141,22 @@ public:
res->ErrorMessage = STR_INVALID_SELECTION_OF_OBJECTS;
return res;
}
if (!map_can_construct_at({ _loc.ToTileStart(), baseHeight, clearanceHeight }, { 0b1111, 0 }))
auto constructResult = MapCanConstructAt({ _loc.ToTileStart(), baseHeight, clearanceHeight }, { 0b1111, 0 });
if (constructResult->Error != GA_ERROR::OK)
{
return MakeResult(
GA_ERROR::NO_CLEARANCE, res->ErrorTitle.GetStringId(), gGameCommandErrorText, gCommonFormatArgs);
GA_ERROR::NO_CLEARANCE, res->ErrorTitle.GetStringId(), constructResult->ErrorMessage.GetStringId(),
constructResult->ErrorMessageArgs.data());
}
if (gMapGroundFlags & ELEMENT_IS_UNDERWATER)
if (constructResult->GroundFlags & ELEMENT_IS_UNDERWATER)
{
res->Error = GA_ERROR::NO_CLEARANCE;
res->ErrorMessage = STR_RIDE_CANT_BUILD_THIS_UNDERWATER;
return res;
}
if (gMapGroundFlags & ELEMENT_IS_UNDERGROUND)
if (constructResult->GroundFlags & ELEMENT_IS_UNDERGROUND)
{
res->Error = GA_ERROR::NO_CLEARANCE;
res->ErrorMessage = STR_CAN_ONLY_BUILD_THIS_ABOVE_GROUND;

View File

@@ -109,9 +109,9 @@ public:
return MakeResult(GA_ERROR::UNKNOWN, STR_ERR_CANT_PLACE_PERSON_HERE);
}
if (!peep->Place(TileCoordsXYZ(_loc), false))
if (auto res2 = peep->Place(TileCoordsXYZ(_loc), false); res2->Error != GA_ERROR::OK)
{
return MakeResult(GA_ERROR::UNKNOWN, STR_ERR_CANT_PLACE_PERSON_HERE, gGameCommandErrorText);
return res2;
}
break;
default:
@@ -179,9 +179,9 @@ public:
break;
case PeepPickupType::Place:
res->Position = _loc;
if (!peep->Place(TileCoordsXYZ(_loc), true))
if (auto res2 = peep->Place(TileCoordsXYZ(_loc), true); res2->Error != GA_ERROR::OK)
{
return MakeResult(GA_ERROR::UNKNOWN, STR_ERR_CANT_PLACE_PERSON_HERE, gGameCommandErrorText);
return res2;
}
CancelConcurrentPickups(peep);
break;

View File

@@ -93,10 +93,11 @@ public:
entranceLoc.y += CoordsDirectionDelta[(_loc.direction + 1) & 0x3].y * 2;
}
if (!map_can_construct_at({ entranceLoc, zLow, zHigh }, { 0b1111, 0 }))
if (auto res2 = MapCanConstructAt({ entranceLoc, zLow, zHigh }, { 0b1111, 0 }); res2->Error != GA_ERROR::OK)
{
return std::make_unique<GameActionResult>(
GA_ERROR::NO_CLEARANCE, STR_CANT_BUILD_PARK_ENTRANCE_HERE, gGameCommandErrorText, gCommonFormatArgs);
GA_ERROR::NO_CLEARANCE, STR_CANT_BUILD_PARK_ENTRANCE_HERE, res2->ErrorMessage.GetStringId(),
res2->ErrorMessageArgs.data());
}
// Check that entrance element does not already exist at this location

View File

@@ -94,9 +94,10 @@ public:
zLow = temp;
}
if (!map_can_construct_at({ _coords, zLow, zHigh }, { 0b1111, 0b1111 }))
if (auto res2 = MapCanConstructAt({ _coords, zLow, zHigh }, { 0b1111, 0b1111 }); res2->Error != GA_ERROR::OK)
{
return MakeResult(GA_ERROR::NO_CLEARANCE, STR_NONE, gGameCommandErrorText, gCommonFormatArgs);
return MakeResult(
GA_ERROR::NO_CLEARANCE, STR_NONE, res2->ErrorMessage.GetStringId(), res2->ErrorMessageArgs.data());
}
if (surfaceElement->HasTrackThatNeedsWater())
{

View File

@@ -14,6 +14,7 @@
#include "../Game.h"
#include "../Input.h"
#include "../OpenRCT2.h"
#include "../actions/GameAction.h"
#include "../audio/AudioMixer.h"
#include "../audio/audio.h"
#include "../config/Config.h"
@@ -743,8 +744,8 @@ void Peep::PickupAbort(int32_t old_x)
gPickupPeepImage = UINT32_MAX;
}
// Returns true when a peep can be dropped at the given location. When apply is set to true the peep gets dropped.
bool Peep::Place(const TileCoordsXYZ& location, bool apply)
// Returns GA_ERROR::OK when a peep can be dropped at the given location. When apply is set to true the peep gets dropped.
std::unique_ptr<GameActionResult> Peep::Place(const TileCoordsXYZ& location, bool apply)
{
auto* pathElement = map_get_path_element_at(location);
TileElement* tileElement = reinterpret_cast<TileElement*>(pathElement);
@@ -754,7 +755,7 @@ bool Peep::Place(const TileCoordsXYZ& location, bool apply)
}
if (!tileElement)
return false;
return std::make_unique<GameActionResult>(GA_ERROR::INVALID_PARAMETERS, STR_ERR_CANT_PLACE_PERSON_HERE);
// Set the coordinate of destination to be exactly
// in the middle of a tile.
@@ -762,18 +763,19 @@ bool Peep::Place(const TileCoordsXYZ& location, bool apply)
if (!map_is_location_owned(destination))
{
gGameCommandErrorTitle = STR_ERR_CANT_PLACE_PERSON_HERE;
return false;
return std::make_unique<GameActionResult>(GA_ERROR::NOT_OWNED, STR_ERR_CANT_PLACE_PERSON_HERE);
}
if (!map_can_construct_at({ destination, destination.z, destination.z + (1 * 8) }, { 0b1111, 0 }))
if (auto res = MapCanConstructAt({ destination, destination.z, destination.z + (1 * 8) }, { 0b1111, 0 });
res->Error != GA_ERROR::OK)
{
if (gGameCommandErrorText != STR_RAISE_OR_LOWER_LAND_FIRST)
if (res->ErrorMessage.GetStringId() != STR_RAISE_OR_LOWER_LAND_FIRST)
{
if (gGameCommandErrorText != STR_FOOTPATH_IN_THE_WAY)
if (res->ErrorMessage.GetStringId() != STR_FOOTPATH_IN_THE_WAY)
{
gGameCommandErrorTitle = STR_ERR_CANT_PLACE_PERSON_HERE;
return false;
return std::make_unique<GameActionResult>(
GA_ERROR::NO_CLEARANCE, STR_ERR_CANT_PLACE_PERSON_HERE, res->ErrorMessage.GetStringId(),
res->ErrorMessageArgs.data());
}
}
}
@@ -797,7 +799,7 @@ bool Peep::Place(const TileCoordsXYZ& location, bool apply)
}
}
return true;
return std::make_unique<GameActionResult>();
}
/**

View File

@@ -48,6 +48,7 @@ constexpr auto PEEP_CLEARANCE_HEIGHT = 4 * COORDS_Z_STEP;
class Formatter;
struct TileElement;
struct Ride;
class GameActionResult;
enum PeepType : uint8_t
{
@@ -769,7 +770,7 @@ public: // Peep
void SetNextFlags(uint8_t next_direction, bool is_sloped, bool is_surface);
void Pickup();
void PickupAbort(int32_t old_x);
bool Place(const TileCoordsXYZ& location, bool apply);
std::unique_ptr<GameActionResult> Place(const TileCoordsXYZ& location, bool apply);
static Peep* Generate(const CoordsXYZ& coords);
void RemoveFromQueue();
void RemoveFromRide();

View File

@@ -1218,12 +1218,6 @@ TileElement* tile_element_insert(const CoordsXYZ& loc, int32_t occupiedQuadrants
return insertedElement;
}
class ConstructClearResult final : public GameActionResult
{
public:
uint8_t GroundFlags{ 0 };
};
/**
*
* rct2: 0x0068BB18
@@ -1301,7 +1295,7 @@ void map_obstruction_set_error_text(TileElement* tileElement, GameActionResult&
* ebp = clearFunc
* bl = bl
*/
static GameActionResult::Ptr map_can_construct_with_clear_at(
std::unique_ptr<ConstructClearResult> MapCanConstructWithClearAt(
const CoordsXYRangedZ& pos, CLEAR_FUNC clearFunc, QuarterTile quarterTile, uint8_t flags, uint8_t crossingMode)
{
int32_t northZ, eastZ, baseHeight, southZ, westZ, water_height;
@@ -1483,7 +1477,7 @@ bool map_can_construct_with_clear_at(
const CoordsXYRangedZ& pos, CLEAR_FUNC clearFunc, QuarterTile quarterTile, uint8_t flags, money32* price,
uint8_t crossingMode)
{
GameActionResult::Ptr res = map_can_construct_with_clear_at(pos, clearFunc, quarterTile, flags, crossingMode);
auto res = MapCanConstructWithClearAt(pos, clearFunc, quarterTile, flags, crossingMode);
if (auto message = res->ErrorMessage.AsStringId())
gGameCommandErrorText = *message;
else
@@ -1493,12 +1487,8 @@ bool map_can_construct_with_clear_at(
{
*price += res->Cost;
}
auto ccr = dynamic_cast<ConstructClearResult*>(res.get());
if (ccr == nullptr)
{
return false;
}
gMapGroundFlags = ccr->GroundFlags;
gMapGroundFlags = res->GroundFlags;
return res->Error == GA_ERROR::OK;
}
@@ -1511,6 +1501,10 @@ int32_t map_can_construct_at(const CoordsXYRangedZ& pos, QuarterTile bl)
return map_can_construct_with_clear_at(pos, nullptr, bl, 0, nullptr, CREATE_CROSSING_MODE_NONE);
}
std::unique_ptr<ConstructClearResult> MapCanConstructAt(const CoordsXYRangedZ& pos, QuarterTile bl)
{
return MapCanConstructWithClearAt(pos, nullptr, bl, 0, CREATE_CROSSING_MODE_NONE);
}
/**
* Updates grass length, scenery age and jumping fountains.
*

View File

@@ -187,6 +187,9 @@ void map_reorganise_elements();
bool map_check_free_elements_and_reorganise(int32_t num_elements);
TileElement* tile_element_insert(const CoordsXYZ& loc, int32_t occupiedQuadrants);
class GameActionResult;
class ConstructClearResult;
using CLEAR_FUNC = int32_t (*)(TileElement** tile_element, const CoordsXY& coords, uint8_t flags, money32* price);
int32_t map_place_non_scenery_clear_func(TileElement** tile_element, const CoordsXY& coords, uint8_t flags, money32* price);
@@ -194,6 +197,9 @@ int32_t map_place_scenery_clear_func(TileElement** tile_element, const CoordsXY&
bool map_can_construct_with_clear_at(
const CoordsXYRangedZ& pos, CLEAR_FUNC clearFunc, QuarterTile quarterTile, uint8_t flags, money32* price,
uint8_t crossingMode);
std::unique_ptr<ConstructClearResult> MapCanConstructWithClearAt(
const CoordsXYRangedZ& pos, CLEAR_FUNC clearFunc, QuarterTile quarterTile, uint8_t flags, uint8_t crossingMode);
std::unique_ptr<ConstructClearResult> MapCanConstructAt(const CoordsXYRangedZ& pos, QuarterTile bl);
int32_t map_can_construct_at(const CoordsXYRangedZ& pos, QuarterTile bl);
struct tile_element_iterator
@@ -253,7 +259,6 @@ TileElement* map_get_track_element_at_from_ride(const CoordsXYZ& trackPos, ride_
TileElement* map_get_track_element_at_with_direction_from_ride(const CoordsXYZD& trackPos, ride_id_t rideIndex);
bool map_is_location_at_edge(const CoordsXY& loc);
class GameActionResult;
void map_obstruction_set_error_text(TileElement* tileElement, GameActionResult& res);
uint16_t check_max_allowable_land_rights_for_tile(const CoordsXYZ& tileMapPos);