mirror of https://github.com/OpenRCT2/OpenRCT2.git
435 lines
14 KiB
C++
435 lines
14 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.
|
|
*****************************************************************************/
|
|
|
|
#include "SmallSceneryPlaceAction.h"
|
|
|
|
#include "../Cheats.h"
|
|
#include "../OpenRCT2.h"
|
|
#include "../common.h"
|
|
#include "../core/MemoryStream.h"
|
|
#include "../interface/Window.h"
|
|
#include "../localisation/Localisation.h"
|
|
#include "../localisation/StringIds.h"
|
|
#include "../management/Finance.h"
|
|
#include "../ride/Ride.h"
|
|
#include "../ride/TrackDesign.h"
|
|
#include "../world/ConstructionClearance.h"
|
|
#include "../world/MapAnimation.h"
|
|
#include "../world/Park.h"
|
|
#include "../world/SmallScenery.h"
|
|
#include "../world/Surface.h"
|
|
#include "../world/TileElement.h"
|
|
#include "GameAction.h"
|
|
#include "SmallSceneryRemoveAction.h"
|
|
|
|
SmallSceneryPlaceAction::SmallSceneryPlaceAction(
|
|
const CoordsXYZD& loc, uint8_t quadrant, ObjectEntryIndex sceneryType, uint8_t primaryColour, uint8_t secondaryColour)
|
|
: _loc(loc)
|
|
, _quadrant(quadrant)
|
|
, _sceneryType(sceneryType)
|
|
, _primaryColour(primaryColour)
|
|
, _secondaryColour(secondaryColour)
|
|
{
|
|
}
|
|
|
|
void SmallSceneryPlaceAction::AcceptParameters(GameActionParameterVisitor& visitor)
|
|
{
|
|
visitor.Visit(_loc);
|
|
visitor.Visit("quadrant", _quadrant);
|
|
visitor.Visit("object", _sceneryType);
|
|
visitor.Visit("primaryColour", _primaryColour);
|
|
visitor.Visit("secondaryColour", _secondaryColour);
|
|
}
|
|
|
|
uint32_t SmallSceneryPlaceAction::GetCooldownTime() const
|
|
{
|
|
return 20;
|
|
}
|
|
|
|
uint16_t SmallSceneryPlaceAction::GetActionFlags() const
|
|
{
|
|
return GameAction::GetActionFlags();
|
|
}
|
|
|
|
void SmallSceneryPlaceAction::Serialise(DataSerialiser& stream)
|
|
{
|
|
GameAction::Serialise(stream);
|
|
|
|
stream << DS_TAG(_loc) << DS_TAG(_quadrant) << DS_TAG(_sceneryType) << DS_TAG(_primaryColour) << DS_TAG(_secondaryColour);
|
|
}
|
|
|
|
GameActions::Result::Ptr SmallSceneryPlaceAction::Query() const
|
|
{
|
|
bool isOnWater = false;
|
|
bool supportsRequired = false;
|
|
if (_loc.z != 0)
|
|
{
|
|
supportsRequired = true;
|
|
}
|
|
int32_t landHeight = tile_element_height(_loc);
|
|
int16_t waterHeight = tile_element_water_height(_loc);
|
|
|
|
int32_t surfaceHeight = landHeight;
|
|
// If on water
|
|
if (waterHeight > 0)
|
|
{
|
|
surfaceHeight = waterHeight;
|
|
}
|
|
auto res = MakeResult();
|
|
auto centre = _loc.ToTileCentre();
|
|
res->Position.x = centre.x;
|
|
res->Position.y = centre.y;
|
|
res->Position.z = surfaceHeight;
|
|
if (_loc.z != 0)
|
|
{
|
|
surfaceHeight = _loc.z;
|
|
res->Position.z = surfaceHeight;
|
|
}
|
|
|
|
if (!LocationValid(_loc))
|
|
{
|
|
return MakeResult(GameActions::Status::InvalidParameters, STR_CANT_POSITION_THIS_HERE, STR_NONE);
|
|
}
|
|
|
|
if (!MapCheckCapacityAndReorganise(_loc))
|
|
{
|
|
return MakeResult(GameActions::Status::NoFreeElements, STR_CANT_POSITION_THIS_HERE, STR_TILE_ELEMENT_LIMIT_REACHED);
|
|
}
|
|
|
|
if (!_trackDesignDrawingPreview && (_loc.x > GetMapSizeMaxXY() || _loc.y > GetMapSizeMaxXY()))
|
|
{
|
|
return MakeResult(GameActions::Status::InvalidParameters, STR_CANT_POSITION_THIS_HERE, STR_NONE);
|
|
}
|
|
|
|
auto* sceneryEntry = get_small_scenery_entry(_sceneryType);
|
|
if (sceneryEntry == nullptr)
|
|
{
|
|
return MakeResult(GameActions::Status::InvalidParameters, STR_CANT_POSITION_THIS_HERE, STR_NONE);
|
|
}
|
|
|
|
auto quadrant = _quadrant;
|
|
if (sceneryEntry->HasFlag(SMALL_SCENERY_FLAG_FULL_TILE) || !sceneryEntry->HasFlag(SMALL_SCENERY_FLAG_DIAGONAL))
|
|
{
|
|
if (sceneryEntry->HasFlag(
|
|
SMALL_SCENERY_FLAG_DIAGONAL | SMALL_SCENERY_FLAG_HALF_SPACE | SMALL_SCENERY_FLAG_THREE_QUARTERS))
|
|
{
|
|
quadrant = 0;
|
|
}
|
|
}
|
|
|
|
// Check if sub tile height is any different compared to actual surface tile height
|
|
auto loc2 = _loc;
|
|
if (sceneryEntry->HasFlag(SMALL_SCENERY_FLAG_FULL_TILE))
|
|
{
|
|
loc2 = loc2.ToTileCentre();
|
|
}
|
|
else
|
|
{
|
|
loc2.x += SceneryQuadrantOffsets[quadrant & 3].x - 1;
|
|
loc2.y += SceneryQuadrantOffsets[quadrant & 3].y - 1;
|
|
}
|
|
landHeight = tile_element_height(loc2);
|
|
waterHeight = tile_element_water_height(loc2);
|
|
|
|
surfaceHeight = landHeight;
|
|
// If on water
|
|
if (waterHeight > 0)
|
|
{
|
|
// base_height2 is now the water height
|
|
surfaceHeight = waterHeight;
|
|
if (_loc.z == 0)
|
|
{
|
|
isOnWater = true;
|
|
}
|
|
}
|
|
auto targetHeight = _loc.z;
|
|
if (_loc.z == 0)
|
|
{
|
|
targetHeight = surfaceHeight;
|
|
}
|
|
|
|
if (!(gScreenFlags & SCREEN_FLAGS_SCENARIO_EDITOR) && !gCheatsSandboxMode
|
|
&& !map_is_location_owned({ _loc.x, _loc.y, targetHeight }))
|
|
{
|
|
return MakeResult(GameActions::Status::NotOwned, STR_CANT_POSITION_THIS_HERE, STR_LAND_NOT_OWNED_BY_PARK);
|
|
}
|
|
|
|
auto* surfaceElement = map_get_surface_element_at(_loc);
|
|
|
|
if (surfaceElement != nullptr && !gCheatsDisableClearanceChecks && surfaceElement->GetWaterHeight() > 0)
|
|
{
|
|
int32_t water_height = surfaceElement->GetWaterHeight() - 1;
|
|
if (water_height > targetHeight)
|
|
{
|
|
return MakeResult(GameActions::Status::Disallowed, STR_CANT_POSITION_THIS_HERE, STR_CANT_BUILD_THIS_UNDERWATER);
|
|
}
|
|
}
|
|
|
|
if (!gCheatsDisableClearanceChecks && !(sceneryEntry->HasFlag(SMALL_SCENERY_FLAG_STACKABLE)))
|
|
{
|
|
if (isOnWater)
|
|
{
|
|
return MakeResult(GameActions::Status::Disallowed, STR_CANT_POSITION_THIS_HERE, STR_CAN_ONLY_BUILD_THIS_ON_LAND);
|
|
}
|
|
|
|
if (surfaceElement != nullptr && surfaceElement->GetWaterHeight() > 0)
|
|
{
|
|
if (surfaceElement->GetWaterHeight() > targetHeight)
|
|
{
|
|
return MakeResult(
|
|
GameActions::Status::Disallowed, STR_CANT_POSITION_THIS_HERE, STR_CAN_ONLY_BUILD_THIS_ON_LAND);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!gCheatsDisableClearanceChecks && (sceneryEntry->HasFlag(SMALL_SCENERY_FLAG_REQUIRE_FLAT_SURFACE)) && !supportsRequired
|
|
&& !isOnWater && surfaceElement != nullptr && (surfaceElement->GetSlope() != TILE_ELEMENT_SLOPE_FLAT))
|
|
{
|
|
return MakeResult(GameActions::Status::Disallowed, STR_CANT_POSITION_THIS_HERE, STR_LEVEL_LAND_REQUIRED);
|
|
}
|
|
|
|
if (!gCheatsDisableSupportLimits && !(sceneryEntry->HasFlag(SMALL_SCENERY_FLAG_STACKABLE)) && supportsRequired)
|
|
{
|
|
if (!isOnWater)
|
|
{
|
|
if (surfaceElement != nullptr)
|
|
{
|
|
if (surfaceElement->GetWaterHeight() > 0 || (surfaceElement->GetBaseZ()) != targetHeight)
|
|
{
|
|
return MakeResult(GameActions::Status::Disallowed, STR_CANT_POSITION_THIS_HERE, STR_LEVEL_LAND_REQUIRED);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return MakeResult(GameActions::Status::Disallowed, STR_CANT_POSITION_THIS_HERE, STR_CAN_ONLY_BUILD_THIS_ON_LAND);
|
|
}
|
|
}
|
|
|
|
int32_t zLow = targetHeight;
|
|
int32_t zHigh = zLow + ceil2(sceneryEntry->height, COORDS_Z_STEP);
|
|
uint8_t collisionQuadrants = 0b1111;
|
|
auto quadRotation{ 0 };
|
|
if (!(sceneryEntry->HasFlag(SMALL_SCENERY_FLAG_FULL_TILE)))
|
|
{
|
|
quadRotation = (quadrant ^ 2);
|
|
collisionQuadrants = 0b0001;
|
|
}
|
|
if (!(sceneryEntry->HasFlag(SMALL_SCENERY_FLAG_HALF_SPACE)))
|
|
{
|
|
if (sceneryEntry->HasFlag(SMALL_SCENERY_FLAG_DIAGONAL) && sceneryEntry->HasFlag(SMALL_SCENERY_FLAG_FULL_TILE))
|
|
{
|
|
if (sceneryEntry->HasFlag(SMALL_SCENERY_FLAG_THREE_QUARTERS))
|
|
{
|
|
quadRotation = ((quadrant ^ 2) + _loc.direction) & 3;
|
|
collisionQuadrants = 0b1011;
|
|
}
|
|
else
|
|
{
|
|
quadRotation = (quadrant + _loc.direction) & 1;
|
|
collisionQuadrants = 0b1010;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
quadRotation = ((quadrant ^ 2) + _loc.direction) & 3;
|
|
collisionQuadrants = 0b0011;
|
|
}
|
|
uint8_t supports = 0;
|
|
if (!supportsRequired)
|
|
{
|
|
supports = 0b1111;
|
|
}
|
|
|
|
QuarterTile quarterTile = QuarterTile{ collisionQuadrants, supports }.Rotate(quadRotation);
|
|
const auto isTree = sceneryEntry->HasFlag(SMALL_SCENERY_FLAG_IS_TREE);
|
|
auto canBuild = MapCanConstructWithClearAt(
|
|
{ _loc, zLow, zHigh }, &map_place_scenery_clear_func, quarterTile, GetFlags(), CREATE_CROSSING_MODE_NONE, isTree);
|
|
if (canBuild->Error != GameActions::Status::Ok)
|
|
{
|
|
canBuild->ErrorTitle = STR_CANT_POSITION_THIS_HERE;
|
|
return canBuild;
|
|
}
|
|
|
|
const auto clearanceData = canBuild->GetData<ConstructClearResult>();
|
|
const uint8_t groundFlags = clearanceData.GroundFlags & (ELEMENT_IS_ABOVE_GROUND | ELEMENT_IS_UNDERGROUND);
|
|
res->SetData(SmallSceneryPlaceActionResult{ groundFlags, 0, 0 });
|
|
|
|
res->Expenditure = ExpenditureType::Landscaping;
|
|
res->Cost = (sceneryEntry->price * 10) + canBuild->Cost;
|
|
|
|
return res;
|
|
}
|
|
|
|
GameActions::Result::Ptr SmallSceneryPlaceAction::Execute() const
|
|
{
|
|
bool supportsRequired = false;
|
|
if (_loc.z != 0)
|
|
{
|
|
supportsRequired = true;
|
|
}
|
|
int32_t landHeight = tile_element_height(_loc);
|
|
int16_t waterHeight = tile_element_water_height(_loc);
|
|
|
|
int32_t surfaceHeight = landHeight;
|
|
// If on water
|
|
if (waterHeight > 0)
|
|
{
|
|
surfaceHeight = waterHeight;
|
|
}
|
|
auto res = MakeResult();
|
|
auto centre = _loc.ToTileCentre();
|
|
res->Position.x = centre.x;
|
|
res->Position.y = centre.y;
|
|
res->Position.z = surfaceHeight;
|
|
if (_loc.z != 0)
|
|
{
|
|
surfaceHeight = _loc.z;
|
|
res->Position.z = surfaceHeight;
|
|
}
|
|
|
|
auto* sceneryEntry = get_small_scenery_entry(_sceneryType);
|
|
if (sceneryEntry == nullptr)
|
|
{
|
|
return MakeResult(GameActions::Status::InvalidParameters, STR_CANT_POSITION_THIS_HERE, STR_NONE);
|
|
}
|
|
|
|
auto quadrant = _quadrant;
|
|
if (sceneryEntry->HasFlag(SMALL_SCENERY_FLAG_FULL_TILE) || !sceneryEntry->HasFlag(SMALL_SCENERY_FLAG_DIAGONAL))
|
|
{
|
|
if (sceneryEntry->HasFlag(
|
|
SMALL_SCENERY_FLAG_DIAGONAL | SMALL_SCENERY_FLAG_HALF_SPACE | SMALL_SCENERY_FLAG_THREE_QUARTERS))
|
|
{
|
|
quadrant = 0;
|
|
}
|
|
}
|
|
|
|
// Check if sub tile height is any different compared to actual surface tile height
|
|
int32_t x2 = _loc.x;
|
|
int32_t y2 = _loc.y;
|
|
if (sceneryEntry->HasFlag(SMALL_SCENERY_FLAG_FULL_TILE))
|
|
{
|
|
x2 += 16;
|
|
y2 += 16;
|
|
}
|
|
else
|
|
{
|
|
x2 += SceneryQuadrantOffsets[quadrant & 3].x - 1;
|
|
y2 += SceneryQuadrantOffsets[quadrant & 3].y - 1;
|
|
}
|
|
landHeight = tile_element_height({ x2, y2 });
|
|
waterHeight = tile_element_water_height({ x2, y2 });
|
|
|
|
surfaceHeight = landHeight;
|
|
// If on water
|
|
if (waterHeight > 0)
|
|
{
|
|
// base_height2 is now the water height
|
|
surfaceHeight = waterHeight;
|
|
}
|
|
auto targetHeight = _loc.z;
|
|
if (_loc.z == 0)
|
|
{
|
|
targetHeight = surfaceHeight;
|
|
}
|
|
|
|
if (!(GetFlags() & GAME_COMMAND_FLAG_GHOST))
|
|
{
|
|
footpath_remove_litter({ _loc, targetHeight });
|
|
if (!gCheatsDisableClearanceChecks && (sceneryEntry->HasFlag(SMALL_SCENERY_FLAG_NO_WALLS)))
|
|
{
|
|
wall_remove_at({ _loc, targetHeight, targetHeight + sceneryEntry->height });
|
|
}
|
|
}
|
|
|
|
int32_t zLow = targetHeight;
|
|
int32_t zHigh = zLow + ceil2(sceneryEntry->height, 8);
|
|
uint8_t collisionQuadrants = 0b1111;
|
|
auto quadRotation{ 0 };
|
|
if (!(sceneryEntry->HasFlag(SMALL_SCENERY_FLAG_FULL_TILE)))
|
|
{
|
|
quadRotation = (quadrant ^ 2);
|
|
collisionQuadrants = 0b0001;
|
|
}
|
|
if (!(sceneryEntry->HasFlag(SMALL_SCENERY_FLAG_HALF_SPACE)))
|
|
{
|
|
if (sceneryEntry->HasFlag(SMALL_SCENERY_FLAG_DIAGONAL) && sceneryEntry->HasFlag(SMALL_SCENERY_FLAG_FULL_TILE))
|
|
{
|
|
if (sceneryEntry->HasFlag(SMALL_SCENERY_FLAG_THREE_QUARTERS))
|
|
{
|
|
quadRotation = ((quadrant ^ 2) + _loc.direction) & 3;
|
|
collisionQuadrants = 0b1011;
|
|
}
|
|
else
|
|
{
|
|
quadRotation = (quadrant + _loc.direction) & 1;
|
|
collisionQuadrants = 0b1010;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
quadRotation = ((quadrant ^ 2) + _loc.direction) & 3;
|
|
collisionQuadrants = 0b0011;
|
|
}
|
|
uint8_t supports = 0;
|
|
if (!supportsRequired)
|
|
{
|
|
supports = 0b1111;
|
|
}
|
|
|
|
QuarterTile quarterTile = QuarterTile{ collisionQuadrants, supports }.Rotate(quadRotation);
|
|
const auto isTree = sceneryEntry->HasFlag(SMALL_SCENERY_FLAG_IS_TREE);
|
|
auto canBuild = MapCanConstructWithClearAt(
|
|
{ _loc, zLow, zHigh }, &map_place_scenery_clear_func, quarterTile, GetFlags() | GAME_COMMAND_FLAG_APPLY,
|
|
CREATE_CROSSING_MODE_NONE, isTree);
|
|
if (canBuild->Error != GameActions::Status::Ok)
|
|
{
|
|
canBuild->ErrorTitle = STR_CANT_POSITION_THIS_HERE;
|
|
return canBuild;
|
|
}
|
|
|
|
res->Expenditure = ExpenditureType::Landscaping;
|
|
res->Cost = (sceneryEntry->price * 10) + canBuild->Cost;
|
|
|
|
auto* sceneryElement = TileElementInsert<SmallSceneryElement>(
|
|
CoordsXYZ{ _loc, zLow }, quarterTile.GetBaseQuarterOccupied());
|
|
if (sceneryElement == nullptr)
|
|
{
|
|
return MakeResult(GameActions::Status::NoFreeElements, STR_CANT_POSITION_THIS_HERE, STR_TILE_ELEMENT_LIMIT_REACHED);
|
|
}
|
|
|
|
sceneryElement->SetDirection(_loc.direction);
|
|
sceneryElement->SetSceneryQuadrant(quadrant);
|
|
sceneryElement->SetEntryIndex(_sceneryType);
|
|
sceneryElement->SetAge(0);
|
|
sceneryElement->SetPrimaryColour(_primaryColour);
|
|
sceneryElement->SetSecondaryColour(_secondaryColour);
|
|
sceneryElement->SetClearanceZ(sceneryElement->GetBaseZ() + sceneryEntry->height + 7);
|
|
sceneryElement->SetGhost(GetFlags() & GAME_COMMAND_FLAG_GHOST);
|
|
if (supportsRequired)
|
|
{
|
|
sceneryElement->SetNeedsSupports();
|
|
}
|
|
|
|
const auto clearanceData = canBuild->GetData<ConstructClearResult>();
|
|
const uint8_t groundFlags = clearanceData.GroundFlags & (ELEMENT_IS_ABOVE_GROUND | ELEMENT_IS_UNDERGROUND);
|
|
res->SetData(
|
|
SmallSceneryPlaceActionResult{ groundFlags, sceneryElement->GetBaseZ(), sceneryElement->GetSceneryQuadrant() });
|
|
|
|
map_invalidate_tile_full(_loc);
|
|
if (sceneryEntry->HasFlag(SMALL_SCENERY_FLAG_ANIMATED))
|
|
{
|
|
map_animation_create(MAP_ANIMATION_TYPE_SMALL_SCENERY, CoordsXYZ{ _loc, sceneryElement->GetBaseZ() });
|
|
}
|
|
|
|
return res;
|
|
}
|