Merge pull request #8966 from duncanspumpkin/wall_place_ga

Implement wall place game action
This commit is contained in:
Duncan 2019-04-02 19:34:06 +01:00 committed by GitHub
commit 6b9e36f929
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 753 additions and 610 deletions

View File

@ -34,6 +34,7 @@
#include <openrct2/actions/PauseToggleAction.hpp>
#include <openrct2/actions/SmallSceneryPlaceAction.hpp>
#include <openrct2/actions/SurfaceSetStyleAction.hpp>
#include <openrct2/actions/WallPlaceAction.hpp>
#include <openrct2/actions/WaterLowerAction.hpp>
#include <openrct2/actions/WaterRaiseAction.hpp>
#include <openrct2/audio/audio.h>
@ -1805,32 +1806,42 @@ static void window_top_toolbar_scenery_tool_down(int16_t x, int16_t y, rct_windo
for (; zAttemptRange != 0; zAttemptRange--)
{
int32_t flags = (parameter_1 & 0xFF00) | GAME_COMMAND_FLAG_APPLY;
auto primaryColour = (parameter_2 >> 8) & 0xFF;
auto edges = parameter_2 & 0xFF;
auto type = (parameter_1 >> 8) & 0xFF;
auto wallPlaceAction = WallPlaceAction(
type, { gridX, gridY, gSceneryPlaceZ }, edges, primaryColour, _secondaryColour, _tertiaryColour);
gDisableErrorWindowSound = true;
gGameCommandErrorTitle = STR_CANT_BUILD_PARK_ENTRANCE_HERE;
int32_t cost = game_do_command(
gridX, flags, gridY, parameter_2, GAME_COMMAND_PLACE_WALL, gSceneryPlaceZ,
_secondaryColour | (_tertiaryColour << 8));
gDisableErrorWindowSound = false;
if (cost != MONEY32_UNDEFINED)
{
window_close_by_class(WC_ERROR);
audio_play_sound_at_location(SOUND_PLACE_ITEM, gCommandPosition.x, gCommandPosition.y, gCommandPosition.z);
return;
}
if (gGameCommandErrorText == STR_NOT_ENOUGH_CASH_REQUIRES
|| gGameCommandErrorText == STR_CAN_ONLY_BUILD_THIS_ON_WATER)
auto res = GameActions::Query(&wallPlaceAction);
if (res->Error == GA_ERROR::OK)
{
break;
}
gSceneryPlaceZ += 8;
if (res->ErrorMessage == STR_NOT_ENOUGH_CASH_REQUIRES || res->ErrorMessage == STR_CAN_ONLY_BUILD_THIS_ON_WATER)
{
break;
}
if (zAttemptRange != 1)
{
gSceneryPlaceZ += 8;
}
}
audio_play_sound_at_location(SOUND_ERROR, gCommandPosition.x, gCommandPosition.y, gCommandPosition.z);
auto primaryColour = (parameter_2 >> 8) & 0xFF;
auto edges = parameter_2 & 0xFF;
auto type = (parameter_1 >> 8) & 0xFF;
auto wallPlaceAction = WallPlaceAction(
type, { gridX, gridY, gSceneryPlaceZ }, edges, primaryColour, _secondaryColour, _tertiaryColour);
wallPlaceAction.SetCallback([](const GameAction* ga, const GameActionResult* result) {
if (result->Error == GA_ERROR::OK)
{
audio_play_sound_at_location(SOUND_PLACE_ITEM, result->Position.x, result->Position.y, result->Position.z);
}
});
auto res = GameActions::Execute(&wallPlaceAction);
break;
}
case SCENERY_TYPE_LARGE:
@ -2495,24 +2506,34 @@ static money32 try_place_ghost_scenery(
break;
}
case 2:
{
// Walls
// 6e26b0
cost = game_do_command(
map_tile.x, parameter_1 | 0x69, map_tile.y, parameter_2, GAME_COMMAND_PLACE_WALL, gSceneryPlaceZ,
_secondaryColour | (_tertiaryColour << 8));
auto primaryColour = (parameter_2 >> 8) & 0xFF;
auto edges = parameter_2 & 0xFF;
auto type = (parameter_1 >> 8) & 0xFF;
auto wallPlaceAction = WallPlaceAction(
type, { map_tile.x, map_tile.y, gSceneryPlaceZ }, edges, primaryColour, _secondaryColour, _tertiaryColour);
wallPlaceAction.SetFlags(
GAME_COMMAND_FLAG_GHOST | GAME_COMMAND_FLAG_ALLOW_DURING_PAUSED | GAME_COMMAND_FLAG_PATH_SCENERY);
wallPlaceAction.SetCallback([=](const GameAction* ga, const GameActionResult* result) {
if (result->Error != GA_ERROR::OK)
return;
if (cost == MONEY32_UNDEFINED)
return cost;
gSceneryGhostPosition.x = map_tile.x;
gSceneryGhostPosition.y = map_tile.y;
gSceneryGhostWallRotation = edges;
gSceneryGhostPosition.z = gSceneryTileElement->base_height;
gSceneryGhostPosition.x = map_tile.x;
gSceneryGhostPosition.y = map_tile.y;
gSceneryGhostWallRotation = (parameter_2 & 0xFF);
gSceneryGhostType |= SCENERY_GHOST_FLAG_2;
});
tileElement = gSceneryTileElement;
gSceneryGhostPosition.z = tileElement->base_height;
gSceneryGhostType |= SCENERY_GHOST_FLAG_2;
auto res = GameActions::Execute(&wallPlaceAction);
if (res->Error != GA_ERROR::OK)
return MONEY32_UNDEFINED;
cost = res->Cost;
break;
}
case 3:
// Large Scenery
// 6e25a7

View File

