#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 "MapAnimation.h" #include "Park.h" #include "Scenery.h" #include "SmallScenery.h" #include "TileInspector.h" #include "Wall.h" #include /** * Replaces 0x00993CCC, 0x00993CCE */ const CoordsXY 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 gVirtualFloorLastMinLocation; static LocationXYZ16 gVirtualFloorLastMaxLocation; PeepSpawn 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_surface_element_at(CoordsXY coords) { return map_get_surface_element_at(coords.x / 32, coords.y / 32); } 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(); auto intent = Intent(INTENT_ACTION_MAP); context_broadcast_intent(&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, y_tile}); 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, y}); 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, y}); 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, y}); 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((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(¤tTile.x, ¤tTile.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((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(¤tTile.x, ¤tTile.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, y}); 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 constexpr 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 constexpr 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, y}); 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, y}); 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, yi}); 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, yi}); 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, yi}); 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, yi}); 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, yi}); 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, yi}); 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, yi}); 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, yi}); 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, y}); nextTileElement = map_get_surface_element_at({x + stepX, y + stepY}); 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, y + stepY}); 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, y}); 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, mapTop}); 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, y}); 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, y}); 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, y}); 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, y}); 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, y}); 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, curTile.y}); 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 != nullptr); 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++) { PeepSpawn *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; } CoordsXY translate_3d_to_2d_with_z(sint32 rotation, CoordsXYZ pos) { CoordsXY result = { 0 }; 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, y}); 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; } } auto intent = Intent(INTENT_ACTION_UPDATE_BANNER); intent.putExtra(INTENT_EXTRA_BANNER_INDEX, *ecx); context_broadcast_intent(&intent); *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_TYPE instruction = static_cast(*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_ENTRANCE_MAKE_USABLE: { const sint32 elementIndex = *edx; *ebx = tile_inspector_entrance_make_usable(x, y, elementIndex, 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() { if (gMapVirtualFloorVisible) { return; } // Force invalidation on the next draw. gVirtualFloorLastMinLocation.z = std::numeric_limits::max(); gVirtualFloorLastMaxLocation.z = std::numeric_limits::lowest(); gMapVirtualFloorVisible = true; } void map_remove_virtual_floor() { if (!gMapVirtualFloorVisible) { return; } // Force invalidation, even if the position hasn't changed. gVirtualFloorLastMinLocation.z = std::numeric_limits::max(); gVirtualFloorLastMaxLocation.z = std::numeric_limits::lowest(); map_invalidate_virtual_floor_tiles(); gMapVirtualFloorHeight = 0; gMapVirtualFloorVisible = false; } void map_invalidate_virtual_floor_tiles() { if (!gMapVirtualFloorVisible) { return; } // First, let's figure out how big our selection is. LocationXY16 min_position = { std::numeric_limits::max(), std::numeric_limits::max() }; LocationXY16 max_position = { std::numeric_limits::lowest(), std::numeric_limits::lowest() }; 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); } } // Apply the virtual floor size to the computed invalidation area. min_position.x -= gMapVirtualFloorBaseSize + 1; min_position.y -= gMapVirtualFloorBaseSize + 1; max_position.x += gMapVirtualFloorBaseSize + 1; max_position.y += gMapVirtualFloorBaseSize + 1; // Do not invalidate if floor hasn't moved. if (gVirtualFloorLastMinLocation.x == min_position.x && gVirtualFloorLastMinLocation.y == min_position.y && gVirtualFloorLastMinLocation.z == gMapVirtualFloorHeight) { return; } LocationXY16 corr_min_position = min_position; LocationXY16 corr_max_position = max_position; // Invalidate previous locations, too, if appropriate. if (gVirtualFloorLastMinLocation.z != std::numeric_limits::max() && gVirtualFloorLastMaxLocation.z != std::numeric_limits::lowest()) { corr_min_position.x = std::min(min_position.x, gVirtualFloorLastMinLocation.x); corr_min_position.y = std::min(min_position.y, gVirtualFloorLastMinLocation.y); corr_max_position.x = std::max(max_position.x, gVirtualFloorLastMaxLocation.x); corr_max_position.y = std::max(max_position.y, gVirtualFloorLastMaxLocation.y); } for (sint16 x = corr_min_position.x; x < corr_max_position.x; x++) { for (sint16 y = corr_min_position.y; y < corr_max_position.y; y++) { map_invalidate_tile_full(x, y); } } // Save minimal and maximal positions. Note: not their corrected positions! gVirtualFloorLastMinLocation.x = min_position.x; gVirtualFloorLastMinLocation.y = min_position.y; gVirtualFloorLastMinLocation.z = gMapVirtualFloorHeight; gVirtualFloorLastMaxLocation.x = max_position.x; gVirtualFloorLastMaxLocation.y = max_position.y; gVirtualFloorLastMaxLocation.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; } void FixLandOwnershipTiles(std::initializer_list tiles) { FixLandOwnershipTilesWithOwnership(tiles, OWNERSHIP_AVAILABLE); } void FixLandOwnershipTilesWithOwnership(std::initializer_list tiles, uint8 ownership) { rct_tile_element * currentElement; for (const TileCoordsXY * tile = tiles.begin(); tile != tiles.end(); ++tile) { currentElement = map_get_surface_element_at((*tile).x, (*tile).y); currentElement->properties.surface.ownership |= ownership; update_park_fences_around_tile((*tile).x * 32, (*tile).y * 32); } } uint8 entrance_element_get_type(const rct_tile_element * tileElement) { return (tileElement->properties.entrance.type); }