/***************************************************************************** * Copyright (c) 2014-2020 OpenRCT2 developers * * For a complete list of all authors, please refer to contributors.md * Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2 * * OpenRCT2 is licensed under the GNU General Public License version 3. *****************************************************************************/ #include "TileInspector.h" #include "../Context.h" #include "../Game.h" #include "../actions/GameAction.h" #include "../common.h" #include "../core/Guard.hpp" #include "../interface/Window.h" #include "../interface/Window_internal.h" #include "../localisation/Localisation.h" #include "../ride/Station.h" #include "../ride/Track.h" #include "../ride/TrackData.h" #include "../windows/Intent.h" #include "../windows/tile_inspector.h" #include "Banner.h" #include "Footpath.h" #include "LargeScenery.h" #include "Map.h" #include "Park.h" #include "Scenery.h" #include "Surface.h" #include #include TileCoordsXY windowTileInspectorTile; int32_t windowTileInspectorElementCount = 0; int32_t windowTileInspectorSelectedIndex; namespace OpenRCT2::TileInspector { static bool SwapTileElements(const CoordsXY& loc, int16_t first, int16_t second) { TileElement* const firstElement = map_get_nth_element_at(loc, first); TileElement* const secondElement = map_get_nth_element_at(loc, second); if (firstElement == nullptr) { log_error("First element is out of range for the tile"); return false; } if (secondElement == nullptr) { log_error("Second element is out of range for the tile"); return false; } if (firstElement == secondElement) { log_error("Can't swap the element with itself"); return false; } // Swap their memory std::swap(*firstElement, *secondElement); // Swap the 'last map element for tile' flag if either one of them was last if ((firstElement)->IsLastForTile() || (secondElement)->IsLastForTile()) { firstElement->SetLastForTile(!firstElement->IsLastForTile()); secondElement->SetLastForTile(!secondElement->IsLastForTile()); } return true; } static rct_window* GetTileInspectorWithPos(const CoordsXY& loc) { // Return the tile inspector window for everyone who has the tile selected auto* window = window_find_by_class(WC_TILE_INSPECTOR); if (window != nullptr && loc == windowTileInspectorTile.ToCoordsXY()) { return window; } return nullptr; } /** * Inserts a corrupt element under a given element on a given tile * @param x The x coordinate of the tile * @param y The y coordinate of the tile * @param elementIndex The nth element on this tile * Returns 0 on success, MONEY_UNDEFINED otherwise. */ GameActionResultPtr InsertCorruptElementAt(const CoordsXY& loc, int16_t elementIndex, bool isExecuting) { // Make sure there is enough space for the new element if (!map_check_free_elements_and_reorganise(1)) return std::make_unique(GameActions::Status::NoFreeElements, STR_NONE); if (isExecuting) { // Create new corrupt element TileElement* corruptElement = tile_element_insert( { loc, (-1 * COORDS_Z_STEP) }, 0b0000, TileElementType::Corrupt); // Ugly hack: -1 guarantees this to be placed first if (corruptElement == nullptr) { log_warning("Failed to insert corrupt element."); return std::make_unique(GameActions::Status::Unknown, STR_NONE); } // Set the base height to be the same as the selected element TileElement* const selectedElement = map_get_nth_element_at(loc, elementIndex + 1); if (selectedElement == nullptr) { return std::make_unique(GameActions::Status::Unknown, STR_NONE); } corruptElement->base_height = corruptElement->clearance_height = selectedElement->base_height; // Move the corrupt element up until the selected list item is reached // this way it's placed under the selected element, even when there are multiple elements with the same base height for (int16_t i = 0; i < elementIndex; i++) { if (!SwapTileElements(loc, i, i + 1)) { // don't return error here, we've already inserted an element // and moved it as far as we could, the only sensible thing left // to do is to invalidate the window. break; } } map_invalidate_tile_full(loc); if (auto* inspector = GetTileInspectorWithPos(loc); inspector != nullptr) { windowTileInspectorElementCount++; if (windowTileInspectorSelectedIndex > elementIndex) { windowTileInspectorSelectedIndex++; } inspector->Invalidate(); } } // Nothing went wrong return std::make_unique(); } static int32_t numLargeScenerySequences(const CoordsXY& loc, const LargeSceneryElement* const largeScenery) { const rct_scenery_entry* const largeEntry = largeScenery->GetEntry(); const auto direction = largeScenery->GetDirection(); const auto sequenceIndex = largeScenery->GetSequenceIndex(); const auto* tiles = largeEntry->large_scenery.tiles; const auto& tile = tiles[sequenceIndex]; const auto rotatedFirstTile = CoordsXYZ{ CoordsXY{ tile.x_offset, tile.y_offset }.Rotate(direction), tile.z_offset, }; const auto firstTile = CoordsXYZ{ loc, largeScenery->GetBaseZ() } - rotatedFirstTile; auto numFoundElements = 0; for (int32_t i = 0; tiles[i].x_offset != -1; i++) { const auto rotatedCurrentTile = CoordsXYZ{ CoordsXY{ tiles[i].x_offset, tiles[i].y_offset }.Rotate(direction), tiles[i].z_offset }; const auto currentTile = firstTile + rotatedCurrentTile; const TileElement* tileElement = map_get_first_element_at(currentTile); if (tileElement != nullptr) { do { if (tileElement->GetType() != TILE_ELEMENT_TYPE_LARGE_SCENERY) continue; if (tileElement->GetDirection() != direction) continue; if (tileElement->AsLargeScenery()->GetSequenceIndex() != i) continue; if (tileElement->GetBaseZ() != currentTile.z) continue; numFoundElements++; break; } while (!(tileElement++)->IsLastForTile()); } } return numFoundElements; } /** * Forcefully removes an element for a given tile * @param x The x coordinate of the tile * @param y The y coordinate of the tile * @param elementIndex The nth element on this tile */ GameActionResultPtr RemoveElementAt(const CoordsXY& loc, int16_t elementIndex, bool isExecuting) { if (isExecuting) { // Forcefully remove the element TileElement* const tileElement = map_get_nth_element_at(loc, elementIndex); if (tileElement == nullptr) { return std::make_unique(GameActions::Status::Unknown, STR_NONE); } auto largeScenery = tileElement->AsLargeScenery(); if (largeScenery) { // Only delete the banner entry if there are no other parts of the large scenery to delete if (numLargeScenerySequences(loc, largeScenery) == 1) { tileElement->RemoveBannerEntry(); } } else { // Removes any potential banners from the entry tileElement->RemoveBannerEntry(); } tile_element_remove(tileElement); map_invalidate_tile_full(loc); if (auto* inspector = GetTileInspectorWithPos(loc); inspector != nullptr) { // Update the window windowTileInspectorElementCount--; if (windowTileInspectorSelectedIndex > elementIndex) { windowTileInspectorSelectedIndex--; } else if (windowTileInspectorSelectedIndex == elementIndex) { windowTileInspectorSelectedIndex = -1; } inspector->Invalidate(); } } return std::make_unique(); } GameActionResultPtr SwapElementsAt(const CoordsXY& loc, int16_t first, int16_t second, bool isExecuting) { if (isExecuting) { if (!SwapTileElements(loc, first, second)) { return std::make_unique(GameActions::Status::Unknown, STR_NONE); } map_invalidate_tile_full(loc); if (auto* inspector = GetTileInspectorWithPos(loc); inspector != nullptr) { // If one of them was selected, update selected list item if (windowTileInspectorSelectedIndex == first) windowTileInspectorSelectedIndex = second; else if (windowTileInspectorSelectedIndex == second) windowTileInspectorSelectedIndex = first; inspector->Invalidate(); } } return std::make_unique(); } GameActionResultPtr RotateElementAt(const CoordsXY& loc, int32_t elementIndex, bool isExecuting) { if (isExecuting) { uint8_t newRotation, pathEdges, pathCorners; TileElement* const tileElement = map_get_nth_element_at(loc, elementIndex); if (tileElement == nullptr) { return std::make_unique(GameActions::Status::Unknown, STR_NONE); } switch (tileElement->GetType()) { case TILE_ELEMENT_TYPE_PATH: if (tileElement->AsPath()->IsSloped()) { newRotation = (tileElement->AsPath()->GetSlopeDirection() + 1) & TILE_ELEMENT_DIRECTION_MASK; tileElement->AsPath()->SetSlopeDirection(newRotation); } pathEdges = tileElement->AsPath()->GetEdges(); pathCorners = tileElement->AsPath()->GetCorners(); tileElement->AsPath()->SetEdges((pathEdges << 1) | (pathEdges >> 3)); tileElement->AsPath()->SetCorners((pathCorners << 1) | (pathCorners >> 3)); break; case TILE_ELEMENT_TYPE_ENTRANCE: { // Update element rotation newRotation = tileElement->GetDirectionWithOffset(1); tileElement->SetDirection(newRotation); // Update ride's known entrance/exit rotation auto ride = get_ride(tileElement->AsEntrance()->GetRideIndex()); if (ride != nullptr) { auto stationIndex = tileElement->AsEntrance()->GetStationIndex(); auto entrance = ride_get_entrance_location(ride, stationIndex); auto exit = ride_get_exit_location(ride, stationIndex); uint8_t entranceType = tileElement->AsEntrance()->GetEntranceType(); uint8_t z = tileElement->base_height; // Make sure this is the correct entrance or exit if (entranceType == ENTRANCE_TYPE_RIDE_ENTRANCE && entrance.x == loc.x / 32 && entrance.y == loc.y / 32 && entrance.z == z) { ride_set_entrance_location(ride, stationIndex, { entrance.x, entrance.y, entrance.z, newRotation }); } else if ( entranceType == ENTRANCE_TYPE_RIDE_EXIT && exit.x == loc.x / 32 && exit.y == loc.y / 32 && exit.z == z) { ride_set_exit_location(ride, stationIndex, { exit.x, exit.y, exit.z, newRotation }); } } break; } case TILE_ELEMENT_TYPE_TRACK: case TILE_ELEMENT_TYPE_SMALL_SCENERY: case TILE_ELEMENT_TYPE_WALL: newRotation = tileElement->GetDirectionWithOffset(1); tileElement->SetDirection(newRotation); break; case TILE_ELEMENT_TYPE_BANNER: { uint8_t unblockedEdges = tileElement->AsBanner()->GetAllowedEdges(); unblockedEdges = (unblockedEdges << 1 | unblockedEdges >> 3) & 0xF; tileElement->AsBanner()->SetAllowedEdges(unblockedEdges); tileElement->AsBanner()->SetPosition((tileElement->AsBanner()->GetPosition() + 1) & 3); break; } } map_invalidate_tile_full(loc); if (auto* inspector = GetTileInspectorWithPos(loc); inspector != nullptr) { inspector->Invalidate(); } } return std::make_unique(); } GameActionResultPtr PasteElementAt(const CoordsXY& loc, TileElement element, bool isExecuting) { // Make sure there is enough space for the new element if (!map_check_free_elements_and_reorganise(1)) { return std::make_unique(GameActions::Status::NoFreeElements, STR_NONE); } auto tileLoc = TileCoordsXY(loc); if (isExecuting) { // Check if the element to be pasted refers to a banner index auto bannerIndex = element.GetBannerIndex(); if (bannerIndex != BANNER_INDEX_NULL) { // The element to be pasted refers to a banner index - make a copy of it auto newBannerIndex = create_new_banner(GAME_COMMAND_FLAG_APPLY); if (newBannerIndex == BANNER_INDEX_NULL) { return std::make_unique(GameActions::Status::Unknown, STR_NONE); } auto& newBanner = *GetBanner(newBannerIndex); newBanner = *GetBanner(bannerIndex); newBanner.position = tileLoc; // Use the new banner index element.SetBannerIndex(newBannerIndex); } // The occupiedQuadrants will be automatically set when the element is copied over, so it's not necessary to set // them correctly _here_. TileElement* const pastedElement = tile_element_insert( { loc, element.GetBaseZ() }, 0b0000, TileElementType::Surface); bool lastForTile = pastedElement->IsLastForTile(); *pastedElement = element; pastedElement->SetLastForTile(lastForTile); map_invalidate_tile_full(loc); if (auto* inspector = GetTileInspectorWithPos(loc); inspector != nullptr) { windowTileInspectorElementCount++; // Select new element if there was none selected already int16_t newIndex = static_cast((pastedElement - map_get_first_element_at(loc))); if (windowTileInspectorSelectedIndex == -1) windowTileInspectorSelectedIndex = newIndex; else if (windowTileInspectorSelectedIndex >= newIndex) windowTileInspectorSelectedIndex++; inspector->Invalidate(); } } return std::make_unique(); } GameActionResultPtr SortElementsAt(const CoordsXY& loc, bool isExecuting) { if (isExecuting) { const TileElement* const firstElement = map_get_first_element_at(loc); if (firstElement == nullptr) return std::make_unique(GameActions::Status::Unknown, STR_NONE); // Count elements on tile int32_t numElement = 0; const TileElement* elementIterator = firstElement; do { numElement++; } while (!(elementIterator++)->IsLastForTile()); // Bubble sort for (int32_t loopStart = 1; loopStart < numElement; loopStart++) { int32_t currentId = loopStart; const TileElement* currentElement = firstElement + currentId; const TileElement* otherElement = currentElement - 1; // While current element's base height is lower, or (when their baseheight is the same) the other map element's // clearance height is lower... while (currentId > 0 && (otherElement->base_height > currentElement->base_height || (otherElement->base_height == currentElement->base_height && otherElement->clearance_height > currentElement->clearance_height))) { if (!SwapTileElements(loc, currentId - 1, currentId)) { // don't return error here, we've already ran some actions // and moved things as far as we could, the only sensible // thing left to do is to invalidate the window. loopStart = numElement; break; } currentId--; currentElement--; otherElement--; } } map_invalidate_tile_full(loc); if (auto* inspector = GetTileInspectorWithPos(loc); inspector != nullptr) { // Deselect tile for clients who had it selected windowTileInspectorSelectedIndex = -1; inspector->Invalidate(); } } return std::make_unique(); } static GameActionResultPtr ValidateTileHeight(TileElement* const tileElement, int8_t heightOffset) { int16_t newBaseHeight = static_cast(tileElement->base_height + heightOffset); int16_t newClearanceHeight = static_cast(tileElement->clearance_height + heightOffset); if (newBaseHeight < 0) { return std::make_unique(GameActions::Status::TooLow, STR_CANT_LOWER_ELEMENT_HERE, STR_TOO_LOW); } else if (newBaseHeight > MAX_ELEMENT_HEIGHT) { return std::make_unique( GameActions::Status::TooHigh, STR_CANT_RAISE_ELEMENT_HERE, STR_TOO_HIGH); } else if (newClearanceHeight < 0) { return std::make_unique( GameActions::Status::NoClearance, STR_CANT_LOWER_ELEMENT_HERE, STR_NO_CLEARANCE); } else if (newClearanceHeight > MAX_ELEMENT_HEIGHT) { return std::make_unique( GameActions::Status::NoClearance, STR_CANT_RAISE_ELEMENT_HERE, STR_NO_CLEARANCE); } return std::make_unique(); } GameActionResultPtr AnyBaseHeightOffset(const CoordsXY& loc, int16_t elementIndex, int8_t heightOffset, bool isExecuting) { TileElement* const tileElement = map_get_nth_element_at(loc, elementIndex); if (tileElement == nullptr) return std::make_unique(GameActions::Status::Unknown, STR_NONE); auto heightValidationResult = ValidateTileHeight(tileElement, heightOffset); if (heightValidationResult->Error != GameActions::Status::Ok) return heightValidationResult; if (isExecuting) { if (tileElement->GetType() == TILE_ELEMENT_TYPE_ENTRANCE) { uint8_t entranceType = tileElement->AsEntrance()->GetEntranceType(); if (entranceType != ENTRANCE_TYPE_PARK_ENTRANCE) { // Update the ride's known entrance or exit height auto ride = get_ride(tileElement->AsEntrance()->GetRideIndex()); if (ride != nullptr) { auto entranceIndex = tileElement->AsEntrance()->GetStationIndex(); auto entrance = ride_get_entrance_location(ride, entranceIndex); auto exit = ride_get_exit_location(ride, entranceIndex); uint8_t z = tileElement->base_height; // Make sure this is the correct entrance or exit if (entranceType == ENTRANCE_TYPE_RIDE_ENTRANCE && entrance == TileCoordsXYZ{ loc, z }) ride_set_entrance_location( ride, entranceIndex, { entrance.x, entrance.y, z + heightOffset, entrance.direction }); else if (entranceType == ENTRANCE_TYPE_RIDE_EXIT && exit == TileCoordsXYZ{ loc, z }) ride_set_exit_location(ride, entranceIndex, { exit.x, exit.y, z + heightOffset, exit.direction }); } } } tileElement->base_height += heightOffset; tileElement->clearance_height += heightOffset; map_invalidate_tile_full(loc); if (auto* inspector = GetTileInspectorWithPos(loc); inspector != nullptr) { inspector->Invalidate(); } } return std::make_unique(); } GameActionResultPtr SurfaceShowParkFences(const CoordsXY& loc, bool showFences, bool isExecuting) { auto* const surfaceelement = map_get_surface_element_at(loc); // No surface element on tile if (surfaceelement == nullptr) return std::make_unique(GameActions::Status::Unknown, STR_NONE); if (isExecuting) { if (!showFences) surfaceelement->SetParkFences(0); else update_park_fences(loc); map_invalidate_tile_full(loc); if (auto* inspector = GetTileInspectorWithPos(loc); inspector != nullptr) { inspector->Invalidate(); } } return std::make_unique(); } GameActionResultPtr SurfaceToggleCorner(const CoordsXY& loc, int32_t cornerIndex, bool isExecuting) { auto* const surfaceElement = map_get_surface_element_at(loc); // No surface element on tile if (surfaceElement == nullptr) return std::make_unique(GameActions::Status::Unknown, STR_NONE); if (isExecuting) { const uint8_t originalSlope = surfaceElement->GetSlope(); const bool diagonal = (originalSlope & TILE_ELEMENT_SLOPE_DOUBLE_HEIGHT) >> 4; uint8_t newSlope = surfaceElement->GetSlope() ^ (1 << cornerIndex); surfaceElement->SetSlope(newSlope); if (surfaceElement->GetSlope() & TILE_ELEMENT_SLOPE_ALL_CORNERS_UP) { surfaceElement->clearance_height = surfaceElement->base_height + 2; } else { surfaceElement->clearance_height = surfaceElement->base_height; } // All corners are raised if ((surfaceElement->GetSlope() & TILE_ELEMENT_SLOPE_ALL_CORNERS_UP) == TILE_ELEMENT_SLOPE_ALL_CORNERS_UP) { uint8_t slope = TILE_ELEMENT_SLOPE_FLAT; if (diagonal) { switch (originalSlope & TILE_ELEMENT_SLOPE_ALL_CORNERS_UP) { case TILE_ELEMENT_SLOPE_S_CORNER_DN: slope |= TILE_ELEMENT_SLOPE_N_CORNER_UP; break; case TILE_ELEMENT_SLOPE_W_CORNER_DN: slope |= TILE_ELEMENT_SLOPE_E_CORNER_UP; break; case TILE_ELEMENT_SLOPE_N_CORNER_DN: slope |= TILE_ELEMENT_SLOPE_S_CORNER_UP; break; case TILE_ELEMENT_SLOPE_E_CORNER_DN: slope |= TILE_ELEMENT_SLOPE_W_CORNER_UP; break; } } surfaceElement->SetSlope(slope); // Update base and clearance heights surfaceElement->base_height += 2; surfaceElement->clearance_height = surfaceElement->base_height + (diagonal ? 2 : 0); } map_invalidate_tile_full(loc); if (auto* inspector = GetTileInspectorWithPos(loc); inspector != nullptr) { inspector->Invalidate(); } } return std::make_unique(); } GameActionResultPtr SurfaceToggleDiagonal(const CoordsXY& loc, bool isExecuting) { auto* const surfaceElement = map_get_surface_element_at(loc); // No surface element on tile if (surfaceElement == nullptr) return std::make_unique(GameActions::Status::Unknown, STR_NONE); if (isExecuting) { uint8_t newSlope = surfaceElement->GetSlope() ^ TILE_ELEMENT_SLOPE_DOUBLE_HEIGHT; surfaceElement->SetSlope(newSlope); if (surfaceElement->GetSlope() & TILE_ELEMENT_SLOPE_DOUBLE_HEIGHT) { surfaceElement->clearance_height = surfaceElement->base_height + 4; } else if (surfaceElement->GetSlope() & TILE_ELEMENT_SLOPE_ALL_CORNERS_UP) { surfaceElement->clearance_height = surfaceElement->base_height + 2; } else { surfaceElement->clearance_height = surfaceElement->base_height; } map_invalidate_tile_full(loc); if (auto* inspector = GetTileInspectorWithPos(loc); inspector != nullptr) { inspector->Invalidate(); } } return std::make_unique(); } GameActionResultPtr PathSetSloped(const CoordsXY& loc, int32_t elementIndex, bool sloped, bool isExecuting) { TileElement* const pathElement = map_get_nth_element_at(loc, elementIndex); if (pathElement == nullptr || pathElement->GetType() != TILE_ELEMENT_TYPE_PATH) return std::make_unique(GameActions::Status::Unknown, STR_NONE); if (isExecuting) { pathElement->AsPath()->SetSloped(sloped); map_invalidate_tile_full(loc); if (auto* inspector = GetTileInspectorWithPos(loc); inspector != nullptr) { inspector->Invalidate(); } } return std::make_unique(); } GameActionResultPtr PathSetBroken(const CoordsXY& loc, int32_t elementIndex, bool broken, bool isExecuting) { TileElement* const pathElement = map_get_nth_element_at(loc, elementIndex); if (pathElement == nullptr || pathElement->GetType() != TILE_ELEMENT_TYPE_PATH) return std::make_unique(GameActions::Status::Unknown, STR_NONE); if (isExecuting) { pathElement->AsPath()->SetIsBroken(broken); map_invalidate_tile_full(loc); if (auto* inspector = GetTileInspectorWithPos(loc); inspector != nullptr) { inspector->Invalidate(); } } return std::make_unique(); } GameActionResultPtr PathToggleEdge(const CoordsXY& loc, int32_t elementIndex, int32_t edgeIndex, bool isExecuting) { TileElement* const pathElement = map_get_nth_element_at(loc, elementIndex); if (pathElement == nullptr || pathElement->GetType() != TILE_ELEMENT_TYPE_PATH) return std::make_unique(GameActions::Status::Unknown, STR_NONE); if (isExecuting) { uint8_t newEdges = pathElement->AsPath()->GetEdgesAndCorners() ^ (1 << edgeIndex); pathElement->AsPath()->SetEdgesAndCorners(newEdges); map_invalidate_tile_full(loc); if (auto* inspector = GetTileInspectorWithPos(loc); inspector != nullptr) { inspector->Invalidate(); } } return std::make_unique(); } GameActionResultPtr EntranceMakeUsable(const CoordsXY& loc, int32_t elementIndex, bool isExecuting) { TileElement* const entranceElement = map_get_nth_element_at(loc, elementIndex); if (entranceElement == nullptr || entranceElement->GetType() != TILE_ELEMENT_TYPE_ENTRANCE) return std::make_unique(GameActions::Status::Unknown, STR_NONE); auto ride = get_ride(entranceElement->AsEntrance()->GetRideIndex()); if (ride == nullptr) return std::make_unique(GameActions::Status::Unknown, STR_NONE); if (isExecuting) { auto stationIndex = entranceElement->AsEntrance()->GetStationIndex(); switch (entranceElement->AsEntrance()->GetEntranceType()) { case ENTRANCE_TYPE_RIDE_ENTRANCE: ride_set_entrance_location( ride, stationIndex, { loc.x / 32, loc.y / 32, entranceElement->base_height, static_cast(entranceElement->GetDirection()) }); break; case ENTRANCE_TYPE_RIDE_EXIT: ride_set_exit_location( ride, stationIndex, { loc.x / 32, loc.y / 32, entranceElement->base_height, static_cast(entranceElement->GetDirection()) }); break; } if (auto* inspector = GetTileInspectorWithPos(loc); inspector != nullptr) { inspector->Invalidate(); } } return std::make_unique(); } GameActionResultPtr WallSetSlope(const CoordsXY& loc, int32_t elementIndex, int32_t slopeValue, bool isExecuting) { TileElement* const wallElement = map_get_nth_element_at(loc, elementIndex); if (wallElement == nullptr || wallElement->GetType() != TILE_ELEMENT_TYPE_WALL) return std::make_unique(GameActions::Status::Unknown, STR_NONE); if (isExecuting) { // Set new slope value wallElement->AsWall()->SetSlope(slopeValue); map_invalidate_tile_full(loc); if (auto* inspector = GetTileInspectorWithPos(loc); inspector != nullptr) { inspector->Invalidate(); } } return std::make_unique(); } GameActionResultPtr WallAnimationFrameOffset( const CoordsXY& loc, int16_t elementIndex, int8_t animationFrameOffset, bool isExecuting) { TileElement* const wallElement = map_get_nth_element_at(loc, elementIndex); if (wallElement == nullptr || wallElement->GetType() != TILE_ELEMENT_TYPE_WALL) return std::make_unique(GameActions::Status::Unknown, STR_NONE); if (isExecuting) { uint8_t animationFrame = wallElement->AsWall()->GetAnimationFrame(); wallElement->AsWall()->SetAnimationFrame(animationFrame + animationFrameOffset); map_invalidate_tile_full(loc); if (auto* inspector = GetTileInspectorWithPos(loc); inspector != nullptr) { inspector->Invalidate(); } } return std::make_unique(); } // Changes the height of all track elements that belong to the same track piece // Broxzier: Copied from track_remove and stripped of unneeded code, but I think this should be smaller GameActionResultPtr TrackBaseHeightOffset(const CoordsXY& loc, int32_t elementIndex, int8_t offset, bool isExecuting) { if (offset == 0) return std::make_unique(); TileElement* const trackElement = map_get_nth_element_at(loc, elementIndex); if (trackElement == nullptr || trackElement->GetType() != TILE_ELEMENT_TYPE_TRACK) return std::make_unique(GameActions::Status::Unknown, STR_NONE); if (isExecuting) { auto type = trackElement->AsTrack()->GetTrackType(); int16_t originX = loc.x; int16_t originY = loc.y; int16_t originZ = trackElement->GetBaseZ(); uint8_t rotation = trackElement->GetDirection(); auto rideIndex = trackElement->AsTrack()->GetRideIndex(); auto ride = get_ride(rideIndex); if (ride == nullptr) return std::make_unique(GameActions::Status::Unknown, STR_NONE); auto trackBlock = TrackBlocks[type]; trackBlock += trackElement->AsTrack()->GetSequenceIndex(); uint8_t originDirection = trackElement->GetDirection(); CoordsXY offsets = { trackBlock->x, trackBlock->y }; CoordsXY coords = { originX, originY }; coords += offsets.Rotate(direction_reverse(originDirection)); originX = static_cast(coords.x); originY = static_cast(coords.y); originZ -= trackBlock->z; trackBlock = TrackBlocks[type]; for (; trackBlock->index != 255; trackBlock++) { CoordsXYZD elem = { originX, originY, originZ + trackBlock->z, rotation }; offsets.x = trackBlock->x; offsets.y = trackBlock->y; elem += offsets.Rotate(originDirection); TrackElement* tileElement = map_get_track_element_at_of_type_seq(elem, type, trackBlock->index); if (tileElement == nullptr) { log_error("Track map element part not found!"); return std::make_unique(GameActions::Status::Unknown, STR_NONE); } // track_remove returns here on failure, not sure when this would ever be hit. Only thing I can think of is // for when you decrease the map size. openrct2_assert(map_get_surface_element_at(elem) != nullptr, "No surface at %d,%d", elem.x >> 5, elem.y >> 5); map_invalidate_tile_full(elem); // Keep? // invalidate_test_results(ride); tileElement->base_height += offset; tileElement->clearance_height += offset; } if (auto* inspector = GetTileInspectorWithPos(loc); inspector != nullptr) { inspector->Invalidate(); } } return std::make_unique(); } // Sets chainlift, optionally for an entire track block // Broxzier: Basically a copy of the above function, with just two different lines... should probably be combined // somehow GameActionResultPtr TrackSetChain( const CoordsXY& loc, int32_t elementIndex, bool entireTrackBlock, bool setChain, bool isExecuting) { TileElement* const trackElement = map_get_nth_element_at(loc, elementIndex); if (trackElement == nullptr || trackElement->GetType() != TILE_ELEMENT_TYPE_TRACK) return std::make_unique(GameActions::Status::Unknown, STR_NONE); if (isExecuting) { if (!entireTrackBlock) { // Set chain for only the selected piece if (trackElement->AsTrack()->HasChain() != setChain) { trackElement->AsTrack()->SetHasChain(setChain); } return std::make_unique(); } auto type = trackElement->AsTrack()->GetTrackType(); int16_t originX = loc.x; int16_t originY = loc.y; int16_t originZ = trackElement->GetBaseZ(); uint8_t rotation = trackElement->GetDirection(); auto rideIndex = trackElement->AsTrack()->GetRideIndex(); auto ride = get_ride(rideIndex); if (ride == nullptr) return std::make_unique(GameActions::Status::Unknown, STR_NONE); auto trackBlock = TrackBlocks[type]; trackBlock += trackElement->AsTrack()->GetSequenceIndex(); uint8_t originDirection = trackElement->GetDirection(); CoordsXY offsets = { trackBlock->x, trackBlock->y }; CoordsXY coords = { originX, originY }; coords += offsets.Rotate(direction_reverse(originDirection)); originX = static_cast(coords.x); originY = static_cast(coords.y); originZ -= trackBlock->z; trackBlock = TrackBlocks[type]; for (; trackBlock->index != 255; trackBlock++) { CoordsXYZD elem = { originX, originY, originZ + trackBlock->z, rotation }; offsets.x = trackBlock->x; offsets.y = trackBlock->y; elem += offsets.Rotate(originDirection); TrackElement* tileElement = map_get_track_element_at_of_type_seq(elem, type, trackBlock->index); if (tileElement == nullptr) { log_error("Track map element part not found!"); return std::make_unique(GameActions::Status::Unknown, STR_NONE); } // track_remove returns here on failure, not sure when this would ever be hit. Only thing I can think of is // for when you decrease the map size. openrct2_assert(map_get_surface_element_at(elem) != nullptr, "No surface at %d,%d", elem.x >> 5, elem.y >> 5); map_invalidate_tile_full(elem); // Keep? // invalidate_test_results(ride); if (tileElement->AsTrack()->HasChain() != setChain) { tileElement->AsTrack()->SetHasChain(setChain); } } if (auto* inspector = GetTileInspectorWithPos(loc); inspector != nullptr) { inspector->Invalidate(); } } return std::make_unique(); } GameActionResultPtr TrackSetBlockBrake(const CoordsXY& loc, int32_t elementIndex, bool blockBrake, bool isExecuting) { TileElement* const trackElement = map_get_nth_element_at(loc, elementIndex); if (trackElement == nullptr || trackElement->GetType() != TILE_ELEMENT_TYPE_TRACK) return std::make_unique(GameActions::Status::Unknown, STR_NONE); if (isExecuting) { trackElement->AsTrack()->SetBlockBrakeClosed(blockBrake); map_invalidate_tile_full(loc); if (auto* inspector = GetTileInspectorWithPos(loc); inspector != nullptr) { inspector->Invalidate(); } } return std::make_unique(); } GameActionResultPtr TrackSetIndestructible( const CoordsXY& loc, int32_t elementIndex, bool isIndestructible, bool isExecuting) { TileElement* const trackElement = map_get_nth_element_at(loc, elementIndex); if (trackElement == nullptr || trackElement->GetType() != TILE_ELEMENT_TYPE_TRACK) return std::make_unique(GameActions::Status::Unknown, STR_NONE); if (isExecuting) { trackElement->AsTrack()->SetIsIndestructible(isIndestructible); map_invalidate_tile_full(loc); if (auto* inspector = GetTileInspectorWithPos(loc); inspector != nullptr) { inspector->Invalidate(); } } return std::make_unique(); } GameActionResultPtr ScenerySetQuarterLocation( const CoordsXY& loc, int32_t elementIndex, int32_t quarterIndex, bool isExecuting) { TileElement* const tileElement = map_get_nth_element_at(loc, elementIndex); if (tileElement == nullptr || tileElement->GetType() != TILE_ELEMENT_TYPE_SMALL_SCENERY) return std::make_unique(GameActions::Status::Unknown, STR_NONE); if (isExecuting) { // Set quadrant index tileElement->AsSmallScenery()->SetSceneryQuadrant(quarterIndex); // Update collision tileElement->SetOccupiedQuadrants(1 << ((quarterIndex + 2) & 3)); map_invalidate_tile_full(loc); if (auto* inspector = GetTileInspectorWithPos(loc); inspector != nullptr) { inspector->Invalidate(); } } return std::make_unique(); } GameActionResultPtr ScenerySetQuarterCollision( const CoordsXY& loc, int32_t elementIndex, int32_t quarterIndex, bool isExecuting) { TileElement* const tileElement = map_get_nth_element_at(loc, elementIndex); if (tileElement == nullptr || tileElement->GetType() != TILE_ELEMENT_TYPE_SMALL_SCENERY) return std::make_unique(GameActions::Status::Unknown, STR_NONE); if (isExecuting) { auto occupiedQuadrants = tileElement->GetOccupiedQuadrants(); occupiedQuadrants ^= 1 << quarterIndex; tileElement->SetOccupiedQuadrants(occupiedQuadrants); map_invalidate_tile_full(loc); if (auto* inspector = GetTileInspectorWithPos(loc); inspector != nullptr) { inspector->Invalidate(); } } return std::make_unique(); } GameActionResultPtr BannerToggleBlockingEdge(const CoordsXY& loc, int32_t elementIndex, int32_t edgeIndex, bool isExecuting) { TileElement* const bannerElement = map_get_nth_element_at(loc, elementIndex); if (bannerElement == nullptr || bannerElement->GetType() != TILE_ELEMENT_TYPE_BANNER) return std::make_unique(GameActions::Status::Unknown, STR_NONE); if (isExecuting) { uint8_t edges = bannerElement->AsBanner()->GetAllowedEdges(); edges ^= (1 << edgeIndex); bannerElement->AsBanner()->SetAllowedEdges(edges); if (auto* inspector = GetTileInspectorWithPos(loc); inspector != nullptr) { inspector->Invalidate(); } } return std::make_unique(); } GameActionResultPtr CorruptClamp(const CoordsXY& loc, int32_t elementIndex, bool isExecuting) { TileElement* const corruptElement = map_get_nth_element_at(loc, elementIndex); if (corruptElement == nullptr || corruptElement->GetType() != TILE_ELEMENT_TYPE_CORRUPT) return std::make_unique(GameActions::Status::Unknown, STR_NONE); if (corruptElement->IsLastForTile()) return std::make_unique(GameActions::Status::Unknown, STR_NONE); if (isExecuting) { TileElement* const nextElement = corruptElement + 1; corruptElement->base_height = corruptElement->clearance_height = nextElement->base_height; if (auto* inspector = GetTileInspectorWithPos(loc); inspector != nullptr) { inspector->Invalidate(); } } return std::make_unique(); } } // namespace OpenRCT2::TileInspector