OpenRCT2/src/openrct2/world/TileInspector.cpp

981 lines
34 KiB
C++

#pragma region Copyright (c) 2014-2017 OpenRCT2 Developers
/*****************************************************************************
* OpenRCT2, an open source clone of Roller Coaster Tycoon 2.
*
* OpenRCT2 is the work of many authors, a full list can be found in contributors.md
* For more information, visit https://github.com/OpenRCT2/OpenRCT2
*
* OpenRCT2 is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* A full copy of the GNU General Public License can be found in licence.txt
*****************************************************************************/
#pragma endregion
#include "../common.h"
#include "../Context.h"
#include "../core/Guard.hpp"
#include "../Game.h"
#include "../interface/Window.h"
#include "../interface/Window_internal.h"
#include "../ride/Track.h"
#include "../windows/Intent.h"
#include "../windows/tile_inspector.h"
#include "Footpath.h"
#include "Map.h"
#include "TileInspector.h"
uint32 windowTileInspectorTileX;
uint32 windowTileInspectorTileY;
sint32 windowTileInspectorElementCount = 0;
static void window_tile_inspector_set_page(rct_window * w, const tile_inspector_page page)
{
auto intent = Intent(INTENT_ACTION_SET_TILE_INSPECTOR_PAGE);
intent.putExtra(INTENT_EXTRA_PAGE, page);
context_broadcast_intent(&intent);
}
static void window_tile_inspector_auto_set_buttons(rct_window * w)
{
auto intent = Intent(INTENT_ACTION_SET_TILE_INSPECTOR_BUTTONS);
context_broadcast_intent(&intent);
}
static bool map_swap_elements_at(sint32 x, sint32 y, sint16 first, sint16 second)
{
rct_tile_element * const firstElement = map_get_nth_element_at(x, y, first);
rct_tile_element * const secondElement = map_get_nth_element_at(x, y, 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
rct_tile_element temp = *firstElement;
*firstElement = *secondElement;
*secondElement = temp;
// Swap the 'last map element for tile' flag if either one of them was last
if (tile_element_is_last_for_tile(firstElement) || tile_element_is_last_for_tile(secondElement))
{
firstElement->flags ^= TILE_ELEMENT_FLAG_LAST_TILE;
secondElement->flags ^= TILE_ELEMENT_FLAG_LAST_TILE;
}
return true;
}
/**
* Inserts a corrupt element under a given element on a given tile
* @param x, y: The coordinates of the tile
* @param elementIndex: The nth element on this tile
* Returns 0 on success, MONEY_UNDEFINED otherwise.
*/
sint32 tile_inspector_insert_corrupt_at(sint32 x, sint32 y, sint16 elementIndex, sint32 flags)
{
// Make sure there is enough space for the new element
if (!map_check_free_elements_and_reorganise(1))
{
return MONEY32_UNDEFINED;
}
if (flags & GAME_COMMAND_FLAG_APPLY)
{
// Create new corrupt element
rct_tile_element * corruptElement = tile_element_insert(x, y, -1, 0); // Ugly hack: -1 guarantees this to be placed first
if (corruptElement == nullptr)
{
log_warning("Failed to insert corrupt element.");
return MONEY32_UNDEFINED;
}
corruptElement->type = TILE_ELEMENT_TYPE_CORRUPT;
// Set the base height to be the same as the selected element
rct_tile_element * const selectedElement = map_get_nth_element_at(x, y, elementIndex + 1);
if (!selectedElement)
{
return MONEY32_UNDEFINED;
}
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 (sint16 i = 0; i < elementIndex; i++)
{
if (!map_swap_elements_at(x, y, 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(x << 5, y << 5);
// Update the tile inspector's list for everyone who has the tile selected
rct_window * const tileInspectorWindow = window_find_by_class(WC_TILE_INSPECTOR);
if (tileInspectorWindow != nullptr && (uint32) x == windowTileInspectorTileX && (uint32) y == windowTileInspectorTileY)
{
windowTileInspectorElementCount++;
// Keep other elements (that are not being hidden) selected
if (tileInspectorWindow->selected_list_item > elementIndex)
{
tileInspectorWindow->selected_list_item++;
}
if (tileInspectorWindow->selected_list_item == elementIndex)
{
window_tile_inspector_set_page(tileInspectorWindow, TILE_INSPECTOR_PAGE_CORRUPT);
}
window_tile_inspector_auto_set_buttons(tileInspectorWindow);
window_invalidate(tileInspectorWindow);
}
}
// Nothing went wrong
return 0;
}
/**
* Forcefully removes an element for a given tile
* @param x, y: The coordinates of the tile
* @param elementIndex: The nth element on this tile
*/
sint32 tile_inspector_remove_element_at(sint32 x, sint32 y, sint16 elementIndex, sint32 flags)
{
if (flags & GAME_COMMAND_FLAG_APPLY)
{
// Forcefully remove the element
rct_tile_element * const tileElement = map_get_nth_element_at(x, y, elementIndex);
if (!tileElement)
{
return MONEY32_UNDEFINED;
}
tile_element_remove(tileElement);
map_invalidate_tile_full(x << 5, y << 5);
// Update the window
rct_window * const tileInspectorWindow = window_find_by_class(WC_TILE_INSPECTOR);
if (tileInspectorWindow != nullptr && (uint32) x == windowTileInspectorTileX && (uint32) y == windowTileInspectorTileY)
{
windowTileInspectorElementCount--;
if (tileInspectorWindow->selected_list_item > elementIndex)
{
tileInspectorWindow->selected_list_item--;
}
else if (tileInspectorWindow->selected_list_item == elementIndex)
{
tileInspectorWindow->selected_list_item = -1;
window_tile_inspector_set_page(tileInspectorWindow, TILE_INSPECTOR_PAGE_DEFAULT);
}
window_tile_inspector_auto_set_buttons(tileInspectorWindow);
window_invalidate(tileInspectorWindow);
}
}
return 0;
}
sint32 tile_inspector_swap_elements_at(sint32 x, sint32 y, sint16 first, sint16 second, sint32 flags)
{
if (flags & GAME_COMMAND_FLAG_APPLY)
{
if (!map_swap_elements_at(x, y, first, second))
{
return MONEY32_UNDEFINED;
}
map_invalidate_tile_full(x << 5, y << 5);
// Update the window
rct_window * const tileInspectorWindow = window_find_by_class(WC_TILE_INSPECTOR);
if (tileInspectorWindow != nullptr && (uint32) x == windowTileInspectorTileX && (uint32) y == windowTileInspectorTileY)
{
// If one of them was selected, update selected list item
if (tileInspectorWindow->selected_list_item == first)
tileInspectorWindow->selected_list_item = second;
else if (tileInspectorWindow->selected_list_item == second)
tileInspectorWindow->selected_list_item = first;
window_tile_inspector_auto_set_buttons(tileInspectorWindow);
window_invalidate(tileInspectorWindow);
}
}
return 0;
}
sint32 tile_inspector_rotate_element_at(sint32 x, sint32 y, sint32 elementIndex, sint32 flags)
{
if (flags & GAME_COMMAND_FLAG_APPLY)
{
uint8 newRotation, pathEdges, pathCorners;
rct_tile_element * const tileElement = map_get_nth_element_at(x, y, elementIndex);
if (!tileElement)
{
return MONEY32_UNDEFINED;
}
switch (tile_element_get_type(tileElement))
{
case TILE_ELEMENT_TYPE_PATH:
if (footpath_element_is_sloped(tileElement))
{
newRotation = (footpath_element_get_slope_direction(tileElement) + 1) & TILE_ELEMENT_DIRECTION_MASK;
tileElement->properties.path.type &= ~TILE_ELEMENT_DIRECTION_MASK;
tileElement->properties.path.type |= newRotation;
}
pathEdges = tileElement->properties.path.edges & 0x0F;
pathCorners = tileElement->properties.path.edges & 0xF0;
tileElement->properties.path.edges = 0;
tileElement->properties.path.edges |= ((pathEdges << 1) | (pathEdges >> 3)) & 0x0F;
tileElement->properties.path.edges |= ((pathCorners << 1) | (pathCorners >> 3)) & 0xF0;
break;
case TILE_ELEMENT_TYPE_TRACK:
case TILE_ELEMENT_TYPE_SMALL_SCENERY:
case TILE_ELEMENT_TYPE_ENTRANCE:
case TILE_ELEMENT_TYPE_WALL:
newRotation = tile_element_get_direction_with_offset(tileElement, 1);
tileElement->type &= ~TILE_ELEMENT_DIRECTION_MASK;
tileElement->type |= newRotation;
break;
case TILE_ELEMENT_TYPE_BANNER:
tileElement->properties.banner.flags ^= 1 << tileElement->properties.banner.position;
tileElement->properties.banner.position++;
tileElement->properties.banner.position &= 3;
tileElement->properties.banner.flags ^= 1 << tileElement->properties.banner.position;
break;
}
map_invalidate_tile_full(x << 5, y << 5);
if ((uint32) x == windowTileInspectorTileX && (uint32) y == windowTileInspectorTileY)
{
window_invalidate_by_class(WC_TILE_INSPECTOR);
}
}
return 0;
}
sint32 tile_inspector_paste_element_at(sint32 x, sint32 y, rct_tile_element element, sint32 flags)
{
// Make sure there is enough space for the new element
if (!map_check_free_elements_and_reorganise(1))
{
return MONEY32_UNDEFINED;
}
if (flags & GAME_COMMAND_FLAG_APPLY)
{
rct_tile_element * const pastedElement = tile_element_insert(x, y, element.base_height, 0);
bool lastForTile = tile_element_is_last_for_tile(pastedElement);
*pastedElement = element;
pastedElement->flags &= ~TILE_ELEMENT_FLAG_LAST_TILE;
if (lastForTile)
{
pastedElement->flags |= TILE_ELEMENT_FLAG_LAST_TILE;
}
map_invalidate_tile_full(x << 5, y << 5);
rct_window * const tileInspectorWindow = window_find_by_class(WC_TILE_INSPECTOR);
if (tileInspectorWindow != nullptr && (uint32) x == windowTileInspectorTileX && (uint32) y == windowTileInspectorTileY)
{
windowTileInspectorElementCount++;
// Select new element if there was none selected already
sint16 newIndex = (sint16) (pastedElement - map_get_first_element_at(x, y));
if (tileInspectorWindow->selected_list_item == -1)
tileInspectorWindow->selected_list_item = newIndex;
else if (tileInspectorWindow->selected_list_item >= newIndex)
tileInspectorWindow->selected_list_item++;
window_tile_inspector_auto_set_buttons(tileInspectorWindow);
window_invalidate(tileInspectorWindow);
}
}
return 0;
}
sint32 tile_inspector_sort_elements_at(sint32 x, sint32 y, sint32 flags)
{
if (flags & GAME_COMMAND_FLAG_APPLY)
{
const rct_tile_element * const firstElement = map_get_first_element_at(x, y);
// Count elements on tile
sint32 numElement = 0;
const rct_tile_element * elementIterator = firstElement;
do
{
numElement++;
}
while (!tile_element_is_last_for_tile(elementIterator++));
// Bubble sort
for (sint32 loopStart = 1; loopStart < numElement; loopStart++)
{
sint32 currentId = loopStart;
const rct_tile_element * currentElement = firstElement + currentId;
const rct_tile_element * 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 (!map_swap_elements_at(x, y, 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(x << 5, y << 5);
// Deselect tile for clients who had it selected
rct_window * const tileInspectorWindow = window_find_by_class(WC_TILE_INSPECTOR);
if (tileInspectorWindow != nullptr && (uint32) x == windowTileInspectorTileX && (uint32) y == windowTileInspectorTileY)
{
window_tile_inspector_set_page(tileInspectorWindow, TILE_INSPECTOR_PAGE_DEFAULT);
tileInspectorWindow->selected_list_item = -1;
window_tile_inspector_auto_set_buttons(tileInspectorWindow);
window_invalidate(tileInspectorWindow);
}
}
return 0;
}
sint32 tile_inspector_any_base_height_offset(sint32 x, sint32 y, sint16 elementIndex, sint8 heightOffset, sint32 flags)
{
rct_tile_element * const tileElement = map_get_nth_element_at(x, y, elementIndex);
if (!tileElement)
{
return MONEY32_UNDEFINED;
}
sint16 newBaseHeight = (sint16) tileElement->base_height + heightOffset;
sint16 newClearanceHeight = (sint16) tileElement->clearance_height + heightOffset;
if (newBaseHeight < 0 || newBaseHeight > 0xff || newClearanceHeight < 0 || newClearanceHeight > 0xff)
{
return MONEY32_UNDEFINED;
}
if (flags & GAME_COMMAND_FLAG_APPLY)
{
tileElement->base_height += heightOffset;
tileElement->clearance_height += heightOffset;
map_invalidate_tile_full(x << 5, y << 5);
rct_window * const tileInspectorWindow = window_find_by_class(WC_TILE_INSPECTOR);
if (tileInspectorWindow != nullptr && (uint32) x == windowTileInspectorTileX && (uint32) y == windowTileInspectorTileY)
{
window_invalidate(tileInspectorWindow);
}
}
return 0;
}
sint32 tile_inspector_surface_show_park_fences(sint32 x, sint32 y, bool showFences, sint32 flags)
{
rct_tile_element * const surfaceelement = map_get_surface_element_at(x, y);
// No surface element on tile
if (surfaceelement == nullptr)
return MONEY32_UNDEFINED;
if (flags & GAME_COMMAND_FLAG_APPLY)
{
if (!showFences)
surfaceelement->properties.surface.ownership &= ~0x0F;
else
update_park_fences(x << 5, y << 5);
map_invalidate_tile_full(x << 5, y << 5);
rct_window * const tileInspectorWindow = window_find_by_class(WC_TILE_INSPECTOR);
if (tileInspectorWindow != nullptr && (uint32) x == windowTileInspectorTileX && (uint32) y == windowTileInspectorTileY)
{
window_invalidate(tileInspectorWindow);
}
}
return 0;
}
sint32 tile_inspector_surface_toggle_corner(sint32 x, sint32 y, sint32 cornerIndex, sint32 flags)
{
rct_tile_element * const surfaceElement = map_get_surface_element_at(x, y);
// No surface element on tile
if (surfaceElement == nullptr)
return MONEY32_UNDEFINED;
if (flags & GAME_COMMAND_FLAG_APPLY)
{
const uint8 originalSlope = surfaceElement->properties.surface.slope;
const bool diagonal = (originalSlope & TILE_ELEMENT_SLOPE_DOUBLE_HEIGHT) >> 4;
surfaceElement->properties.surface.slope ^= 1 << cornerIndex;
if (surfaceElement->properties.surface.slope & 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->properties.surface.slope & TILE_ELEMENT_SLOPE_ALL_CORNERS_UP) == TILE_ELEMENT_SLOPE_ALL_CORNERS_UP)
{
surfaceElement->properties.surface.slope &= ~TILE_ELEMENT_SURFACE_SLOPE_MASK;
if (diagonal)
{
switch (originalSlope & TILE_ELEMENT_SLOPE_ALL_CORNERS_UP)
{
case TILE_ELEMENT_SLOPE_S_CORNER_DN:
surfaceElement->properties.surface.slope |= TILE_ELEMENT_SLOPE_N_CORNER_UP;
break;
case TILE_ELEMENT_SLOPE_W_CORNER_DN:
surfaceElement->properties.surface.slope |= TILE_ELEMENT_SLOPE_E_CORNER_UP;
break;
case TILE_ELEMENT_SLOPE_N_CORNER_DN:
surfaceElement->properties.surface.slope |= TILE_ELEMENT_SLOPE_S_CORNER_UP;
break;
case TILE_ELEMENT_SLOPE_E_CORNER_DN:
surfaceElement->properties.surface.slope |= TILE_ELEMENT_SLOPE_W_CORNER_UP;
break;
}
}
// Update base and clearance heights
surfaceElement->base_height += 2;
surfaceElement->clearance_height = surfaceElement->base_height + (diagonal ? 2 : 0);
}
map_invalidate_tile_full(x << 5, y << 5);
rct_window * const tileInspectorWindow = window_find_by_class(WC_TILE_INSPECTOR);
if (tileInspectorWindow != nullptr && (uint32) x == windowTileInspectorTileX && (uint32) y == windowTileInspectorTileY)
{
window_invalidate(tileInspectorWindow);
}
}
return 0;
}
sint32 tile_inspector_surface_toggle_diagonal(sint32 x, sint32 y, sint32 flags)
{
rct_tile_element * const surfaceElement = map_get_surface_element_at(x, y);
// No surface element on tile
if (surfaceElement == nullptr)
return MONEY32_UNDEFINED;
if (flags & GAME_COMMAND_FLAG_APPLY)
{
surfaceElement->properties.surface.slope ^= TILE_ELEMENT_SLOPE_DOUBLE_HEIGHT;
if (surfaceElement->properties.surface.slope & TILE_ELEMENT_SLOPE_DOUBLE_HEIGHT)
{
surfaceElement->clearance_height = surfaceElement->base_height + 4;
}
else if (surfaceElement->properties.surface.slope & TILE_ELEMENT_SLOPE_ALL_CORNERS_UP)
{
surfaceElement->clearance_height = surfaceElement->base_height + 2;
}
else
{
surfaceElement->clearance_height = surfaceElement->base_height;
}
map_invalidate_tile_full(x << 5, y << 5);
rct_window * const tileInspectorWindow = window_find_by_class(WC_TILE_INSPECTOR);
if (tileInspectorWindow != nullptr && (uint32) x == windowTileInspectorTileX && (uint32) y == windowTileInspectorTileY)
{
window_invalidate(tileInspectorWindow);
}
}
return 0;
}
sint32 tile_inspector_path_set_sloped(sint32 x, sint32 y, sint32 elementIndex, bool sloped, sint32 flags)
{
rct_tile_element * const pathElement = map_get_nth_element_at(x, y, elementIndex);
if (!pathElement || tile_element_get_type(pathElement) != TILE_ELEMENT_TYPE_PATH)
{
return MONEY32_UNDEFINED;
}
if (flags & GAME_COMMAND_FLAG_APPLY)
{
pathElement->properties.path.type &= ~(1 << 2);
if (sloped)
{
pathElement->properties.path.type |= (1 << 2);
}
map_invalidate_tile_full(x << 5, y << 5);
rct_window * const tileInspectorWindow = window_find_by_class(WC_TILE_INSPECTOR);
if (tileInspectorWindow != nullptr && (uint32) x == windowTileInspectorTileX && (uint32) y == windowTileInspectorTileY)
{
window_invalidate(tileInspectorWindow);
}
}
return 0;
}
sint32 tile_inspector_path_toggle_edge(sint32 x, sint32 y, sint32 elementIndex, sint32 edgeIndex, sint32 flags)
{
rct_tile_element * const pathElement = map_get_nth_element_at(x, y, elementIndex);
if (!pathElement || tile_element_get_type(pathElement) != TILE_ELEMENT_TYPE_PATH)
{
return MONEY32_UNDEFINED;
}
if (flags & GAME_COMMAND_FLAG_APPLY)
{
pathElement->properties.path.edges ^= 1 << edgeIndex;
map_invalidate_tile_full(x << 5, y << 5);
rct_window * const tileInspectorWindow = window_find_by_class(WC_TILE_INSPECTOR);
if (tileInspectorWindow != nullptr && (uint32) x == windowTileInspectorTileX && (uint32) y == windowTileInspectorTileY)
{
window_invalidate(tileInspectorWindow);
}
}
return 0;
}
sint32 tile_inspector_wall_set_slope(sint32 x, sint32 y, sint32 elementIndex, sint32 slopeValue, sint32 flags)
{
rct_tile_element * const wallElement = map_get_nth_element_at(x, y, elementIndex);
if (!wallElement || tile_element_get_type(wallElement) != TILE_ELEMENT_TYPE_WALL)
{
return MONEY32_UNDEFINED;
}
if (flags & GAME_COMMAND_FLAG_APPLY)
{
// Set new slope value
wallElement->type &= ~0xC0;
wallElement->type |= slopeValue;
map_invalidate_tile_full(x << 5, y << 5);
rct_window * const tileInspectorWindow = window_find_by_class(WC_TILE_INSPECTOR);
if (tileInspectorWindow != nullptr && (uint32) x == windowTileInspectorTileX && (uint32) y == windowTileInspectorTileY)
{
window_invalidate(tileInspectorWindow);
}
}
return 0;
}
// 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
sint32 tile_inspector_track_base_height_offset(sint32 x, sint32 y, sint32 elementIndex, sint8 offset, sint32 flags)
{
rct_tile_element * const trackElement = map_get_nth_element_at(x, y, elementIndex);
if (offset == 0)
{
return MONEY32_UNDEFINED;
}
if (!trackElement || tile_element_get_type(trackElement) != TILE_ELEMENT_TYPE_TRACK)
{
return MONEY32_UNDEFINED;
}
if (flags & GAME_COMMAND_FLAG_APPLY)
{
uint8 type = track_element_get_type(trackElement);
sint16 originX = x << 5;
sint16 originY = y << 5;
sint16 originZ = trackElement->base_height * 8;
uint8 rotation = tile_element_get_direction(trackElement);
uint8 rideIndex = track_element_get_ride_index(trackElement);
Ride * ride = get_ride(rideIndex);
const rct_preview_track * trackBlock = get_track_def_from_ride(ride, type);
trackBlock += tile_element_get_track_sequence(trackElement);
uint8 originDirection = tile_element_get_direction(trackElement);
switch (originDirection)
{
case 0:
originX -= trackBlock->x;
originY -= trackBlock->y;
break;
case 1:
originX -= trackBlock->y;
originY += trackBlock->x;
break;
case 2:
originX += trackBlock->x;
originY += trackBlock->y;
break;
case 3:
originX += trackBlock->y;
originY -= trackBlock->x;
break;
}
originZ -= trackBlock->z;
trackBlock = get_track_def_from_ride(ride, type);
for (; trackBlock->index != 255; trackBlock++)
{
sint16 elemX = originX, elemY = originY, elemZ = originZ;
switch (originDirection)
{
case 0:
elemX += trackBlock->x;
elemY += trackBlock->y;
break;
case 1:
elemX += trackBlock->y;
elemY -= trackBlock->x;
break;
case 2:
elemX -= trackBlock->x;
elemY -= trackBlock->y;
break;
case 3:
elemX -= trackBlock->y;
elemY += trackBlock->x;
break;
}
elemZ += trackBlock->z;
map_invalidate_tile_full(elemX, elemY);
bool found = false;
rct_tile_element * tileElement = map_get_first_element_at(elemX >> 5, elemY >> 5);
do
{
if (tileElement->base_height != elemZ / 8)
continue;
if (tile_element_get_type(tileElement) != TILE_ELEMENT_TYPE_TRACK)
continue;
if ((tile_element_get_direction(tileElement)) != rotation)
continue;
if (tile_element_get_track_sequence(tileElement) != trackBlock->index)
continue;
if (track_element_get_type(tileElement) != type)
continue;
found = true;
break;
}
while (!tile_element_is_last_for_tile(tileElement++));
if (!found)
{
log_error("Track map element part not found!");
return MONEY32_UNDEFINED;
}
// 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(elemX >> 5, elemY >> 5) != nullptr, "No surface at %d,%d", elemX >> 5, elemY >> 5);
// Keep?
//invalidate_test_results(rideIndex);
tileElement->base_height += offset;
tileElement->clearance_height += offset;
}
}
// TODO: Only invalidate when one of the affected tiles is selected
window_invalidate_by_class(WC_TILE_INSPECTOR);
return 0;
}
// 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
sint32 tile_inspector_track_set_chain(sint32 x, sint32 y, sint32 elementIndex, bool entireTrackBlock, bool setChain, sint32 flags)
{
rct_tile_element * const trackElement = map_get_nth_element_at(x, y, elementIndex);
if (!trackElement || tile_element_get_type(trackElement) != TILE_ELEMENT_TYPE_TRACK)
{
return MONEY32_UNDEFINED;
}
if (flags & GAME_COMMAND_FLAG_APPLY)
{
if (!entireTrackBlock)
{
// Set chain for only the selected piece
if (track_element_is_lift_hill(trackElement) != setChain)
{
trackElement->type ^= TRACK_ELEMENT_FLAG_CHAIN_LIFT;
}
return 0;
}
uint8 type = track_element_get_type(trackElement);
sint16 originX = x << 5;
sint16 originY = y << 5;
sint16 originZ = trackElement->base_height * 8;
uint8 rotation = tile_element_get_direction(trackElement);
uint8 rideIndex = track_element_get_ride_index(trackElement);
Ride * ride = get_ride(rideIndex);
const rct_preview_track * trackBlock = get_track_def_from_ride(ride, type);
trackBlock += tile_element_get_track_sequence(trackElement);
uint8 originDirection = tile_element_get_direction(trackElement);
switch (originDirection)
{
case 0:
originX -= trackBlock->x;
originY -= trackBlock->y;
break;
case 1:
originX -= trackBlock->y;
originY += trackBlock->x;
break;
case 2:
originX += trackBlock->x;
originY += trackBlock->y;
break;
case 3:
originX += trackBlock->y;
originY -= trackBlock->x;
break;
}
originZ -= trackBlock->z;
trackBlock = get_track_def_from_ride(ride, type);
for (; trackBlock->index != 255; trackBlock++)
{
sint16 elemX = originX, elemY = originY, elemZ = originZ;
switch (originDirection)
{
case 0:
elemX += trackBlock->x;
elemY += trackBlock->y;
break;
case 1:
elemX += trackBlock->y;
elemY -= trackBlock->x;
break;
case 2:
elemX -= trackBlock->x;
elemY -= trackBlock->y;
break;
case 3:
elemX -= trackBlock->y;
elemY += trackBlock->x;
break;
}
elemZ += trackBlock->z;
map_invalidate_tile_full(elemX, elemY);
bool found = false;
rct_tile_element * tileElement = map_get_first_element_at(elemX >> 5, elemY >> 5);
do
{
if (tileElement->base_height != elemZ / 8)
continue;
if (tile_element_get_type(tileElement) != TILE_ELEMENT_TYPE_TRACK)
continue;
if ((tile_element_get_direction(tileElement)) != rotation)
continue;
if (tile_element_get_track_sequence(tileElement) != trackBlock->index)
continue;
if (track_element_get_type(tileElement) != type)
continue;
found = true;
break;
}
while (!tile_element_is_last_for_tile(tileElement++));
if (!found)
{
log_error("Track map element part not found!");
return MONEY32_UNDEFINED;
}
// 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(elemX >> 5, elemY >> 5) != nullptr, "No surface at %d,%d", elemX >> 5, elemY >> 5);
// Keep?
//invalidate_test_results(rideIndex);
if (track_element_is_lift_hill(tileElement) != setChain)
{
tileElement->type ^= TRACK_ELEMENT_FLAG_CHAIN_LIFT;
}
}
}
// TODO: Only invalidate when one of the affected tiles is selected
window_invalidate_by_class(WC_TILE_INSPECTOR);
return 0;
}
sint32 tile_inspector_scenery_set_quarter_location(sint32 x, sint32 y, sint32 elementIndex, sint32 quarterIndex, sint32 flags)
{
rct_tile_element * const tileElement = map_get_nth_element_at(x, y, elementIndex);
if (!tileElement || tile_element_get_type(tileElement) != TILE_ELEMENT_TYPE_SMALL_SCENERY)
{
return MONEY32_UNDEFINED;
}
if (flags & GAME_COMMAND_FLAG_APPLY)
{
// Set quadrant index
tileElement->type &= ~TILE_ELEMENT_QUADRANT_MASK;
tileElement->type |= quarterIndex << 6;
// Update collision
tileElement->flags &= 0xF0;
tileElement->flags |= 1 << ((quarterIndex + 2) & 3);
map_invalidate_tile_full(x << 5, y << 5);
if ((uint32) x == windowTileInspectorTileX && (uint32) y == windowTileInspectorTileY)
{
window_invalidate_by_class(WC_TILE_INSPECTOR);
}
}
return 0;
}
sint32 tile_inspector_scenery_set_quarter_collision(sint32 x, sint32 y, sint32 elementIndex, sint32 quarterIndex, sint32 flags)
{
rct_tile_element * const tileElement = map_get_nth_element_at(x, y, elementIndex);
if (!tileElement || tile_element_get_type(tileElement) != TILE_ELEMENT_TYPE_SMALL_SCENERY)
{
return MONEY32_UNDEFINED;
}
if (flags & GAME_COMMAND_FLAG_APPLY)
{
tileElement->flags ^= 1 << quarterIndex;
map_invalidate_tile_full(x << 5, y << 5);
if ((uint32) x == windowTileInspectorTileX && (uint32) y == windowTileInspectorTileY)
{
window_invalidate_by_class(WC_TILE_INSPECTOR);
}
}
return 0;
}
sint32 tile_inspector_banner_toggle_blocking_edge(sint32 x, sint32 y, sint32 elementIndex, sint32 edgeIndex, sint32 flags)
{
rct_tile_element * const bannerElement = map_get_nth_element_at(x, y, elementIndex);
if (!bannerElement || tile_element_get_type(bannerElement) != TILE_ELEMENT_TYPE_BANNER)
{
return MONEY32_UNDEFINED;
}
if (flags & GAME_COMMAND_FLAG_APPLY)
{
bannerElement->properties.banner.flags ^= 1 << edgeIndex;
if ((uint32) x == windowTileInspectorTileX && (uint32) y == windowTileInspectorTileY)
{
window_invalidate_by_class(WC_TILE_INSPECTOR);
}
}
return 0;
}
sint32 tile_inspector_corrupt_clamp(sint32 x, sint32 y, sint32 elementIndex, sint32 flags)
{
rct_tile_element * const corruptElement = map_get_nth_element_at(x, y, elementIndex);
if (!corruptElement || tile_element_get_type(corruptElement) != TILE_ELEMENT_TYPE_CORRUPT)
{
return MONEY32_UNDEFINED;
}
if (tile_element_is_last_for_tile(corruptElement))
{
return MONEY32_UNDEFINED;
}
if (flags & GAME_COMMAND_FLAG_APPLY)
{
rct_tile_element * const nextElement = corruptElement + 1;
corruptElement->base_height = corruptElement->clearance_height = nextElement->base_height;
if ((uint32) x == windowTileInspectorTileX && (uint32) y == windowTileInspectorTileY)
{
window_invalidate_by_class(WC_TILE_INSPECTOR);
}
}
return 0;
}