@ -409,9 +409,8 @@ int32_t game_do_command_p(
// Remove ghost scenery so it doesn't interfere with incoming network command
if ((flags & GAME_COMMAND_FLAG_NETWORKED) && !(flags & GAME_COMMAND_FLAG_GHOST)
&& (command == GAME_COMMAND_PLACE_WALL || command == GAME_COMMAND_PLACE_SCENERY
|| command == GAME_COMMAND_PLACE_LARGE_SCENERY || command == GAME_COMMAND_PLACE_BANNER
|| command == GAME_COMMAND_PLACE_PATH))
&& (command == GAME_COMMAND_PLACE_SCENERY || command == GAME_COMMAND_PLACE_LARGE_SCENERY
|| command == GAME_COMMAND_PLACE_BANNER || command == GAME_COMMAND_PLACE_PATH))
{
scenery_remove_ghost_tool_placement();
}
@ -611,9 +610,7 @@ void game_log_multiplayer_command(int command, const int* eax, const int* ebx, c
format_string(log_msg, 256, STR_LOG_DEMOLISH_RIDE, args);
network_append_server_log(log_msg);
}
else if (
command == GAME_COMMAND_PLACE_WALL || command == GAME_COMMAND_PLACE_LARGE_SCENERY
|| command == GAME_COMMAND_PLACE_BANNER)
else if (command == GAME_COMMAND_PLACE_LARGE_SCENERY || command == GAME_COMMAND_PLACE_BANNER)
{
uint8_t flags = *ebx & 0xFF;
if (flags & GAME_COMMAND_FLAG_GHOST)
@ -1284,7 +1281,7 @@ GAME_COMMAND_POINTER* new_game_command_table[GAME_COMMAND_COUNT] = {
game_command_set_maze_track,
game_command_set_park_entrance_fee,
nullptr,
game_command_place_wall,
nullptr,
nullptr,
game_command_place_large_scenery,
nullptr,

View File

@ -60,8 +60,8 @@ enum GAME_COMMAND
GAME_COMMAND_SET_MAZE_TRACK, // GA
GAME_COMMAND_SET_PARK_ENTRANCE_FEE, // GA
GAME_COMMAND_SET_STAFF_COLOUR, // GA
GAME_COMMAND_PLACE_WALL,
GAME_COMMAND_REMOVE_WALL, // GA
GAME_COMMAND_PLACE_WALL, // GA
GAME_COMMAND_REMOVE_WALL, // GA
GAME_COMMAND_PLACE_LARGE_SCENERY,
GAME_COMMAND_REMOVE_LARGE_SCENERY, // GA
GAME_COMMAND_SET_CURRENT_LOAN, // GA

View File

@ -62,6 +62,7 @@
#include "TrackPlaceAction.hpp"
#include "TrackRemoveAction.hpp"
#include "TrackSetBrakeSpeedAction.hpp"
#include "WallPlaceAction.hpp"
#include "WallRemoveAction.hpp"
#include "WaterLowerAction.hpp"
#include "WaterRaiseAction.hpp"
@ -110,6 +111,7 @@ namespace GameActions
Register<StaffSetCostumeAction>();
Register<StaffSetPatrolAreaAction>();
Register<SurfaceSetStyleAction>();
Register<WallPlaceAction>();
Register<WallRemoveAction>();
Register<SmallSceneryPlaceAction>();
Register<SmallSceneryRemoveAction>();

View File

@ -0,0 +1,676 @@
/*****************************************************************************
* Copyright (c) 2014-2019 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 "../OpenRCT2.h"
#include "../management/Finance.h"
#include "../ride/RideGroupManager.h"
#include "../ride/Track.h"
#include "../ride/TrackData.h"
#include "../world/Banner.h"
#include "../world/LargeScenery.h"
#include "../world/MapAnimation.h"
#include "../world/Scenery.h"
#include "../world/SmallScenery.h"
#include "../world/Surface.h"
#include "GameAction.h"
DEFINE_GAME_ACTION(WallPlaceAction, GAME_COMMAND_PLACE_WALL, GameActionResult)
{
private:
int32_t _wallType{ -1 };
CoordsXYZ _loc;
uint8_t _edge{ std::numeric_limits<uint8_t>::max() };
int32_t _primaryColour;
int32_t _secondaryColour;
int32_t _tertiaryColour;
public:
WallPlaceAction()
{
}
WallPlaceAction(
int32_t wallType, CoordsXYZ loc, uint8_t edge, int32_t primaryColour, int32_t secondaryColour, int32_t tertiaryColour)
: _wallType(wallType)
, _loc(loc)
, _edge(edge)
, _primaryColour(primaryColour)
, _secondaryColour(secondaryColour)
, _tertiaryColour(tertiaryColour)
{
}
uint16_t GetActionFlags() const override
{
return GameAction::GetActionFlags();
}
void Serialise(DataSerialiser & stream) override
{
GameAction::Serialise(stream);
stream << DS_TAG(_wallType) << DS_TAG(_loc) << DS_TAG(_edge) << DS_TAG(_primaryColour) << DS_TAG(_secondaryColour)
<< DS_TAG(_tertiaryColour);
}
GameActionResult::Ptr Query() const override
{
auto res = MakeResult();
res->ErrorTitle = STR_CANT_BUILD_PARK_ENTRANCE_HERE;
res->Position = _loc;
res->ExpenditureType = RCT_EXPENDITURE_TYPE_LANDSCAPING;
res->Position.x += 16;
res->Position.y += 16;
if (res->Position.z == 0)
{
res->Position.z = tile_element_height(res->Position.x, res->Position.y) & 0xFFFF;
}
if (!(gScreenFlags & SCREEN_FLAGS_SCENARIO_EDITOR) && !(GetFlags() & GAME_COMMAND_FLAG_PATH_SCENERY)
&& !gCheatsSandboxMode)
{
if (_loc.z == 0)
{
if (!map_is_location_in_park({ _loc.x, _loc.y }))
{
return MakeResult(GA_ERROR::NOT_OWNED, STR_CANT_BUILD_PARK_ENTRANCE_HERE);
}
}
else if (!map_is_location_owned(_loc.x, _loc.y, _loc.z))
{
return MakeResult(GA_ERROR::NOT_OWNED, STR_CANT_BUILD_PARK_ENTRANCE_HERE);
}
}
else if ((_loc.x > gMapSizeMaxXY || _loc.y > gMapSizeMaxXY))
{
log_error("Invalid x/y coordinates. x = %d y = %d", _loc.x, _loc.y);
return MakeResult(GA_ERROR::INVALID_PARAMETERS, STR_CANT_BUILD_PARK_ENTRANCE_HERE);
}
if (_edge > 3)
{
return MakeResult(GA_ERROR::INVALID_PARAMETERS, STR_CANT_BUILD_PARK_ENTRANCE_HERE);
}
uint8_t edgeSlope = 0;
auto targetHeight = _loc.z;
if (targetHeight == 0)
{
TileElement* surfaceElement = map_get_surface_element_at({ _loc.x, _loc.y });
if (surfaceElement == nullptr)
{
log_error("Surface element not found at %d, %d.", _loc.x, _loc.y);
return MakeResult(GA_ERROR::INVALID_PARAMETERS, STR_CANT_BUILD_PARK_ENTRANCE_HERE);
}
targetHeight = surfaceElement->base_height * 8;
uint8_t slope = surfaceElement->AsSurface()->GetSlope();
edgeSlope = EdgeSlopes[slope][_edge & 3];
if (edgeSlope & EDGE_SLOPE_ELEVATED)
{
targetHeight += 16;
edgeSlope &= ~EDGE_SLOPE_ELEVATED;
}
}
TileElement* surfaceElement = map_get_surface_element_at({ _loc.x, _loc.y });
if (surfaceElement == nullptr)
{
log_error("Surface element not found at %d, %d.", _loc.x, _loc.y);
return MakeResult(GA_ERROR::INVALID_PARAMETERS, STR_CANT_BUILD_PARK_ENTRANCE_HERE);
}
if (surfaceElement->AsSurface()->GetWaterHeight() > 0)
{
uint16_t waterHeight = surfaceElement->AsSurface()->GetWaterHeight() * 16;
if (targetHeight < waterHeight && !gCheatsDisableClearanceChecks)
{
return MakeResult(GA_ERROR::DISALLOWED, STR_CANT_BUILD_PARK_ENTRANCE_HERE, STR_CANT_BUILD_THIS_UNDERWATER);
}
}
if (targetHeight / 8 < surfaceElement->base_height && !gCheatsDisableClearanceChecks)
{
return MakeResult(GA_ERROR::DISALLOWED, STR_CANT_BUILD_PARK_ENTRANCE_HERE, STR_CAN_ONLY_BUILD_THIS_ABOVE_GROUND);
}
if (!(edgeSlope & (EDGE_SLOPE_UPWARDS | EDGE_SLOPE_DOWNWARDS)))
{
uint8_t newEdge = (_edge + 2) & 3;
uint8_t newBaseHeight = surfaceElement->base_height;
newBaseHeight += 2;
if (surfaceElement->AsSurface()->GetSlope() & (1 << newEdge))
{
if (targetHeight / 8 < newBaseHeight)
{
return MakeResult(
GA_ERROR::DISALLOWED, STR_CANT_BUILD_PARK_ENTRANCE_HERE, STR_CAN_ONLY_BUILD_THIS_ABOVE_GROUND);
}
if (surfaceElement->AsSurface()->GetSlope() & TILE_ELEMENT_SLOPE_DOUBLE_HEIGHT)
{
newEdge = (newEdge - 1) & 3;
if (surfaceElement->AsSurface()->GetSlope() & (1 << newEdge))
{
newEdge = (newEdge + 2) & 3;
if (surfaceElement->AsSurface()->GetSlope() & (1 << newEdge))
{
newBaseHeight += 2;
if (targetHeight / 8 < newBaseHeight)
{
return MakeResult(
GA_ERROR::DISALLOWED, STR_CANT_BUILD_PARK_ENTRANCE_HERE,
STR_CAN_ONLY_BUILD_THIS_ABOVE_GROUND);
}
newBaseHeight -= 2;
}
}
}
}
newEdge = (_edge + 3) & 3;
if (surfaceElement->AsSurface()->GetSlope() & (1 << newEdge))
{
if (targetHeight / 8 < newBaseHeight)
{
return MakeResult(
GA_ERROR::DISALLOWED, STR_CANT_BUILD_PARK_ENTRANCE_HERE, STR_CAN_ONLY_BUILD_THIS_ABOVE_GROUND);
}
if (surfaceElement->AsSurface()->GetSlope() & TILE_ELEMENT_SLOPE_DOUBLE_HEIGHT)
{
newEdge = (newEdge - 1) & 3;
if (surfaceElement->AsSurface()->GetSlope() & (1 << newEdge))
{
newEdge = (newEdge + 2) & 3;
if (surfaceElement->AsSurface()->GetSlope() & (1 << newEdge))
{
newBaseHeight += 2;
if (targetHeight / 8 < newBaseHeight)
{
return MakeResult(
GA_ERROR::DISALLOWED, STR_CANT_BUILD_PARK_ENTRANCE_HERE,
STR_CAN_ONLY_BUILD_THIS_ABOVE_GROUND);
}
}
}
}
}
}
rct_scenery_entry* wallEntry = get_wall_entry(_wallType);
if (wallEntry == nullptr)
{
log_error("Wall Type not found %d", _wallType);
return MakeResult(GA_ERROR::INVALID_PARAMETERS, STR_CANT_BUILD_PARK_ENTRANCE_HERE);
}
if (wallEntry->wall.scrolling_mode != SCROLLING_MODE_NONE)
{
auto bannerIndex = create_new_banner(0);
if (bannerIndex == 0xFF)
{
return MakeResult(GA_ERROR::NO_FREE_ELEMENTS, STR_CANT_BUILD_PARK_ENTRANCE_HERE, STR_TOO_MANY_BANNERS_IN_GAME);
}
}
uint8_t clearanceHeight = targetHeight / 8;
if (edgeSlope & (EDGE_SLOPE_UPWARDS | EDGE_SLOPE_DOWNWARDS))
{
if (wallEntry->wall.flags & WALL_SCENERY_CANT_BUILD_ON_SLOPE)
{
return MakeResult(
GA_ERROR::DISALLOWED, STR_CANT_BUILD_PARK_ENTRANCE_HERE, STR_ERR_UNABLE_TO_BUILD_THIS_ON_SLOPE);
}
clearanceHeight += 2;
}
clearanceHeight += wallEntry->wall.height;
bool wallAcrossTrack = false;
if (!(GetFlags() & GAME_COMMAND_FLAG_PATH_SCENERY) && !gCheatsDisableClearanceChecks)
{
if (!WallCheckObstruction(wallEntry, targetHeight / 8, clearanceHeight, &wallAcrossTrack))
{
return MakeResult(
GA_ERROR::NO_CLEARANCE, STR_CANT_BUILD_PARK_ENTRANCE_HERE, gGameCommandErrorText, gCommonFormatArgs);
}
}
if (!map_check_free_elements_and_reorganise(1))
{
return MakeResult(GA_ERROR::NO_FREE_ELEMENTS, STR_CANT_BUILD_PARK_ENTRANCE_HERE, gGameCommandErrorText);
}
res->Cost = wallEntry->wall.price;
return res;
}
GameActionResult::Ptr Execute() const override
{
auto res = MakeResult();
res->ErrorTitle = STR_CANT_BUILD_PARK_ENTRANCE_HERE;
res->Position = _loc;
res->ExpenditureType = RCT_EXPENDITURE_TYPE_LANDSCAPING;
res->Position.x += 16;
res->Position.y += 16;
if (res->Position.z == 0)
{
res->Position.z = tile_element_height(res->Position.x, res->Position.y) & 0xFFFF;
}
uint8_t edgeSlope = 0;
auto targetHeight = _loc.z;
if (targetHeight == 0)
{
TileElement* surfaceElement = map_get_surface_element_at({ _loc.x, _loc.y });
if (surfaceElement == nullptr)
{
log_error("Surface element not found at %d, %d.", _loc.x, _loc.y);
return MakeResult(GA_ERROR::INVALID_PARAMETERS, STR_CANT_BUILD_PARK_ENTRANCE_HERE);
}
targetHeight = surfaceElement->base_height * 8;
uint8_t slope = surfaceElement->AsSurface()->GetSlope();
edgeSlope = EdgeSlopes[slope][_edge & 3];
if (edgeSlope & EDGE_SLOPE_ELEVATED)
{
targetHeight += 16;
edgeSlope &= ~EDGE_SLOPE_ELEVATED;
}
}
BannerIndex bannerIndex = BANNER_INDEX_NULL;
rct_scenery_entry* wallEntry = get_wall_entry(_wallType);
if (wallEntry == nullptr)
{
log_error("Wall Type not found %d", _wallType);
return MakeResult(GA_ERROR::INVALID_PARAMETERS, STR_CANT_BUILD_PARK_ENTRANCE_HERE);
}
if (wallEntry->wall.scrolling_mode != SCROLLING_MODE_NONE)
{
bannerIndex = create_new_banner(GAME_COMMAND_FLAG_APPLY);
if (bannerIndex == 0xFF)
{
return MakeResult(GA_ERROR::NO_FREE_ELEMENTS, STR_CANT_BUILD_PARK_ENTRANCE_HERE, STR_TOO_MANY_BANNERS_IN_GAME);
}
rct_banner* banner = &gBanners[bannerIndex];
banner->flags |= BANNER_FLAG_IS_WALL;
banner->type = 0;
banner->x = _loc.x / 32;
banner->y = _loc.y / 32;
ride_id_t rideIndex = banner_get_closest_ride_index(_loc.x, _loc.y, targetHeight);
if (rideIndex != RIDE_ID_NULL)
{
banner->ride_index = rideIndex;
banner->flags |= BANNER_FLAG_LINKED_TO_RIDE;
}
}
uint8_t clearanceHeight = targetHeight / 8;
if (edgeSlope & (EDGE_SLOPE_UPWARDS | EDGE_SLOPE_DOWNWARDS))
{
clearanceHeight += 2;
}
clearanceHeight += wallEntry->wall.height;
bool wallAcrossTrack = false;
if (!(GetFlags() & GAME_COMMAND_FLAG_PATH_SCENERY) && !gCheatsDisableClearanceChecks)
{
if (!WallCheckObstruction(wallEntry, targetHeight / 8, clearanceHeight, &wallAcrossTrack))
{
return MakeResult(
GA_ERROR::NO_CLEARANCE, STR_CANT_BUILD_PARK_ENTRANCE_HERE, gGameCommandErrorText, gCommonFormatArgs);
}
}
if (!map_check_free_elements_and_reorganise(1))
{
return MakeResult(GA_ERROR::NO_FREE_ELEMENTS, STR_CANT_BUILD_PARK_ENTRANCE_HERE, gGameCommandErrorText);
}
TileElement* tileElement = tile_element_insert(_loc.x / 32, _loc.y / 32, targetHeight / 8, 0);
assert(tileElement != nullptr);
map_animation_create(MAP_ANIMATION_TYPE_WALL, _loc.x, _loc.y, targetHeight / 8);
tileElement->SetType(TILE_ELEMENT_TYPE_WALL);
WallElement* wallElement = tileElement->AsWall();
wallElement->clearance_height = clearanceHeight;
wallElement->SetDirection(_edge);
// TODO: Normalise the edge slope code.
wallElement->SetSlope(edgeSlope >> 6);
wallElement->SetPrimaryColour(_primaryColour);
wallElement->SetSecondaryColour(_secondaryColour);
if (wallAcrossTrack)
{
wallElement->SetAcrossTrack(true);
}
wallElement->SetEntryIndex(_wallType);
if (bannerIndex != 0xFF)
{
wallElement->SetBannerIndex(bannerIndex);
}
if (wallEntry->wall.flags & WALL_SCENERY_HAS_TERNARY_COLOUR)
{
wallElement->SetTertiaryColour(_tertiaryColour);
}
if (GetFlags() & GAME_COMMAND_FLAG_GHOST)
{
wallElement->SetGhost(true);
}
gSceneryTileElement = tileElement;
map_invalidate_tile_zoom1(_loc.x, _loc.y, wallElement->base_height * 8, wallElement->base_height * 8 + 72);
res->Cost = wallEntry->wall.price;
return res;
}
private:
#pragma region Edge Slopes Table
// clang-format off
enum EDGE_SLOPE
{
EDGE_SLOPE_ELEVATED = (1 << 0), // 0x01
EDGE_SLOPE_UPWARDS = (1 << 6), // 0x40
EDGE_SLOPE_DOWNWARDS = (1 << 7), // 0x80
EDGE_SLOPE_UPWARDS_ELEVATED = EDGE_SLOPE_UPWARDS | EDGE_SLOPE_ELEVATED,
EDGE_SLOPE_DOWNWARDS_ELEVATED = EDGE_SLOPE_DOWNWARDS | EDGE_SLOPE_ELEVATED,
};
/** rct2: 0x009A3FEC */
static constexpr const uint8_t EdgeSlopes[][4] = {
// Top right Bottom right Bottom left Top left
{ 0, 0, 0, 0 },
{ 0, EDGE_SLOPE_UPWARDS, EDGE_SLOPE_DOWNWARDS, 0 },
{ 0, 0, EDGE_SLOPE_UPWARDS, EDGE_SLOPE_DOWNWARDS },
{ 0, EDGE_SLOPE_UPWARDS, EDGE_SLOPE_ELEVATED, EDGE_SLOPE_DOWNWARDS },
{ EDGE_SLOPE_DOWNWARDS, 0, 0, EDGE_SLOPE_UPWARDS },
{ EDGE_SLOPE_DOWNWARDS, EDGE_SLOPE_UPWARDS, EDGE_SLOPE_DOWNWARDS, EDGE_SLOPE_UPWARDS },
{ EDGE_SLOPE_DOWNWARDS, 0, EDGE_SLOPE_UPWARDS, EDGE_SLOPE_ELEVATED },
{ EDGE_SLOPE_DOWNWARDS, EDGE_SLOPE_UPWARDS, EDGE_SLOPE_ELEVATED, EDGE_SLOPE_ELEVATED },
{ EDGE_SLOPE_UPWARDS, EDGE_SLOPE_DOWNWARDS, 0, 0 },
{ EDGE_SLOPE_UPWARDS, EDGE_SLOPE_ELEVATED, EDGE_SLOPE_DOWNWARDS, 0 },
{ EDGE_SLOPE_UPWARDS, EDGE_SLOPE_DOWNWARDS, EDGE_SLOPE_UPWARDS, EDGE_SLOPE_DOWNWARDS },
{ EDGE_SLOPE_UPWARDS, EDGE_SLOPE_ELEVATED, EDGE_SLOPE_ELEVATED, EDGE_SLOPE_DOWNWARDS },
{ EDGE_SLOPE_ELEVATED, EDGE_SLOPE_DOWNWARDS, 0, EDGE_SLOPE_UPWARDS },
{ EDGE_SLOPE_ELEVATED, EDGE_SLOPE_ELEVATED, EDGE_SLOPE_DOWNWARDS, EDGE_SLOPE_UPWARDS },
{ EDGE_SLOPE_ELEVATED, EDGE_SLOPE_DOWNWARDS, EDGE_SLOPE_UPWARDS, EDGE_SLOPE_ELEVATED },
{ EDGE_SLOPE_ELEVATED, EDGE_SLOPE_ELEVATED, EDGE_SLOPE_ELEVATED, EDGE_SLOPE_ELEVATED },
{ 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 },
{ EDGE_SLOPE_DOWNWARDS, EDGE_SLOPE_UPWARDS, EDGE_SLOPE_UPWARDS_ELEVATED, EDGE_SLOPE_DOWNWARDS_ELEVATED },
{ 0, 0, 0, 0 },
{ 0, 0, 0, 0 },
{ 0, 0, 0, 0 },
{ EDGE_SLOPE_UPWARDS, EDGE_SLOPE_UPWARDS_ELEVATED, EDGE_SLOPE_DOWNWARDS_ELEVATED, EDGE_SLOPE_DOWNWARDS },
{ 0, 0, 0, 0 },
{ EDGE_SLOPE_UPWARDS_ELEVATED, EDGE_SLOPE_DOWNWARDS_ELEVATED, EDGE_SLOPE_DOWNWARDS, EDGE_SLOPE_UPWARDS },
{ EDGE_SLOPE_DOWNWARDS_ELEVATED, EDGE_SLOPE_DOWNWARDS, EDGE_SLOPE_UPWARDS, EDGE_SLOPE_UPWARDS_ELEVATED },
{ 0, 0, 0, 0 },
};
// clang-format on
#pragma endregion
/**
*
* rct2: 0x006E5CBA
*/
bool WallCheckObstructionWithTrack(rct_scenery_entry * wall, int32_t z0, TrackElement * trackElement, bool* wallAcrossTrack)
const
{
int32_t trackType = trackElement->GetTrackType();
int32_t sequence = trackElement->GetSequenceIndex();
int32_t direction = (_edge - trackElement->GetDirection()) & TILE_ELEMENT_DIRECTION_MASK;
Ride* ride = get_ride(trackElement->GetRideIndex());
if (ride == nullptr)
{
return false;
}
if (TrackIsAllowedWallEdges(ride->type, trackType, sequence, direction))
{
return true;
}
if (!(wall->wall.flags & WALL_SCENERY_IS_DOOR))
{
return false;
}
if (RideGroupManager::RideTypeHasRideGroups(ride->type))
{
auto rideEntry = get_ride_entry(ride->subtype);
if (rideEntry == nullptr)
{
return false;
}
auto rideGroup = RideGroupManager::GetRideGroup(ride->type, rideEntry);
if (rideGroup == nullptr)
{
return false;
}
if (!(rideGroup->Flags & RIDE_GROUP_FLAG_ALLOW_DOORS_ON_TRACK))
{
return false;
}
}
else if (!(RideData4[ride->type].flags & RIDE_TYPE_FLAG4_ALLOW_DOORS_ON_TRACK))
{
return false;
}
*wallAcrossTrack = true;
if (z0 & 1)
{
return false;
}
int32_t z;
if (sequence == 0)
{
if (TrackSequenceProperties[trackType][0] & TRACK_SEQUENCE_FLAG_DISALLOW_DOORS)
{
return false;
}
if (TrackDefinitions[trackType].bank_start == 0)
{
if (!(TrackCoordinates[trackType].rotation_begin & 4))
{
direction = trackElement->GetDirectionWithOffset(2);
if (direction == _edge)
{
const rct_preview_track* trackBlock = &TrackBlocks[trackType][sequence];
z = TrackCoordinates[trackType].z_begin;
z = trackElement->base_height + ((z - trackBlock->z) * 8);
if (z == z0)
{
return true;
}
}
}
}
}
const rct_preview_track* trackBlock = &TrackBlocks[trackType][sequence + 1];
if (trackBlock->index != 0xFF)
{
return false;
}
if (TrackDefinitions[trackType].bank_end != 0)
{
return false;
}
direction = TrackCoordinates[trackType].rotation_end;
if (direction & 4)
{
return false;
}
direction = trackElement->GetDirection();
if (direction != _edge)
{
return false;
}
trackBlock = &TrackBlocks[trackType][sequence];
z = TrackCoordinates[trackType].z_end;
z = trackElement->base_height + ((z - trackBlock->z) * 8);
return z == z0;
}
/**
*
* rct2: 0x006E5C1A
*/
bool WallCheckObstruction(rct_scenery_entry * wall, int32_t z0, int32_t z1, bool* wallAcrossTrack) const
{
int32_t entryType, sequence;
rct_scenery_entry* entry;
rct_large_scenery_tile* tile;
*wallAcrossTrack = false;
gMapGroundFlags = ELEMENT_IS_ABOVE_GROUND;
if (map_is_location_at_edge(_loc.x, _loc.y))
{
gGameCommandErrorText = STR_OFF_EDGE_OF_MAP;
return false;
}
TileElement* tileElement = map_get_first_element_at(_loc.x / 32, _loc.y / 32);
do
{
if (tileElement == nullptr)
break;
int32_t elementType = tileElement->GetType();
if (elementType == TILE_ELEMENT_TYPE_SURFACE)
continue;
if (tileElement->IsGhost())
continue;
if (z0 >= tileElement->clearance_height)
continue;
if (z1 <= tileElement->base_height)
continue;
if (elementType == TILE_ELEMENT_TYPE_WALL)
{
int32_t direction = tileElement->GetDirection();
if (_edge == direction)
{
map_obstruction_set_error_text(tileElement);
return false;
}
continue;
}
if ((tileElement->flags & 0x0F) == 0)
continue;
switch (elementType)
{
case TILE_ELEMENT_TYPE_ENTRANCE:
map_obstruction_set_error_text(tileElement);
return false;
case TILE_ELEMENT_TYPE_PATH:
if (tileElement->AsPath()->GetEdges() & (1 << _edge))
{
map_obstruction_set_error_text(tileElement);
return false;
}
break;
case TILE_ELEMENT_TYPE_LARGE_SCENERY:
entryType = tileElement->AsLargeScenery()->GetEntryIndex();
sequence = tileElement->AsLargeScenery()->GetSequenceIndex();
entry = get_large_scenery_entry(entryType);
tile = &entry->large_scenery.tiles[sequence];
{
int32_t direction = ((_edge - tileElement->GetDirection()) & TILE_ELEMENT_DIRECTION_MASK) + 8;
if (!(tile->flags & (1 << direction)))
{
map_obstruction_set_error_text(tileElement);
return false;
}
}
break;
case TILE_ELEMENT_TYPE_SMALL_SCENERY:
entry = tileElement->AsSmallScenery()->GetEntry();
if (scenery_small_entry_has_flag(entry, SMALL_SCENERY_FLAG_NO_WALLS))
{
map_obstruction_set_error_text(tileElement);
return false;
}
break;
case TILE_ELEMENT_TYPE_TRACK:
if (!WallCheckObstructionWithTrack(wall, z0, tileElement->AsTrack(), wallAcrossTrack))
{
return false;
}
break;
}
} while (!(tileElement++)->IsLastForTile());
return true;
}
/**
* Gets whether the given track type can have a wall placed on the edge of the given direction.
* Some thin tracks for example are allowed to have walls either side of the track, but wider tracks can not.
*/
static bool TrackIsAllowedWallEdges(uint8_t rideType, uint8_t trackType, uint8_t trackSequence, uint8_t direction)
{
if (!ride_type_has_flag(rideType, RIDE_TYPE_FLAG_TRACK_NO_WALLS))
{
if (ride_type_has_flag(rideType, RIDE_TYPE_FLAG_FLAT_RIDE))
{
if (FlatRideTrackSequenceElementAllowedWallEdges[trackType][trackSequence] & (1 << direction))
{
return true;
}
}
else
{
if (TrackSequenceElementAllowedWallEdges[trackType][trackSequence] & (1 << direction))
{
return true;
}
}
}
return false;
}
};

View File

@ -31,7 +31,7 @@
// This string specifies which version of network stream current build uses.
// It is used for making sure only compatible builds get connected, even within
// single OpenRCT2 version.
#define NETWORK_STREAM_VERSION "14"
#define NETWORK_STREAM_VERSION "15"
#define NETWORK_STREAM_ID OPENRCT2_VERSION "-" NETWORK_STREAM_VERSION
static Peep* _pickup_peep = nullptr;

View File

@ -13,6 +13,7 @@
#include "../Game.h"
#include "../GameState.h"
#include "../ParkImporter.h"
#include "../actions/WallPlaceAction.hpp"
#include "../audio/audio.h"
#include "../core/Collections.hpp"
#include "../core/Console.hpp"
@ -2773,10 +2774,13 @@ private:
ConvertWall(&type, &colourA, &colourB);
type = _wallTypeToEntryMap[type];
const uint8_t flags = GAME_COMMAND_FLAG_APPLY | GAME_COMMAND_FLAG_ALLOW_DURING_PAUSED
| GAME_COMMAND_FLAG_5 | GAME_COMMAND_FLAG_PATH_SCENERY;
wall_place(type, x * 32, y * 32, 0, edge, colourA, colourB, colourC, flags);
auto wallPlaceAction = WallPlaceAction(
type, { x * 32, y * 32, 0 }, edge, colourA, colourB, colourC);
wallPlaceAction.SetFlags(
GAME_COMMAND_FLAG_ALLOW_DURING_PAUSED | GAME_COMMAND_FLAG_5
| GAME_COMMAND_FLAG_PATH_SCENERY);
GameActions::Execute(&wallPlaceAction);
}
}
break;

