OpenRCT2/src/openrct2/actions/LandSmoothAction.hpp

673 lines
31 KiB
C++

/*****************************************************************************
* Copyright (c) 2014-2020 OpenRCT2 developers
*
* For a complete list of all authors, please refer to contributors.md
* Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
*
* OpenRCT2 is licensed under the GNU General Public License version 3.
*****************************************************************************/
#pragma once
#include "../Context.h"
#include "../OpenRCT2.h"
#include "../actions/LandLowerAction.hpp"
#include "../actions/LandRaiseAction.hpp"
#include "../actions/LandSetHeightAction.hpp"
#include "../audio/audio.h"
#include "../interface/Window.h"
#include "../localisation/Localisation.h"
#include "../localisation/StringIds.h"
#include "../management/Finance.h"
#include "../ride/RideData.h"
#include "../windows/Intent.h"
#include "../world/Park.h"
#include "../world/Scenery.h"
#include "../world/Sprite.h"
#include "../world/Surface.h"
#include "GameAction.h"
DEFINE_GAME_ACTION(LandSmoothAction, GAME_COMMAND_EDIT_LAND_SMOOTH, GameActions::Result)
{
private:
CoordsXY _coords;
MapRange _range;
uint8_t _selectionType{};
bool _isLowering{};
constexpr static rct_string_id _ErrorTitles[] = { STR_CANT_LOWER_LAND_HERE, STR_CANT_RAISE_LAND_HERE };
public:
LandSmoothAction() = default;
LandSmoothAction(const CoordsXY& coords, MapRange range, uint8_t selectionType, bool isLowering)
: _coords(coords)
, _range(range)
, _selectionType(selectionType)
, _isLowering(isLowering)
{
}
uint16_t GetActionFlags() const override
{
return GameAction::GetActionFlags();
}
void Serialise(DataSerialiser & stream) override
{
GameAction::Serialise(stream);
stream << DS_TAG(_coords) << DS_TAG(_range) << DS_TAG(_selectionType) << DS_TAG(_isLowering);
}
GameActions::Result::Ptr Query() const override
{
return SmoothLand(false);
}
GameActions::Result::Ptr Execute() const override
{
return SmoothLand(true);
}
private:
GameActions::Result::Ptr SmoothLandTile(
int32_t direction, bool isExecuting, const CoordsXY& loc, SurfaceElement* surfaceElement) const
{
int32_t targetBaseZ = surfaceElement->base_height;
int32_t slope = surfaceElement->GetSlope();
if (_isLowering)
{
slope = tile_element_lower_styles[direction][slope];
if (slope & SURFACE_STYLE_FLAG_RAISE_OR_LOWER_BASE_HEIGHT)
{
targetBaseZ -= 2;
slope &= ~SURFACE_STYLE_FLAG_RAISE_OR_LOWER_BASE_HEIGHT;
}
}
else
{
slope = tile_element_raise_styles[direction][slope];
if (slope & SURFACE_STYLE_FLAG_RAISE_OR_LOWER_BASE_HEIGHT)
{
targetBaseZ += 2;
slope &= ~SURFACE_STYLE_FLAG_RAISE_OR_LOWER_BASE_HEIGHT;
}
}
auto landSetHeightAction = LandSetHeightAction(loc, targetBaseZ, slope);
landSetHeightAction.SetFlags(GetFlags());
auto res = isExecuting ? GameActions::ExecuteNested(&landSetHeightAction)
: GameActions::QueryNested(&landSetHeightAction);
return res;
}
money32 SmoothLandRowByEdge(
bool isExecuting, const CoordsXY& loc, int32_t expectedLandHeight1, int32_t expectedLandHeight2, int32_t stepX,
int32_t stepY, int32_t direction1, int32_t direction2, int32_t checkDirection1, int32_t checkDirection2) const
{
uint8_t shouldContinue = 0xF;
int32_t landChangePerTile = _isLowering ? 2 : -2;
money32 totalCost = 0;
// check if we need to start at all
if (!LocationValid(loc) || !LocationValid({ loc.x + stepX, loc.y + stepY }))
{
return 0;
}
auto surfaceElement = map_get_surface_element_at(loc);
auto nextSurfaceElement = map_get_surface_element_at(CoordsXY{ loc.x + stepX, loc.y + stepY });
if (surfaceElement == nullptr || nextSurfaceElement == nullptr)
{
return 0;
}
if (tile_element_get_corner_height(surfaceElement, checkDirection1) != expectedLandHeight1 + landChangePerTile)
{
shouldContinue &= ~0x1;
}
if (tile_element_get_corner_height(surfaceElement, checkDirection2) != expectedLandHeight2 + landChangePerTile)
{
shouldContinue &= ~0x2;
}
if (tile_element_get_corner_height(surfaceElement, checkDirection1)
!= tile_element_get_corner_height(nextSurfaceElement, direction1))
{
shouldContinue &= ~0x1;
}
if (tile_element_get_corner_height(surfaceElement, checkDirection2)
!= tile_element_get_corner_height(nextSurfaceElement, direction2))
{
shouldContinue &= ~0x2;
}
auto nextLoc = loc;
while ((shouldContinue & 0x3) != 0)
{
shouldContinue = ((shouldContinue << 2) | 0x3) & shouldContinue;
nextLoc.x += stepX;
nextLoc.y += stepY;
// check if we need to continue after raising the current tile
// this needs to be checked before the tile is changed
if (!LocationValid({ nextLoc.x + stepX, nextLoc.y + stepY }))
{
shouldContinue &= ~0x3;
}
else
{
surfaceElement = nextSurfaceElement;
nextSurfaceElement = map_get_surface_element_at(CoordsXY{ nextLoc.x + stepX, nextLoc.y + stepY });
if (nextSurfaceElement == nullptr)
{
shouldContinue &= ~0x3;
}
if (tile_element_get_corner_height(surfaceElement, direction1) + landChangePerTile
!= tile_element_get_corner_height(surfaceElement, checkDirection1))
{
shouldContinue &= ~0x1;
}
if (tile_element_get_corner_height(surfaceElement, direction2) + landChangePerTile
!= tile_element_get_corner_height(surfaceElement, checkDirection2))
{
shouldContinue &= ~0x2;
}
if ((shouldContinue & 0x1)
&& tile_element_get_corner_height(surfaceElement, checkDirection1)
!= tile_element_get_corner_height(nextSurfaceElement, direction1))
{
shouldContinue &= ~0x1;
}
if ((shouldContinue & 0x2)
&& tile_element_get_corner_height(surfaceElement, checkDirection2)
!= tile_element_get_corner_height(nextSurfaceElement, direction2))
{
shouldContinue &= ~0x2;
}
}
expectedLandHeight1 += landChangePerTile;
// change land of current tile
int32_t targetBaseZ = surfaceElement->base_height;
int32_t slope = surfaceElement->GetSlope();
int32_t oldSlope = slope;
if (_isLowering)
{
if (shouldContinue & 0x4)
{
slope = tile_element_lower_styles[direction1][slope];
if (slope & SURFACE_STYLE_FLAG_RAISE_OR_LOWER_BASE_HEIGHT)
{
targetBaseZ -= 2;
slope &= ~SURFACE_STYLE_FLAG_RAISE_OR_LOWER_BASE_HEIGHT;
}
}
if ((shouldContinue & 0x8)
&& map_get_corner_height(surfaceElement->base_height, oldSlope, direction2)
== map_get_corner_height(targetBaseZ, slope, direction2))
{
slope = tile_element_lower_styles[direction2][slope];
if (slope & SURFACE_STYLE_FLAG_RAISE_OR_LOWER_BASE_HEIGHT)
{
targetBaseZ -= 2;
slope &= ~SURFACE_STYLE_FLAG_RAISE_OR_LOWER_BASE_HEIGHT;
}
}
}
else
{
if (shouldContinue & 0x4)
{
slope = tile_element_raise_styles[direction1][slope];
if (slope & SURFACE_STYLE_FLAG_RAISE_OR_LOWER_BASE_HEIGHT)
{
targetBaseZ += 2;
slope &= ~SURFACE_STYLE_FLAG_RAISE_OR_LOWER_BASE_HEIGHT;
}
}
if ((shouldContinue & 0x8)
&& map_get_corner_height(surfaceElement->base_height, oldSlope, direction2)
== map_get_corner_height(targetBaseZ, slope, direction2))
{
slope = tile_element_raise_styles[direction2][slope];
if (slope & SURFACE_STYLE_FLAG_RAISE_OR_LOWER_BASE_HEIGHT)
{
targetBaseZ += 2;
slope &= ~SURFACE_STYLE_FLAG_RAISE_OR_LOWER_BASE_HEIGHT;
}
}
}
auto landSetHeightAction = LandSetHeightAction(nextLoc, targetBaseZ, slope);
landSetHeightAction.SetFlags(GetFlags());
auto res = isExecuting ? GameActions::ExecuteNested(&landSetHeightAction)
: GameActions::QueryNested(&landSetHeightAction);
if (res->Error == GameActions::Status::Ok)
{
totalCost += res->Cost;
}
}
return totalCost;
}
money32 SmoothLandRowByCorner(
bool isExecuting, const CoordsXY& loc, int32_t expectedLandHeight, int32_t stepX, int32_t stepY, int32_t direction,
int32_t checkDirection) const
{
bool shouldContinue = true;
money32 totalCost = 0;
int32_t landChangePerTile;
if (stepX == 0 || stepY == 0)
{
landChangePerTile = _isLowering ? 2 : -2;
}
else
{
landChangePerTile = _isLowering ? 4 : -4;
}
// check if we need to start at all
if (!LocationValid(loc) || !LocationValid({ loc.x + stepX, loc.y + stepY }))
{
return 0;
}
auto surfaceElement = map_get_surface_element_at(loc);
auto nextSurfaceElement = map_get_surface_element_at(CoordsXY{ loc.x + stepX, loc.y + stepY });
if (surfaceElement == nullptr || nextSurfaceElement == nullptr)
{
return 0;
}
if (tile_element_get_corner_height(surfaceElement, checkDirection) != expectedLandHeight + (_isLowering ? 2 : -2))
{
return 0;
}
if (tile_element_get_corner_height(surfaceElement, checkDirection)
!= tile_element_get_corner_height(nextSurfaceElement, direction))
{
return 0;
}
auto nextLoc = loc;
while (shouldContinue)
{
nextLoc.x += stepX;
nextLoc.y += stepY;
// check if we need to continue after raising the current tile
// this needs to be checked before the tile is changed
if (!LocationValid({ nextLoc.x + stepX, nextLoc.y + stepY }))
{
shouldContinue = false;
}
else
{
surfaceElement = nextSurfaceElement;
nextSurfaceElement = map_get_surface_element_at(CoordsXY{ nextLoc.x + stepX, nextLoc.y + stepY });
if (nextSurfaceElement == nullptr)
{
shouldContinue = false;
}
if (tile_element_get_corner_height(surfaceElement, direction) + landChangePerTile
!= tile_element_get_corner_height(surfaceElement, checkDirection))
{
shouldContinue = false;
}
if (shouldContinue
&& tile_element_get_corner_height(surfaceElement, checkDirection)
!= tile_element_get_corner_height(nextSurfaceElement, direction))
{
shouldContinue = false;
}
}
if (stepX * stepY != 0)
{
totalCost += SmoothLandRowByCorner(
isExecuting, nextLoc, expectedLandHeight + (landChangePerTile / 2), 0, stepY, direction,
checkDirection ^ 3);
totalCost += SmoothLandRowByCorner(
isExecuting, nextLoc, expectedLandHeight + (landChangePerTile / 2), stepX, 0, direction,
checkDirection ^ 1);
}
expectedLandHeight += landChangePerTile;
// change land of current tile
auto result = SmoothLandTile(direction, isExecuting, nextLoc, surfaceElement);
if (result->Error == GameActions::Status::Ok)
{
totalCost += result->Cost;
}
}
return totalCost;
}
GameActions::Result::Ptr SmoothLand(bool isExecuting) const
{
const bool raiseLand = !_isLowering;
const int32_t selectionType = _selectionType;
const int32_t heightOffset = raiseLand ? 2 : -2;
auto normRange = _range.Normalise();
// Cap bounds to map
auto l = std::max(normRange.GetLeft(), 32);
auto t = std::max(normRange.GetTop(), 32);
auto r = std::clamp(normRange.GetRight(), 0, MAXIMUM_TILE_START_XY);
auto b = std::clamp(normRange.GetBottom(), 0, MAXIMUM_TILE_START_XY);
auto validRange = MapRange{ l, t, r, b };
int32_t centreZ = tile_element_height(_coords);
auto res = MakeResult();
res->ErrorTitle = _ErrorTitles[_isLowering ? 0 : 1];
res->Expenditure = ExpenditureType::Landscaping;
res->Position = { _coords.x, _coords.y, centreZ };
// Do the smoothing
switch (selectionType)
{
case MAP_SELECT_TYPE_FULL:
{
uint8_t minHeight = heightOffset + map_get_lowest_land_height(validRange);
uint8_t maxHeight = heightOffset + map_get_highest_land_height(validRange);
// Smooth the 4 corners
{ // top-left
auto surfaceElement = map_get_surface_element_at(CoordsXY{ validRange.GetLeft(), validRange.GetTop() });
if (surfaceElement != nullptr)
{
int32_t z = std::clamp(
static_cast<uint8_t>(tile_element_get_corner_height(surfaceElement, 2)), minHeight, maxHeight);
res->Cost += SmoothLandRowByCorner(
isExecuting, { validRange.GetLeft(), validRange.GetTop() }, z, -32, -32, 0, 2);
}
}
{ // bottom-left
auto surfaceElement = map_get_surface_element_at(CoordsXY{ validRange.GetLeft(), validRange.GetBottom() });
if (surfaceElement != nullptr)
{
int32_t z = std::clamp(
static_cast<uint8_t>(tile_element_get_corner_height(surfaceElement, 3)), minHeight, maxHeight);
res->Cost += SmoothLandRowByCorner(
isExecuting, { validRange.GetLeft(), validRange.GetBottom() }, z, -32, 32, 1, 3);
}
}
{ // bottom-right
auto surfaceElement = map_get_surface_element_at(CoordsXY{ validRange.GetRight(), validRange.GetBottom() });
if (surfaceElement != nullptr)
{
int32_t z = std::clamp(
static_cast<uint8_t>(tile_element_get_corner_height(surfaceElement, 0)), minHeight, maxHeight);
res->Cost += SmoothLandRowByCorner(
isExecuting, { validRange.GetRight(), validRange.GetBottom() }, z, 32, 32, 2, 0);
}
}
{ // top-right
auto surfaceElement = map_get_surface_element_at(CoordsXY{ validRange.GetRight(), validRange.GetTop() });
if (surfaceElement != nullptr)
{
int32_t z = std::clamp(
static_cast<uint8_t>(tile_element_get_corner_height(surfaceElement, 1)), minHeight, maxHeight);
res->Cost += SmoothLandRowByCorner(
isExecuting, { validRange.GetRight(), validRange.GetTop() }, z, 32, -32, 3, 1);
}
}
// Smooth the edges
int32_t z1, z2;
for (int32_t y = validRange.GetTop(); y <= validRange.GetBottom(); y += COORDS_XY_STEP)
{
auto surfaceElement = map_get_surface_element_at(CoordsXY{ validRange.GetLeft(), y });
if (surfaceElement != nullptr)
{
z1 = std::clamp(
static_cast<uint8_t>(tile_element_get_corner_height(surfaceElement, 3)), minHeight, maxHeight);
z2 = std::clamp(
static_cast<uint8_t>(tile_element_get_corner_height(surfaceElement, 2)), minHeight, maxHeight);
res->Cost += SmoothLandRowByEdge(isExecuting, { validRange.GetLeft(), y }, z1, z2, -32, 0, 0, 1, 3, 2);
}
surfaceElement = map_get_surface_element_at(CoordsXY{ validRange.GetRight(), y });
if (surfaceElement != nullptr)
{
z1 = std::clamp(
static_cast<uint8_t>(tile_element_get_corner_height(surfaceElement, 1)), minHeight, maxHeight);
z2 = std::clamp(
static_cast<uint8_t>(tile_element_get_corner_height(surfaceElement, 0)), minHeight, maxHeight);
res->Cost += SmoothLandRowByEdge(isExecuting, { validRange.GetRight(), y }, z1, z2, 32, 0, 2, 3, 1, 0);
}
}
for (int32_t x = validRange.GetLeft(); x <= validRange.GetRight(); x += COORDS_XY_STEP)
{
auto surfaceElement = map_get_surface_element_at(CoordsXY{ x, validRange.GetTop() });
if (surfaceElement != nullptr)
{
z1 = std::clamp(
static_cast<uint8_t>(tile_element_get_corner_height(surfaceElement, 1)), minHeight, maxHeight);
z2 = std::clamp(
static_cast<uint8_t>(tile_element_get_corner_height(surfaceElement, 2)), minHeight, maxHeight);
res->Cost += SmoothLandRowByEdge(isExecuting, { x, validRange.GetTop() }, z1, z2, 0, -32, 0, 3, 1, 2);
}
surfaceElement = map_get_surface_element_at(CoordsXY{ x, validRange.GetBottom() });
if (surfaceElement != nullptr)
{
z1 = std::clamp(
static_cast<uint8_t>(tile_element_get_corner_height(surfaceElement, 0)), minHeight, maxHeight);
z2 = std::clamp(
static_cast<uint8_t>(tile_element_get_corner_height(surfaceElement, 3)), minHeight, maxHeight);
res->Cost += SmoothLandRowByEdge(isExecuting, { x, validRange.GetBottom() }, z1, z2, 0, 32, 1, 2, 0, 3);
}
}
break;
}
case MAP_SELECT_TYPE_CORNER_0:
case MAP_SELECT_TYPE_CORNER_1:
case MAP_SELECT_TYPE_CORNER_2:
case MAP_SELECT_TYPE_CORNER_3:
{
auto surfaceElement = map_get_surface_element_at(CoordsXY{ validRange.GetLeft(), validRange.GetTop() });
if (surfaceElement == nullptr)
break;
uint8_t newBaseZ = surfaceElement->base_height;
uint8_t newSlope = surfaceElement->GetSlope();
if (raiseLand)
{
newSlope = tile_element_raise_styles[selectionType][newSlope];
}
else
{
newSlope = tile_element_lower_styles[selectionType][newSlope];
}
if (newSlope & SURFACE_STYLE_FLAG_RAISE_OR_LOWER_BASE_HEIGHT)
{
newBaseZ += heightOffset;
newSlope &= ~SURFACE_STYLE_FLAG_RAISE_OR_LOWER_BASE_HEIGHT;
}
// Smooth the corners
int32_t z = map_get_corner_height(newBaseZ, newSlope, 2);
res->Cost += SmoothLandRowByCorner(
isExecuting, { validRange.GetLeft(), validRange.GetTop() }, z, -32, -32, 0, 2);
z = map_get_corner_height(newBaseZ, newSlope, 0);
res->Cost += SmoothLandRowByCorner(isExecuting, { validRange.GetLeft(), validRange.GetTop() }, z, 32, 32, 2, 0);
z = map_get_corner_height(newBaseZ, newSlope, 3);
res->Cost += SmoothLandRowByCorner(
isExecuting, { validRange.GetLeft(), validRange.GetTop() }, z, -32, 32, 1, 3);
z = map_get_corner_height(newBaseZ, newSlope, 1);
res->Cost += SmoothLandRowByCorner(
isExecuting, { validRange.GetLeft(), validRange.GetTop() }, z, 32, -32, 3, 1);
// Smooth the edges
switch (selectionType)
{
case MAP_SELECT_TYPE_CORNER_0:
z = map_get_corner_height(newBaseZ, newSlope, 0);
res->Cost += SmoothLandRowByCorner(
isExecuting, { validRange.GetLeft(), validRange.GetTop() }, z, 32, 0, 3, 0);
res->Cost += SmoothLandRowByCorner(
isExecuting, { validRange.GetLeft(), validRange.GetTop() }, z, 0, 32, 1, 0);
z = map_get_corner_height(newBaseZ, newSlope, 3);
res->Cost += SmoothLandRowByCorner(
isExecuting, { validRange.GetLeft(), validRange.GetTop() }, z, -32, 0, 0, 3);
z = map_get_corner_height(newBaseZ, newSlope, 1);
res->Cost += SmoothLandRowByCorner(
isExecuting, { validRange.GetLeft(), validRange.GetTop() }, z, 0, -32, 0, 1);
break;
case MAP_SELECT_TYPE_CORNER_1:
z = map_get_corner_height(newBaseZ, newSlope, 1);
res->Cost += SmoothLandRowByCorner(
isExecuting, { validRange.GetLeft(), validRange.GetTop() }, z, 32, 0, 2, 1);
res->Cost += SmoothLandRowByCorner(
isExecuting, { validRange.GetLeft(), validRange.GetTop() }, z, 0, -32, 0, 1);
z = map_get_corner_height(newBaseZ, newSlope, 2);
res->Cost += SmoothLandRowByCorner(
isExecuting, { validRange.GetLeft(), validRange.GetTop() }, z, -32, 0, 1, 2);
z = map_get_corner_height(newBaseZ, newSlope, 0);
res->Cost += SmoothLandRowByCorner(
isExecuting, { validRange.GetLeft(), validRange.GetTop() }, z, 0, 32, 1, 0);
break;
case MAP_SELECT_TYPE_CORNER_2:
z = map_get_corner_height(newBaseZ, newSlope, 2);
res->Cost += SmoothLandRowByCorner(
isExecuting, { validRange.GetLeft(), validRange.GetTop() }, z, -32, 0, 1, 2);
res->Cost += SmoothLandRowByCorner(
isExecuting, { validRange.GetLeft(), validRange.GetTop() }, z, 0, -32, 3, 2);
z = map_get_corner_height(newBaseZ, newSlope, 1);
res->Cost += SmoothLandRowByCorner(
isExecuting, { validRange.GetLeft(), validRange.GetTop() }, z, 32, 0, 2, 1);
z = map_get_corner_height(newBaseZ, newSlope, 3);
res->Cost += SmoothLandRowByCorner(
isExecuting, { validRange.GetLeft(), validRange.GetTop() }, z, 0, 32, 2, 3);
break;
case MAP_SELECT_TYPE_CORNER_3:
z = map_get_corner_height(newBaseZ, newSlope, 3);
res->Cost += SmoothLandRowByCorner(
isExecuting, { validRange.GetLeft(), validRange.GetTop() }, z, -32, 0, 0, 3);
res->Cost += SmoothLandRowByCorner(
isExecuting, { validRange.GetLeft(), validRange.GetTop() }, z, 0, 32, 2, 3);
z = map_get_corner_height(newBaseZ, newSlope, 0);
res->Cost += SmoothLandRowByCorner(
isExecuting, { validRange.GetLeft(), validRange.GetTop() }, z, 32, 0, 3, 0);
z = map_get_corner_height(newBaseZ, newSlope, 2);
res->Cost += SmoothLandRowByCorner(
isExecuting, { validRange.GetLeft(), validRange.GetTop() }, z, 0, -32, 3, 2);
break;
}
break;
}
case MAP_SELECT_TYPE_EDGE_0:
case MAP_SELECT_TYPE_EDGE_1:
case MAP_SELECT_TYPE_EDGE_2:
case MAP_SELECT_TYPE_EDGE_3:
{
// TODO: Handle smoothing by edge
// Get the two corners to raise
auto surfaceElement = map_get_surface_element_at(CoordsXY{ validRange.GetLeft(), validRange.GetTop() });
if (surfaceElement == nullptr)
break;
uint8_t newBaseZ = surfaceElement->base_height;
uint8_t oldSlope = surfaceElement->GetSlope();
uint8_t newSlope = oldSlope;
int32_t rowIndex = selectionType - (MAP_SELECT_TYPE_EDGE_0 - MAP_SELECT_TYPE_FULL - 1);
if (raiseLand)
{
newSlope = tile_element_raise_styles[rowIndex][oldSlope];
}
else
{
newSlope = tile_element_lower_styles[rowIndex][oldSlope];
}
const bool changeBaseHeight = newSlope & SURFACE_STYLE_FLAG_RAISE_OR_LOWER_BASE_HEIGHT;
if (changeBaseHeight)
{
newBaseZ += heightOffset;
newSlope &= ~SURFACE_STYLE_FLAG_RAISE_OR_LOWER_BASE_HEIGHT;
}
const uint8_t edge = selectionType - MAP_SELECT_TYPE_EDGE_0;
// Table with corners for each edge selection. The first two are the selected corners, the latter
// two are the opposites
static constexpr uint8_t cornerIndices[][4] = {
{ 2, 3, 1, 0 }, // MAP_SELECT_TYPE_EDGE_0
{ 3, 0, 2, 1 }, // MAP_SELECT_TYPE_EDGE_1
{ 0, 1, 3, 2 }, // MAP_SELECT_TYPE_EDGE_2
{ 1, 2, 0, 3 }, // MAP_SELECT_TYPE_EDGE_3
};
// Big coordinate offsets for the neigbouring tile for the given edge selection
static constexpr CoordsXY stepOffsets[] = {
{ -32, 0 },
{ 0, 32 },
{ 32, 0 },
{ 0, -32 },
};
// Smooth higher and lower edges
uint8_t c1 = cornerIndices[edge][0];
uint8_t c2 = cornerIndices[edge][1];
uint8_t c3 = cornerIndices[edge][2];
uint8_t c4 = cornerIndices[edge][3];
uint8_t z1 = map_get_corner_height(newBaseZ, newSlope, c1);
uint8_t z2 = map_get_corner_height(newBaseZ, newSlope, c2);
uint8_t z3 = map_get_corner_height(newBaseZ, newSlope, c3);
uint8_t z4 = map_get_corner_height(newBaseZ, newSlope, c4);
// Smooth the edge at the top of the new slope
res->Cost += SmoothLandRowByEdge(
isExecuting, { validRange.GetLeft(), validRange.GetTop() }, z1, z2, stepOffsets[edge].x,
stepOffsets[edge].y, c3, c4, c1, c2);
// Smooth the edge at the bottom of the new slope
res->Cost += SmoothLandRowByEdge(
isExecuting, { validRange.GetLeft(), validRange.GetTop() }, z3, z4, -stepOffsets[edge].x,
-stepOffsets[edge].y, c1, c2, c3, c4);
// Smooth corners
res->Cost += SmoothLandRowByCorner(
isExecuting, { validRange.GetLeft(), validRange.GetTop() }, z1, -stepOffsets[edge].y, stepOffsets[edge].x,
c2, c1);
res->Cost += SmoothLandRowByCorner(
isExecuting, { validRange.GetLeft(), validRange.GetTop() }, z2, stepOffsets[edge].y, -stepOffsets[edge].x,
c1, c2);
int32_t z = map_get_corner_height(newBaseZ, newSlope, 2);
res->Cost += SmoothLandRowByCorner(
isExecuting, { validRange.GetLeft(), validRange.GetTop() }, z, -32, -32, 0, 2);
z = map_get_corner_height(newBaseZ, newSlope, 0);
res->Cost += SmoothLandRowByCorner(isExecuting, { validRange.GetLeft(), validRange.GetTop() }, z, 32, 32, 2, 0);
z = map_get_corner_height(newBaseZ, newSlope, 3);
res->Cost += SmoothLandRowByCorner(
isExecuting, { validRange.GetLeft(), validRange.GetTop() }, z, -32, 32, 1, 3);
z = map_get_corner_height(newBaseZ, newSlope, 1);
res->Cost += SmoothLandRowByCorner(
isExecuting, { validRange.GetLeft(), validRange.GetTop() }, z, 32, -32, 3, 1);
break;
}
default:
log_error("Invalid map selection %u", _selectionType);
return MakeResult(GameActions::Status::InvalidParameters, res->ErrorTitle.GetStringId());
} // switch selectionType
// Raise / lower the land tool selection area
GameActions::Result::Ptr result;
if (raiseLand)
{
auto raiseLandAction = LandRaiseAction({ _coords.x, _coords.y }, validRange, selectionType);
raiseLandAction.SetFlags(GetFlags());
result = isExecuting ? GameActions::ExecuteNested(&raiseLandAction) : GameActions::QueryNested(&raiseLandAction);
}
else
{
auto lowerLandAction = LandLowerAction({ _coords.x, _coords.y }, validRange, selectionType);
lowerLandAction.SetFlags(GetFlags());
result = isExecuting ? GameActions::ExecuteNested(&lowerLandAction) : GameActions::QueryNested(&lowerLandAction);
}
if (result->Error != GameActions::Status::Ok)
{
return result;
}
if (isExecuting)
{
OpenRCT2::Audio::Play3D(OpenRCT2::Audio::SoundId::PlaceItem, { _coords.x, _coords.y, centreZ });
}
res->Cost += result->Cost;
return res;
}
};