1
0
mirror of https://github.com/OpenRCT2/OpenRCT2 synced 2026-01-16 19:43:06 +01:00
Files
OpenRCT2/src/openrct2/world/Map.cpp
Aaron van Geffen 84026529b4 Introduce new variable gMapVirtualFloorVisible to determine visibility.
This addresses compilation issues for projects that contain no UI code.
Also working towards better invalidation for the software renderer.
2018-01-11 10:37:40 +01:00

4857 lines
160 KiB
C++

#pragma region Copyright (c) 2014-2017 OpenRCT2 Developers
/*****************************************************************************
* OpenRCT2, an open source clone of Roller Coaster Tycoon 2.
*
* OpenRCT2 is the work of many authors, a full list can be found in contributors.md
* For more information, visit https://github.com/OpenRCT2/OpenRCT2
*
* OpenRCT2 is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* A full copy of the GNU General Public License can be found in licence.txt
*****************************************************************************/
#pragma endregion
#include "../audio/audio.h"
#include "../Cheats.h"
#include "../config/Config.h"
#include "../Context.h"
#include "../core/Math.hpp"
#include "../core/Util.hpp"
#include "../Game.h"
#include "../interface/Cursors.h"
#include "../interface/Window.h"
#include "../localisation/Date.h"
#include "../localisation/Localisation.h"
#include "../Input.h"
#include "../management/Finance.h"
#include "../network/network.h"
#include "../OpenRCT2.h"
#include "../ride/RideData.h"
#include "../ride/Track.h"
#include "../ride/TrackData.h"
#include "../scenario/Scenario.h"
#include "../util/Util.h"
#include "../windows/Intent.h"
#include "Banner.h"
#include "Climate.h"
#include "footpath.h"
#include "LargeScenery.h"
#include "Map.h"
#include "map_animation.h"
#include "Park.h"
#include "scenery.h"
#include "SmallScenery.h"
#include "TileInspector.h"
#include "Wall.h"
/**
* Replaces 0x00993CCC, 0x00993CCE
*/
const LocationXY16 TileDirectionDelta[] = {
{ -32, 0 },
{ 0, +32 },
{ +32, 0 },
{ 0, -32 },
{ -32, +32 },
{ +32, +32 },
{ +32, -32 },
{ -32, -32 }
};
/** rct2: 0x0097B8B8 */
const money32 TerrainPricing[] = {
300, // TERRAIN_GRASS
100, // TERRAIN_SAND
80, // TERRAIN_DIRT
120, // TERRAIN_ROCK
100, // TERRAIN_MARTIAN
100, // TERRAIN_CHECKERBOARD
110, // TERRAIN_GRASS_CLUMPS
130, // TERRAIN_ICE
110, // TERRAIN_GRID_RED
110, // TERRAIN_GRID_YELLOW
110, // TERRAIN_GRID_BLUE
110, // TERRAIN_GRID_GREEN
110, // TERRAIN_SAND_DARK
110, // TERRAIN_SAND_LIGHT
};
uint16 gMapSelectFlags;
uint16 gMapSelectType;
LocationXY16 gMapSelectPositionA;
LocationXY16 gMapSelectPositionB;
LocationXYZ16 gMapSelectArrowPosition;
uint8 gMapSelectArrowDirection;
uint16 gMapVirtualFloorBaseSize = 5*32;
uint16 gMapVirtualFloorHeight;
bool gMapVirtualFloorVisible = false;
uint8 gMapGroundFlags;
uint16 gWidePathTileLoopX;
uint16 gWidePathTileLoopY;
uint16 gGrassSceneryTileLoopPosition;
sint16 gMapSizeUnits;
sint16 gMapSizeMinus2;
sint16 gMapSize;
sint16 gMapSizeMaxXY;
sint16 gMapBaseZ;
rct_tile_element gTileElements[MAX_TILE_TILE_ELEMENT_POINTERS * 3];
rct_tile_element *gTileElementTilePointers[MAX_TILE_TILE_ELEMENT_POINTERS];
LocationXY16 gMapSelectionTiles[300];
static LocationXYZ16 gVirtualFloorLastLocation;
rct2_peep_spawn gPeepSpawns[MAX_PEEP_SPAWNS];
rct_tile_element *gNextFreeTileElement;
uint32 gNextFreeTileElementPointerIndex;
bool gLandMountainMode;
bool gLandPaintMode;
bool gClearSmallScenery;
bool gClearLargeScenery;
bool gClearFootpath;
uint16 gLandRemainingOwnershipSales;
uint16 gLandRemainingConstructionSales;
LocationXYZ16 gCommandPosition;
bool gMapLandRightsUpdateSuccess;
static void map_update_grass_length(sint32 x, sint32 y, rct_tile_element *tileElement);
static void map_set_grass_length(sint32 x, sint32 y, rct_tile_element *tileElement, sint32 length);
static void clear_elements_at(sint32 x, sint32 y);
static void translate_3d_to_2d(sint32 rotation, sint32 *x, sint32 *y);
void rotate_map_coordinates(sint16 *x, sint16 *y, sint32 rotation)
{
sint32 temp;
switch (rotation) {
case TILE_ELEMENT_DIRECTION_WEST:
break;
case TILE_ELEMENT_DIRECTION_NORTH:
temp = *x;
*x = *y;
*y = -temp;
break;
case TILE_ELEMENT_DIRECTION_EAST:
*x = -*x;
*y = -*y;
break;
case TILE_ELEMENT_DIRECTION_SOUTH:
temp = *y;
*y = *x;
*x = -temp;
break;
}
}
LocationXY16 coordinate_3d_to_2d(const LocationXYZ16* coordinate_3d, sint32 rotation){
LocationXY16 coordinate_2d;
switch (rotation){
default:
case 0:
coordinate_2d.x = coordinate_3d->y - coordinate_3d->x;
coordinate_2d.y = ((coordinate_3d->y + coordinate_3d->x) >> 1) - coordinate_3d->z;
break;
case 1:
coordinate_2d.x = -coordinate_3d->y - coordinate_3d->x;
coordinate_2d.y = ((coordinate_3d->y - coordinate_3d->x) >> 1) - coordinate_3d->z;
break;
case 2:
coordinate_2d.x = -coordinate_3d->y + coordinate_3d->x;
coordinate_2d.y = ((-coordinate_3d->y - coordinate_3d->x) >> 1) - coordinate_3d->z;
break;
case 3:
coordinate_2d.x = coordinate_3d->y + coordinate_3d->x;
coordinate_2d.y = ((-coordinate_3d->y + coordinate_3d->x) >> 1) - coordinate_3d->z;
break;
}
return coordinate_2d;
}
void tile_element_iterator_begin(tile_element_iterator *it)
{
it->x = 0;
it->y = 0;
it->element = map_get_first_element_at(0, 0);
}
sint32 tile_element_iterator_next(tile_element_iterator *it)
{
if (it->element == nullptr) {
it->element = map_get_first_element_at(it->x, it->y);
return 1;
}
if (!tile_element_is_last_for_tile(it->element)) {
it->element++;
return 1;
}
if (it->x < (MAXIMUM_MAP_SIZE_TECHNICAL - 1)) {
it->x++;
it->element = map_get_first_element_at(it->x, it->y);
return 1;
}
if (it->y < (MAXIMUM_MAP_SIZE_TECHNICAL - 1)) {
it->x = 0;
it->y++;
it->element = map_get_first_element_at(it->x, it->y);
return 1;
}
return 0;
}
void tile_element_iterator_restart_for_tile(tile_element_iterator *it)
{
it->element = nullptr;
}
rct_tile_element *map_get_first_element_at(sint32 x, sint32 y)
{
if (x < 0 || y < 0 || x > (MAXIMUM_MAP_SIZE_TECHNICAL - 1) || y > (MAXIMUM_MAP_SIZE_TECHNICAL - 1)) {
log_error("Trying to access element outside of range");
return nullptr;
}
return gTileElementTilePointers[x + y * MAXIMUM_MAP_SIZE_TECHNICAL];
}
rct_tile_element *map_get_nth_element_at(sint32 x, sint32 y, sint32 n)
{
rct_tile_element * tileElement = map_get_first_element_at(x, y);
if (tileElement == nullptr) {
return nullptr;
}
// Iterate through elements on this tile. This has to be walked, rather than
// jumped directly to, because n may exceed element count for given tile,
// and the order of tiles (unlike elements) is not synced over multiplayer.
while (n >= 0) {
if (n == 0) {
return tileElement;
}
if (tile_element_is_last_for_tile(tileElement)) {
break;
}
tileElement++;
n--;
}
// The element sought for is not within given tile.
return nullptr;
}
void map_set_tile_elements(sint32 x, sint32 y, rct_tile_element *elements)
{
if (x < 0 || y < 0 || x > (MAXIMUM_MAP_SIZE_TECHNICAL - 1) || y > (MAXIMUM_MAP_SIZE_TECHNICAL - 1)) {
log_error("Trying to access element outside of range");
return;
}
gTileElementTilePointers[x + y * MAXIMUM_MAP_SIZE_TECHNICAL] = elements;
}
bool tile_element_is_last_for_tile(const rct_tile_element *element)
{
return (element->flags & TILE_ELEMENT_FLAG_LAST_TILE) != 0;
}
bool tile_element_is_ghost(const rct_tile_element *element)
{
return element->flags & TILE_ELEMENT_FLAG_GHOST;
}
uint8 tile_element_get_scenery_quadrant(const rct_tile_element *element)
{
return (element->type & TILE_ELEMENT_QUADRANT_MASK) >> 6;
}
sint32 tile_element_get_type(const rct_tile_element *element)
{
return element->type & TILE_ELEMENT_TYPE_MASK;
}
sint32 tile_element_get_direction(const rct_tile_element *element)
{
return element->type & TILE_ELEMENT_DIRECTION_MASK;
}
sint32 tile_element_get_direction_with_offset(const rct_tile_element *element, uint8 offset)
{
return ((element->type & TILE_ELEMENT_DIRECTION_MASK) + offset) & TILE_ELEMENT_DIRECTION_MASK;
}
sint32 tile_element_get_terrain(const rct_tile_element *element)
{
sint32 terrain = (element->properties.surface.terrain >> 5) & 7;
if (element->type & 1)
terrain |= (1 << 3);
return terrain;
}
sint32 tile_element_get_terrain_edge(const rct_tile_element *element)
{
sint32 terrain_edge = (element->properties.surface.slope >> 5) & 7;
if (element->type & 128)
terrain_edge |= (1 << 3);
return terrain_edge;
}
void tile_element_set_terrain(rct_tile_element *element, sint32 terrain)
{
// Bit 3 for terrain is stored in element.type bit 0
if (terrain & 8)
element->type |= 1;
else
element->type &= ~1;
// Bits 0, 1, 2 for terrain are stored in element.terrain bit 5, 6, 7
element->properties.surface.terrain &= ~0xE0;
element->properties.surface.terrain |= (terrain & 7) << 5;
}
void tile_element_set_terrain_edge(rct_tile_element *element, sint32 terrain)
{
// Bit 3 for terrain is stored in element.type bit 7
if (terrain & 8)
element->type |= 128;
else
element->type &= ~128;
// Bits 0, 1, 2 for terrain are stored in element.slope bit 5, 6, 7
element->properties.surface.slope &= ~TILE_ELEMENT_SURFACE_EDGE_STYLE_MASK;
element->properties.surface.slope |= (terrain & 7) << 5;
}
rct_tile_element *map_get_surface_element_at(sint32 x, sint32 y)
{
rct_tile_element *tileElement = map_get_first_element_at(x, y);
if (tileElement == nullptr)
return nullptr;
// Find the first surface element
while (tile_element_get_type(tileElement) != TILE_ELEMENT_TYPE_SURFACE) {
if (tile_element_is_last_for_tile(tileElement))
return nullptr;
tileElement++;
}
return tileElement;
}
rct_tile_element* map_get_path_element_at(sint32 x, sint32 y, sint32 z){
rct_tile_element *tileElement = map_get_first_element_at(x, y);
if (tileElement == nullptr)
return nullptr;
// Find the path element at known z
do {
if (tileElement->flags & TILE_ELEMENT_FLAG_GHOST)
continue;
if (tile_element_get_type(tileElement) != TILE_ELEMENT_TYPE_PATH)
continue;
if (tileElement->base_height != z)
continue;
return tileElement;
} while (!tile_element_is_last_for_tile(tileElement++));
return nullptr;
}
rct_tile_element* map_get_banner_element_at(sint32 x, sint32 y, sint32 z, uint8 position) {
rct_tile_element *tileElement = map_get_first_element_at(x, y);
if (tileElement == nullptr)
return nullptr;
// Find the banner element at known z and position
do {
if (tile_element_get_type(tileElement) != TILE_ELEMENT_TYPE_BANNER)
continue;
if (tileElement->base_height != z)
continue;
if (tileElement->properties.banner.position != position)
continue;
return tileElement;
} while (!tile_element_is_last_for_tile(tileElement++));
return nullptr;
}
/**
*
* rct2: 0x0068AB4C
*/
void map_init(sint32 size)
{
gNumMapAnimations = 0;
gNextFreeTileElementPointerIndex = 0;
for (sint32 i = 0; i < MAX_TILE_TILE_ELEMENT_POINTERS; i++) {
rct_tile_element *tile_element = &gTileElements[i];
tile_element->type = (TILE_ELEMENT_TYPE_SURFACE << 2);
tile_element->flags = TILE_ELEMENT_FLAG_LAST_TILE;
tile_element->base_height = 14;
tile_element->clearance_height = 14;
tile_element->properties.surface.slope = TILE_ELEMENT_SLOPE_FLAT;
tile_element->properties.surface.grass_length = GRASS_LENGTH_CLEAR_0;
tile_element->properties.surface.ownership = 0;
tile_element->properties.surface.terrain = 0;
tile_element_set_terrain(tile_element, TERRAIN_GRASS);
tile_element_set_terrain_edge(tile_element, TERRAIN_EDGE_ROCK);
}
gGrassSceneryTileLoopPosition = 0;
gWidePathTileLoopX = 0;
gWidePathTileLoopY = 0;
gMapSizeUnits = size * 32 - 32;
gMapSizeMinus2 = size * 32 - 2;
gMapSize = size;
gMapSizeMaxXY = size * 32 - 33;
gMapBaseZ = 7;
map_update_tile_pointers();
map_remove_out_of_range_elements();
Intent * intent = intent_create(INTENT_ACTION_MAP);
context_broadcast_intent(intent);
intent_release(intent);
}
/**
* Counts the number of surface tiles that offer land ownership rights for sale,
* but haven't been bought yet. It updates gLandRemainingOwnershipSales and
* gLandRemainingConstructionSales.
*/
void map_count_remaining_land_rights()
{
gLandRemainingOwnershipSales = 0;
gLandRemainingConstructionSales = 0;
for (sint32 x = 0; x < MAXIMUM_MAP_SIZE_TECHNICAL; x++)
{
for (sint32 y = 0; y < MAXIMUM_MAP_SIZE_TECHNICAL; y++)
{
rct_tile_element *element = map_get_surface_element_at(x, y);
// Surface elements are sometimes hacked out to save some space for other map elements
if (element == nullptr)
{
continue;
}
uint8 flags = element->properties.surface.ownership;
// Do not combine this condition with (flags & OWNERSHIP_AVAILABLE)
// As some RCT1 parks have owned tiles with the 'construction rights available' flag also set
if (!(flags & OWNERSHIP_OWNED))
{
if (flags & OWNERSHIP_AVAILABLE)
{
gLandRemainingOwnershipSales++;
}
else if ((flags & OWNERSHIP_CONSTRUCTION_RIGHTS_AVAILABLE) && (flags & OWNERSHIP_CONSTRUCTION_RIGHTS_OWNED) == 0)
{
gLandRemainingConstructionSales++;
}
}
}
}
}
/**
* This is meant to strip TILE_ELEMENT_FLAG_GHOST flag from all elements when
* importing a park.
*
* This can only exist in hacked parks, as we remove ghost elements while saving.
*
* This is less invasive than removing ghost elements themselves, as they can
* contain valid data.
*/
void map_strip_ghost_flag_from_elements()
{
rct_tile_element *tileElement = gTileElements;
do {
tileElement->flags &= ~TILE_ELEMENT_FLAG_GHOST;
} while (++tileElement < gTileElements + MAX_TILE_ELEMENTS);
}
/**
*
* rct2: 0x0068AFFD
*/
void map_update_tile_pointers()
{
sint32 i, x, y;
for (i = 0; i < MAX_TILE_TILE_ELEMENT_POINTERS; i++) {
gTileElementTilePointers[i] = TILE_UNDEFINED_TILE_ELEMENT;
}
rct_tile_element *tileElement = gTileElements;
rct_tile_element **tile = gTileElementTilePointers;
for (y = 0; y < MAXIMUM_MAP_SIZE_TECHNICAL; y++) {
for (x = 0; x < MAXIMUM_MAP_SIZE_TECHNICAL; x++) {
*tile++ = tileElement;
while (!tile_element_is_last_for_tile(tileElement++));
}
}
gNextFreeTileElement = tileElement;
}
/**
* Return the absolute height of an element, given its (x,y) coordinates
*
* ax: x
* cx: y
* dx: return remember to & with 0xFFFF if you don't want water affecting results
* rct2: 0x00662783
*/
sint32 tile_element_height(sint32 x, sint32 y)
{
rct_tile_element *tileElement;
// Off the map
if ((unsigned)x >= 8192 || (unsigned)y >= 8192)
return 16;
// Truncate subtile coordinates
sint32 x_tile = x & 0xFFFFFFE0;
sint32 y_tile = y & 0xFFFFFFE0;
// Get the surface element for the tile
tileElement = map_get_surface_element_at(x_tile / 32, y_tile / 32);
if (tileElement == nullptr) {
return 16;
}
uint32 height =
(map_get_water_height(tileElement) << 20) |
(tileElement->base_height << 3);
uint32 slope = (tileElement->properties.surface.slope & TILE_ELEMENT_SURFACE_SLOPE_MASK);
uint8 extra_height = (slope & TILE_ELEMENT_SLOPE_DOUBLE_HEIGHT) >> 4; // 0x10 is the 5th bit - sets slope to double height
// Remove the extra height bit
slope &= TILE_ELEMENT_SLOPE_ALL_CORNERS_UP;
sint8 quad = 0, quad_extra = 0; // which quadrant the element is in?
// quad_extra is for extra height tiles
uint8 xl, yl; // coordinates across this tile
uint8 TILE_SIZE = 31;
xl = x & 0x1f;
yl = y & 0x1f;
// Slope logic:
// Each of the four bits in slope represents that corner being raised
// slope == 15 (all four bits) is not used and slope == 0 is flat
// If the extra_height bit is set, then the slope goes up two z-levels
// We arbitrarily take the SW corner to be closest to the viewer
// One corner up
if (slope == TILE_ELEMENT_SLOPE_N_CORNER_UP ||
slope == TILE_ELEMENT_SLOPE_E_CORNER_UP ||
slope == TILE_ELEMENT_SLOPE_S_CORNER_UP ||
slope == TILE_ELEMENT_SLOPE_W_CORNER_UP)
{
switch (slope) {
case TILE_ELEMENT_SLOPE_N_CORNER_UP:
quad = xl + yl - TILE_SIZE;
break;
case TILE_ELEMENT_SLOPE_E_CORNER_UP:
quad = xl - yl;
break;
case TILE_ELEMENT_SLOPE_S_CORNER_UP:
quad = TILE_SIZE - yl - xl;
break;
case TILE_ELEMENT_SLOPE_W_CORNER_UP:
quad = yl - xl;
break;
}
// If the element is in the quadrant with the slope, raise its height
if (quad > 0) {
height += quad / 2;
}
}
// One side up
switch (slope) {
case TILE_ELEMENT_SLOPE_NE_SIDE_UP:
height += xl / 2 + 1;
break;
case TILE_ELEMENT_SLOPE_SE_SIDE_UP:
height += (TILE_SIZE - yl) / 2;
break;
case TILE_ELEMENT_SLOPE_NW_SIDE_UP:
height += yl / 2;
height++;
break;
case TILE_ELEMENT_SLOPE_SW_SIDE_UP:
height += (TILE_SIZE - xl) / 2;
break;
}
// One corner down
if ((slope == TILE_ELEMENT_SLOPE_W_CORNER_DN) ||
(slope == TILE_ELEMENT_SLOPE_S_CORNER_DN) ||
(slope == TILE_ELEMENT_SLOPE_E_CORNER_DN) ||
(slope == TILE_ELEMENT_SLOPE_N_CORNER_DN)) {
switch (slope) {
case TILE_ELEMENT_SLOPE_W_CORNER_DN:
quad_extra = xl + TILE_SIZE - yl;
quad = xl - yl;
break;
case TILE_ELEMENT_SLOPE_S_CORNER_DN:
quad_extra = xl + yl;
quad = xl + yl - TILE_SIZE - 1;
break;
case TILE_ELEMENT_SLOPE_E_CORNER_DN:
quad_extra = TILE_SIZE - xl + yl;
quad = yl - xl;
break;
case TILE_ELEMENT_SLOPE_N_CORNER_DN:
quad_extra = (TILE_SIZE - xl) + (TILE_SIZE - yl);
quad = TILE_SIZE - yl - xl - 1;
break;
}
if (extra_height) {
height += quad_extra / 2;
height++;
return height;
}
// This tile is essentially at the next height level
height += 0x10;
// so we move *down* the slope
if (quad < 0) {
height += quad / 2;
}
}
// Valleys
if ((slope == TILE_ELEMENT_SLOPE_W_E_VALLEY) ||
(slope == TILE_ELEMENT_SLOPE_N_S_VALLEY)) {
switch (slope) {
case TILE_ELEMENT_SLOPE_W_E_VALLEY:
if (xl + yl <= TILE_SIZE + 1) {
return height;
}
quad = TILE_SIZE - xl - yl;
break;
case TILE_ELEMENT_SLOPE_N_S_VALLEY:
quad = xl - yl;
break;
}
if (quad > 0) {
height += quad / 2;
}
}
return height;
}
/**
*
* rct2: 0x0068B089
*/
void sub_68B089()
{
sint32 i;
rct_tile_element *tileElementFirst, *tileElement;
if (gTrackDesignSaveMode)
return;
i = gNextFreeTileElementPointerIndex;
do {
i++;
if (i >= MAX_TILE_TILE_ELEMENT_POINTERS)
i = 0;
} while (gTileElementTilePointers[i] == TILE_UNDEFINED_TILE_ELEMENT);
gNextFreeTileElementPointerIndex = i;
tileElementFirst = tileElement = gTileElementTilePointers[i];
do {
tileElement--;
if (tileElement < gTileElements)
break;
} while (tileElement->base_height == 255);
tileElement++;
if (tileElement == tileElementFirst)
return;
//
gTileElementTilePointers[i] = tileElement;
do {
*tileElement = *tileElementFirst;
tileElementFirst->base_height = 255;
tileElementFirst++;
} while (!tile_element_is_last_for_tile(tileElement++));
tileElement = gNextFreeTileElement;
do {
tileElement--;
} while (tileElement->base_height == 255);
tileElement++;
gNextFreeTileElement = tileElement;
}
/**
* Checks if the tile at coordinate at height counts as connected.
* @return 1 if connected, 0 otherwise
*/
bool map_coord_is_connected(sint32 x, sint32 y, sint32 z, uint8 faceDirection)
{
rct_tile_element *tileElement = map_get_first_element_at(x, y);
do {
if (tile_element_get_type(tileElement) != TILE_ELEMENT_TYPE_PATH)
continue;
rct_tile_element_path_properties props = tileElement->properties.path;
uint8 pathType = props.type >> 2;
uint8 pathDirection = props.type & 3;
if (pathType & 1) {
if (pathDirection == faceDirection) {
if (z == tileElement->base_height + 2)
return true;
} else if ((pathDirection ^ 2) == faceDirection && z == tileElement->base_height) {
return true;
}
} else {
if (z == tileElement->base_height)
return true;
}
} while (!tile_element_is_last_for_tile(tileElement++));
return false;
}
/**
*
* rct2: 0x006A876D
*/
void map_update_path_wide_flags()
{
if (gScreenFlags & (SCREEN_FLAGS_TRACK_DESIGNER | SCREEN_FLAGS_TRACK_MANAGER)) {
return;
}
// Presumably update_path_wide_flags is too computationally expensive to call for every
// tile every update, so gWidePathTileLoopX and gWidePathTileLoopY store the x and y
// progress. A maximum of 128 calls is done per update.
uint16 x = gWidePathTileLoopX;
uint16 y = gWidePathTileLoopY;
for (sint32 i = 0; i < 128; i++) {
footpath_update_path_wide_flags(x, y);
// Next x, y tile
x += 32;
if (x >= 8192) {
x = 0;
y += 32;
if (y >= 8192) {
y = 0;
}
}
}
gWidePathTileLoopX = x;
gWidePathTileLoopY = y;
}
/**
*
* rct2: 0x006A7B84
*/
sint32 map_height_from_slope(sint32 x, sint32 y, sint32 slope)
{
if (!(slope & TILE_ELEMENT_SLOPE_S_CORNER_UP))
return 0;
switch (slope & TILE_ELEMENT_SLOPE_NE_SIDE_UP) {
case TILE_ELEMENT_SLOPE_FLAT:
return (31 - (x & 31)) / 2;
case TILE_ELEMENT_SLOPE_N_CORNER_UP:
return (y & 31) / 2;
case TILE_ELEMENT_SLOPE_E_CORNER_UP:
return (x & 31) / 2;
case TILE_ELEMENT_SLOPE_NE_SIDE_UP:
return (31 - (y & 31)) / 2;
}
return 0;
}
bool map_is_location_valid(sint32 x, sint32 y)
{
if (x < (MAXIMUM_MAP_SIZE_TECHNICAL * 32) && x >= 0 && y < (MAXIMUM_MAP_SIZE_TECHNICAL * 32) && y >= 0) {
return true;
}
return false;
}
bool map_can_build_at(sint32 x, sint32 y, sint32 z)
{
if (gScreenFlags & SCREEN_FLAGS_SCENARIO_EDITOR)
return true;
if (gCheatsSandboxMode)
return true;
if (map_is_location_owned(x, y, z))
return true;
return false;
}
/**
*
* rct2: 0x00664F72
*/
bool map_is_location_owned(sint32 x, sint32 y, sint32 z)
{
// This check is to avoid throwing lots of messages in logs.
if (map_is_location_valid(x, y)) {
rct_tile_element *tileElement = map_get_surface_element_at(x / 32, y / 32);
if (tileElement != nullptr) {
if (tileElement->properties.surface.ownership & OWNERSHIP_OWNED)
return true;
if (tileElement->properties.surface.ownership & OWNERSHIP_CONSTRUCTION_RIGHTS_OWNED) {
z /= 8;
if (z < tileElement->base_height || z - 2 > tileElement->base_height)
return true;
}
}
}
gGameCommandErrorText = STR_LAND_NOT_OWNED_BY_PARK;
return false;
}
/**
*
* rct2: 0x00664F2C
*/
bool map_is_location_in_park(sint32 x, sint32 y)
{
if (map_is_location_valid(x, y)) {
rct_tile_element *tileElement = map_get_surface_element_at(x / 32, y / 32);
if (tileElement == nullptr)
return false;
if (tileElement->properties.surface.ownership & OWNERSHIP_OWNED)
return true;
}
gGameCommandErrorText = STR_LAND_NOT_OWNED_BY_PARK;
return false;
}
bool map_is_location_owned_or_has_rights(sint32 x, sint32 y)
{
if (map_is_location_valid(x, y)) {
rct_tile_element *tileElement = map_get_surface_element_at(x / 32, y / 32);
if (tileElement == nullptr) {
return false;
}
if (tileElement->properties.surface.ownership & OWNERSHIP_OWNED) return true;
if (tileElement->properties.surface.ownership & OWNERSHIP_CONSTRUCTION_RIGHTS_OWNED) return true;
}
return false;
}
/**
*
* rct2: 0x006B8E1B
*/
void game_command_remove_large_scenery(sint32* eax, sint32* ebx, sint32* ecx, sint32* edx, sint32* esi, sint32* edi, sint32* ebp)
{
uint8 base_height = *edx;
uint8 tileIndex = *edx >> 8;
uint8 tile_element_direction = *ebx >> 8;
sint32 x = *eax;
sint32 y = *ecx;
sint32 z = tile_element_height(x, y);
uint8 flags = *ebx & 0xFF;
gCommandPosition.x = x + 16;
gCommandPosition.y = y + 16;
gCommandPosition.z = z;
gCommandExpenditureType = RCT_EXPENDITURE_TYPE_LANDSCAPING;
if (!(flags & GAME_COMMAND_FLAG_GHOST) && game_is_paused() && !gCheatsBuildInPauseMode) {
gGameCommandErrorText = STR_CONSTRUCTION_NOT_POSSIBLE_WHILE_GAME_IS_PAUSED;
*ebx = MONEY32_UNDEFINED;
return;
}
bool element_found = false;
rct_tile_element* tile_element = map_get_first_element_at(x / 32, y / 32);
if (tile_element == nullptr)
{
log_warning("Invalid game command for scenery removal, x = %d, y = %d", x, y);
*ebx = MONEY32_UNDEFINED;
return;
}
do {
if (tile_element_get_type(tile_element) != TILE_ELEMENT_TYPE_LARGE_SCENERY)
continue;
if (tile_element->base_height != base_height)
continue;
if (scenery_large_get_sequence(tile_element) != tileIndex)
continue;
if ((tile_element_get_direction(tile_element)) != tile_element_direction)
continue;
// If we are removing ghost elements
if((flags & GAME_COMMAND_FLAG_GHOST) && !(tile_element->flags & TILE_ELEMENT_FLAG_GHOST))
continue;
element_found = true;
break;
} while (!tile_element_is_last_for_tile(tile_element++));
if (element_found == false){
*ebx = 0;
return;
}
if (flags & GAME_COMMAND_FLAG_APPLY) {
tile_element_remove_banner_entry(tile_element);
}
rct_scenery_entry* scenery_entry = get_large_scenery_entry(scenery_large_get_type(tile_element));
LocationXYZ16 firstTile = {
scenery_entry->large_scenery.tiles[tileIndex].x_offset,
scenery_entry->large_scenery.tiles[tileIndex].y_offset,
static_cast<sint16>((base_height * 8) - scenery_entry->large_scenery.tiles[tileIndex].z_offset)
};
rotate_map_coordinates(&firstTile.x, &firstTile.y, tile_element_direction);
firstTile.x = x - firstTile.x;
firstTile.y = y - firstTile.y;
bool calculate_cost = true;
for (sint32 i = 0; scenery_entry->large_scenery.tiles[i].x_offset != -1; i++){
LocationXYZ16 currentTile = {
scenery_entry->large_scenery.tiles[i].x_offset,
scenery_entry->large_scenery.tiles[i].y_offset,
scenery_entry->large_scenery.tiles[i].z_offset
};
rotate_map_coordinates(&currentTile.x, &currentTile.y, tile_element_direction);
currentTile.x += firstTile.x;
currentTile.y += firstTile.y;
currentTile.z += firstTile.z;
if (!(gScreenFlags & SCREEN_FLAGS_SCENARIO_EDITOR) && !gCheatsSandboxMode){
if (!map_is_location_owned(currentTile.x, currentTile.y, currentTile.z)){
*ebx = MONEY32_UNDEFINED;
return;
}
}
// If not applying then no need to delete the actual element
if (!(flags & GAME_COMMAND_FLAG_APPLY)) {
if (flags & (1 << 7)) {
if (tile_element->flags & (1 << 6))
calculate_cost = false;
tile_element->flags |= (1 << 6);
}
continue;
}
rct_tile_element* sceneryElement = map_get_first_element_at(currentTile.x / 32, currentTile.y / 32);
element_found = false;
do
{
if (tile_element_get_type(sceneryElement) != TILE_ELEMENT_TYPE_LARGE_SCENERY)
continue;
if (tile_element_get_direction(sceneryElement) != tile_element_direction)
continue;
if (scenery_large_get_sequence(sceneryElement) != i)
continue;
if (sceneryElement->base_height != currentTile.z / 8)
continue;
// If we are removing ghost elements
if ((flags & GAME_COMMAND_FLAG_GHOST) && !(sceneryElement->flags & TILE_ELEMENT_FLAG_GHOST))
continue;
map_invalidate_tile_full(currentTile.x, currentTile.y);
tile_element_remove(sceneryElement);
element_found = true;
break;
} while (!tile_element_is_last_for_tile(sceneryElement++));
if (element_found == false){
log_error("Tile not found when trying to remove element!");
}
}
if (flags & GAME_COMMAND_FLAG_APPLY && gGameCommandNestLevel == 1 && !(flags & GAME_COMMAND_FLAG_GHOST)) {
LocationXYZ16 coord;
coord.x = x + 16;
coord.y = 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);
}
*ebx = scenery_entry->large_scenery.removal_price * 10;
if (gParkFlags & PARK_FLAGS_NO_MONEY ||
calculate_cost == false){
*ebx = 0;
}
}
/**
*
* rct2: 0x006B909A
*/
void game_command_set_large_scenery_colour(sint32* eax, sint32* ebx, sint32* ecx, sint32* edx, sint32* esi, sint32* edi, sint32* ebp)
{
gCommandExpenditureType = RCT_EXPENDITURE_TYPE_LANDSCAPING;
sint32 x = *eax;
sint32 y = *ecx;
uint8 tile_element_direction = *ebx >> 8;
uint8 flags = *ebx & 0xFF;
uint8 base_height = *edx;
uint8 tileIndex = *edx >> 8;
uint8 colour1 = *ebp;
uint8 colour2 = *ebp >> 8;
sint32 z = tile_element_height(x, y);
gCommandPosition.x = x + 16;
gCommandPosition.y = y + 16;
gCommandPosition.z = z;
rct_tile_element *tile_element = map_get_large_scenery_segment(x, y, base_height, tile_element_direction, tileIndex);
if(tile_element == nullptr){
*ebx = 0;
return;
}
if((flags & GAME_COMMAND_FLAG_GHOST) && !(tile_element->flags & TILE_ELEMENT_FLAG_GHOST)){
*ebx = 0;
return;
}
rct_scenery_entry * scenery_entry = get_large_scenery_entry(scenery_large_get_type(tile_element));
// Work out the base tile coordinates (Tile with index 0)
LocationXYZ16 baseTile = {
scenery_entry->large_scenery.tiles[tileIndex].x_offset,
scenery_entry->large_scenery.tiles[tileIndex].y_offset,
static_cast<sint16>((base_height * 8) - scenery_entry->large_scenery.tiles[tileIndex].z_offset)
};
rotate_map_coordinates(&baseTile.x, &baseTile.y, tile_element_direction);
baseTile.x = x - baseTile.x;
baseTile.y = y - baseTile.y;
for (sint32 i = 0; scenery_entry->large_scenery.tiles[i].x_offset != -1; ++i) {
assert(i < MAXIMUM_MAP_SIZE_TECHNICAL);
// Work out the current tile coordinates
LocationXYZ16 currentTile = {
scenery_entry->large_scenery.tiles[i].x_offset,
scenery_entry->large_scenery.tiles[i].y_offset,
scenery_entry->large_scenery.tiles[i].z_offset
};
rotate_map_coordinates(&currentTile.x, &currentTile.y, tile_element_direction);
currentTile.x += baseTile.x;
currentTile.y += baseTile.y;
currentTile.z += baseTile.z;
if (!(gScreenFlags & SCREEN_FLAGS_SCENARIO_EDITOR) && !gCheatsSandboxMode){
if (!map_is_location_owned(currentTile.x, currentTile.y, currentTile.z)){
*ebx = MONEY32_UNDEFINED;
return;
}
}
if(flags & GAME_COMMAND_FLAG_APPLY){
rct_tile_element* tileElement = map_get_large_scenery_segment(
currentTile.x,
currentTile.y,
base_height,
tile_element_direction,
i);
scenery_large_set_primary_colour(tileElement, colour1);
scenery_large_set_secondary_colour(tileElement, colour2);
map_invalidate_tile_full(currentTile.x, currentTile.y);
}
}
*ebx = 0;
}
// This will cause clear scenery to remove paths
// This should be a flag for the game command which can be set via a checkbox on the clear scenery window.
// #define CLEAR_SCENERY_REMOVES_PATHS
/**
*
* rct2: 0x0068DFE4
*/
static money32 map_clear_scenery_from_tile(sint32 x, sint32 y, sint32 clear, sint32 flags)
{
sint32 type;
money32 cost, totalCost;
rct_tile_element *tileElement;
totalCost = 0;
restart_from_beginning:
tileElement = map_get_first_element_at(x, y);
do {
type = tile_element_get_type(tileElement);
switch (type) {
case TILE_ELEMENT_TYPE_PATH:
if (clear & (1 << 2)) {
sint32 eax = x * 32;
sint32 ebx = flags;
sint32 ecx = y * 32;
sint32 edx = tileElement->base_height;
sint32 edi = 0, ebp = 0;
cost = game_do_command(eax, ebx, ecx, edx, GAME_COMMAND_REMOVE_PATH, edi, ebp);
if (cost == MONEY32_UNDEFINED)
return MONEY32_UNDEFINED;
totalCost += cost;
if (flags & 1)
goto restart_from_beginning;
} break;
case TILE_ELEMENT_TYPE_SMALL_SCENERY:
if (clear & (1 << 0)) {
sint32 eax = x * 32;
sint32 ebx = (tileElement->type << 8) | flags;
sint32 ecx = y * 32;
sint32 edx = (tileElement->properties.scenery.type << 8) | (tileElement->base_height);
sint32 edi = 0, ebp = 0;
cost = game_do_command(eax, ebx, ecx, edx, GAME_COMMAND_REMOVE_SCENERY, edi, ebp);
if (cost == MONEY32_UNDEFINED)
return MONEY32_UNDEFINED;
totalCost += cost;
if (flags & 1)
goto restart_from_beginning;
} break;
case TILE_ELEMENT_TYPE_WALL:
if (clear & (1 << 0)) {
sint32 eax = x * 32;
sint32 ebx = flags;
sint32 ecx = y * 32;
sint32 edx = (tileElement->base_height << 8) | (tile_element_get_direction(tileElement));
sint32 edi = 0, ebp = 0;
cost = game_do_command(eax, ebx, ecx, edx, GAME_COMMAND_REMOVE_WALL, edi, ebp);
if (cost == MONEY32_UNDEFINED)
return MONEY32_UNDEFINED;
totalCost += cost;
if (flags & 1)
goto restart_from_beginning;
} break;
case TILE_ELEMENT_TYPE_LARGE_SCENERY:
if (clear & (1 << 1)) {
sint32 eax = x * 32;
sint32 ebx = flags | ((tile_element_get_direction(tileElement)) << 8);
sint32 ecx = y * 32;
sint32 edx = tileElement->base_height | (scenery_large_get_sequence(tileElement) << 8);
sint32 edi = 0, ebp = 0;
cost = game_do_command(eax, ebx | (1 << 7), ecx, edx, GAME_COMMAND_REMOVE_LARGE_SCENERY, edi, ebp);
if (cost == MONEY32_UNDEFINED)
return MONEY32_UNDEFINED;
totalCost += cost;
if (flags & 1)
goto restart_from_beginning;
} break;
}
} while (!tile_element_is_last_for_tile(tileElement++));
return totalCost;
}
/**
* Function to clear the flag that is set to prevent cost duplication
* when using the clear scenery tool with large scenery.
*/
static void map_reset_clear_large_scenery_flag(){
rct_tile_element* tileElement;
// TODO: Improve efficiency of this
for (sint32 y = 0; y < MAXIMUM_MAP_SIZE_TECHNICAL; y++) {
for (sint32 x = 0; x < MAXIMUM_MAP_SIZE_TECHNICAL; x++) {
tileElement = map_get_first_element_at(x, y);
do {
if (tile_element_get_type(tileElement) == TILE_ELEMENT_TYPE_LARGE_SCENERY) {
tileElement->flags &= ~(1 << 6);
}
} while (!tile_element_is_last_for_tile(tileElement++));
}
}
}
money32 map_clear_scenery(sint32 x0, sint32 y0, sint32 x1, sint32 y1, sint32 clear, sint32 flags)
{
sint32 x, y, z;
money32 totalCost, cost;
bool noValidTiles;
gCommandExpenditureType = RCT_EXPENDITURE_TYPE_LANDSCAPING;
x = (x0 + x1) / 2 + 16;
y = (y0 + y1) / 2 + 16;
z = tile_element_height(x, y);
gCommandPosition.x = x;
gCommandPosition.y = y;
gCommandPosition.z = z;
x0 = std::max(x0, 32);
y0 = std::max(y0, 32);
x1 = std::min(x1, (sint32)gMapSizeMaxXY);
y1 = std::min(y1, (sint32)gMapSizeMaxXY);
noValidTiles = true;
totalCost = 0;
for (y = y0; y <= y1; y += 32) {
for (x = x0; x <= x1; x += 32) {
if ((gScreenFlags & SCREEN_FLAGS_SCENARIO_EDITOR) || gCheatsSandboxMode || map_is_location_owned_or_has_rights(x, y)) {
cost = map_clear_scenery_from_tile(x / 32, y / 32, clear, flags);
if (cost != MONEY32_UNDEFINED) {
noValidTiles = false;
totalCost += cost;
}
} else {
gGameCommandErrorText = STR_LAND_NOT_OWNED_BY_PARK;
}
}
}
if (gGameCommandNestLevel == 1 && flags & GAME_COMMAND_FLAG_APPLY) {
LocationXYZ16 coord;
coord.x = ((x0 + x1) / 2) + 16;
coord.y = ((y0 + y1) / 2) + 16;
coord.z = tile_element_height(coord.x, coord.y);
network_set_player_last_action_coord(network_get_player_index(game_command_playerid), coord);
}
if (clear & (1 << 1)) {
map_reset_clear_large_scenery_flag();
}
return noValidTiles ? MONEY32_UNDEFINED : totalCost;
}
/**
*
* rct2: 0x0068DF91
*/
void game_command_clear_scenery(sint32* eax, sint32* ebx, sint32* ecx, sint32* edx, sint32* esi, sint32* edi, sint32* ebp)
{
*ebx = map_clear_scenery(
(sint16)(*eax & 0xFFFF),
(sint16)(*ecx & 0xFFFF),
(sint16)(*edi & 0xFFFF),
(sint16)(*ebp & 0xFFFF),
*edx,
*ebx & 0xFF
);
}
/**
*
* rct2: 0x00663CCD
*/
static money32 map_change_surface_style(sint32 x0, sint32 y0, sint32 x1, sint32 y1, uint8 surfaceStyle, uint8 edgeStyle, uint8 flags)
{
gCommandExpenditureType = RCT_EXPENDITURE_TYPE_LANDSCAPING;
x0 = std::max(x0, 32);
y0 = std::max(y0, 32);
x1 = std::min(x1, (sint32)gMapSizeMaxXY);
y1 = std::min(y1, (sint32)gMapSizeMaxXY);
sint32 xMid, yMid;
xMid = (x0 + x1) / 2 + 16;
yMid = (y0 + y1) / 2 + 16;
sint32 heightMid = tile_element_height(xMid, yMid);
gCommandPosition.x = xMid;
gCommandPosition.y = yMid;
gCommandPosition.z = heightMid;
// Do nothing during pause
if (game_is_paused() && !gCheatsBuildInPauseMode) {
return 0;
}
// Do nothing if not in editor, sandbox mode or landscaping is forbidden
if (!(gScreenFlags & SCREEN_FLAGS_SCENARIO_EDITOR) &&
!gCheatsSandboxMode &&
(gParkFlags & PARK_FLAGS_FORBID_LANDSCAPE_CHANGES)
) {
return 0;
}
money32 surfaceCost = 0;
money32 edgeCost = 0;
for (sint32 x = x0; x <= x1; x += 32) {
for (sint32 y = y0; y <= y1; y += 32) {
if (x > 0x1FFF) continue;
if (y > 0x1FFF) continue;
if (!(gScreenFlags & SCREEN_FLAGS_SCENARIO_EDITOR) && !gCheatsSandboxMode) {
if (!map_is_location_in_park(x, y)) continue;
}
rct_tile_element* tileElement = map_get_surface_element_at(x / 32, y / 32);
if (surfaceStyle != 0xFF){
uint8 cur_terrain = (
tile_element_get_direction(tileElement) << 3) |
(tileElement->properties.surface.terrain >> 5);
if (surfaceStyle != cur_terrain) {
// Prevent network-originated value of surfaceStyle from causing
// invalid access.
uint8 style = surfaceStyle & 0x1F;
if (style >= Util::CountOf(TerrainPricing)) {
return MONEY32_UNDEFINED;
}
surfaceCost += TerrainPricing[style];
if (flags & GAME_COMMAND_FLAG_APPLY)
{
tileElement->properties.surface.terrain &= TILE_ELEMENT_SURFACE_WATER_HEIGHT_MASK;
tileElement->type &= TILE_ELEMENT_QUADRANT_MASK | TILE_ELEMENT_TYPE_MASK;
//Save the new terrain
tileElement->properties.surface.terrain |= surfaceStyle << 5;
//Save the new direction mask
tileElement->type |= (surfaceStyle >> 3) & TILE_ELEMENT_DIRECTION_MASK;
map_invalidate_tile_full(x, y);
footpath_remove_litter(x, y, tile_element_height(x, y));
}
}
}
if (edgeStyle != 0xFF) {
uint8 currentEdge =
((tileElement->type & 0x80) >> 4) |
(tileElement->properties.surface.slope >> 5);
if (edgeStyle != currentEdge){
edgeCost += 100;
if (flags & 1){
tileElement->properties.surface.slope &= TILE_ELEMENT_SURFACE_SLOPE_MASK;
tileElement->type &= 0x7F;
//Save edge style
tileElement->properties.surface.slope |= edgeStyle << 5;
//Save ???
tileElement->type |= (edgeStyle << 4) & 0x80;
map_invalidate_tile_full(x, y);
}
}
}
if (flags & 1)
{
if (!(tileElement->properties.surface.terrain & TILE_ELEMENT_SURFACE_TERRAIN_MASK))
{
if (!(tile_element_get_direction(tileElement)))
{
if ((tileElement->properties.surface.grass_length & 7) != GRASS_LENGTH_CLEAR_0) {
tileElement->properties.surface.grass_length = GRASS_LENGTH_CLEAR_0;
map_invalidate_tile_full(x, y);
}
}
}
}
}
}
if (flags & GAME_COMMAND_FLAG_APPLY && gGameCommandNestLevel == 1) {
LocationXYZ16 coord;
coord.x = ((x0 + x1) / 2) + 16;
coord.y = ((y0 + y1) / 2) + 16;
coord.z = tile_element_height(coord.x, coord.y);
network_set_player_last_action_coord(network_get_player_index(game_command_playerid), coord);
}
return (gParkFlags & PARK_FLAGS_NO_MONEY) ? 0 : surfaceCost + edgeCost;
}
/**
*
* rct2: 0x00663CCD
*/
void game_command_change_surface_style(sint32* eax, sint32* ebx, sint32* ecx, sint32* edx, sint32* esi, sint32* edi, sint32* ebp)
{
*ebx = map_change_surface_style(
(sint16)(*eax & 0xFFFF),
(sint16)(*ecx & 0xFFFF),
(sint16)(*edi & 0xFFFF),
(sint16)(*ebp & 0xFFFF),
*edx & 0xFF,
(*edx & 0xFF00) >> 8,
*ebx & 0xFF
);
}
//0x00981A1E
static const uint8 tile_element_raise_styles[5][32] = {
{ 0x01, 0x1B, 0x03, 0x1B, 0x05, 0x21, 0x07, 0x21, 0x09, 0x1B, 0x0B, 0x1B, 0x0D, 0x21, 0x20, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x23, 0x18, 0x19, 0x1A, 0x3B, 0x1C, 0x29, 0x24, 0x1F },
{ 0x02, 0x03, 0x17, 0x17, 0x06, 0x07, 0x17, 0x17, 0x0A, 0x0B, 0x22, 0x22, 0x0E, 0x20, 0x22, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x37, 0x18, 0x19, 0x1A, 0x23, 0x1C, 0x28, 0x26, 0x1F },
{ 0x04, 0x05, 0x06, 0x07, 0x1E, 0x24, 0x1E, 0x24, 0x0C, 0x0D, 0x0E, 0x20, 0x1E, 0x24, 0x1E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x26, 0x18, 0x19, 0x1A, 0x21, 0x1C, 0x2C, 0x3E, 0x1F },
{ 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x20, 0x1D, 0x1D, 0x28, 0x28, 0x1D, 0x1D, 0x28, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x22, 0x18, 0x19, 0x1A, 0x29, 0x1C, 0x3D, 0x2C, 0x1F },
{ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x20, 0x20, 0x20, 0x21, 0x20, 0x28, 0x24, 0x20 },
};
//0x00981ABE
static const uint8 tile_element_lower_styles[5][32] = {
{ 0x2E, 0x00, 0x2E, 0x02, 0x3E, 0x04, 0x3E, 0x06, 0x2E, 0x08, 0x2E, 0x0A, 0x3E, 0x0C, 0x3E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x06, 0x18, 0x19, 0x1A, 0x0B, 0x1C, 0x0C, 0x3E, 0x1F },
{ 0x2D, 0x2D, 0x00, 0x01, 0x2D, 0x2D, 0x04, 0x05, 0x3D, 0x3D, 0x08, 0x09, 0x3D, 0x3D, 0x0C, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x07, 0x18, 0x19, 0x1A, 0x09, 0x1C, 0x3D, 0x0C, 0x1F },
{ 0x2B, 0x3B, 0x2B, 0x3B, 0x00, 0x01, 0x02, 0x03, 0x2B, 0x3B, 0x2B, 0x3B, 0x08, 0x09, 0x0A, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x03, 0x18, 0x19, 0x1A, 0x3B, 0x1C, 0x09, 0x0E, 0x1F },
{ 0x27, 0x27, 0x37, 0x37, 0x27, 0x27, 0x37, 0x37, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x37, 0x18, 0x19, 0x1A, 0x03, 0x1C, 0x0D, 0x06, 0x1F },
{ 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x0D, 0x0E, 0x00 },
};
/**
*
* rct2: 0x00663CB9
*/
static sint32 map_set_land_height_clear_func(rct_tile_element** tile_element, sint32 x, sint32 y, uint8 flags, money32* price) {
if (tile_element_get_type(*tile_element) == TILE_ELEMENT_TYPE_SURFACE)
return 0;
if (tile_element_get_type(*tile_element) == TILE_ELEMENT_TYPE_SMALL_SCENERY)
return 0;
return 1;
}
static sint32 map_get_corner_height(sint32 z, sint32 slope, sint32 direction)
{
switch (direction) {
case 0:
if (slope & TILE_ELEMENT_SLOPE_N_CORNER_UP) {
z += 2;
if (slope == (TILE_ELEMENT_SLOPE_S_CORNER_DN | TILE_ELEMENT_SLOPE_DOUBLE_HEIGHT)) {
z += 2;
}
}
break;
case 1:
if (slope & TILE_ELEMENT_SLOPE_E_CORNER_UP) {
z += 2;
if (slope == (TILE_ELEMENT_SLOPE_W_CORNER_DN | TILE_ELEMENT_SLOPE_DOUBLE_HEIGHT)) {
z += 2;
}
}
break;
case 2:
if (slope & TILE_ELEMENT_SLOPE_S_CORNER_UP) {
z += 2;
if (slope == (TILE_ELEMENT_SLOPE_N_CORNER_DN | TILE_ELEMENT_SLOPE_DOUBLE_HEIGHT)) {
z += 2;
}
}
break;
case 3:
if (slope & TILE_ELEMENT_SLOPE_W_CORNER_UP) {
z += 2;
if (slope == (TILE_ELEMENT_SLOPE_E_CORNER_DN | TILE_ELEMENT_SLOPE_DOUBLE_HEIGHT)) {
z += 2;
}
}
break;
}
return z;
}
static sint32 tile_element_get_corner_height(rct_tile_element *tileElement, sint32 direction)
{
sint32 z = tileElement->base_height;
sint32 slope = tileElement->properties.surface.slope & TILE_ELEMENT_SURFACE_SLOPE_MASK;
return map_get_corner_height(z, slope, direction);
}
static money32 map_set_land_height(sint32 flags, sint32 x, sint32 y, sint32 height, sint32 style, sint32 selectionType)
{
rct_tile_element *tileElement;
if (game_is_paused() && !gCheatsBuildInPauseMode) {
gGameCommandErrorText = STR_CONSTRUCTION_NOT_POSSIBLE_WHILE_GAME_IS_PAUSED;
return MONEY32_UNDEFINED;
}
if (!(gScreenFlags & SCREEN_FLAGS_SCENARIO_EDITOR) && !gCheatsSandboxMode) {
if (gParkFlags & PARK_FLAGS_FORBID_LANDSCAPE_CHANGES) {
gGameCommandErrorText = STR_FORBIDDEN_BY_THE_LOCAL_AUTHORITY;
return MONEY32_UNDEFINED;
}
}
if (x > gMapSizeMaxXY || y > gMapSizeMaxXY) {
gGameCommandErrorText = STR_OFF_EDGE_OF_MAP;
return MONEY32_UNDEFINED;
}
if (height < MINIMUM_LAND_HEIGHT) {
gGameCommandErrorText = STR_TOO_LOW;
return MONEY32_UNDEFINED;
}
// Divide by 2 and subtract 7 to get the in-game units.
if (height > MAXIMUM_LAND_HEIGHT) {
gGameCommandErrorText = STR_TOO_HIGH;
return MONEY32_UNDEFINED;
} else if (height > MAXIMUM_LAND_HEIGHT - 2 && (style & 0x1F) != 0) {
gGameCommandErrorText = STR_TOO_HIGH;
return MONEY32_UNDEFINED;
}
if (height == MAXIMUM_LAND_HEIGHT - 2 && (style & 0x10)) {
gGameCommandErrorText = STR_TOO_HIGH;
return MONEY32_UNDEFINED;
}
if (!(gScreenFlags & SCREEN_FLAGS_SCENARIO_EDITOR) && !gCheatsSandboxMode) {
if (!map_is_location_in_park(x, y)) {
return MONEY32_UNDEFINED;
}
}
money32 cost = MONEY(0, 0);
if(flags & GAME_COMMAND_FLAG_APPLY)
{
footpath_remove_litter(x, y, tile_element_height(x, y));
if(!gCheatsDisableClearanceChecks)
wall_remove_at(x, y, height * 8 - 16, height * 8 + 32);
}
if (!gCheatsDisableClearanceChecks) {
//Check for obstructing scenery
tileElement = map_get_first_element_at(x / 32, y / 32);
do {
if (tile_element_get_type(tileElement) != TILE_ELEMENT_TYPE_SMALL_SCENERY)
continue;
if (height > tileElement->clearance_height)
continue;
if (height + 4 < tileElement->base_height)
continue;
rct_scenery_entry *sceneryEntry = get_small_scenery_entry(tileElement->properties.scenery.type);
if (sceneryEntry->small_scenery.height > 64 && gParkFlags & PARK_FLAGS_FORBID_TREE_REMOVAL)
{
map_obstruction_set_error_text(tileElement);
return MONEY32_UNDEFINED;
}
cost += MONEY(sceneryEntry->small_scenery.removal_price, 0);
if (flags & GAME_COMMAND_FLAG_APPLY)
tile_element_remove(tileElement--);
} while (!tile_element_is_last_for_tile(tileElement++));
}
// Check for ride support limits
if (!gCheatsDisableSupportLimits)
{
tileElement = map_get_first_element_at(x / 32, y / 32);
do
{
if (tile_element_get_type(tileElement) == TILE_ELEMENT_TYPE_TRACK)
{
sint32 rideIndex = track_element_get_ride_index(tileElement);
Ride * ride = get_ride(rideIndex);
if (ride != nullptr)
{
rct_ride_entry * rideEntry = get_ride_entry_by_ride(ride);
if (rideEntry != nullptr)
{
sint32 maxHeight = rideEntry->max_height;
if (maxHeight == 0)
{
maxHeight = RideData5[get_ride(rideIndex)->type].max_height;
}
sint32 zDelta = tileElement->clearance_height - height;
if (zDelta >= 0 && zDelta / 2 > maxHeight)
{
gGameCommandErrorText = STR_SUPPORTS_CANT_BE_EXTENDED;
return MONEY32_UNDEFINED;
}
}
}
}
}
while(!tile_element_is_last_for_tile(tileElement++));
}
uint8 zCorner = height; //z position of highest corner of tile
rct_tile_element *surfaceElement = map_get_surface_element_at(x / 32, y / 32);
if(surfaceElement->type & TILE_ELEMENT_TYPE_FLAG_HIGHLIGHT)
{
sint32 waterHeight = map_get_water_height(surfaceElement);
if(waterHeight != 0)
{
if(style & 0x1F)
{
zCorner += 2;
if(style & 0x10)
{
zCorner += 2;
}
}
if(zCorner > waterHeight * 2 - 2)
{
surfaceElement++;
map_obstruction_set_error_text(surfaceElement);
return MONEY32_UNDEFINED;
}
}
}
zCorner = height;
if(style & 0xF)
{
zCorner += 2;
if(style & 0x10)
{
zCorner += 2;
}
}
if (!gCheatsDisableClearanceChecks) {
if (!map_can_construct_with_clear_at(x, y, height, zCorner, &map_set_land_height_clear_func, 0xF, 0, nullptr, CREATE_CROSSING_MODE_NONE)) {
return MONEY32_UNDEFINED;
}
}
if (!gCheatsDisableClearanceChecks) {
tileElement = map_get_first_element_at(x / 32, y / 32);
do {
sint32 elementType = tile_element_get_type(tileElement);
if (elementType == TILE_ELEMENT_TYPE_WALL)
continue;
if (elementType == TILE_ELEMENT_TYPE_SMALL_SCENERY)
continue;
if (tileElement->flags & 0x10)
continue;
if (tileElement == surfaceElement)
continue;
if (tileElement > surfaceElement)
{
if (zCorner > tileElement->base_height)
{
map_obstruction_set_error_text(tileElement);
return MONEY32_UNDEFINED;
}
continue;
}
if (height < tileElement->clearance_height)
{
map_obstruction_set_error_text(tileElement);
return MONEY32_UNDEFINED;
}
} while (!tile_element_is_last_for_tile(tileElement++));
}
for (sint32 i = 0; i < 4; i += 1) {
sint32 cornerHeight = tile_element_get_corner_height(surfaceElement, i);
cornerHeight -= map_get_corner_height(height, style & TILE_ELEMENT_SURFACE_SLOPE_MASK, i);
cost += MONEY(abs(cornerHeight) * 5 / 2, 0);
}
if(flags & GAME_COMMAND_FLAG_APPLY)
{
if (gGameCommandNestLevel == 1) {
LocationXYZ16 coord;
coord.x = x + 16;
coord.y = 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);
}
surfaceElement = map_get_surface_element_at(x / 32, y / 32);
surfaceElement->base_height = height;
surfaceElement->clearance_height = height;
surfaceElement->properties.surface.slope &= TILE_ELEMENT_SURFACE_EDGE_STYLE_MASK;
surfaceElement->properties.surface.slope |= style;
sint32 slope = surfaceElement->properties.surface.terrain & TILE_ELEMENT_SURFACE_SLOPE_MASK;
if(slope != TILE_ELEMENT_SLOPE_FLAT && slope <= height / 2)
surfaceElement->properties.surface.terrain &= TILE_ELEMENT_SURFACE_TERRAIN_MASK;
map_invalidate_tile_full(x, y);
}
if(gParkFlags & PARK_FLAGS_NO_MONEY)
return 0;
return cost;
}
void game_command_set_land_height(sint32 *eax, sint32 *ebx, sint32 *ecx, sint32 *edx, sint32 *esi, sint32 *edi, sint32 *ebp)
{
*ebx = map_set_land_height(
*ebx & 0xFF,
*eax & 0xFFFF,
*ecx & 0xFFFF,
*edx & 0xFF,
(*edx >> 8) & 0xFF,
*edi >> 5
);
}
static money32 map_set_land_ownership(uint8 flags, sint16 x1, sint16 y1, sint16 x2, sint16 y2, uint8 newOwnership) {
gCommandExpenditureType = RCT_EXPENDITURE_TYPE_LAND_PURCHASE;
if (!(flags & GAME_COMMAND_FLAG_APPLY))
return 0;
// Clamp to maximum addressable element to prevent long loop spamming the log
x1 = Math::Clamp(32, (sint32)x1, gMapSizeUnits - 32);
y1 = Math::Clamp(32, (sint32)y1, gMapSizeUnits - 32);
x2 = Math::Clamp(32, (sint32)x2, gMapSizeUnits - 32);
y2 = Math::Clamp(32, (sint32)y2, gMapSizeUnits - 32);
gMapLandRightsUpdateSuccess = false;
map_buy_land_rights(x1, y1, x2, y2, BUY_LAND_RIGHTS_FLAG_SET_OWNERSHIP_WITH_CHECKS, flags | (newOwnership << 8));
if (!gMapLandRightsUpdateSuccess)
return 0;
sint16 x = Math::Clamp(32, (sint32)x1, gMapSizeUnits - 32);
sint16 y = Math::Clamp(32, (sint32)y1, gMapSizeUnits - 32);
x += 16;
y += 16;
sint16 z = tile_element_height(x, y) & 0xFFFF;
audio_play_sound_at_location(SOUND_PLACE_ITEM, x, y, z);
return 0;
}
/**
*
* rct2: 0x006648E3
*/
void game_command_set_land_ownership(sint32 *eax, sint32 *ebx, sint32 *ecx, sint32 *edx, sint32 *esi, sint32 *edi, sint32 *ebp)
{
sint32 flags = *ebx & 0xFF;
*ebx = map_set_land_ownership(
flags,
*eax & 0xFFFF,
*ecx & 0xFFFF,
*edi & 0xFFFF,
*ebp & 0xFFFF,
*edx & 0xFF
);
if (flags & GAME_COMMAND_FLAG_APPLY) {
map_count_remaining_land_rights();
}
}
static uint8 map_get_lowest_land_height(sint32 xMin, sint32 xMax, sint32 yMin, sint32 yMax)
{
xMin = std::max(xMin, 32);
yMin = std::max(yMin, 32);
xMax = std::min(xMax, (sint32)gMapSizeMaxXY);
yMax = std::min(yMax, (sint32)gMapSizeMaxXY);
uint8 min_height = 0xFF;
for (sint32 yi = yMin; yi <= yMax; yi += 32) {
for (sint32 xi = xMin; xi <= xMax; xi += 32) {
rct_tile_element *tile_element = map_get_surface_element_at(xi / 32, yi / 32);
if (tile_element != nullptr && min_height > tile_element->base_height) {
min_height = tile_element->base_height;
}
}
}
return min_height;
}
static uint8 map_get_highest_land_height(sint32 xMin, sint32 xMax, sint32 yMin, sint32 yMax)
{
xMin = std::max(xMin, 32);
yMin = std::max(yMin, 32);
xMax = std::min(xMax, (sint32)gMapSizeMaxXY);
yMax = std::min(yMax, (sint32)gMapSizeMaxXY);
uint8 max_height = 0;
for (sint32 yi = yMin; yi <= yMax; yi += 32) {
for (sint32 xi = xMin; xi <= xMax; xi += 32) {
rct_tile_element *tile_element = map_get_surface_element_at(xi / 32, yi / 32);
if (tile_element != nullptr) {
uint8 base_height = tile_element->base_height;
if (tile_element->properties.surface.slope & TILE_ELEMENT_SLOPE_ALL_CORNERS_UP)
base_height += 2;
if (tile_element->properties.surface.slope & TILE_ELEMENT_SLOPE_DOUBLE_HEIGHT)
base_height += 2;
if (max_height < base_height)
max_height = base_height;
}
}
}
return max_height;
}
static money32 raise_land(sint32 flags, sint32 x, sint32 y, sint32 z, sint32 ax, sint32 ay, sint32 bx, sint32 by, sint32 selectionType)
{
money32 cost = 0;
if (selectionType < 0 || selectionType >= (sint32)Util::CountOf(tile_element_raise_styles))
{
log_warning("Invalid selection type %d for raising land", selectionType);
return MONEY32_UNDEFINED;
}
if ((flags & GAME_COMMAND_FLAG_APPLY) && gGameCommandNestLevel == 1) {
audio_play_sound_at_location(SOUND_PLACE_ITEM, x, y, z);
}
uint8 min_height = map_get_lowest_land_height(ax, bx, ay, by);
for (sint32 yi = ay; yi <= by; yi += 32) {
for (sint32 xi = ax; xi <= bx; xi += 32) {
rct_tile_element *tile_element = map_get_surface_element_at(xi / 32, yi / 32);
if (tile_element != nullptr) {
uint8 height = tile_element->base_height;
if (height <= min_height){
uint8 newStyle = tile_element_raise_styles[selectionType][tile_element->properties.surface.slope & TILE_ELEMENT_SURFACE_SLOPE_MASK];
if (newStyle & 0x20) { // needs to be raised
height += 2;
newStyle &= ~0x20;
}
money32 tileCost = map_set_land_height(flags, xi, yi, height, newStyle, selectionType);
if (tileCost == MONEY32_UNDEFINED)
return MONEY32_UNDEFINED;
cost += tileCost;
}
}
}
}
// Force ride construction to recheck area
_currentTrackSelectionFlags |= TRACK_SELECTION_FLAG_RECHECK;
gCommandExpenditureType = RCT_EXPENDITURE_TYPE_LANDSCAPING;
gCommandPosition.x = x;
gCommandPosition.y = y;
gCommandPosition.z = z;
return cost;
}
static money32 lower_land(sint32 flags, sint32 x, sint32 y, sint32 z, sint32 ax, sint32 ay, sint32 bx, sint32 by, sint32 selectionType)
{
money32 cost = 0;
if ((flags & GAME_COMMAND_FLAG_APPLY) && gGameCommandNestLevel == 1) {
audio_play_sound_at_location(SOUND_PLACE_ITEM, x, y, z);
}
if (selectionType < 0 || selectionType >= 5)
{
log_warning("Improper selection type %d", selectionType);
return MONEY32_UNDEFINED;
}
uint8 max_height = map_get_highest_land_height(ax, bx, ay, by);
for (sint32 yi = ay; yi <= by; yi += 32) {
for (sint32 xi = ax; xi <= bx; xi += 32) {
rct_tile_element *tile_element = map_get_surface_element_at(xi / 32, yi / 32);
if (tile_element != nullptr) {
uint8 height = tile_element->base_height;
if (tile_element->properties.surface.slope & TILE_ELEMENT_SLOPE_ALL_CORNERS_UP)
height += 2;
if (tile_element->properties.surface.slope & TILE_ELEMENT_SLOPE_DOUBLE_HEIGHT)
height += 2;
if (height >= max_height) {
height = tile_element->base_height;
uint8 newStyle = tile_element_lower_styles[selectionType][tile_element->properties.surface.slope & TILE_ELEMENT_SURFACE_SLOPE_MASK];
if (newStyle & 0x20) { // needs to be lowered
height -= 2;
newStyle &= ~0x20;
}
money32 tileCost = map_set_land_height(flags, xi, yi, height, newStyle, selectionType);
if (tileCost == MONEY32_UNDEFINED)
return MONEY32_UNDEFINED;
cost += tileCost;
}
}
}
}
// Force ride construction to recheck area
_currentTrackSelectionFlags |= TRACK_SELECTION_FLAG_RECHECK;
gCommandExpenditureType = RCT_EXPENDITURE_TYPE_LANDSCAPING;
gCommandPosition.x = x;
gCommandPosition.y = y;
gCommandPosition.z = z;
return cost;
}
sint32 map_get_water_height(const rct_tile_element * tileElement)
{
return tileElement->properties.surface.terrain & TILE_ELEMENT_SURFACE_WATER_HEIGHT_MASK;
}
money32 raise_water(sint16 x0, sint16 y0, sint16 x1, sint16 y1, uint8 flags)
{
money32 cost = 0;
bool waterHeightChanged = false;
uint8 max_height = 0xFF;
x0 = std::max(x0, (sint16)32);
y0 = std::max(y0, (sint16)32);
x1 = std::min(x1, gMapSizeMaxXY);
y1 = std::min(y1, gMapSizeMaxXY);
for (sint32 yi = y0; yi <= y1; yi += 32) {
for (sint32 xi = x0; xi <= x1; xi += 32) {
rct_tile_element* tile_element = map_get_surface_element_at(xi / 32, yi / 32);
if (tile_element != nullptr) {
uint8 height = tile_element->base_height;
if (map_get_water_height(tile_element) > 0)
height = map_get_water_height(tile_element) * 2;
if (max_height > height)
max_height = height;
}
}
}
for (sint32 yi = y0; yi <= y1; yi += 32) {
for (sint32 xi = x0; xi <= x1; xi += 32) {
rct_tile_element* tile_element = map_get_surface_element_at(xi / 32, yi / 32);
if (tile_element != nullptr) {
if (tile_element->base_height <= max_height){
uint8 height = map_get_water_height(tile_element);
if (height != 0) {
height *= 2;
if (height > max_height)
continue;
height += 2;
} else {
height = tile_element->base_height + 2;
}
money32 tileCost = game_do_command(xi, flags, yi, (max_height << 8) + height, GAME_COMMAND_SET_WATER_HEIGHT, 0, 0);
if (tileCost == MONEY32_UNDEFINED)
return MONEY32_UNDEFINED;
cost += tileCost;
waterHeightChanged = true;
}
}
}
}
if (flags & GAME_COMMAND_FLAG_APPLY) {
sint32 x = ((x0 + x1) / 2) + 16;
sint32 y = ((y0 + y1) / 2) + 16;
sint32 z = tile_element_height(x, y);
sint16 water_height_z = z >> 16;
sint16 base_height_z = z;
z = water_height_z;
if (z != 0)
z = base_height_z;
LocationXYZ16 coord;
coord.x = x;
coord.y = y;
coord.z = z;
network_set_player_last_action_coord(network_get_player_index(game_command_playerid), coord);
gCommandPosition.x = x;
gCommandPosition.y = y;
gCommandPosition.z = z;
if (waterHeightChanged) {
audio_play_sound_at_location(SOUND_LAYING_OUT_WATER, x, y, z);
}
}
// Force ride construction to recheck area
_currentTrackSelectionFlags |= TRACK_SELECTION_FLAG_RECHECK;
return cost;
}
money32 lower_water(sint16 x0, sint16 y0, sint16 x1, sint16 y1, uint8 flags)
{
money32 cost = 0;
bool waterHeightChanged = false;
uint8 min_height = 0;
x0 = std::max(x0, (sint16)32);
y0 = std::max(y0, (sint16)32);
x1 = std::min(x1, gMapSizeMaxXY);
y1 = std::min(y1, gMapSizeMaxXY);
for (sint32 yi = y0; yi <= y1; yi += 32){
for (sint32 xi = x0; xi <= x1; xi += 32){
rct_tile_element* tile_element = map_get_surface_element_at(xi / 32, yi / 32);
if (tile_element != nullptr) {
uint8 height = map_get_water_height(tile_element);
if (height != 0) {
height *= 2;
if (height > min_height)
min_height = height;
}
}
}
}
for (sint32 yi = y0; yi <= y1; yi += 32) {
for (sint32 xi = x0; xi <= x1; xi += 32) {
rct_tile_element* tile_element = map_get_surface_element_at(xi / 32, yi / 32);
if (tile_element != nullptr) {
uint8 height = map_get_water_height(tile_element);
if (height != 0) {
height *= 2;
if (height < min_height)
continue;
height -= 2;
sint32 tileCost = game_do_command(xi, flags, yi, (min_height << 8) + height, GAME_COMMAND_SET_WATER_HEIGHT, 0, 0);
if (tileCost == MONEY32_UNDEFINED)
return MONEY32_UNDEFINED;
cost += tileCost;
waterHeightChanged = true;
}
}
}
}
if (flags & GAME_COMMAND_FLAG_APPLY) {
sint32 x = ((x0 + x1) / 2) + 16;
sint32 y = ((y0 + y1) / 2) + 16;
sint32 z = tile_element_height(x, y);
sint16 water_height_z = z >> 16;
sint16 base_height_z = z;
z = water_height_z;
if (z == 0)
z = base_height_z;
LocationXYZ16 coord;
coord.x = x;
coord.y = y;
coord.z = z;
network_set_player_last_action_coord(network_get_player_index(game_command_playerid), coord);
gCommandPosition.x = x;
gCommandPosition.y = y;
gCommandPosition.z = z;
if (waterHeightChanged) {
audio_play_sound_at_location(SOUND_LAYING_OUT_WATER, x, y, z);
}
}
// Force ride construction to recheck area
_currentTrackSelectionFlags |= TRACK_SELECTION_FLAG_RECHECK;
return cost;
}
/**
*
* rct2: 0x0068C542
*/
void game_command_raise_land(sint32* eax, sint32* ebx, sint32* ecx, sint32* edx, sint32* esi, sint32* edi, sint32* ebp)
{
*ebx = raise_land(
*ebx,
*eax,
*ecx,
tile_element_height(*eax, *ecx),
(sint16)(*edx & 0xFFFF),
(sint16)(*ebp & 0xFFFF),
*edx >> 16,
*ebp >> 16,
*edi & 0xFFFF
);
}
/**
*
* rct2: 0x0068C6D1
*/
void game_command_lower_land(sint32* eax, sint32* ebx, sint32* ecx, sint32* edx, sint32* esi, sint32* edi, sint32* ebp)
{
*ebx = lower_land(
*ebx,
*eax,
*ecx,
tile_element_height(*eax, *ecx),
(sint16)(*edx & 0xFFFF),
(sint16)(*ebp & 0xFFFF),
*edx >> 16,
*ebp >> 16,
*edi & 0xFFFF
);
}
static money32 smooth_land_tile(sint32 direction, uint8 flags, sint32 x, sint32 y, rct_tile_element * tileElement, bool raiseLand)
{
sint32 targetBaseZ = tileElement->base_height;
sint32 slope = tileElement->properties.surface.slope & TILE_ELEMENT_SURFACE_SLOPE_MASK;
if (raiseLand) {
slope = tile_element_raise_styles[direction][slope];
if (slope & 0x20) {
targetBaseZ += 2;
slope &= ~0x20;
}
}
else {
slope = tile_element_lower_styles[direction][slope];
if (slope & 0x20) {
targetBaseZ -= 2;
slope &= ~0x20;
}
}
return game_do_command(x, flags, y, targetBaseZ | (slope << 8), GAME_COMMAND_SET_LAND_HEIGHT, 0, 0);
}
static money32 smooth_land_row_by_edge(sint32 flags, sint32 x, sint32 y, sint32 expectedLandHeight1, sint32 expectedLandHeight2, sint32 stepX, sint32 stepY, sint32 direction1, sint32 direction2, sint32 checkDirection1, sint32 checkDirection2, bool raiseLand)
{
uint8 shouldContinue = 0xF;
sint32 landChangePerTile = raiseLand ? -2 : 2;
rct_tile_element *tileElement, *nextTileElement;
money32 totalCost = 0;
money32 result;
// check if we need to start at all
if (!map_is_location_valid(x, y) || !map_is_location_valid(x + stepX, y + stepY)) {
return 0;
}
tileElement = map_get_surface_element_at(x >> 5, y >> 5);
nextTileElement = map_get_surface_element_at((x + stepX) >> 5, (y + stepY) >> 5);
if (tileElement == nullptr || nextTileElement == nullptr) {
return 0;
}
if (tile_element_get_corner_height(tileElement, checkDirection1) != expectedLandHeight1 + (raiseLand ? -2 : 2)) {
shouldContinue &= ~0x1;
}
if (tile_element_get_corner_height(tileElement, checkDirection2) != expectedLandHeight2 + (raiseLand ? -2 : 2)) {
shouldContinue &= ~0x2;
}
if (tile_element_get_corner_height(tileElement, checkDirection1) != tile_element_get_corner_height(nextTileElement, direction1)) {
shouldContinue &= ~0x1;
}
if (tile_element_get_corner_height(tileElement, checkDirection2) != tile_element_get_corner_height(nextTileElement, direction2)) {
shouldContinue &= ~0x2;
}
while ((shouldContinue & 0x3) != 0)
{
shouldContinue = ((shouldContinue << 2) | 0x3) & shouldContinue;
x += stepX;
y += stepY;
// check if we need to continue after raising the current tile
// this needs to be checked before the tile is changed
if (!map_is_location_valid(x + stepX, y + stepY)) {
shouldContinue &= ~0x3;
}
else
{
tileElement = nextTileElement;
nextTileElement = map_get_surface_element_at((x + stepX) >> 5, (y + stepY) >> 5);
if (nextTileElement == nullptr) {
shouldContinue &= ~0x3;
}
if (tile_element_get_corner_height(tileElement, direction1) + landChangePerTile != tile_element_get_corner_height(tileElement, checkDirection1)) {
shouldContinue &= ~0x1;
}
if (tile_element_get_corner_height(tileElement, direction2) + landChangePerTile != tile_element_get_corner_height(tileElement, checkDirection2)) {
shouldContinue &= ~0x2;
}
if ((shouldContinue & 0x1) && tile_element_get_corner_height(tileElement, checkDirection1) != tile_element_get_corner_height(nextTileElement, direction1)) {
shouldContinue &= ~0x1;
}
if ((shouldContinue & 0x2) && tile_element_get_corner_height(tileElement, checkDirection2) != tile_element_get_corner_height(nextTileElement, direction2)) {
shouldContinue &= ~0x2;
}
}
expectedLandHeight1 += landChangePerTile;
// change land of current tile
sint32 targetBaseZ = tileElement->base_height;
sint32 slope = tileElement->properties.surface.slope & TILE_ELEMENT_SURFACE_SLOPE_MASK;
sint32 oldSlope = slope;
if (raiseLand) {
if (shouldContinue & 0x4) {
slope = tile_element_raise_styles[direction1][slope];
if (slope & 0x20) {
targetBaseZ += 2;
slope &= ~0x20;
}
}
if ((shouldContinue & 0x8) &&
map_get_corner_height(tileElement->base_height, oldSlope, direction2) ==
map_get_corner_height(targetBaseZ, slope, direction2))
{
slope = tile_element_raise_styles[direction2][slope];
if (slope & 0x20) {
targetBaseZ += 2;
slope &= ~0x20;
}
}
}
else
{
if (shouldContinue & 0x4) {
slope = tile_element_lower_styles[direction1][slope];
if (slope & 0x20) {
targetBaseZ -= 2;
slope &= ~0x20;
}
}
if ((shouldContinue & 0x8) &&
map_get_corner_height(tileElement->base_height, oldSlope, direction2) ==
map_get_corner_height(targetBaseZ, slope, direction2))
{
slope = tile_element_lower_styles[direction2][slope];
if (slope & 0x20) {
targetBaseZ -= 2;
slope &= ~0x20;
}
}
}
result = game_do_command(x, flags, y, targetBaseZ | (slope << 8), GAME_COMMAND_SET_LAND_HEIGHT, 0, 0);
if (result != MONEY32_UNDEFINED) {
totalCost += result;
}
}
return totalCost;
}
static money32 smooth_land_row_by_corner(sint32 flags, sint32 x, sint32 y, sint32 expectedLandHeight, sint32 stepX, sint32 stepY, sint32 direction, sint32 checkDirection, bool raiseLand)
{
bool shouldContinue = true;
rct_tile_element *tileElement, *nextTileElement;
money32 totalCost = 0;
money32 result;
sint32 landChangePerTile;
if (stepX == 0 || stepY == 0)
{
landChangePerTile = raiseLand ? -2 : 2;
}
else
{
landChangePerTile = raiseLand ? -4 : 4;
}
// check if we need to start at all
if (!map_is_location_valid(x, y) || !map_is_location_valid(x + stepX, y + stepY)) {
return 0;
}
tileElement = map_get_surface_element_at(x >> 5, y >> 5);
nextTileElement = map_get_surface_element_at((x + stepX) >> 5, (y + stepY) >> 5);
if (tileElement == nullptr || nextTileElement == nullptr) {
return 0;
}
if (tile_element_get_corner_height(tileElement, checkDirection) != expectedLandHeight + (raiseLand ? -2 : 2)) {
return 0;
}
if (tile_element_get_corner_height(tileElement, checkDirection) != tile_element_get_corner_height(nextTileElement, direction)) {
return 0;
}
while (shouldContinue)
{
x += stepX;
y += stepY;
// check if we need to continue after raising the current tile
// this needs to be checked before the tile is changed
if (!map_is_location_valid(x + stepX, y + stepY)) {
shouldContinue = false;
}
else
{
tileElement = nextTileElement;
nextTileElement = map_get_surface_element_at((x + stepX) >> 5, (y + stepY) >> 5);
if (nextTileElement == nullptr) {
shouldContinue = false;
}
if (tile_element_get_corner_height(tileElement, direction) + landChangePerTile != tile_element_get_corner_height(tileElement, checkDirection)) {
shouldContinue = false;
}
if (shouldContinue && tile_element_get_corner_height(tileElement, checkDirection) != tile_element_get_corner_height(nextTileElement, direction)) {
shouldContinue = false;
}
}
if (stepX*stepY != 0)
{
totalCost += smooth_land_row_by_corner(flags, x, y, expectedLandHeight + (landChangePerTile / 2), 0, stepY, direction, checkDirection ^ 3, raiseLand);
totalCost += smooth_land_row_by_corner(flags, x, y, expectedLandHeight + (landChangePerTile / 2), stepX, 0, direction, checkDirection ^ 1, raiseLand);
}
expectedLandHeight += landChangePerTile;
// change land of current tile
result = smooth_land_tile(direction, flags, x, y, tileElement, raiseLand);
if (result != MONEY32_UNDEFINED) {
totalCost += result;
}
}
return totalCost;
}
static money32 smooth_land(sint32 flags, sint32 centreX, sint32 centreY, sint32 mapLeft, sint32 mapTop, sint32 mapRight, sint32 mapBottom, sint32 command)
{
// Cap bounds to map
mapLeft = std::max(mapLeft, 32);
mapTop = std::max(mapTop, 32);
mapRight = Math::Clamp(0, mapRight, (MAXIMUM_MAP_SIZE_TECHNICAL - 1) * 32);
mapBottom = Math::Clamp(0, mapBottom, (MAXIMUM_MAP_SIZE_TECHNICAL - 1) * 32);
bool raiseLand = command < 0x7FFF;
sint32 commandType = raiseLand ? GAME_COMMAND_RAISE_LAND : GAME_COMMAND_LOWER_LAND;
sint32 centreZ = tile_element_height(centreX, centreY);
sint32 mapLeftRight = mapLeft | (mapRight << 16);
sint32 mapTopBottom = mapTop | (mapBottom << 16);
bool fullTile = ((command & 0x7FFF) == MAP_SELECT_TYPE_FULL);
// Play sound (only once)
if ((flags & GAME_COMMAND_FLAG_APPLY) && gGameCommandNestLevel == 1) {
audio_play_sound_at_location(SOUND_PLACE_ITEM, centreX, centreY, centreZ);
}
money32 totalCost = 0;
money32 result;
rct_tile_element *tileElement = map_get_surface_element_at(mapLeft >> 5, mapTop >> 5);
if (tileElement == nullptr)
{
log_warning("Invalid coordinates for land smoothing, x = %d, y = %d", mapLeft, mapTop);
return MONEY32_UNDEFINED;
}
if (flags & GAME_COMMAND_FLAG_APPLY) {
LocationXYZ16 coord;
coord.x = centreX + 16;
coord.y = centreY + 16;
coord.z = tile_element_height(coord.x, coord.y);
network_set_player_last_action_coord(network_get_player_index(game_command_playerid), coord);
}
uint8 maxHeight = 0;
uint8 minHeight = 0xFF;
uint32 newBaseZ = 0;
uint32 newSlope = 0;
// Predict the land height for future use
if (fullTile)
{
minHeight = map_get_lowest_land_height(mapLeft, mapRight, mapTop, mapBottom);
maxHeight = map_get_highest_land_height(mapLeft, mapRight, mapTop, mapBottom);
if (commandType == GAME_COMMAND_RAISE_LAND) {
minHeight += 2;
maxHeight += 2;
}
else {
maxHeight -= 2;
minHeight -= 2;
}
}
else
{
// One corner tile selected
newBaseZ = tileElement->base_height;
newSlope = tileElement->properties.surface.slope & TILE_ELEMENT_SURFACE_SLOPE_MASK;
if (commandType == GAME_COMMAND_RAISE_LAND) {
newSlope = tile_element_raise_styles[command & 0xFF][newSlope];
if (newSlope & 0x20) {
newBaseZ += 2;
newSlope &= ~0x20;
}
}
else {
newSlope = tile_element_lower_styles[command & 0xFF][newSlope];
if (newSlope & 0x20) {
newBaseZ -= 2;
newSlope &= ~0x20;
}
}
}
// Then do the smoothing
if (fullTile) {
// Smooth the corners
sint32 z = Math::Clamp(minHeight, (uint8)tile_element_get_corner_height(tileElement, 2), maxHeight);
totalCost += smooth_land_row_by_corner(flags, mapLeft, mapTop, z, -32, -32, 0, 2, raiseLand);
tileElement = map_get_surface_element_at(mapLeft >> 5, mapBottom >> 5);
z = Math::Clamp(minHeight, (uint8)tile_element_get_corner_height(tileElement, 3), maxHeight);
totalCost += smooth_land_row_by_corner(flags, mapLeft, mapBottom, z, -32, 32, 1, 3, raiseLand);
tileElement = map_get_surface_element_at(mapRight >> 5, mapBottom >> 5);
z = Math::Clamp(minHeight, (uint8)tile_element_get_corner_height(tileElement, 0), maxHeight);
totalCost += smooth_land_row_by_corner(flags, mapRight, mapBottom, z, 32, 32, 2, 0, raiseLand);
tileElement = map_get_surface_element_at(mapRight >> 5, mapTop >> 5);
z = Math::Clamp(minHeight, (uint8)tile_element_get_corner_height(tileElement, 1), maxHeight);
totalCost += smooth_land_row_by_corner(flags, mapRight, mapTop, z, 32, -32, 3, 1, raiseLand);
// Smooth the edges
sint32 x = mapLeft;
sint32 y, z2;
for (y = mapTop; y <= mapBottom; y += 32)
{
tileElement = map_get_surface_element_at(x >> 5, y >> 5);
z = Math::Clamp(minHeight, (uint8)tile_element_get_corner_height(tileElement, 3), maxHeight);
z2 = Math::Clamp(minHeight, (uint8)tile_element_get_corner_height(tileElement, 2), maxHeight);
totalCost += smooth_land_row_by_edge(flags, x, y, z, z2, -32, 0, 0, 1, 3, 2, raiseLand);
}
x = mapRight;
for (y = mapTop; y <= mapBottom; y += 32)
{
tileElement = map_get_surface_element_at(x >> 5, y >> 5);
z = Math::Clamp(minHeight, (uint8)tile_element_get_corner_height(tileElement, 1), maxHeight);
z2 = Math::Clamp(minHeight, (uint8)tile_element_get_corner_height(tileElement, 0), maxHeight);
totalCost += smooth_land_row_by_edge(flags, x, y, z, z2, 32, 0, 2, 3, 1, 0, raiseLand);
}
y = mapTop;
for (x = mapLeft; x <= mapRight; x += 32)
{
tileElement = map_get_surface_element_at(x >> 5, y >> 5);
z = Math::Clamp(minHeight, (uint8)tile_element_get_corner_height(tileElement, 1), maxHeight);
z2 = Math::Clamp(minHeight, (uint8)tile_element_get_corner_height(tileElement, 2), maxHeight);
totalCost += smooth_land_row_by_edge(flags, x, y, z, z2, 0, -32, 0, 3, 1, 2, raiseLand);
}
y = mapBottom;
for (x = mapLeft; x <= mapRight; x += 32)
{
tileElement = map_get_surface_element_at(x >> 5, y >> 5);
z = Math::Clamp(minHeight, (uint8)tile_element_get_corner_height(tileElement, 0), maxHeight);
z2 = Math::Clamp(minHeight, (uint8)tile_element_get_corner_height(tileElement, 3), maxHeight);
totalCost += smooth_land_row_by_edge(flags, x, y, z, z2, 0, 32, 1, 2, 0, 3, raiseLand);
}
}
else
{
// One corner tile selected
// Smooth the corners
sint32 z = map_get_corner_height(newBaseZ, newSlope, 2);
totalCost += smooth_land_row_by_corner(flags, mapLeft, mapTop, z, -32, -32, 0, 2, raiseLand);
z = map_get_corner_height(newBaseZ, newSlope, 0);
totalCost += smooth_land_row_by_corner(flags, mapLeft, mapTop, z, 32, 32, 2, 0, raiseLand);
z = map_get_corner_height(newBaseZ, newSlope, 3);
totalCost += smooth_land_row_by_corner(flags, mapLeft, mapTop, z, -32, 32, 1, 3, raiseLand);
z = map_get_corner_height(newBaseZ, newSlope, 1);
totalCost += smooth_land_row_by_corner(flags, mapLeft, mapTop, z, 32, -32, 3, 1, raiseLand);
// Smooth the edges
switch (command & 0x7FFF) {
case MAP_SELECT_TYPE_CORNER_0:
z = map_get_corner_height(newBaseZ, newSlope, 0);
totalCost += smooth_land_row_by_corner(flags, mapLeft, mapTop, z, 32, 0, 3, 0, raiseLand);
totalCost += smooth_land_row_by_corner(flags, mapLeft, mapTop, z, 0, 32, 1, 0, raiseLand);
z = map_get_corner_height(newBaseZ, newSlope, 3);
totalCost += smooth_land_row_by_corner(flags, mapLeft, mapTop, z, -32, 0, 0, 3, raiseLand);
z = map_get_corner_height(newBaseZ, newSlope, 1);
totalCost += smooth_land_row_by_corner(flags, mapLeft, mapTop, z, 0, -32, 0, 1, raiseLand);
break;
case MAP_SELECT_TYPE_CORNER_1:
z = map_get_corner_height(newBaseZ, newSlope, 1);
totalCost += smooth_land_row_by_corner(flags, mapLeft, mapTop, z, 32, 0, 2, 1, raiseLand);
totalCost += smooth_land_row_by_corner(flags, mapLeft, mapTop, z, 0, -32, 0, 1, raiseLand);
z = map_get_corner_height(newBaseZ, newSlope, 2);
totalCost += smooth_land_row_by_corner(flags, mapLeft, mapTop, z, -32, 0, 1, 2, raiseLand);
z = map_get_corner_height(newBaseZ, newSlope, 0);
totalCost += smooth_land_row_by_corner(flags, mapLeft, mapTop, z, 0, 32, 1, 0, raiseLand);
break;
case MAP_SELECT_TYPE_CORNER_2:
z = map_get_corner_height(newBaseZ, newSlope, 2);
totalCost += smooth_land_row_by_corner(flags, mapLeft, mapTop, z, -32, 0, 1, 2, raiseLand);
totalCost += smooth_land_row_by_corner(flags, mapLeft, mapTop, z, 0, -32, 3, 2, raiseLand);
z = map_get_corner_height(newBaseZ, newSlope, 1);
totalCost += smooth_land_row_by_corner(flags, mapLeft, mapTop, z, 32, 0, 2, 1, raiseLand);
z = map_get_corner_height(newBaseZ, newSlope, 3);
totalCost += smooth_land_row_by_corner(flags, mapLeft, mapTop, z, 0, 32, 2, 3, raiseLand);
break;
case MAP_SELECT_TYPE_CORNER_3:
z = map_get_corner_height(newBaseZ, newSlope, 3);
totalCost += smooth_land_row_by_corner(flags, mapLeft, mapTop, z, -32, 0, 0, 3, raiseLand);
totalCost += smooth_land_row_by_corner(flags, mapLeft, mapTop, z, 0, 32, 2, 3, raiseLand);
z = map_get_corner_height(newBaseZ, newSlope, 0);
totalCost += smooth_land_row_by_corner(flags, mapLeft, mapTop, z, 32, 0, 3, 0, raiseLand);
z = map_get_corner_height(newBaseZ, newSlope, 2);
totalCost += smooth_land_row_by_corner(flags, mapLeft, mapTop, z, 0, -32, 3, 2, raiseLand);
break;
}
}
// Finally raise / lower the centre tile
result = game_do_command(centreX, flags, centreY, mapLeftRight, commandType, command & 0x7FFF, mapTopBottom);
if (result != MONEY32_UNDEFINED) {
totalCost += result;
} else {
return MONEY32_UNDEFINED;
}
gCommandExpenditureType = RCT_EXPENDITURE_TYPE_LANDSCAPING;
gCommandPosition.x = centreX;
gCommandPosition.y = centreY;
gCommandPosition.z = centreZ;
return totalCost;
}
/**
*
* rct2: 0x0068BC01
*/
void game_command_smooth_land(sint32* eax, sint32* ebx, sint32* ecx, sint32* edx, sint32* esi, sint32* edi, sint32* ebp)
{
sint32 flags = *ebx & 0xFF;
sint32 centreX = *eax & 0xFFFF;
sint32 centreY = *ecx & 0xFFFF;
sint32 mapLeft = (sint16)(*edx & 0xFFFF);
sint32 mapTop = (sint16)(*ebp & 0xFFFF);
sint32 mapRight = (sint16)(*edx >> 16);
sint32 mapBottom = (sint16)(*ebp >> 16);
sint32 command = *edi;
*ebx = smooth_land(flags, centreX, centreY, mapLeft, mapTop, mapRight, mapBottom, command);
}
/**
*
* rct2: 0x006E66A0
*/
void game_command_raise_water(sint32* eax, sint32* ebx, sint32* ecx, sint32* edx, sint32* esi, sint32* edi, sint32* ebp)
{
*ebx = raise_water(
(sint16)(*eax & 0xFFFF),
(sint16)(*ecx & 0xFFFF),
(sint16)(*edi & 0xFFFF),
(sint16)(*ebp & 0xFFFF),
(uint8)*ebx
);
}
/**
*
* rct2: 0x006E6878
*/
void game_command_lower_water(sint32* eax, sint32* ebx, sint32* ecx, sint32* edx, sint32* esi, sint32* edi, sint32* ebp)
{
*ebx = lower_water(
(sint16)(*eax & 0xFFFF),
(sint16)(*ecx & 0xFFFF),
(sint16)(*edi & 0xFFFF),
(sint16)(*ebp & 0xFFFF),
(uint8)*ebx
);
}
/**
*
* rct2: 0x006E650F
*/
void game_command_set_water_height(sint32* eax, sint32* ebx, sint32* ecx, sint32* edx, sint32* esi, sint32* edi, sint32* ebp)
{
sint32 x = *eax;
sint32 y = *ecx;
uint8 base_height = *edx;
gCommandExpenditureType = RCT_EXPENDITURE_TYPE_LANDSCAPING;
gCommandPosition.x = x + 16;
gCommandPosition.y = y + 16;
gCommandPosition.z = base_height * 8;
if(game_is_paused() && !gCheatsBuildInPauseMode){
gGameCommandErrorText = STR_CONSTRUCTION_NOT_POSSIBLE_WHILE_GAME_IS_PAUSED;
*ebx = MONEY32_UNDEFINED;
return;
}
if(!(gScreenFlags & SCREEN_FLAGS_SCENARIO_EDITOR) && !gCheatsSandboxMode && gParkFlags & PARK_FLAGS_FORBID_LANDSCAPE_CHANGES){
gGameCommandErrorText = STR_FORBIDDEN_BY_THE_LOCAL_AUTHORITY;
*ebx = MONEY32_UNDEFINED;
return;
}
if(base_height < 2){
gGameCommandErrorText = STR_TOO_LOW;
*ebx = MONEY32_UNDEFINED;
return;
}
if(base_height >= 58){
gGameCommandErrorText = STR_TOO_HIGH;
*ebx = MONEY32_UNDEFINED;
return;
}
if(x >= gMapSizeUnits || y >= gMapSizeUnits){
gGameCommandErrorText = STR_OFF_EDGE_OF_MAP;
*ebx = MONEY32_UNDEFINED;
return;
}
if(!(gScreenFlags & SCREEN_FLAGS_SCENARIO_EDITOR) && !gCheatsSandboxMode && !map_is_location_in_park(x, y)){
*ebx = MONEY32_UNDEFINED;
return;
}
if(*ebx & GAME_COMMAND_FLAG_APPLY){
sint32 element_height = tile_element_height(x, y);
footpath_remove_litter(x, y, element_height);
if(!gCheatsDisableClearanceChecks)
wall_remove_at_z(x, y, element_height);
}
rct_tile_element* tile_element = map_get_surface_element_at(x / 32, y / 32);
sint32 zHigh = tile_element->base_height;
sint32 zLow = base_height;
if (map_get_water_height(tile_element) > 0)
{
zHigh = map_get_water_height(tile_element) * 2;
}
if(zLow > zHigh){
sint32 temp = zHigh;
zHigh = zLow;
zLow = temp;
}
if (gCheatsDisableClearanceChecks || map_can_construct_at(x, y, zLow, zHigh, 0xFF)) {
if(tile_element->type & 0x40){
gGameCommandErrorText = 0;
*ebx = MONEY32_UNDEFINED;
return;
}
if(*ebx & GAME_COMMAND_FLAG_APPLY){
sint32 new_terrain = tile_element->properties.surface.terrain & 0xE0;
if(base_height > tile_element->base_height){
new_terrain |= (base_height / 2);
}
tile_element->properties.surface.terrain = new_terrain;
map_invalidate_tile_full(x, y);
}
*ebx = 250;
if(gParkFlags & PARK_FLAGS_NO_MONEY){
*ebx = 0;
}
}else{
*ebx = MONEY32_UNDEFINED;
}
}
bool map_is_location_at_edge(sint32 x, sint32 y)
{
return x < 32 || y < 32 || x >= ((MAXIMUM_MAP_SIZE_TECHNICAL - 1) * 32) || y >= ((MAXIMUM_MAP_SIZE_TECHNICAL - 1) * 32);
}
/**
*
* rct2: 0x006B893C
*/
void game_command_place_large_scenery(sint32* eax, sint32* ebx, sint32* ecx, sint32* edx, sint32* esi, sint32* edi, sint32* ebp)
{
gCommandExpenditureType = RCT_EXPENDITURE_TYPE_LANDSCAPING;
sint32 x = (sint16)*eax;
sint32 y = (sint16)*ecx;
sint32 z = (sint16)*ebp;
colour_t colour1 = *edx & TILE_ELEMENT_COLOUR_MASK;
colour_t colour2 = (*edx >> 8) & TILE_ELEMENT_COLOUR_MASK;
uint8 flags = *ebx;
uint8 rotation = *ebx >> 8;
uint8 entry_index = *edi;
sint32 base_height = tile_element_height(x, y);
gCommandPosition.x = x + 16;
gCommandPosition.y = y + 16;
gCommandPosition.z = base_height;
gSceneryGroundFlags = 0;
uint8 banner_id = 0xFF;
money32 supportsCost = 0;
if (game_is_paused() && !gCheatsBuildInPauseMode) {
gGameCommandErrorText = STR_CONSTRUCTION_NOT_POSSIBLE_WHILE_GAME_IS_PAUSED;
*ebx = MONEY32_UNDEFINED;
return;
}
if (entry_index >= 128)
{
log_warning("Invalid game command for scenery placement, entry_index = %u", entry_index);
*ebx = MONEY32_UNDEFINED;
return;
}
rct_scenery_entry *scenery_entry = get_large_scenery_entry(entry_index);
if (scenery_entry == nullptr)
{
log_warning("Invalid game command for scenery placement, entry_index = %u", entry_index);
*ebx = MONEY32_UNDEFINED;
return;
}
if(scenery_entry->large_scenery.scrolling_mode != 0xFF){
banner_id = create_new_banner(flags);
if (banner_id == MAX_BANNERS) {
*ebx = MONEY32_UNDEFINED;
return;
}
if (flags & GAME_COMMAND_FLAG_APPLY) {
rct_banner* banner = &gBanners[banner_id];
banner->flags |= BANNER_FLAG_IS_LARGE_SCENERY;
banner->type = 0;
banner->x = x / 32;
banner->y = y / 32;
sint32 rideIndex = banner_get_closest_ride_index(x, y, z);
if (rideIndex != -1) {
banner->colour = rideIndex;
banner->flags |= BANNER_FLAG_LINKED_TO_RIDE;
}
}
}
uint32 num_elements = 0;
sint16 maxHeight = -1;
for (rct_large_scenery_tile* tile = scenery_entry->large_scenery.tiles;
tile->x_offset != -1;
tile++) {
num_elements++;
LocationXY16 curTile = {
tile->x_offset,
tile->y_offset
};
rotate_map_coordinates(&curTile.x, &curTile.y, rotation);
curTile.x += x;
curTile.y += y;
if(curTile.x >= 0x1FFF || curTile.y >= 0x1FFF || curTile.x < 0 || curTile.y < 0){
continue;
}
rct_tile_element * tile_element = map_get_surface_element_at(curTile.x / 32, curTile.y / 32);
if(tile_element != nullptr)
{
sint32 height = tile_element->base_height * 8;
sint32 slope = tile_element->properties.surface.slope;
if (slope & 0xF)
{
height += 16;
if (slope & 0x10)
{
height += 16;
}
}
if(height > maxHeight){
maxHeight = height;
}
}
}
if(z != 0){
maxHeight = z;
}
if (!map_check_free_elements_and_reorganise(num_elements)) {
*ebx = MONEY32_UNDEFINED;
return;
}
gCommandPosition.z = maxHeight;
uint8 tile_num = 0;
for (rct_large_scenery_tile* tile = scenery_entry->large_scenery.tiles;
tile->x_offset != -1;
tile++, tile_num++) {
LocationXY16 curTile = {
tile->x_offset,
tile->y_offset
};
rotate_map_coordinates(&curTile.x, &curTile.y, rotation);
curTile.x += x;
curTile.y += y;
sint32 zLow = (tile->z_offset + maxHeight) / 8;
sint32 zHigh = (tile->z_clearance / 8) + zLow;
sint32 bx = tile->var_7 >> 12;
bx <<= rotation;
uint8 bl = bx;
uint8 bh = bl >> 4;
bl &= 0xF;
bl |= bh;
uint8 F43887 = bl;
if (!gCheatsDisableClearanceChecks && !map_can_construct_with_clear_at(curTile.x, curTile.y, zLow, zHigh, &map_place_scenery_clear_func, bl, flags, &supportsCost, CREATE_CROSSING_MODE_NONE)) {
*ebx = MONEY32_UNDEFINED;
return;
}
if ((gMapGroundFlags & ELEMENT_IS_UNDERWATER) || (gMapGroundFlags & ELEMENT_IS_UNDERGROUND)) {
*ebx = MONEY32_UNDEFINED;
return;
}
sint32 b = gMapGroundFlags & (ELEMENT_IS_ABOVE_GROUND | ELEMENT_IS_UNDERGROUND);
if (!gCheatsDisableClearanceChecks) {
if (gSceneryGroundFlags && !(gSceneryGroundFlags & b)) {
gGameCommandErrorText = STR_CANT_BUILD_PARTLY_ABOVE_AND_PARTLY_BELOW_GROUND;
*ebx = MONEY32_UNDEFINED;
return;
}
}
gSceneryGroundFlags = b;
if (curTile.x >= gMapSizeUnits || curTile.y >= gMapSizeUnits) {
gGameCommandErrorText = STR_OFF_EDGE_OF_MAP;
*ebx = MONEY32_UNDEFINED;
return;
}
if (!(gScreenFlags & SCREEN_FLAGS_SCENARIO_EDITOR) &&
!map_is_location_owned(curTile.x, curTile.y, zLow * 8) &&
!gCheatsSandboxMode) {
*ebx = MONEY32_UNDEFINED;
return;
}
if (flags & GAME_COMMAND_FLAG_APPLY) {
if (!(flags & GAME_COMMAND_FLAG_GHOST)) {
footpath_remove_litter(curTile.x, curTile.y, zLow * 8);
if (!gCheatsDisableClearanceChecks) {
wall_remove_at(curTile.x, curTile.y, zLow * 8, zHigh * 8);
}
}
if (gGameCommandNestLevel == 1 && !(*ebx & GAME_COMMAND_FLAG_GHOST)) {
LocationXYZ16 coord;
coord.x = x + 16;
coord.y = 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);
}
rct_tile_element *new_tile_element = tile_element_insert(curTile.x / 32, curTile.y / 32, zLow, F43887);
assert(new_tile_element != NULL);
map_animation_create(MAP_ANIMATION_TYPE_LARGE_SCENERY, curTile.x, curTile.y, zLow);
new_tile_element->clearance_height = zHigh;
new_tile_element->type = TILE_ELEMENT_TYPE_LARGE_SCENERY | rotation;
scenery_large_set_type(new_tile_element, entry_index);
scenery_large_set_sequence(new_tile_element, tile_num);
scenery_large_set_primary_colour(new_tile_element, colour1);
scenery_large_set_secondary_colour(new_tile_element, colour2);
if (banner_id != 0xFF)
{
scenery_large_set_banner_id(new_tile_element, banner_id);
}
if (flags & GAME_COMMAND_FLAG_GHOST) {
new_tile_element->flags |= TILE_ELEMENT_FLAG_GHOST;
}
if (tile_num == 0) {
gSceneryTileElement = new_tile_element;
}
map_invalidate_tile_full(curTile.x, curTile.y);
}
}
// Force ride construction to recheck area
_currentTrackSelectionFlags |= TRACK_SELECTION_FLAG_RECHECK;
*ebx = (scenery_entry->large_scenery.price * 10) + supportsCost;
if(gParkFlags & PARK_FLAGS_NO_MONEY){
*ebx = 0;
}
}
sint32 tile_element_get_station(const rct_tile_element * tileElement)
{
return (tileElement->properties.track.sequence & MAP_ELEM_TRACK_SEQUENCE_STATION_INDEX_MASK) >> 4;
}
/**
*
* rct2: 0x0068B280
*/
void tile_element_remove(rct_tile_element *tileElement)
{
// Replace Nth element by (N+1)th element.
// This loop will make tileElement point to the old last element position,
// after copy it to it's new position
if (!tile_element_is_last_for_tile(tileElement)){
do{
*tileElement = *(tileElement + 1);
} while (!tile_element_is_last_for_tile(++tileElement));
}
// Mark the latest element with the last element flag.
(tileElement - 1)->flags |= TILE_ELEMENT_FLAG_LAST_TILE;
tileElement->base_height = 0xFF;
if ((tileElement + 1) == gNextFreeTileElement){
gNextFreeTileElement--;
}
}
/**
*
* rct2: 0x00675A8E
*/
void map_remove_all_rides()
{
tile_element_iterator it;
tile_element_iterator_begin(&it);
do {
switch (tile_element_get_type(it.element)) {
case TILE_ELEMENT_TYPE_PATH:
if (footpath_element_is_queue(it.element))
{
it.element->properties.path.type &= ~8;
it.element->properties.path.addition_status = 255;
}
break;
case TILE_ELEMENT_TYPE_ENTRANCE:
if (it.element->properties.entrance.type == ENTRANCE_TYPE_PARK_ENTRANCE)
break;
// fall-through
case TILE_ELEMENT_TYPE_TRACK:
footpath_queue_chain_reset();
footpath_remove_edges_at(it.x * 32, it.y * 32, it.element);
tile_element_remove(it.element);
tile_element_iterator_restart_for_tile(&it);
break;
}
} while (tile_element_iterator_next(&it));
}
/**
*
* rct2: 0x0068AB1B
*/
void map_invalidate_map_selection_tiles()
{
LocationXY16 *position;
if (!(gMapSelectFlags & MAP_SELECT_FLAG_ENABLE_CONSTRUCT))
return;
for (position = gMapSelectionTiles; position->x != -1; position++)
map_invalidate_tile_full(position->x, position->y);
}
static void map_get_bounding_box(sint32 ax, sint32 ay, sint32 bx, sint32 by, sint32 *left, sint32 *top, sint32 *right, sint32 *bottom)
{
sint32 x, y;
x = ax;
y = ay;
uint32 rotation = get_current_rotation();
translate_3d_to_2d(rotation, &x, &y);
*left = x;
*right = x;
*top = y;
*bottom = y;
x = bx;
y = ay;
translate_3d_to_2d(rotation, &x, &y);
if (x < *left) *left = x;
if (x > *right) *right = x;
if (y > *bottom) *bottom = y;
if (y < *top) *top = y;
x = bx;
y = by;
translate_3d_to_2d(rotation, &x, &y);
if (x < *left) *left = x;
if (x > *right) *right = x;
if (y > *bottom) *bottom = y;
if (y < *top) *top = y;
x = ax;
y = by;
translate_3d_to_2d(rotation, &x, &y);
if (x < *left) *left = x;
if (x > *right) *right = x;
if (y > *bottom) *bottom = y;
if (y < *top) *top = y;
}
/**
*
* rct2: 0x0068AAE1
*/
void map_invalidate_selection_rect()
{
sint32 x0, y0, x1, y1, left, right, top, bottom;
if (!(gMapSelectFlags & MAP_SELECT_FLAG_ENABLE))
return;
x0 = gMapSelectPositionA.x + 16;
y0 = gMapSelectPositionA.y + 16;
x1 = gMapSelectPositionB.x + 16;
y1 = gMapSelectPositionB.y + 16;
map_get_bounding_box(x0, y0, x1, y1, &left, &top, &right, &bottom);
left -= 32;
right += 32;
bottom += 32;
top -= 32 + 2080;
for (sint32 i = 0; i < MAX_VIEWPORT_COUNT; i++) {
rct_viewport *viewport = &g_viewport_list[i];
if (viewport->width != 0) {
viewport_invalidate(viewport, left, top, right, bottom);
}
}
}
/**
*
* rct2: 0x0068B111
*/
void map_reorganise_elements()
{
context_setcurrentcursor(CURSOR_ZZZ);
rct_tile_element* new_tile_elements = (rct_tile_element *)malloc(3 * (MAXIMUM_MAP_SIZE_TECHNICAL * MAXIMUM_MAP_SIZE_TECHNICAL) * sizeof(rct_tile_element));
rct_tile_element* new_elements_pointer = new_tile_elements;
if (new_tile_elements == nullptr) {
log_fatal("Unable to allocate memory for map elements.");
return;
}
uint32 num_elements;
for (sint32 y = 0; y < MAXIMUM_MAP_SIZE_TECHNICAL; y++) {
for (sint32 x = 0; x < MAXIMUM_MAP_SIZE_TECHNICAL; x++) {
rct_tile_element *startElement = map_get_first_element_at(x, y);
rct_tile_element *endElement = startElement;
while (!tile_element_is_last_for_tile(endElement++));
num_elements = (uint32)(endElement - startElement);
memcpy(new_elements_pointer, startElement, num_elements * sizeof(rct_tile_element));
new_elements_pointer += num_elements;
}
}
num_elements = (uint32)(new_elements_pointer - new_tile_elements);
memcpy(gTileElements, new_tile_elements, num_elements * sizeof(rct_tile_element));
memset(gTileElements + num_elements, 0, (3 * (MAXIMUM_MAP_SIZE_TECHNICAL * MAXIMUM_MAP_SIZE_TECHNICAL) - num_elements) * sizeof(rct_tile_element));
free(new_tile_elements);
map_update_tile_pointers();
}
/**
*
* rct2: 0x0068B044
* Returns true on space available for more elements
* Reorganises the map elements to check for space
*/
bool map_check_free_elements_and_reorganise(sint32 num_elements)
{
if ((gNextFreeTileElement + num_elements) <= gTileElements + MAX_TILE_ELEMENTS)
return true;
for (sint32 i = 1000; i != 0; --i)
sub_68B089();
if ((gNextFreeTileElement + num_elements) <= gTileElements + MAX_TILE_ELEMENTS)
return true;
map_reorganise_elements();
if ((gNextFreeTileElement + num_elements) <= gTileElements + MAX_TILE_ELEMENTS)
return true;
else{
gGameCommandErrorText = STR_ERR_LANDSCAPE_DATA_AREA_FULL;
return false;
}
}
/**
*
* rct2: 0x0068B1F6
*/
rct_tile_element *tile_element_insert(sint32 x, sint32 y, sint32 z, sint32 flags)
{
rct_tile_element *originalTileElement, *newTileElement, *insertedElement;
if (!map_check_free_elements_and_reorganise(1)) {
log_error("Cannot insert new element");
return nullptr;
}
newTileElement = gNextFreeTileElement;
originalTileElement = gTileElementTilePointers[y * MAXIMUM_MAP_SIZE_TECHNICAL + x];
// Set tile index pointer to point to new element block
gTileElementTilePointers[y * MAXIMUM_MAP_SIZE_TECHNICAL + x] = newTileElement;
// Copy all elements that are below the insert height
while (z >= originalTileElement->base_height) {
// Copy over map element
*newTileElement = *originalTileElement;
originalTileElement->base_height = 255;
originalTileElement++;
newTileElement++;
if ((newTileElement - 1)->flags & TILE_ELEMENT_FLAG_LAST_TILE) {
// No more elements above the insert element
(newTileElement - 1)->flags &= ~TILE_ELEMENT_FLAG_LAST_TILE;
flags |= TILE_ELEMENT_FLAG_LAST_TILE;
break;
}
}
// Insert new map element
insertedElement = newTileElement;
newTileElement->base_height = z;
newTileElement->flags = flags;
newTileElement->clearance_height = z;
memset(&newTileElement->properties, 0, sizeof(newTileElement->properties));
newTileElement++;
// Insert rest of map elements above insert height
if (!(flags & TILE_ELEMENT_FLAG_LAST_TILE)) {
do {
// Copy over map element
*newTileElement = *originalTileElement;
originalTileElement->base_height = 255;
originalTileElement++;
newTileElement++;
} while (!((newTileElement - 1)->flags & TILE_ELEMENT_FLAG_LAST_TILE));
}
gNextFreeTileElement = newTileElement;
return insertedElement;
}
/**
* This function will validate element address. It will only check if element lies within
* the user-accessible part of map elements, there is some scratch space behind that is not
* considered valid here.
*/
bool tile_element_check_address(const rct_tile_element * const element)
{
if (element >= gTileElements
&& element < gTileElements + MAX_TILE_ELEMENTS
// condition below checks alignment
&& gTileElements + (((uintptr_t)element - (uintptr_t)gTileElements) / sizeof(rct_tile_element)) == element)
{
return true;
}
return false;
}
/**
*
* rct2: 0x0068BB18
*/
void map_obstruction_set_error_text(rct_tile_element *tileElement)
{
rct_string_id errorStringId;
Ride *ride;
rct_scenery_entry *sceneryEntry;
errorStringId = STR_OBJECT_IN_THE_WAY;
switch (tile_element_get_type(tileElement)) {
case TILE_ELEMENT_TYPE_SURFACE:
errorStringId = STR_RAISE_OR_LOWER_LAND_FIRST;
break;
case TILE_ELEMENT_TYPE_PATH:
errorStringId = STR_FOOTPATH_IN_THE_WAY;
break;
case TILE_ELEMENT_TYPE_TRACK:
ride = get_ride(track_element_get_ride_index(tileElement));
errorStringId = STR_X_IN_THE_WAY;
set_format_arg(0, rct_string_id, ride->name);
set_format_arg(2, uint32, ride->name_arguments);
break;
case TILE_ELEMENT_TYPE_SMALL_SCENERY:
sceneryEntry = get_small_scenery_entry(tileElement->properties.scenery.type);
errorStringId = STR_X_IN_THE_WAY;
set_format_arg(0, rct_string_id, sceneryEntry->name);
break;
case TILE_ELEMENT_TYPE_ENTRANCE:
switch (tileElement->properties.entrance.type) {
case ENTRANCE_TYPE_RIDE_ENTRANCE:
errorStringId = STR_RIDE_ENTRANCE_IN_THE_WAY;
break;
case ENTRANCE_TYPE_RIDE_EXIT:
errorStringId = STR_RIDE_EXIT_IN_THE_WAY;
break;
case ENTRANCE_TYPE_PARK_ENTRANCE:
errorStringId = STR_PARK_ENTRANCE_IN_THE_WAY;
break;
}
break;
case TILE_ELEMENT_TYPE_WALL:
sceneryEntry = get_wall_entry(tileElement->properties.scenery.type);
errorStringId = STR_X_IN_THE_WAY;
set_format_arg(0, rct_string_id, sceneryEntry->name);
break;
case TILE_ELEMENT_TYPE_LARGE_SCENERY:
sceneryEntry = get_large_scenery_entry(tileElement->properties.scenery.type);
errorStringId = STR_X_IN_THE_WAY;
set_format_arg(0, rct_string_id, sceneryEntry->name);
break;
}
gGameCommandErrorText = errorStringId;
}
/**
*
* rct2: 0x0068B932
* ax = x
* cx = y
* dl = zLow
* dh = zHigh
* ebp = clearFunc
* bl = bl
*/
sint32 map_can_construct_with_clear_at(sint32 x, sint32 y, sint32 zLow, sint32 zHigh, CLEAR_FUNC *clearFunc, uint8 bl, uint8 flags, money32 *price, uint8 crossingMode)
{
sint32 al, ah, bh, cl, ch, water_height;
al = ah = bh = cl = ch = water_height = 0;
uint8 slope = 0;
gMapGroundFlags = ELEMENT_IS_ABOVE_GROUND;
bool canBuildCrossing = false;
if (x >= gMapSizeUnits || y >= gMapSizeUnits || x < 32 || y < 32) {
gGameCommandErrorText = STR_OFF_EDGE_OF_MAP;
return false;
}
rct_tile_element* tile_element = map_get_first_element_at(x / 32, y / 32);
do {
if (tile_element_get_type(tile_element) != TILE_ELEMENT_TYPE_SURFACE) {
if (zLow < tile_element->clearance_height && zHigh > tile_element->base_height && !(tile_element->flags & TILE_ELEMENT_FLAG_GHOST)) {
if (tile_element->flags & (bl & 0x0F)) {
goto loc_68BABC;
}
}
continue;
}
water_height = map_get_water_height(tile_element) * 2;
if (water_height && water_height > zLow && tile_element->base_height < zHigh && !gCheatsDisableClearanceChecks) {
gMapGroundFlags |= ELEMENT_IS_UNDERWATER;
if (water_height < zHigh) {
goto loc_68BAE6;
}
}
loc_68B9B7:
if (gParkFlags & PARK_FLAGS_FORBID_HIGH_CONSTRUCTION) {
al = zHigh - tile_element->base_height;
if (al >= 0) {
if (al > 18) {
gGameCommandErrorText = STR_LOCAL_AUTHORITY_WONT_ALLOW_CONSTRUCTION_ABOVE_TREE_HEIGHT;
return false;
}
}
}
// Only allow building crossings directly on a flat surface tile.
if (tile_element_get_type(tile_element) == TILE_ELEMENT_TYPE_SURFACE &&
(tile_element->properties.surface.slope & TILE_ELEMENT_SURFACE_SLOPE_MASK) == TILE_ELEMENT_SLOPE_FLAT &&
tile_element->base_height == zLow)
{
canBuildCrossing = true;
}
if ((bl & 0xF0) != 0xF0) {
if (tile_element->base_height >= zHigh) {
// loc_68BA81
gMapGroundFlags |= ELEMENT_IS_UNDERGROUND;
gMapGroundFlags &= ~ELEMENT_IS_ABOVE_GROUND;
} else {
al = tile_element->base_height;
ah = al;
cl = al;
ch = al;
slope = tile_element->properties.surface.slope & TILE_ELEMENT_SURFACE_SLOPE_MASK;
if (slope & TILE_ELEMENT_SLOPE_N_CORNER_UP) {
al += 2;
if (slope == (TILE_ELEMENT_SLOPE_S_CORNER_DN | TILE_ELEMENT_SLOPE_DOUBLE_HEIGHT))
al += 2;
}
if (slope & TILE_ELEMENT_SLOPE_E_CORNER_UP) {
ah += 2;
if (slope == (TILE_ELEMENT_SLOPE_W_CORNER_DN | TILE_ELEMENT_SLOPE_DOUBLE_HEIGHT))
ah += 2;
}
if (slope & TILE_ELEMENT_SLOPE_S_CORNER_UP) {
cl += 2;
if (slope == (TILE_ELEMENT_SLOPE_N_CORNER_DN | TILE_ELEMENT_SLOPE_DOUBLE_HEIGHT))
cl += 2;
}
if (slope & TILE_ELEMENT_SLOPE_W_CORNER_UP) {
ch += 2;
if (slope == (TILE_ELEMENT_SLOPE_E_CORNER_DN | TILE_ELEMENT_SLOPE_DOUBLE_HEIGHT))
ch += 2;
}
bh = zLow + 4;
if ((!(bl & 1) || ((bl & 0x10 || zLow >= al) && bh >= al)) &&
(!(bl & 2) || ((bl & 0x20 || zLow >= ah) && bh >= ah)) &&
(!(bl & 4) || ((bl & 0x40 || zLow >= cl) && bh >= cl)) &&
(!(bl & 8) || ((bl & 0x80 || zLow >= ch) && bh >= ch))) {
continue;
}
loc_68BABC:
if (clearFunc != nullptr) {
if (!clearFunc(&tile_element, x, y, flags, price)) {
continue;
}
}
// Crossing mode 1: building track over path
if (crossingMode == 1 &&
canBuildCrossing &&
tile_element_get_type(tile_element) == TILE_ELEMENT_TYPE_PATH &&
tile_element->base_height == zLow &&
!footpath_element_is_queue(tile_element) &&
!footpath_element_is_sloped(tile_element))
{
continue;
}
// Crossing mode 2: building path over track
else if (crossingMode == 2 &&
canBuildCrossing &&
tile_element_get_type(tile_element) == TILE_ELEMENT_TYPE_TRACK &&
tile_element->base_height == zLow &&
track_element_get_type(tile_element) == TRACK_ELEM_FLAT)
{
Ride * ride = get_ride(track_element_get_ride_index(tile_element));
if (ride->type == RIDE_TYPE_MINIATURE_RAILWAY)
{
continue;
}
}
if (tile_element != nullptr)
{
map_obstruction_set_error_text(tile_element);
}
return false;
loc_68BAE6:
if (clearFunc != nullptr) {
if (!clearFunc(&tile_element, x, y, flags, price)) {
goto loc_68B9B7;
}
}
if (tile_element != nullptr) {
gGameCommandErrorText = STR_CANNOT_BUILD_PARTLY_ABOVE_AND_PARTLY_BELOW_WATER;
}
return false;
}
}
} while (!tile_element_is_last_for_tile(tile_element++));
return true;
}
/**
*
* rct2: 0x0068B93A
*/
sint32 map_can_construct_at(sint32 x, sint32 y, sint32 zLow, sint32 zHigh, uint8 bl)
{
return gCheatsDisableClearanceChecks || map_can_construct_with_clear_at(x, y, zLow, zHigh, nullptr, bl, 0, nullptr, CREATE_CROSSING_MODE_NONE);
}
/**
* Updates grass length, scenery age and jumping fountains.
*
* rct2: 0x006646E1
*/
void map_update_tiles()
{
sint32 ignoreScreenFlags = SCREEN_FLAGS_SCENARIO_EDITOR | SCREEN_FLAGS_TRACK_DESIGNER | SCREEN_FLAGS_TRACK_MANAGER;
if (gScreenFlags & ignoreScreenFlags)
return;
// Update 43 more tiles
for (sint32 j = 0; j < 43; j++) {
sint32 x = 0;
sint32 y = 0;
uint16 interleaved_xy = gGrassSceneryTileLoopPosition;
for (sint32 i = 0; i < 8; i++) {
x = (x << 1) | (interleaved_xy & 1);
interleaved_xy >>= 1;
y = (y << 1) | (interleaved_xy & 1);
interleaved_xy >>= 1;
}
rct_tile_element *tileElement = map_get_surface_element_at(x, y);
if (tileElement != nullptr) {
map_update_grass_length(x * 32, y * 32, tileElement);
scenery_update_tile(x * 32, y * 32);
}
gGrassSceneryTileLoopPosition++;
gGrassSceneryTileLoopPosition &= 0xFFFF;
}
}
/**
*
* rct2: 0x006647A1
*/
static void map_update_grass_length(sint32 x, sint32 y, rct_tile_element *tileElement)
{
// Check if tile is grass
if ((tileElement->properties.surface.terrain & 0xE0) && !(tileElement->type & 3))
return;
sint32 grassLength = tileElement->properties.surface.grass_length & 7;
// Check if grass is underwater or outside park
sint32 waterHeight = map_get_water_height(tileElement) * 2;
if (waterHeight > tileElement->base_height || !map_is_location_in_park(x, y)) {
if (grassLength != GRASS_LENGTH_CLEAR_0)
map_set_grass_length(x, y, tileElement, GRASS_LENGTH_CLEAR_0);
return;
}
// Grass can't grow any further than CLUMPS_2 but this code also cuts grass
// if there is an object placed on top of it.
sint32 z0 = tileElement->base_height;
sint32 z1 = tileElement->base_height + 2;
if (tileElement->properties.surface.slope & TILE_ELEMENT_SLOPE_DOUBLE_HEIGHT)
z1 += 2;
// Check objects above grass
rct_tile_element *tileElementAbove = tileElement;
for (;;) {
if (tileElementAbove->flags & TILE_ELEMENT_FLAG_LAST_TILE) {
// Grow grass
// Check interim grass lengths
uint8 lengthNibble = (tileElement->properties.surface.grass_length & 0xF0) >> 4;
if (lengthNibble < 0xF) {
tileElement->properties.surface.grass_length += 0x10;
} else {
// Zeros the length nibble
tileElement->properties.surface.grass_length += 0x10;
tileElement->properties.surface.grass_length ^= 8;
if (tileElement->properties.surface.grass_length & 8) {
// Random growth rate (length nibble)
tileElement->properties.surface.grass_length |= scenario_rand() & 0x70;
} else {
// Increase length if not at max length
if (grassLength != GRASS_LENGTH_CLUMPS_2)
map_set_grass_length(x, y, tileElement, grassLength + 1);
}
}
} else {
tileElementAbove++;
if (tile_element_get_type(tileElementAbove) == TILE_ELEMENT_TYPE_WALL)
continue;
// Grass should not be affected by ghost elements.
if (tile_element_is_ghost(tileElementAbove))
continue;
if (z0 >= tileElementAbove->clearance_height)
continue;
if (z1 < tileElementAbove->base_height)
continue;
if (grassLength != GRASS_LENGTH_CLEAR_0)
map_set_grass_length(x, y, tileElement, GRASS_LENGTH_CLEAR_0);
}
break;
}
}
static void map_set_grass_length(sint32 x, sint32 y, rct_tile_element *tileElement, sint32 length)
{
sint32 oldLength = tileElement->properties.surface.grass_length & 0x7;
sint32 newLength = length & 0x7;
tileElement->properties.surface.grass_length = length;
if (newLength == oldLength)
{
return;
}
// If the new grass length won't result in an actual visual change
// then skip invalidating the tile, no point
if (((oldLength > 0 && oldLength < 4) && (newLength > 0 && newLength < 4)) ||
((oldLength > 3 && oldLength < 7) && (newLength > 3 && newLength < 7)))
{
return;
}
sint32 z = tileElement->base_height * 8;
map_invalidate_tile(x, y, z, z + 16);
}
void map_remove_provisional_elements()
{
if (gFootpathProvisionalFlags & PROVISIONAL_PATH_FLAG_1)
{
footpath_provisional_remove();
gFootpathProvisionalFlags |= PROVISIONAL_PATH_FLAG_1;
}
if (window_find_by_class(WC_RIDE_CONSTRUCTION ) != nullptr)
{
ride_remove_provisional_track_piece();
ride_entrance_exit_remove_ghost();
}
}
void map_restore_provisional_elements()
{
if (gFootpathProvisionalFlags & PROVISIONAL_PATH_FLAG_1)
{
gFootpathProvisionalFlags &= ~PROVISIONAL_PATH_FLAG_1;
footpath_provisional_set(gFootpathProvisionalType,
gFootpathProvisionalPosition.x,
gFootpathProvisionalPosition.y,
gFootpathProvisionalPosition.z,
gFootpathProvisionalSlope);
}
if (window_find_by_class(WC_RIDE_CONSTRUCTION) != nullptr)
{
ride_restore_provisional_track_piece();
ride_entrance_exit_place_provisional_ghost();
}
}
sint32 tile_element_get_banner_index(rct_tile_element *tileElement)
{
rct_scenery_entry* sceneryEntry;
switch (tile_element_get_type(tileElement)) {
case TILE_ELEMENT_TYPE_LARGE_SCENERY:
sceneryEntry = get_large_scenery_entry(scenery_large_get_type(tileElement));
if (sceneryEntry->large_scenery.scrolling_mode == 0xFF)
return -1;
return scenery_large_get_banner_id(tileElement);
case TILE_ELEMENT_TYPE_WALL:
sceneryEntry = get_wall_entry(tileElement->properties.wall.type);
if (sceneryEntry == nullptr || sceneryEntry->wall.scrolling_mode == 0xFF)
return -1;
return tileElement->properties.wall.banner_index;
case TILE_ELEMENT_TYPE_BANNER:
return tileElement->properties.banner.index;
default:
return -1;
}
}
void tile_element_remove_banner_entry(rct_tile_element *tileElement)
{
sint32 bannerIndex = tile_element_get_banner_index(tileElement);
if (bannerIndex == -1)
return;
rct_banner* banner = &gBanners[bannerIndex];
if (banner->type != BANNER_NULL) {
window_close_by_number(WC_BANNER, bannerIndex);
banner->type = BANNER_NULL;
user_string_free(banner->string_idx);
}
}
/**
* Removes elements that are out of the map size range and crops the park perimeter.
* rct2: 0x0068ADBC
*/
void map_remove_out_of_range_elements()
{
sint32 mapMaxXY = gMapSizeMaxXY;
for (sint32 y = 0; y < (MAXIMUM_MAP_SIZE_TECHNICAL * 32); y += 32) {
for (sint32 x = 0; x < (MAXIMUM_MAP_SIZE_TECHNICAL * 32); x += 32) {
if (x == 0 || y == 0 || x >= mapMaxXY || y >= mapMaxXY) {
map_buy_land_rights(x, y, x, y, BUY_LAND_RIGHTS_FLAG_UNOWN_TILE, GAME_COMMAND_FLAG_APPLY);
clear_elements_at(x, y);
}
}
}
}
/**
* Copies the terrain and slope from the edge of the map to the new tiles. Used when increasing the size of the map.
* rct2: 0x0068AC15
*/
void map_extend_boundary_surface()
{
rct_tile_element *existingTileElement, *newTileElement;
sint32 x, y, z, slope;
y = gMapSize - 2;
for (x = 0; x < MAXIMUM_MAP_SIZE_TECHNICAL; x++) {
existingTileElement = map_get_surface_element_at(x, y - 1);
newTileElement = map_get_surface_element_at(x, y);
newTileElement->type = (newTileElement->type & 0x7C) | (existingTileElement->type & 0x83);
newTileElement->properties.surface.slope = existingTileElement->properties.surface.slope & TILE_ELEMENT_SURFACE_EDGE_STYLE_MASK;
newTileElement->properties.surface.terrain = existingTileElement->properties.surface.terrain;
newTileElement->properties.surface.grass_length = existingTileElement->properties.surface.grass_length;
newTileElement->properties.surface.ownership = 0;
z = existingTileElement->base_height;
slope = existingTileElement->properties.surface.slope & TILE_ELEMENT_SLOPE_NW_SIDE_UP;
if (slope == TILE_ELEMENT_SLOPE_NW_SIDE_UP) {
z += 2;
slope = TILE_ELEMENT_SLOPE_FLAT;
if (existingTileElement->properties.surface.slope & TILE_ELEMENT_SLOPE_DOUBLE_HEIGHT) {
slope = TILE_ELEMENT_SLOPE_N_CORNER_UP;
if (existingTileElement->properties.surface.slope & TILE_ELEMENT_SLOPE_S_CORNER_UP) {
slope = TILE_ELEMENT_SLOPE_W_CORNER_UP;
if (existingTileElement->properties.surface.slope & TILE_ELEMENT_SLOPE_E_CORNER_UP) {
slope = TILE_ELEMENT_SLOPE_FLAT;
}
}
}
}
if (slope & TILE_ELEMENT_SLOPE_N_CORNER_UP) slope |= TILE_ELEMENT_SLOPE_E_CORNER_UP;
if (slope & TILE_ELEMENT_SLOPE_W_CORNER_UP) slope |= TILE_ELEMENT_SLOPE_S_CORNER_UP;
newTileElement->properties.surface.slope |= slope;
newTileElement->base_height = z;
newTileElement->clearance_height = z;
update_park_fences(x << 5, y << 5);
}
x = gMapSize - 2;
for (y = 0; y < MAXIMUM_MAP_SIZE_TECHNICAL; y++) {
existingTileElement = map_get_surface_element_at(x - 1, y);
newTileElement = map_get_surface_element_at(x, y);
newTileElement->type = (newTileElement->type & 0x7C) | (existingTileElement->type & 0x83);
newTileElement->properties.surface.slope = existingTileElement->properties.surface.slope & TILE_ELEMENT_SURFACE_EDGE_STYLE_MASK;
newTileElement->properties.surface.terrain = existingTileElement->properties.surface.terrain;
newTileElement->properties.surface.grass_length = existingTileElement->properties.surface.grass_length;
newTileElement->properties.surface.ownership = 0;
z = existingTileElement->base_height;
slope = existingTileElement->properties.surface.slope & TILE_ELEMENT_SLOPE_NE_SIDE_UP;
if (slope == TILE_ELEMENT_SLOPE_NE_SIDE_UP) {
z += 2;
slope = TILE_ELEMENT_SLOPE_FLAT;
if (existingTileElement->properties.surface.slope & TILE_ELEMENT_SLOPE_DOUBLE_HEIGHT) {
slope = TILE_ELEMENT_SLOPE_N_CORNER_UP;
if (existingTileElement->properties.surface.slope & TILE_ELEMENT_SLOPE_S_CORNER_UP) {
slope = TILE_ELEMENT_SLOPE_E_CORNER_UP;
if (existingTileElement->properties.surface.slope & TILE_ELEMENT_SLOPE_W_CORNER_UP) {
slope = TILE_ELEMENT_SLOPE_FLAT;
}
}
}
}
if (slope & TILE_ELEMENT_SLOPE_N_CORNER_UP) slope |= TILE_ELEMENT_SLOPE_W_CORNER_UP;
if (slope & TILE_ELEMENT_SLOPE_E_CORNER_UP) slope |= TILE_ELEMENT_SLOPE_S_CORNER_UP;
newTileElement->properties.surface.slope |= slope;
newTileElement->base_height = z;
newTileElement->clearance_height = z;
update_park_fences(x << 5, y << 5);
}
}
/**
* Clears the provided element properly from a certain tile, and updates
* the pointer (when needed) passed to this function to point to the next element.
*/
static void clear_element_at(sint32 x, sint32 y, rct_tile_element **elementPtr)
{
rct_tile_element *element = *elementPtr;
switch (tile_element_get_type(element)) {
case TILE_ELEMENT_TYPE_SURFACE:
element->base_height = 2;
element->clearance_height = 2;
element->properties.surface.slope = TILE_ELEMENT_SLOPE_FLAT;
element->properties.surface.terrain = 0;
element->properties.surface.grass_length = GRASS_LENGTH_CLEAR_0;
element->properties.surface.ownership = 0;
// Because this element is not completely removed, the pointer must be updated manually
// The rest of the elements are removed from the array, so the pointer doesn't need to be updated.
(*elementPtr)++;
break;
case TILE_ELEMENT_TYPE_ENTRANCE:
viewport_interaction_remove_park_entrance(element, x, y);
break;
case TILE_ELEMENT_TYPE_WALL:
gGameCommandErrorTitle = STR_CANT_REMOVE_THIS;
game_do_command(
x,
GAME_COMMAND_FLAG_APPLY,
y,
tile_element_get_direction(element) | (element->base_height << 8),
GAME_COMMAND_REMOVE_WALL,
0,
0
);
break;
case TILE_ELEMENT_TYPE_LARGE_SCENERY:
gGameCommandErrorTitle = STR_CANT_REMOVE_THIS;
game_do_command(
x,
(GAME_COMMAND_FLAG_APPLY) | (tile_element_get_direction(element) << 8),
y,
(element->base_height) | (scenery_large_get_sequence(element) << 8),
GAME_COMMAND_REMOVE_LARGE_SCENERY,
0,
0
);
break;
case TILE_ELEMENT_TYPE_BANNER:
gGameCommandErrorTitle = STR_CANT_REMOVE_THIS;
game_do_command(
x,
GAME_COMMAND_FLAG_APPLY,
y,
(element->base_height) | ((element->properties.banner.position & 3) << 8),
GAME_COMMAND_REMOVE_BANNER,
0,
0
);
break;
default:
tile_element_remove(element);
break;
}
}
/**
* Clears all elements properly from a certain tile.
* rct2: 0x0068AE2A
*/
static void clear_elements_at(sint32 x, sint32 y)
{
// Remove the spawn point (if there is one in the current tile)
for (sint32 i = 0; i < MAX_PEEP_SPAWNS; i++) {
rct2_peep_spawn *peepSpawn = &gPeepSpawns[i];
if (floor2(peepSpawn->x, 32) == x && floor2(peepSpawn->y, 32) == y) {
peepSpawn->x = PEEP_SPAWN_UNDEFINED;
}
}
rct_tile_element *tileElement = map_get_first_element_at(x >> 5, y >> 5);
// Remove all elements except the last one
while(!tile_element_is_last_for_tile(tileElement))
clear_element_at(x, y, &tileElement);
// Remove the last element
clear_element_at(x, y, &tileElement);
}
sint32 map_get_highest_z(sint32 tileX, sint32 tileY)
{
rct_tile_element *tileElement;
sint32 z;
tileElement = map_get_surface_element_at(tileX, tileY);
if (tileElement == nullptr)
return -1;
z = tileElement->base_height * 8;
// Raise z so that is above highest point of land and water on tile
if ((tileElement->properties.surface.slope & TILE_ELEMENT_SLOPE_ALL_CORNERS_UP) != TILE_ELEMENT_SLOPE_FLAT)
z += 16;
if ((tileElement->properties.surface.slope & TILE_ELEMENT_SLOPE_DOUBLE_HEIGHT) != 0)
z += 16;
z = std::max(z, map_get_water_height(tileElement) * 16);
return z;
}
bool tile_element_is_underground(rct_tile_element *tileElement)
{
do {
tileElement++;
if (tile_element_is_last_for_tile(tileElement - 1))
return false;
} while (tile_element_get_type(tileElement) != TILE_ELEMENT_TYPE_SURFACE);
return true;
}
rct_tile_element *map_get_large_scenery_segment(sint32 x, sint32 y, sint32 z, sint32 direction, sint32 sequence)
{
rct_tile_element *tileElement = map_get_first_element_at(x >> 5, y >> 5);
if (tileElement == nullptr)
{
return nullptr;
}
do {
if (tile_element_get_type(tileElement) != TILE_ELEMENT_TYPE_LARGE_SCENERY)
continue;
if (tileElement->base_height != z)
continue;
if (scenery_large_get_sequence(tileElement) != sequence)
continue;
if ((tile_element_get_direction(tileElement)) != direction)
continue;
return tileElement;
} while (!tile_element_is_last_for_tile(tileElement++));
return nullptr;
}
rct_tile_element * map_get_park_entrance_element_at(sint32 x, sint32 y, sint32 z, bool ghost)
{
rct_tile_element* tileElement = map_get_first_element_at(x >> 5, y >> 5);
if (tileElement != nullptr)
{
do
{
if (tile_element_get_type(tileElement) != TILE_ELEMENT_TYPE_ENTRANCE)
continue;
if (tileElement->base_height != z)
continue;
if (tileElement->properties.entrance.type != ENTRANCE_TYPE_PARK_ENTRANCE)
continue;
if ((ghost == false) && (tileElement->flags & TILE_ELEMENT_FLAG_GHOST))
continue;
return tileElement;
}
while (!tile_element_is_last_for_tile(tileElement++));
}
return nullptr;
}
rct_tile_element * map_get_ride_entrance_element_at(sint32 x, sint32 y, sint32 z, bool ghost)
{
rct_tile_element * tileElement = map_get_first_element_at(x >> 5, y >> 5);
if (tileElement != nullptr)
{
do
{
if (tile_element_get_type(tileElement) != TILE_ELEMENT_TYPE_ENTRANCE)
continue;
if (tileElement->base_height != z)
continue;
if (tileElement->properties.entrance.type != ENTRANCE_TYPE_RIDE_ENTRANCE)
continue;
if ((ghost == false) && (tileElement->flags & TILE_ELEMENT_FLAG_GHOST))
continue;
return tileElement;
}
while (!tile_element_is_last_for_tile(tileElement++));
}
return nullptr;
}
rct_tile_element * map_get_ride_exit_element_at(sint32 x, sint32 y, sint32 z, bool ghost)
{
rct_tile_element * tileElement = map_get_first_element_at(x >> 5, y >> 5);
if (tileElement != nullptr)
{
do
{
if (tile_element_get_type(tileElement) != TILE_ELEMENT_TYPE_ENTRANCE)
continue;
if (tileElement->base_height != z)
continue;
if (tileElement->properties.entrance.type != ENTRANCE_TYPE_RIDE_EXIT)
continue;
if ((ghost == false) && (tileElement->flags & TILE_ELEMENT_FLAG_GHOST))
continue;
return tileElement;
}
while (!tile_element_is_last_for_tile(tileElement++));
}
return nullptr;
}
rct_tile_element *map_get_small_scenery_element_at(sint32 x, sint32 y, sint32 z, sint32 type, uint8 quadrant)
{
rct_tile_element *tileElement = map_get_first_element_at(x >> 5, y >> 5);
if (tileElement != nullptr)
{
do
{
if (tile_element_get_type(tileElement) != TILE_ELEMENT_TYPE_SMALL_SCENERY)
continue;
if (tileElement->type >> 6 != quadrant)
continue;
if (tileElement->base_height != z)
continue;
if (tileElement->properties.scenery.type != type)
continue;
return tileElement;
}
while (!tile_element_is_last_for_tile(tileElement++));
}
return nullptr;
}
bool map_large_scenery_get_origin(
sint32 x, sint32 y, sint32 z, sint32 direction, sint32 sequence,
sint32 *outX, sint32 *outY, sint32 *outZ, rct_tile_element** outElement
) {
rct_tile_element *tileElement;
rct_scenery_entry *sceneryEntry;
rct_large_scenery_tile *tile;
sint16 offsetX, offsetY;
tileElement = map_get_large_scenery_segment(x, y, z, direction, sequence);
if (tileElement == nullptr)
return false;
sceneryEntry = get_large_scenery_entry(scenery_large_get_type(tileElement));
tile = &sceneryEntry->large_scenery.tiles[sequence];
offsetX = tile->x_offset;
offsetY = tile->y_offset;
rotate_map_coordinates(&offsetX, &offsetY, direction);
*outX = x - offsetX;
*outY = y - offsetY;
*outZ = (z * 8) - tile->z_offset;
if (outElement != nullptr)
*outElement = tileElement;
return true;
}
/**
*
* rct2: 0x006B9B05
*/
bool sign_set_colour(sint32 x, sint32 y, sint32 z, sint32 direction, sint32 sequence, uint8 mainColour, uint8 textColour)
{
rct_tile_element *tileElement;
rct_scenery_entry *sceneryEntry;
rct_large_scenery_tile *sceneryTiles, *tile;
sint16 offsetX, offsetY;
sint32 x0, y0, z0;
if (!map_large_scenery_get_origin(x, y, z, direction, sequence, &x0, &y0, &z0, &tileElement)) {
return false;
}
sceneryEntry = get_large_scenery_entry(scenery_large_get_type(tileElement));
sceneryTiles = sceneryEntry->large_scenery.tiles;
// Iterate through each tile of the large scenery element
sequence = 0;
for (tile = sceneryTiles; tile->x_offset != -1; tile++, sequence++) {
offsetX = tile->x_offset;
offsetY = tile->y_offset;
rotate_map_coordinates(&offsetX, &offsetY, direction);
x = x0 + offsetX;
y = y0 + offsetY;
z = (z0 + tile->z_offset) / 8;
tileElement = map_get_large_scenery_segment(x, y, z, direction, sequence);
if (tileElement != nullptr)
{
scenery_large_set_primary_colour(tileElement, mainColour);
scenery_large_set_secondary_colour(tileElement, textColour);
map_invalidate_tile(x, y, tileElement->base_height * 8 , tileElement->clearance_height * 8);
}
}
return true;
}
static void translate_3d_to_2d(sint32 rotation, sint32 *x, sint32 *y)
{
sint32 rx, ry;
switch (rotation & 3) {
default:
case 0:
rx = (*y) - (*x);
ry = (*x) + (*y);
break;
case 1:
rx = -(*x) - (*y);
ry = (*y) - (*x);
break;
case 2:
rx = (*x) - (*y);
ry = -(*x) - (*y);
break;
case 3:
rx = (*x) + (*y);
ry = (*x) - (*y);
break;
}
ry /= 2;
*x = rx;
*y = ry;
}
LocationXY32 translate_3d_to_2d_with_z(sint32 rotation, LocationXYZ32 pos)
{
LocationXY32 result;
switch (rotation & 3) {
default:
case 0:
result.x = pos.y - pos.x;
result.y = (pos.x + pos.y) / 2 - pos.z;
break;
case 1:
result.x = -pos.x - pos.y;
result.y = (pos.y - pos.x) / 2 - pos.z;
break;
case 2:
result.x = pos.x - pos.y;
result.y = (-pos.x - pos.y) / 2 - pos.z;
break;
case 3:
result.x = pos.x + pos.y;
result.y = (pos.x - pos.y) / 2 - pos.z;
break;
}
return result;
}
static void map_invalidate_tile_under_zoom(sint32 x, sint32 y, sint32 z0, sint32 z1, sint32 maxZoom)
{
if (gOpenRCT2Headless) return;
sint32 x1, y1, x2, y2;
x += 16;
y += 16;
translate_3d_to_2d(get_current_rotation(), &x, &y);
x1 = x - 32;
y1 = y - 32 - z1;
x2 = x + 32;
y2 = y + 32 - z0;
for (sint32 i = 0; i < MAX_VIEWPORT_COUNT; i++) {
rct_viewport *viewport = &g_viewport_list[i];
if (viewport->width != 0 && (maxZoom == -1 || viewport->zoom <= maxZoom)) {
viewport_invalidate(viewport, x1, y1, x2, y2);
}
}
}
/**
*
* rct2: 0x006EC847
*/
void map_invalidate_tile(sint32 x, sint32 y, sint32 z0, sint32 z1)
{
map_invalidate_tile_under_zoom(x, y, z0, z1, -1);
}
/**
*
* rct2: 0x006ECB60
*/
void map_invalidate_tile_zoom1(sint32 x, sint32 y, sint32 z0, sint32 z1)
{
map_invalidate_tile_under_zoom(x, y, z0, z1, 1);
}
/**
*
* rct2: 0x006EC9CE
*/
void map_invalidate_tile_zoom0(sint32 x, sint32 y, sint32 z0, sint32 z1)
{
map_invalidate_tile_under_zoom(x, y, z0, z1, 0);
}
/**
*
* rct2: 0x006EC6D7
*/
void map_invalidate_tile_full(sint32 x, sint32 y)
{
map_invalidate_tile(x, y, 0, 2080);
}
void map_invalidate_element(sint32 x, sint32 y, rct_tile_element *tileElement)
{
map_invalidate_tile(x, y, tileElement->base_height * 8, tileElement->clearance_height * 8);
}
sint32 map_get_tile_side(sint32 mapX, sint32 mapY)
{
sint32 subMapX = mapX & (32 - 1);
sint32 subMapY = mapY & (32 - 1);
return (subMapX < subMapY) ?
((subMapX + subMapY) < 32 ? 0 : 1):
((subMapX + subMapY) < 32 ? 3 : 2);
}
sint32 map_get_tile_quadrant(sint32 mapX, sint32 mapY)
{
sint32 subMapX = mapX & (32 - 1);
sint32 subMapY = mapY & (32 - 1);
return (subMapX > 16) ?
(subMapY < 16 ? 1 : 0):
(subMapY < 16 ? 2 : 3);
}
/**
*
* rct2: 0x00693BFF
*/
bool map_surface_is_blocked(sint16 x, sint16 y){
rct_tile_element *tileElement;
if (x >= 8192 || y >= 8192)
return true;
tileElement = map_get_surface_element_at(x / 32, y / 32);
if (tileElement == nullptr) {
return true;
}
sint16 water_height = map_get_water_height(tileElement);
water_height *= 2;
if (water_height > tileElement->base_height)
return true;
sint16 base_z = tileElement->base_height;
sint16 clear_z = tileElement->base_height + 2;
if (tileElement->properties.surface.slope & TILE_ELEMENT_SLOPE_DOUBLE_HEIGHT)
clear_z += 2;
while (!tile_element_is_last_for_tile(tileElement++)){
if (clear_z >= tileElement->clearance_height)
continue;
if (base_z < tileElement->base_height)
continue;
if (tile_element_get_type(tileElement) == TILE_ELEMENT_TYPE_PATH ||
tile_element_get_type(tileElement) == TILE_ELEMENT_TYPE_WALL)
continue;
if (tile_element_get_type(tileElement) != TILE_ELEMENT_TYPE_SMALL_SCENERY)
return true;
rct_scenery_entry* scenery = get_small_scenery_entry(tileElement->properties.scenery.type);
if (scenery == nullptr)
{
return false;
}
if (scenery_small_entry_has_flag(scenery, SMALL_SCENERY_FLAG_FULL_TILE))
return true;
}
return false;
}
/* Clears all map elements, to be used before generating a new map */
void map_clear_all_elements()
{
for (sint32 y = 0; y < (MAXIMUM_MAP_SIZE_TECHNICAL * 32); y += 32) {
for (sint32 x = 0; x < (MAXIMUM_MAP_SIZE_TECHNICAL * 32); x += 32) {
clear_elements_at(x, y);
}
}
}
void game_command_set_sign_name(sint32* eax, sint32* ebx, sint32* ecx, sint32* edx, sint32* esi, sint32* edi, sint32* ebp) {
static char newName[128];
if ((*ecx >= MAX_BANNERS) || (*ecx < 0))
{
log_warning("Invalid game command for setting sign name, banner id = %d", *ecx);
*ebx = MONEY32_UNDEFINED;
return;
}
rct_banner* banner = &gBanners[*ecx];
sint32 x = banner->x << 5;
sint32 y = banner->y << 5;
sint32 nameChunkIndex = *eax & 0xFFFF;
gCommandExpenditureType = RCT_EXPENDITURE_TYPE_RIDE_RUNNING_COSTS;
sint32 nameChunkOffset = nameChunkIndex - 1;
if (nameChunkOffset < 0)
nameChunkOffset = 2;
nameChunkOffset *= 12;
nameChunkOffset = std::min(nameChunkOffset, (sint32)Util::CountOf(newName) - 12);
memcpy(newName + nameChunkOffset + 0, edx, 4);
memcpy(newName + nameChunkOffset + 4, ebp, 4);
memcpy(newName + nameChunkOffset + 8, edi, 4);
if (nameChunkIndex != 0) {
*ebx = 0;
return;
}
if (!(*ebx & GAME_COMMAND_FLAG_APPLY)) {
*ebx = 0;
return;
}
if (newName[0] != 0) {
rct_string_id string_id = user_string_allocate(USER_STRING_DUPLICATION_PERMITTED, newName);
if (string_id != 0) {
rct_string_id prev_string_id = banner->string_idx;
banner->string_idx = string_id;
user_string_free(prev_string_id);
banner->flags &= ~(BANNER_FLAG_LINKED_TO_RIDE);
gfx_invalidate_screen();
} else {
gGameCommandErrorText = STR_ERR_CANT_SET_BANNER_TEXT;
*ebx = MONEY32_UNDEFINED;
return;
}
}
else{
sint32 rideIndex = banner_get_closest_ride_index(x, y, 16);
if (rideIndex == -1) {
*ebx = 0;
return;
}
banner->colour = rideIndex;
banner->flags |= BANNER_FLAG_LINKED_TO_RIDE;
rct_string_id prev_string_id = banner->string_idx;
banner->string_idx = STR_DEFAULT_SIGN;
user_string_free(prev_string_id);
gfx_invalidate_screen();
}
*ebx = 0;
}
void game_command_set_sign_style(sint32* eax, sint32* ebx, sint32* ecx, sint32* edx, sint32* esi, sint32* edi, sint32* ebp) {
uint8 bannerId = *ecx & 0xFF;
if (bannerId > Util::CountOf(gBanners)) {
log_warning("Invalid game command for setting sign style, banner id = %d", bannerId);
*ebx = MONEY32_UNDEFINED;
return;
}
rct_banner *banner = &gBanners[bannerId];
sint32 x = banner->x << 5;
sint32 y = banner->y << 5;
uint8 mainColour = (uint8)*edx;
uint8 textColour = (uint8)*edi;
if (*ebp == 0) { // small sign
rct_tile_element* tile_element = map_get_first_element_at(x / 32, y / 32);
bool wall_found = false;
do{
if (tile_element_get_type(tile_element) != TILE_ELEMENT_TYPE_WALL)
continue;
rct_scenery_entry* scenery_entry = get_wall_entry(tile_element->properties.wall.type);
if (scenery_entry->wall.scrolling_mode == 0xFF)
continue;
if (tile_element->properties.wall.banner_index != bannerId)
continue;
wall_found = true;
break;
} while (!tile_element_is_last_for_tile(tile_element++));
if (wall_found == false) {
*ebx = MONEY32_UNDEFINED;
return;
}
if (!(*ebx & GAME_COMMAND_FLAG_APPLY)) {
*ebx = 0;
return;
}
wall_set_primary_colour(tile_element, mainColour);
wall_set_secondary_colour(tile_element, textColour);
map_invalidate_tile(x, y, tile_element->base_height * 8, tile_element->clearance_height * 8);
} else { // large sign
rct_tile_element *tileElement = banner_get_tile_element(bannerId);
if (tileElement == nullptr || tile_element_get_type(tileElement) != TILE_ELEMENT_TYPE_LARGE_SCENERY) {
gGameCommandErrorText = STR_ERR_CANT_SET_BANNER_TEXT;
*ebx = MONEY32_UNDEFINED;
return;
}
if (!(*ebx & GAME_COMMAND_FLAG_APPLY)) {
*ebx = 0;
return;
}
if (!sign_set_colour(
banner->x * 32,
banner->y * 32,
tileElement->base_height,
tile_element_get_direction(tileElement),
scenery_large_get_sequence(tileElement),
mainColour,
textColour
)) {
*ebx = MONEY32_UNDEFINED;
return;
}
}
rct_window* w = window_bring_to_front_by_number(WC_BANNER, *ecx);
if (w) {
window_invalidate(w);
}
*ebx = 0;
}
void game_command_modify_tile(sint32* eax, sint32* ebx, sint32* ecx, sint32* edx, sint32* esi, sint32* edi, sint32* ebp)
{
const sint32 flags = *ebx;
const sint32 x = *ecx & 0xFF;
const sint32 y = (*ecx >> 8) & 0xFF;
const tile_inspector_instruction instruction = (const tile_inspector_instruction)*eax;
switch (instruction)
{
case TILE_INSPECTOR_ANY_REMOVE:
{
const sint16 elementIndex = *edx;
*ebx = tile_inspector_remove_element_at(x, y, elementIndex, flags);
break;
}
case TILE_INSPECTOR_ANY_SWAP:
{
const sint32 firstIndex = *edx;
const sint32 secondIndex = *edi;
*ebx = tile_inspector_swap_elements_at(x, y, firstIndex, secondIndex, flags);
break;
}
case TILE_INSPECTOR_ANY_INSERT_CORRUPT:
{
const sint16 elementIndex = *edx;
*ebx = tile_inspector_insert_corrupt_at(x, y, elementIndex, flags);
break;
}
case TILE_INSPECTOR_ANY_ROTATE:
{
const sint16 elementIndex = *edx;
*ebx = tile_inspector_rotate_element_at(x, y, elementIndex, flags);
break;
}
case TILE_INSPECTOR_ANY_PASTE:
{
rct_tile_element elementToPaste;
const sint32 data[] = { *edx, *edi };
assert_struct_size(data, sizeof(elementToPaste));
memcpy(&elementToPaste, data, 8);
*ebx = tile_inspector_paste_element_at(x, y, elementToPaste, flags);
break;
}
case TILE_INSPECTOR_ANY_SORT:
{
*ebx = tile_inspector_sort_elements_at(x, y, flags);
break;
}
case TILE_INSPECTOR_ANY_BASE_HEIGHT_OFFSET:
{
const sint16 elementIndex = *edx;
const sint8 heightOffset = *edi;
*ebx = tile_inspector_any_base_height_offset(x, y, elementIndex, heightOffset, flags);
break;
}
case TILE_INSPECTOR_SURFACE_SHOW_PARK_FENCES:
{
const bool showFences = *edx;
*ebx = tile_inspector_surface_show_park_fences(x, y, showFences, flags);
break;
}
case TILE_INSPECTOR_SURFACE_TOGGLE_CORNER:
{
const sint32 cornerIndex = *edx;
*ebx = tile_inspector_surface_toggle_corner(x, y, cornerIndex, flags);
break;
}
case TILE_INSPECTOR_SURFACE_TOGGLE_DIAGONAL:
{
*ebx = tile_inspector_surface_toggle_diagonal(x, y, flags);
break;
}
case TILE_INSPECTOR_PATH_SET_SLOPE:
{
const sint32 elementIndex = *edx;
const bool sloped = *edi;
*ebx = tile_inspector_path_set_sloped(x, y, elementIndex, sloped, flags);
break;
}
case TILE_INSPECTOR_PATH_TOGGLE_EDGE:
{
const sint32 elementIndex = *edx;
const sint32 edgeIndex = *edi;
*ebx = tile_inspector_path_toggle_edge(x, y, elementIndex, edgeIndex, flags);
break;
}
case TILE_INSPECTOR_WALL_SET_SLOPE:
{
const sint32 elementIndex = *edx;
const sint32 slopeValue = *edi;
*ebx = tile_inspector_wall_set_slope(x, y, elementIndex, slopeValue, flags);
break;
}
case TILE_INSPECTOR_TRACK_BASE_HEIGHT_OFFSET:
{
const sint32 elementIndex = *edx;
const sint8 heightOffset = *edi;
*ebx = tile_inspector_track_base_height_offset(x, y, elementIndex, heightOffset, flags);
break;
}
case TILE_INSPECTOR_TRACK_SET_CHAIN:
{
const sint32 elementIndex = *edx;
const bool entireTrackBlock = *edi;
const bool setChain = *ebp;
*ebx = tile_inspector_track_set_chain(x, y, elementIndex, entireTrackBlock, setChain, flags);
break;
}
case TILE_INSPECTOR_SCENERY_SET_QUARTER_LOCATION:
{
const sint32 elementIndex = *edx;
const sint32 quarterIndex = *edi;
*ebx = tile_inspector_scenery_set_quarter_location(x, y, elementIndex, quarterIndex, flags);
break;
}
case TILE_INSPECTOR_SCENERY_SET_QUARTER_COLLISION:
{
const sint32 elementIndex = *edx;
const sint32 quarterIndex = *edi;
*ebx = tile_inspector_scenery_set_quarter_collision(x, y, elementIndex, quarterIndex, flags);
break;
}
case TILE_INSPECTOR_BANNER_TOGGLE_BLOCKING_EDGE:
{
const sint32 elementIndex = *edx;
const sint32 edgeIndex = *edi;
*ebx = tile_inspector_banner_toggle_blocking_edge(x, y, elementIndex, edgeIndex, flags);
break;
}
case TILE_INSPECTOR_CORRUPT_CLAMP:
{
const sint32 elementIndex = *edx;
*ebx = tile_inspector_corrupt_clamp(x, y, elementIndex, flags);
break;
}
default:
log_error("invalid instruction");
*ebx = MONEY32_UNDEFINED;
break;
}
if (flags & GAME_COMMAND_FLAG_APPLY &&
gGameCommandNestLevel == 1 &&
!(flags & GAME_COMMAND_FLAG_GHOST) &&
*ebx != MONEY32_UNDEFINED)
{
LocationXYZ16 coord;
coord.x = (x << 5) + 16;
coord.y = (y << 5) + 16;
coord.z = tile_element_height(coord.x, coord.y);
network_set_player_last_action_coord(network_get_player_index(game_command_playerid), coord);
}
}
/**
* Gets the track element at x, y, z.
* @param x x units, not tiles.
* @param y y units, not tiles.
* @param z Base height.
*/
rct_tile_element *map_get_track_element_at(sint32 x, sint32 y, sint32 z)
{
rct_tile_element *tileElement = map_get_first_element_at(x >> 5, y >> 5);
do {
if (tile_element_get_type(tileElement) != TILE_ELEMENT_TYPE_TRACK) continue;
if (tileElement->base_height != z) continue;
return tileElement;
} while (!tile_element_is_last_for_tile(tileElement++));
return nullptr;
}
/**
* Gets the track element at x, y, z that is the given track type.
* @param x x units, not tiles.
* @param y y units, not tiles.
* @param z Base height.
* @param trackType
*/
rct_tile_element *map_get_track_element_at_of_type(sint32 x, sint32 y, sint32 z, sint32 trackType)
{
rct_tile_element *tileElement = map_get_first_element_at(x >> 5, y >> 5);
do {
if (tile_element_get_type(tileElement) != TILE_ELEMENT_TYPE_TRACK) continue;
if (tileElement->base_height != z) continue;
if (track_element_get_type(tileElement) != trackType) continue;
return tileElement;
} while (!tile_element_is_last_for_tile(tileElement++));
return nullptr;
}
/**
* Gets the track element at x, y, z that is the given track type and sequence.
* @param x x units, not tiles.
* @param y y units, not tiles.
* @param z Base height.
* @param trackType
* @param sequence
*/
rct_tile_element *map_get_track_element_at_of_type_seq(sint32 x, sint32 y, sint32 z, sint32 trackType, sint32 sequence)
{
rct_tile_element *tileElement = map_get_first_element_at(x >> 5, y >> 5);
do {
if (tileElement == nullptr) break;
if (tile_element_get_type(tileElement) != TILE_ELEMENT_TYPE_TRACK) continue;
if (tileElement->base_height != z) continue;
if (track_element_get_type(tileElement) != trackType) continue;
if (tile_element_get_track_sequence(tileElement) != sequence) continue;
return tileElement;
} while (!tile_element_is_last_for_tile(tileElement++));
return nullptr;
}
/**
* Gets the track element at x, y, z that is the given track type and sequence.
* @param x x units, not tiles.
* @param y y units, not tiles.
* @param z Base height.
* @param trackType
* @param ride index
*/
rct_tile_element *map_get_track_element_at_of_type_from_ride(sint32 x, sint32 y, sint32 z, sint32 trackType, sint32 rideIndex) {
rct_tile_element *tileElement = map_get_first_element_at(x >> 5, y >> 5);
do {
if (tile_element_get_type(tileElement) != TILE_ELEMENT_TYPE_TRACK) continue;
if (tileElement->base_height != z) continue;
if (track_element_get_ride_index(tileElement) != rideIndex) continue;
if (track_element_get_type(tileElement) != trackType) continue;
return tileElement;
} while (!tile_element_is_last_for_tile(tileElement++));
return nullptr;
};
/**
* Gets the track element at x, y, z that is the given track type and sequence.
* @param x x units, not tiles.
* @param y y units, not tiles.
* @param z Base height.
* @param ride index
*/
rct_tile_element *map_get_track_element_at_from_ride(sint32 x, sint32 y, sint32 z, sint32 rideIndex) {
rct_tile_element *tileElement = map_get_first_element_at(x >> 5, y >> 5);
do {
if (tile_element_get_type(tileElement) != TILE_ELEMENT_TYPE_TRACK) continue;
if (tileElement->base_height != z) continue;
if (track_element_get_ride_index(tileElement) != rideIndex) continue;
return tileElement;
} while (!tile_element_is_last_for_tile(tileElement++));
return nullptr;
};
/**
* Gets the track element at x, y, z that is the given track type and sequence.
* @param x x units, not tiles.
* @param y y units, not tiles.
* @param z Base height.
* @param direction The direction (0 - 3).
* @param ride index
*/
rct_tile_element *map_get_track_element_at_with_direction_from_ride(sint32 x, sint32 y, sint32 z, sint32 direction, sint32 rideIndex)
{
rct_tile_element *tileElement = map_get_first_element_at(x >> 5, y >> 5);
do {
if (tile_element_get_type(tileElement) != TILE_ELEMENT_TYPE_TRACK) continue;
if (tileElement->base_height != z) continue;
if (track_element_get_ride_index(tileElement) != rideIndex) continue;
if (tile_element_get_direction(tileElement) != direction) continue;
return tileElement;
} while (!tile_element_is_last_for_tile(tileElement++));
return nullptr;
};
void map_offset_with_rotation(sint16 *x, sint16 *y, sint16 offsetX, sint16 offsetY, uint8 rotation)
{
switch (rotation & 3) {
case TILE_ELEMENT_DIRECTION_WEST:
*x += offsetX;
*y += offsetY;
break;
case TILE_ELEMENT_DIRECTION_NORTH:
*x += offsetY;
*y -= offsetX;
break;
case TILE_ELEMENT_DIRECTION_EAST:
*x -= offsetX;
*y -= offsetY;
break;
case TILE_ELEMENT_DIRECTION_SOUTH:
*x -= offsetY;
*y += offsetX;
break;
}
}
rct_tile_element *map_get_wall_element_at(sint32 x, sint32 y, sint32 z, sint32 direction)
{
rct_tile_element *tileElement = map_get_first_element_at(x >> 5, y >> 5);
do {
if (tile_element_get_type(tileElement) != TILE_ELEMENT_TYPE_WALL)
continue;
if (tileElement->base_height != z)
continue;
if (tile_element_get_direction(tileElement) != direction)
continue;
return tileElement;
} while (!tile_element_is_last_for_tile(tileElement++));
return nullptr;
}
uint32 map_get_available_peep_spawn_index_list(uint32* peepSpawnIndexList)
{
uint32 numSpawns = 0;
for (uint8 i = 0; i < MAX_PEEP_SPAWNS; i++) {
if (gPeepSpawns[i].x != PEEP_SPAWN_UNDEFINED) {
peepSpawnIndexList[numSpawns++] = i;
}
}
return numSpawns;
}
uint16 check_max_allowable_land_rights_for_tile(uint8 x, uint8 y, uint8 base_z)
{
rct_tile_element * tileElement = map_get_first_element_at(x, y);
uint16 destOwnership = OWNERSHIP_OWNED;
// Sometimes done deliberately.
if (tileElement == nullptr)
{
return OWNERSHIP_OWNED;
}
do
{
sint32 type = tile_element_get_type(tileElement);
if (type == TILE_ELEMENT_TYPE_PATH ||
(type == TILE_ELEMENT_TYPE_ENTRANCE && tileElement->properties.entrance.type == ENTRANCE_TYPE_PARK_ENTRANCE))
{
destOwnership = OWNERSHIP_CONSTRUCTION_RIGHTS_OWNED;
// Do not own construction rights if too high/below surface
if (tileElement->base_height - 3 > base_z ||
tileElement->base_height < base_z)
{
destOwnership = OWNERSHIP_UNOWNED;
break;
}
}
}
while (!tile_element_is_last_for_tile(tileElement++));
return destOwnership;
}
uint8 tile_element_get_ride_index(const rct_tile_element * tileElement)
{
switch (tile_element_get_type(tileElement))
{
case TILE_ELEMENT_TYPE_TRACK:
return track_element_get_ride_index(tileElement);
case TILE_ELEMENT_TYPE_ENTRANCE:
return tileElement->properties.entrance.ride_index;
case TILE_ELEMENT_TYPE_PATH:
return tileElement->properties.path.ride_index;
default:
return 0xFF;
}
}
void map_set_virtual_floor_height(sint16 height)
{
if (!gMapVirtualFloorVisible)
{
// If the modifiers are not set we do not actually care as the floor is invisible.
return;
}
if (gMapVirtualFloorHeight != height)
{
map_invalidate_virtual_floor_tiles();
gMapVirtualFloorHeight = height;
}
}
void map_enable_virtual_floor()
{
gMapVirtualFloorVisible = true;
}
void map_remove_virtual_floor()
{
if (!gMapVirtualFloorVisible)
{
return;
}
// Force invalidation, even if the position hasn't changed.
gVirtualFloorLastLocation.z = -1;
map_invalidate_virtual_floor_tiles();
gMapVirtualFloorHeight = 0;
gMapVirtualFloorVisible = false;
}
void map_invalidate_virtual_floor_tiles()
{
if (!gMapVirtualFloorVisible)
{
return;
}
LocationXY16 min_position = { 0x7FFF, 0x7FFF };
LocationXY16 max_position = { 0, 0 };
if ((gMapSelectFlags & MAP_SELECT_FLAG_ENABLE))
{
min_position = gMapSelectPositionA;
max_position = gMapSelectPositionB;
}
if (gMapSelectFlags & MAP_SELECT_FLAG_ENABLE_CONSTRUCT)
{
for (LocationXY16 * tile = gMapSelectionTiles; tile->x != -1; tile++)
{
min_position.x = std::min(min_position.x, tile->x);
min_position.y = std::min(min_position.y, tile->y);
max_position.x = std::max(max_position.x, tile->x);
max_position.y = std::max(max_position.y, tile->y);
}
}
min_position.x -= gMapVirtualFloorBaseSize + 1;
min_position.y -= gMapVirtualFloorBaseSize + 1;
max_position.x += gMapVirtualFloorBaseSize + 1;
max_position.y += gMapVirtualFloorBaseSize + 1;
if (gVirtualFloorLastLocation.x == min_position.x &&
gVirtualFloorLastLocation.y == min_position.y &&
gVirtualFloorLastLocation.z == gMapVirtualFloorHeight)
return;
for (sint16 x = min_position.x; x < max_position.x; x++)
{
for (sint16 y = min_position.y; y < max_position.y; y++)
{
map_invalidate_tile_full(x, y);
}
}
gVirtualFloorLastLocation.x = min_position.x;
gVirtualFloorLastLocation.y = min_position.y;
gVirtualFloorLastLocation.z = gMapVirtualFloorHeight;
}
bool map_tile_is_part_of_virtual_floor(sint16 x, sint16 y)
{
if (!gMapVirtualFloorVisible)
{
return false;
}
// Check if map selection (usually single tiles) are enabled
// and if the current tile is near or on them
if ((gMapSelectFlags & MAP_SELECT_FLAG_ENABLE) &&
x >= gMapSelectPositionA.x - gMapVirtualFloorBaseSize &&
y >= gMapSelectPositionA.y - gMapVirtualFloorBaseSize &&
x <= gMapSelectPositionB.x + gMapVirtualFloorBaseSize &&
y <= gMapSelectPositionB.y + gMapVirtualFloorBaseSize)
{
return true;
}
else if (gMapSelectFlags & MAP_SELECT_FLAG_ENABLE_CONSTRUCT)
{
// Check if we are anywhere near the selection tiles (larger scenery / rides)
for (LocationXY16 * tile = gMapSelectionTiles; tile->x != -1; tile++)
{
if (x >= tile->x - gMapVirtualFloorBaseSize &&
y >= tile->y - gMapVirtualFloorBaseSize &&
x <= tile->x + gMapVirtualFloorBaseSize &&
y <= tile->y + gMapVirtualFloorBaseSize)
{
return true;
}
}
}
return false;
}