View File

@ -21,6 +21,7 @@
#include "../actions/SmallSceneryRemoveAction.hpp"
#include "../actions/TrackPlaceAction.hpp"
#include "../actions/TrackRemoveAction.hpp"
#include "../actions/WallPlaceAction.hpp"
#include "../actions/WallRemoveAction.hpp"
#include "../audio/audio.h"
#include "../core/File.h"
@ -1021,6 +1022,7 @@ static int32_t track_design_place_scenery(
}
break;
case OBJECT_TYPE_WALLS:
{
if (mode != 0)
{
continue;
@ -1050,17 +1052,15 @@ static int32_t track_design_place_scenery(
flags = 0;
}
gGameCommandErrorTitle = STR_CANT_BUILD_PARK_ENTRANCE_HERE;
auto wallPlaceAction = WallPlaceAction(
entry_index, { mapCoord.x, mapCoord.y, z }, rotation, scenery->primary_colour,
scenery->secondary_colour, scenery->flags & 0xFC);
auto res = flags & GAME_COMMAND_FLAG_APPLY ? GameActions::Execute(&wallPlaceAction)
: GameActions::Query(&wallPlaceAction);
cost = game_do_command(
mapCoord.x, flags | (entry_index << 8), mapCoord.y, rotation | (scenery->primary_colour << 8),
GAME_COMMAND_PLACE_WALL, z, scenery->secondary_colour | ((scenery->flags & 0xFC) << 6));
if (cost == MONEY32_UNDEFINED)
{
cost = 0;
}
cost = res->Cost;
break;
}
case OBJECT_TYPE_PATHS:
if (_trackDesignPlaceOperation == PTD_OPERATION_GET_PLACE_Z)
{

View File

@ -186,9 +186,6 @@ int32_t map_can_construct_at(int32_t x, int32_t y, int32_t zLow, int32_t zHigh,
void rotate_map_coordinates(int16_t* x, int16_t* y, int32_t rotation);
LocationXY16 coordinate_3d_to_2d(const LocationXYZ16* coordinate_3d, int32_t rotation);
money32 map_clear_scenery(int32_t x0, int32_t y0, int32_t x1, int32_t y1, int32_t clear, int32_t flags);
money32 wall_place(
int32_t type, int32_t x, int32_t y, int32_t z, int32_t edge, int32_t primaryColour, int32_t secondaryColour,
int32_t tertiaryColour, int32_t flags);
void game_command_set_land_ownership(
int32_t* eax, int32_t* ebx, int32_t* ecx, int32_t* edx, int32_t* esi, int32_t* edi, int32_t* ebp);
@ -204,7 +201,6 @@ void game_command_set_banner_colour(
int32_t* eax, int32_t* ebx, int32_t* ecx, int32_t* edx, int32_t* esi, int32_t* edi, int32_t* ebp);
void game_command_place_banner(
int32_t* eax, int32_t* ebx, int32_t* ecx, int32_t* edx, int32_t* esi, int32_t* edi, int32_t* ebp);
void game_command_place_wall(int32_t* eax, int32_t* ebx, int32_t* ecx, int32_t* edx, int32_t* esi, int32_t* edi, int32_t* ebp);
void game_command_place_large_scenery(
int32_t* eax, int32_t* ebx, int32_t* ecx, int32_t* edx, int32_t* esi, int32_t* edi, int32_t* ebp);
void game_command_place_park_entrance(

View File

@ -29,532 +29,6 @@
#include "Surface.h"
#include "Wall.h"
/**
* Gets whether the given track type can have a wall placed on the edge of the given direction.
* Some thin tracks for example are allowed to have walls either side of the track, but wider tracks can not.
*/
static bool TrackIsAllowedWallEdges(uint8_t rideType, uint8_t trackType, uint8_t trackSequence, uint8_t direction)
{
if (!ride_type_has_flag(rideType, RIDE_TYPE_FLAG_TRACK_NO_WALLS))
{
if (ride_type_has_flag(rideType, RIDE_TYPE_FLAG_FLAT_RIDE))
{
if (FlatRideTrackSequenceElementAllowedWallEdges[trackType][trackSequence] & (1 << direction))
{
return true;
}
}
else
{
if (TrackSequenceElementAllowedWallEdges[trackType][trackSequence] & (1 << direction))
{
return true;
}
}
}
return false;
}
/**
*
* rct2: 0x006E5CBA
*/
static bool WallCheckObstructionWithTrack(
rct_scenery_entry* wall, int32_t z0, int32_t edge, TileElement* trackElement, bool* wallAcrossTrack)
{
int32_t trackType = trackElement->AsTrack()->GetTrackType();
int32_t sequence = trackElement->AsTrack()->GetSequenceIndex();
int32_t direction = (edge - trackElement->GetDirection()) & TILE_ELEMENT_DIRECTION_MASK;
Ride* ride = get_ride(trackElement->AsTrack()->GetRideIndex());
if (TrackIsAllowedWallEdges(ride->type, trackType, sequence, direction))
{
return true;
}
if (!(wall->wall.flags & WALL_SCENERY_IS_DOOR))
{
return false;
}
if (RideGroupManager::RideTypeHasRideGroups(ride->type))
{
auto rideGroup = RideGroupManager::GetRideGroup(ride->type, get_ride_entry(ride->subtype));
if (!(rideGroup->Flags & RIDE_GROUP_FLAG_ALLOW_DOORS_ON_TRACK))
{
return false;
}
}
else if (!(RideData4[ride->type].flags & RIDE_TYPE_FLAG4_ALLOW_DOORS_ON_TRACK))
{
return false;
}
*wallAcrossTrack = true;
if (z0 & 1)
{
return false;
}
int32_t z;
if (sequence == 0)
{
if (TrackSequenceProperties[trackType][0] & TRACK_SEQUENCE_FLAG_DISALLOW_DOORS)
{
return false;
}
if (TrackDefinitions[trackType].bank_start == 0)
{
if (!(TrackCoordinates[trackType].rotation_begin & 4))
{
direction = trackElement->GetDirectionWithOffset(2);
if (direction == edge)
{
const rct_preview_track* trackBlock = &TrackBlocks[trackType][sequence];
z = TrackCoordinates[trackType].z_begin;
z = trackElement->base_height + ((z - trackBlock->z) * 8);
if (z == z0)
{
return true;
}
}
}
}
}
const rct_preview_track* trackBlock = &TrackBlocks[trackType][sequence + 1];
if (trackBlock->index != 0xFF)
{
return false;
}
if (TrackDefinitions[trackType].bank_end != 0)
{
return false;
}
direction = TrackCoordinates[trackType].rotation_end;
if (direction & 4)
{
return false;
}
direction = trackElement->GetDirection();
if (direction != edge)
{
return false;
}
trackBlock = &TrackBlocks[trackType][sequence];
z = TrackCoordinates[trackType].z_end;
z = trackElement->base_height + ((z - trackBlock->z) * 8);
return z == z0;
}
/**
*
* rct2: 0x006E5C1A
*/
static bool WallCheckObstruction(
rct_scenery_entry* wall, int32_t x, int32_t y, int32_t z0, int32_t z1, int32_t edge, bool* wallAcrossTrack)
{
int32_t entryType, sequence;
rct_scenery_entry* entry;
rct_large_scenery_tile* tile;
*wallAcrossTrack = false;
gMapGroundFlags = ELEMENT_IS_ABOVE_GROUND;
if (map_is_location_at_edge(x, y))
{
gGameCommandErrorText = STR_OFF_EDGE_OF_MAP;
return false;
}
TileElement* tileElement = map_get_first_element_at(x / 32, y / 32);
do
{
int32_t elementType = tileElement->GetType();
if (elementType == TILE_ELEMENT_TYPE_SURFACE)
continue;
if (z0 >= tileElement->clearance_height)
continue;
if (z1 <= tileElement->base_height)
continue;
if (elementType == TILE_ELEMENT_TYPE_WALL)
{
int32_t direction = tileElement->GetDirection();
if (edge == direction)
{
map_obstruction_set_error_text(tileElement);
return false;
}
continue;
}
if ((tileElement->flags & 0x0F) == 0)
continue;
switch (elementType)
{
case TILE_ELEMENT_TYPE_ENTRANCE:
map_obstruction_set_error_text(tileElement);
return false;
case TILE_ELEMENT_TYPE_PATH:
if (tileElement->AsPath()->GetEdges() & (1 << edge))
{
map_obstruction_set_error_text(tileElement);
return false;
}
break;
case TILE_ELEMENT_TYPE_LARGE_SCENERY:
entryType = tileElement->AsLargeScenery()->GetEntryIndex();
sequence = tileElement->AsLargeScenery()->GetSequenceIndex();
entry = get_large_scenery_entry(entryType);
tile = &entry->large_scenery.tiles[sequence];
{
int32_t direction = ((edge - tileElement->GetDirection()) & TILE_ELEMENT_DIRECTION_MASK) + 8;
if (!(tile->flags & (1 << direction)))
{
map_obstruction_set_error_text(tileElement);
return false;
}
}
break;
case TILE_ELEMENT_TYPE_SMALL_SCENERY:
entry = tileElement->AsSmallScenery()->GetEntry();
if (scenery_small_entry_has_flag(entry, SMALL_SCENERY_FLAG_NO_WALLS))
{
map_obstruction_set_error_text(tileElement);
return false;
}
break;
case TILE_ELEMENT_TYPE_TRACK:
if (!WallCheckObstructionWithTrack(wall, z0, edge, tileElement, wallAcrossTrack))
{
return false;
}
break;
}
} while (!(tileElement++)->IsLastForTile());
return true;
}
#pragma region Edge Slopes Table
// clang-format off
enum EDGE_SLOPE
{
EDGE_SLOPE_ELEVATED = (1 << 0), // 0x01
EDGE_SLOPE_UPWARDS = (1 << 6), // 0x40
EDGE_SLOPE_DOWNWARDS = (1 << 7), // 0x80
EDGE_SLOPE_UPWARDS_ELEVATED = EDGE_SLOPE_UPWARDS | EDGE_SLOPE_ELEVATED,
EDGE_SLOPE_DOWNWARDS_ELEVATED = EDGE_SLOPE_DOWNWARDS | EDGE_SLOPE_ELEVATED,
};
/** rct2: 0x009A3FEC */
static constexpr const uint8_t EdgeSlopes[][4] = {
// Top right Bottom right Bottom left Top left
{ 0, 0, 0, 0 },
{ 0, EDGE_SLOPE_UPWARDS, EDGE_SLOPE_DOWNWARDS, 0 },
{ 0, 0, EDGE_SLOPE_UPWARDS, EDGE_SLOPE_DOWNWARDS },
{ 0, EDGE_SLOPE_UPWARDS, EDGE_SLOPE_ELEVATED, EDGE_SLOPE_DOWNWARDS },
{ EDGE_SLOPE_DOWNWARDS, 0, 0, EDGE_SLOPE_UPWARDS },
{ EDGE_SLOPE_DOWNWARDS, EDGE_SLOPE_UPWARDS, EDGE_SLOPE_DOWNWARDS, EDGE_SLOPE_UPWARDS },
{ EDGE_SLOPE_DOWNWARDS, 0, EDGE_SLOPE_UPWARDS, EDGE_SLOPE_ELEVATED },
{ EDGE_SLOPE_DOWNWARDS, EDGE_SLOPE_UPWARDS, EDGE_SLOPE_ELEVATED, EDGE_SLOPE_ELEVATED },
{ EDGE_SLOPE_UPWARDS, EDGE_SLOPE_DOWNWARDS, 0, 0 },
{ EDGE_SLOPE_UPWARDS, EDGE_SLOPE_ELEVATED, EDGE_SLOPE_DOWNWARDS, 0 },
{ EDGE_SLOPE_UPWARDS, EDGE_SLOPE_DOWNWARDS, EDGE_SLOPE_UPWARDS, EDGE_SLOPE_DOWNWARDS },
{ EDGE_SLOPE_UPWARDS, EDGE_SLOPE_ELEVATED, EDGE_SLOPE_ELEVATED, EDGE_SLOPE_DOWNWARDS },
{ EDGE_SLOPE_ELEVATED, EDGE_SLOPE_DOWNWARDS, 0, EDGE_SLOPE_UPWARDS },
{ EDGE_SLOPE_ELEVATED, EDGE_SLOPE_ELEVATED, EDGE_SLOPE_DOWNWARDS, EDGE_SLOPE_UPWARDS },
{ EDGE_SLOPE_ELEVATED, EDGE_SLOPE_DOWNWARDS, EDGE_SLOPE_UPWARDS, EDGE_SLOPE_ELEVATED },
{ EDGE_SLOPE_ELEVATED, EDGE_SLOPE_ELEVATED, EDGE_SLOPE_ELEVATED, EDGE_SLOPE_ELEVATED },
{ 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 },
{ EDGE_SLOPE_DOWNWARDS, EDGE_SLOPE_UPWARDS, EDGE_SLOPE_UPWARDS_ELEVATED, EDGE_SLOPE_DOWNWARDS_ELEVATED },
{ 0, 0, 0, 0 },
{ 0, 0, 0, 0 },
{ 0, 0, 0, 0 },
{ EDGE_SLOPE_UPWARDS, EDGE_SLOPE_UPWARDS_ELEVATED, EDGE_SLOPE_DOWNWARDS_ELEVATED, EDGE_SLOPE_DOWNWARDS },
{ 0, 0, 0, 0 },
{ EDGE_SLOPE_UPWARDS_ELEVATED, EDGE_SLOPE_DOWNWARDS_ELEVATED, EDGE_SLOPE_DOWNWARDS, EDGE_SLOPE_UPWARDS },
{ EDGE_SLOPE_DOWNWARDS_ELEVATED, EDGE_SLOPE_DOWNWARDS, EDGE_SLOPE_UPWARDS, EDGE_SLOPE_UPWARDS_ELEVATED },
{ 0, 0, 0, 0 },
};
// clang-format on
#pragma endregion
static money32 WallPlace(
uint8_t wallType, int16_t x, int16_t y, int16_t z, uint8_t edge, uint8_t primaryColour, uint8_t secondaryColour,
uint8_t tertiaryColour, uint8_t flags)
{
LocationXYZ16 position = { x, y, z };
gCommandExpenditureType = RCT_EXPENDITURE_TYPE_LANDSCAPING;
gCommandPosition.x = position.x + 16;
gCommandPosition.y = position.y + 16;
gCommandPosition.z = position.z;
if (position.z == 0)
{
gCommandPosition.z = tile_element_height(position.x, position.y) & 0xFFFF;
}
if (game_is_paused() && !gCheatsBuildInPauseMode)
{
gGameCommandErrorText = STR_CONSTRUCTION_NOT_POSSIBLE_WHILE_GAME_IS_PAUSED;
return MONEY32_UNDEFINED;
}
if (!(gScreenFlags & SCREEN_FLAGS_SCENARIO_EDITOR) && !(flags & GAME_COMMAND_FLAG_PATH_SCENERY) && !gCheatsSandboxMode)
{
if (position.z == 0)
{
if (!map_is_location_in_park({ position.x, position.y }))
{
return MONEY32_UNDEFINED;
}
}
else if (!map_is_location_owned(position.x, position.y, position.z))
{
return MONEY32_UNDEFINED;
}
}
uint8_t edgeSlope = 0;
if (position.z == 0)
{
TileElement* surfaceElement = map_get_surface_element_at({ position.x, position.y });
if (surfaceElement == nullptr)
{
return MONEY32_UNDEFINED;
}
position.z = surfaceElement->base_height * 8;
uint8_t slope = surfaceElement->AsSurface()->GetSlope();
edgeSlope = EdgeSlopes[slope][edge & 3];
if (edgeSlope & EDGE_SLOPE_ELEVATED)
{
position.z += 16;
edgeSlope &= ~EDGE_SLOPE_ELEVATED;
}
}
TileElement* surfaceElement = map_get_surface_element_at({ position.x, position.y });
if (surfaceElement == nullptr)
{
return MONEY32_UNDEFINED;
}
if (surfaceElement->AsSurface()->GetWaterHeight() > 0)
{
uint16_t waterHeight = surfaceElement->AsSurface()->GetWaterHeight() * 16;
if (position.z < waterHeight && !gCheatsDisableClearanceChecks)
{
gGameCommandErrorText = STR_CANT_BUILD_THIS_UNDERWATER;
return MONEY32_UNDEFINED;
}
}
if (position.z / 8 < surfaceElement->base_height && !gCheatsDisableClearanceChecks)
{
gGameCommandErrorText = STR_CAN_ONLY_BUILD_THIS_ABOVE_GROUND;
return MONEY32_UNDEFINED;
}
if (!(edgeSlope & (EDGE_SLOPE_UPWARDS | EDGE_SLOPE_DOWNWARDS)))
{
uint8_t newEdge = (edge + 2) & 3;
uint8_t newBaseHeight = surfaceElement->base_height;
newBaseHeight += 2;
if (surfaceElement->AsSurface()->GetSlope() & (1 << newEdge))
{
if (position.z / 8 < newBaseHeight)
{
gGameCommandErrorText = STR_CAN_ONLY_BUILD_THIS_ABOVE_GROUND;
return MONEY32_UNDEFINED;
}
if (surfaceElement->AsSurface()->GetSlope() & TILE_ELEMENT_SLOPE_DOUBLE_HEIGHT)
{
newEdge = (newEdge - 1) & 3;
if (surfaceElement->AsSurface()->GetSlope() & (1 << newEdge))
{
newEdge = (newEdge + 2) & 3;
if (surfaceElement->AsSurface()->GetSlope() & (1 << newEdge))
{
newBaseHeight += 2;
if (position.z / 8 < newBaseHeight)
{
gGameCommandErrorText = STR_CAN_ONLY_BUILD_THIS_ABOVE_GROUND;
return MONEY32_UNDEFINED;
}
newBaseHeight -= 2;
}
}
}
}
newEdge = (edge + 3) & 3;
if (surfaceElement->AsSurface()->GetSlope() & (1 << newEdge))
{
if (position.z / 8 < newBaseHeight)
{
gGameCommandErrorText = STR_CAN_ONLY_BUILD_THIS_ABOVE_GROUND;
return MONEY32_UNDEFINED;
}
if (surfaceElement->AsSurface()->GetSlope() & TILE_ELEMENT_SLOPE_DOUBLE_HEIGHT)
{
newEdge = (newEdge - 1) & 3;
if (surfaceElement->AsSurface()->GetSlope() & (1 << newEdge))
{
newEdge = (newEdge + 2) & 3;
if (surfaceElement->AsSurface()->GetSlope() & (1 << newEdge))
{
newBaseHeight += 2;
if (position.z / 8 < newBaseHeight)
{
gGameCommandErrorText = STR_CAN_ONLY_BUILD_THIS_ABOVE_GROUND;
return MONEY32_UNDEFINED;
}
}
}
}
}
}
BannerIndex bannerIndex = BANNER_INDEX_NULL;
rct_scenery_entry* wallEntry = get_wall_entry(wallType);
if (wallEntry == nullptr)
{
return MONEY32_UNDEFINED;
}
if (wallEntry->wall.scrolling_mode != SCROLLING_MODE_NONE)
{
bannerIndex = create_new_banner(flags);
if (bannerIndex == 0xFF)
{
return MONEY32_UNDEFINED;
}
rct_banner* banner = &gBanners[bannerIndex];
if (flags & GAME_COMMAND_FLAG_APPLY)
{
banner->flags |= BANNER_FLAG_IS_WALL;
banner->type = 0;
banner->x = position.x / 32;
banner->y = position.y / 32;
ride_id_t rideIndex = banner_get_closest_ride_index(position.x, position.y, position.z);
if (rideIndex != RIDE_ID_NULL)
{
banner->ride_index = rideIndex;
banner->flags |= BANNER_FLAG_LINKED_TO_RIDE;
}
}
}
uint8_t clearanceHeight = position.z / 8;
if (edgeSlope & (EDGE_SLOPE_UPWARDS | EDGE_SLOPE_DOWNWARDS))
{
if (wallEntry->wall.flags & WALL_SCENERY_CANT_BUILD_ON_SLOPE)
{
gGameCommandErrorText = STR_ERR_UNABLE_TO_BUILD_THIS_ON_SLOPE;
return MONEY32_UNDEFINED;
}
clearanceHeight += 2;
}
clearanceHeight += wallEntry->wall.height;
bool wallAcrossTrack = false;
if (!(flags & GAME_COMMAND_FLAG_PATH_SCENERY) && !gCheatsDisableClearanceChecks)
{
if (!WallCheckObstruction(wallEntry, position.x, position.y, position.z / 8, clearanceHeight, edge, &wallAcrossTrack))
{
return MONEY32_UNDEFINED;
}
}
if (!map_check_free_elements_and_reorganise(1))
{
return MONEY32_UNDEFINED;
}
if (flags & GAME_COMMAND_FLAG_APPLY)
{
if (gGameCommandNestLevel == 1 && !(flags & GAME_COMMAND_FLAG_GHOST))
{
LocationXYZ16 coord;
coord.x = position.x + 16;
coord.y = position.y + 16;
coord.z = tile_element_height(coord.x, coord.y);
network_set_player_last_action_coord(network_get_player_index(game_command_playerid), coord);
}
TileElement* tileElement = tile_element_insert(position.x / 32, position.y / 32, position.z / 8, 0);
assert(tileElement != nullptr);
map_animation_create(MAP_ANIMATION_TYPE_WALL, position.x, position.y, position.z / 8);
tileElement->clearance_height = clearanceHeight;
tileElement->SetType(TILE_ELEMENT_TYPE_WALL);
tileElement->SetDirection(edge);
// TODO: Normalise the edge slope code.
tileElement->AsWall()->SetSlope(edgeSlope >> 6);
tileElement->AsWall()->SetPrimaryColour(primaryColour);
tileElement->AsWall()->SetSecondaryColour(secondaryColour);
if (wallAcrossTrack)
{
tileElement->AsWall()->SetAcrossTrack(true);
}
tileElement->AsWall()->SetEntryIndex(wallType);
if (bannerIndex != 0xFF)
{
tileElement->AsWall()->SetBannerIndex(bannerIndex);
}
if (wallEntry->wall.flags & WALL_SCENERY_HAS_TERNARY_COLOUR)
{
tileElement->AsWall()->SetTertiaryColour(tertiaryColour);
}
if (flags & GAME_COMMAND_FLAG_GHOST)
{
tileElement->SetGhost(true);
}
gSceneryTileElement = tileElement;
map_invalidate_tile_zoom1(position.x, position.y, tileElement->base_height * 8, tileElement->base_height * 8 + 72);
}
if (gParkFlags & PARK_FLAGS_NO_MONEY)
{
return 0;
}
else
{
return wallEntry->wall.price;
}
}
static money32 WallSetColour(
int16_t x, int16_t y, uint8_t baseHeight, uint8_t direction, uint8_t primaryColour, uint8_t secondaryColour,
uint8_t tertiaryColour, uint8_t flags)
@ -662,33 +136,6 @@ void wall_remove_intersecting_walls(int32_t x, int32_t y, int32_t z0, int32_t z1
} while (!(tileElement++)->IsLastForTile());
}
/**
*
* rct2: 0x006E519A
*/
void game_command_place_wall(
int32_t* eax, int32_t* ebx, int32_t* ecx, int32_t* edx, [[maybe_unused]] int32_t* esi, int32_t* edi, int32_t* ebp)
{
*ebx = WallPlace(
(*ebx >> 8) & 0xFF, *eax & 0xFFFF, *ecx & 0xFFFF, *edi & 0xFFFF, *edx & 0xFF, (*edx >> 8) & 0xFF, *ebp & 0xFF,
(*ebp >> 8) & 0xFF, *ebx & 0xFF);
}
money32 wall_place(
int32_t type, int32_t x, int32_t y, int32_t z, int32_t edge, int32_t primaryColour, int32_t secondaryColour,
int32_t tertiaryColour, int32_t flags)
{
int32_t eax = x;
int32_t ebx = flags | (type << 8);
int32_t ecx = y;
int32_t edx = edge | (primaryColour << 8);
int32_t esi = 0;
int32_t edi = z;
int32_t ebp = secondaryColour | (tertiaryColour << 8);
game_command_place_wall(&eax, &ebx, &ecx, &edx, &esi, &edi, &ebp);
return ebx;
}
/**
*
* rct2: 0x006E56B5