mirror of
https://github.com/OpenRCT2/OpenRCT2
synced 2026-01-15 11:03:00 +01:00
1935 lines
54 KiB
C++
1935 lines
54 KiB
C++
/*****************************************************************************
|
|
* Copyright (c) 2014-2019 OpenRCT2 developers
|
|
*
|
|
* For a complete list of all authors, please refer to contributors.md
|
|
* Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
|
|
*
|
|
* OpenRCT2 is licensed under the GNU General Public License version 3.
|
|
*****************************************************************************/
|
|
|
|
#include "Viewport.h"
|
|
|
|
#include "../Context.h"
|
|
#include "../Game.h"
|
|
#include "../Input.h"
|
|
#include "../OpenRCT2.h"
|
|
#include "../config/Config.h"
|
|
#include "../core/Guard.hpp"
|
|
#include "../core/JobPool.hpp"
|
|
#include "../drawing/Drawing.h"
|
|
#include "../paint/Paint.h"
|
|
#include "../peep/Staff.h"
|
|
#include "../ride/Ride.h"
|
|
#include "../ride/TrackDesign.h"
|
|
#include "../ui/UiContext.h"
|
|
#include "../ui/WindowManager.h"
|
|
#include "../world/Climate.h"
|
|
#include "../world/Map.h"
|
|
#include "../world/Sprite.h"
|
|
#include "Colour.h"
|
|
#include "Window.h"
|
|
#include "Window_internal.h"
|
|
|
|
#include <algorithm>
|
|
#include <cstring>
|
|
|
|
using namespace OpenRCT2;
|
|
|
|
//#define DEBUG_SHOW_DIRTY_BOX
|
|
uint8_t gShowGridLinesRefCount;
|
|
uint8_t gShowLandRightsRefCount;
|
|
uint8_t gShowConstuctionRightsRefCount;
|
|
|
|
rct_viewport g_viewport_list[MAX_VIEWPORT_COUNT];
|
|
rct_viewport* g_music_tracking_viewport;
|
|
|
|
static TileElement* _interaction_element = nullptr;
|
|
static std::unique_ptr<JobPool> _paintJobs;
|
|
|
|
int16_t gSavedViewX;
|
|
int16_t gSavedViewY;
|
|
uint8_t gSavedViewZoom;
|
|
uint8_t gSavedViewRotation;
|
|
|
|
paint_entry* gNextFreePaintStruct;
|
|
uint8_t gCurrentRotation;
|
|
|
|
static uint32_t _currentImageType;
|
|
|
|
static rct_drawpixelinfo _viewportDpi1;
|
|
static rct_drawpixelinfo _viewportDpi2;
|
|
static uint8_t _interactionSpriteType;
|
|
static int16_t _interactionMapX;
|
|
static int16_t _interactionMapY;
|
|
static uint16_t _interactionFlags;
|
|
|
|
static void viewport_paint_weather_gloom(rct_drawpixelinfo* dpi);
|
|
|
|
/**
|
|
* This is not a viewport function. It is used to setup many variables for
|
|
* multiple things.
|
|
* rct2: 0x006E6EAC
|
|
*/
|
|
void viewport_init_all()
|
|
{
|
|
if (!gOpenRCT2NoGraphics)
|
|
{
|
|
colours_init_maps();
|
|
}
|
|
|
|
window_init_all();
|
|
|
|
// Setting up viewports
|
|
for (int32_t i = 0; i < MAX_VIEWPORT_COUNT; i++)
|
|
{
|
|
g_viewport_list[i].width = 0;
|
|
}
|
|
|
|
// ?
|
|
input_reset_flags();
|
|
input_set_state(INPUT_STATE_RESET);
|
|
gPressedWidget.window_classification = 255;
|
|
gPickupPeepImage = UINT32_MAX;
|
|
reset_tooltip_not_shown();
|
|
gMapSelectFlags = 0;
|
|
gStaffDrawPatrolAreas = 0xFFFF;
|
|
textinput_cancel();
|
|
}
|
|
|
|
// TODO: Return ScreenCoords, takein CoordsXYZ
|
|
/**
|
|
* Converts between 3d point of a sprite to 2d coordinates for centring on that
|
|
* sprite
|
|
* rct2: 0x006EB0C1
|
|
* x : ax
|
|
* y : bx
|
|
* z : cx
|
|
* out_x : ax
|
|
* out_y : bx
|
|
*/
|
|
void centre_2d_coordinates(int32_t x, int32_t y, int32_t z, int32_t* out_x, int32_t* out_y, rct_viewport* viewport)
|
|
{
|
|
int32_t start_x = x;
|
|
|
|
CoordsXYZ coord_3d = { x, y, z };
|
|
|
|
auto coord_2d = translate_3d_to_2d_with_z(get_current_rotation(), coord_3d);
|
|
|
|
// If the start location was invalid
|
|
// propagate the invalid location to the output.
|
|
// This fixes a bug that caused the game to enter an infinite loop.
|
|
if (start_x == LOCATION_NULL)
|
|
{
|
|
*out_x = LOCATION_NULL;
|
|
*out_y = 0;
|
|
return;
|
|
}
|
|
|
|
*out_x = coord_2d.x - viewport->view_width / 2;
|
|
*out_y = coord_2d.y - viewport->view_height / 2;
|
|
}
|
|
|
|
/**
|
|
* Viewport will look at sprite or at coordinates as specified in flags 0b_1X
|
|
* for sprite 0b_0X for coordinates
|
|
*
|
|
* rct2: 0x006EB009
|
|
* x: ax
|
|
* y: eax (top 16)
|
|
* width: bx
|
|
* height: ebx (top 16)
|
|
* zoom: cl (8 bits)
|
|
* centre_x: edx lower 16 bits
|
|
* centre_y: edx upper 16 bits
|
|
* centre_z: ecx upper 16 bits
|
|
* sprite: edx lower 16 bits
|
|
* flags: edx top most 2 bits 0b_X1 for zoom clear see below for 2nd bit.
|
|
* w: esi
|
|
*/
|
|
void viewport_create(
|
|
rct_window* w, int32_t x, int32_t y, int32_t width, int32_t height, int32_t zoom, int32_t centre_x, int32_t centre_y,
|
|
int32_t centre_z, char flags, uint16_t sprite)
|
|
{
|
|
rct_viewport* viewport = nullptr;
|
|
for (int32_t i = 0; i < MAX_VIEWPORT_COUNT; i++)
|
|
{
|
|
if (g_viewport_list[i].width == 0)
|
|
{
|
|
viewport = &g_viewport_list[i];
|
|
break;
|
|
}
|
|
}
|
|
if (viewport == nullptr)
|
|
{
|
|
log_error("No more viewport slots left to allocate.");
|
|
return;
|
|
}
|
|
|
|
viewport->x = x;
|
|
viewport->y = y;
|
|
viewport->width = width;
|
|
viewport->height = height;
|
|
|
|
if (!(flags & VIEWPORT_FOCUS_TYPE_COORDINATE))
|
|
{
|
|
zoom = 0;
|
|
}
|
|
|
|
viewport->view_width = width << zoom;
|
|
viewport->view_height = height << zoom;
|
|
viewport->zoom = zoom;
|
|
viewport->flags = 0;
|
|
|
|
if (gConfigGeneral.always_show_gridlines)
|
|
viewport->flags |= VIEWPORT_FLAG_GRIDLINES;
|
|
w->viewport = viewport;
|
|
|
|
if (flags & VIEWPORT_FOCUS_TYPE_SPRITE)
|
|
{
|
|
w->viewport_target_sprite = sprite;
|
|
rct_sprite* centre_sprite = get_sprite(sprite);
|
|
centre_x = centre_sprite->generic.x;
|
|
centre_y = centre_sprite->generic.y;
|
|
centre_z = centre_sprite->generic.z;
|
|
}
|
|
else
|
|
{
|
|
w->viewport_target_sprite = SPRITE_INDEX_NULL;
|
|
}
|
|
|
|
int32_t view_x, view_y;
|
|
centre_2d_coordinates(centre_x, centre_y, centre_z, &view_x, &view_y, viewport);
|
|
|
|
w->saved_view_x = view_x;
|
|
w->saved_view_y = view_y;
|
|
viewport->view_x = view_x;
|
|
viewport->view_y = view_y;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* rct2: 0x00689174
|
|
* edx is assumed to be (and always is) the current rotation, so it is not
|
|
* needed as parameter.
|
|
*/
|
|
void viewport_adjust_for_map_height(int16_t* x, int16_t* y, int16_t* z)
|
|
{
|
|
int16_t start_x = *x;
|
|
int16_t start_y = *y;
|
|
int16_t height = 0;
|
|
|
|
uint32_t rotation = get_current_rotation();
|
|
CoordsXY pos{};
|
|
for (int32_t i = 0; i < 6; i++)
|
|
{
|
|
pos = viewport_coord_to_map_coord(start_x, start_y, height);
|
|
height = tile_element_height(pos);
|
|
|
|
// HACK: This is to prevent the x and y values being set to values outside
|
|
// of the map. This can happen when the height is larger than the map size.
|
|
int16_t max = gMapSizeMinus2;
|
|
if (pos.x > max && pos.y > max)
|
|
{
|
|
const CoordsXY corr[] = { { -1, -1 }, { 1, -1 }, { 1, 1 }, { -1, 1 } };
|
|
pos.x += corr[rotation].x * height;
|
|
pos.y += corr[rotation].y * height;
|
|
}
|
|
}
|
|
|
|
*x = pos.x;
|
|
*y = pos.y;
|
|
*z = height;
|
|
}
|
|
|
|
/*
|
|
* rct2: 0x006E7FF3
|
|
*/
|
|
static void viewport_redraw_after_shift(
|
|
rct_drawpixelinfo* dpi, rct_window* window, rct_viewport* viewport, int32_t x, int32_t y)
|
|
{
|
|
// sub-divide by intersecting windows
|
|
if (window != nullptr)
|
|
{
|
|
// skip current window and non-intersecting windows
|
|
if (viewport == window->viewport || viewport->x + viewport->width <= window->x
|
|
|| viewport->x >= window->x + window->width || viewport->y + viewport->height <= window->y
|
|
|| viewport->y >= window->y + window->height)
|
|
{
|
|
auto itWindowPos = window_get_iterator(window);
|
|
auto itNextWindow = itWindowPos != g_window_list.end() ? std::next(itWindowPos) : g_window_list.end();
|
|
viewport_redraw_after_shift(
|
|
dpi, itNextWindow == g_window_list.end() ? nullptr : itNextWindow->get(), viewport, x, y);
|
|
return;
|
|
}
|
|
|
|
// save viewport
|
|
rct_viewport view_copy;
|
|
std::memcpy(&view_copy, viewport, sizeof(rct_viewport));
|
|
|
|
if (viewport->x < window->x)
|
|
{
|
|
viewport->width = window->x - viewport->x;
|
|
viewport->view_width = viewport->width << viewport->zoom;
|
|
viewport_redraw_after_shift(dpi, window, viewport, x, y);
|
|
|
|
viewport->x += viewport->width;
|
|
viewport->view_x += viewport->width << viewport->zoom;
|
|
viewport->width = view_copy.width - viewport->width;
|
|
viewport->view_width = viewport->width << viewport->zoom;
|
|
viewport_redraw_after_shift(dpi, window, viewport, x, y);
|
|
}
|
|
else if (viewport->x + viewport->width > window->x + window->width)
|
|
{
|
|
viewport->width = window->x + window->width - viewport->x;
|
|
viewport->view_width = viewport->width << viewport->zoom;
|
|
viewport_redraw_after_shift(dpi, window, viewport, x, y);
|
|
|
|
viewport->x += viewport->width;
|
|
viewport->view_x += viewport->width << viewport->zoom;
|
|
viewport->width = view_copy.width - viewport->width;
|
|
viewport->view_width = viewport->width << viewport->zoom;
|
|
viewport_redraw_after_shift(dpi, window, viewport, x, y);
|
|
}
|
|
else if (viewport->y < window->y)
|
|
{
|
|
viewport->height = window->y - viewport->y;
|
|
viewport->view_width = viewport->width << viewport->zoom;
|
|
viewport_redraw_after_shift(dpi, window, viewport, x, y);
|
|
|
|
viewport->y += viewport->height;
|
|
viewport->view_y += viewport->height << viewport->zoom;
|
|
viewport->height = view_copy.height - viewport->height;
|
|
viewport->view_width = viewport->width << viewport->zoom;
|
|
viewport_redraw_after_shift(dpi, window, viewport, x, y);
|
|
}
|
|
else if (viewport->y + viewport->height > window->y + window->height)
|
|
{
|
|
viewport->height = window->y + window->height - viewport->y;
|
|
viewport->view_width = viewport->width << viewport->zoom;
|
|
viewport_redraw_after_shift(dpi, window, viewport, x, y);
|
|
|
|
viewport->y += viewport->height;
|
|
viewport->view_y += viewport->height << viewport->zoom;
|
|
viewport->height = view_copy.height - viewport->height;
|
|
viewport->view_width = viewport->width << viewport->zoom;
|
|
viewport_redraw_after_shift(dpi, window, viewport, x, y);
|
|
}
|
|
|
|
// restore viewport
|
|
std::memcpy(viewport, &view_copy, sizeof(rct_viewport));
|
|
}
|
|
else
|
|
{
|
|
int16_t left = viewport->x;
|
|
int16_t right = viewport->x + viewport->width;
|
|
int16_t top = viewport->y;
|
|
int16_t bottom = viewport->y + viewport->height;
|
|
|
|
// if moved more than the viewport size
|
|
if (abs(x) < viewport->width && abs(y) < viewport->height)
|
|
{
|
|
// update whole block ?
|
|
drawing_engine_copy_rect(viewport->x, viewport->y, viewport->width, viewport->height, x, y);
|
|
|
|
if (x > 0)
|
|
{
|
|
// draw left
|
|
int16_t _right = viewport->x + x;
|
|
window_draw_all(dpi, left, top, _right, bottom);
|
|
left += x;
|
|
}
|
|
else if (x < 0)
|
|
{
|
|
// draw right
|
|
int16_t _left = viewport->x + viewport->width + x;
|
|
window_draw_all(dpi, _left, top, right, bottom);
|
|
right += x;
|
|
}
|
|
|
|
if (y > 0)
|
|
{
|
|
// draw top
|
|
bottom = viewport->y + y;
|
|
window_draw_all(dpi, left, top, right, bottom);
|
|
}
|
|
else if (y < 0)
|
|
{
|
|
// draw bottom
|
|
top = viewport->y + viewport->height + y;
|
|
window_draw_all(dpi, left, top, right, bottom);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// redraw whole viewport
|
|
window_draw_all(dpi, left, top, right, bottom);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void viewport_shift_pixels(
|
|
rct_drawpixelinfo* dpi, rct_window* window, rct_viewport* viewport, int16_t x_diff, int16_t y_diff)
|
|
{
|
|
auto it = window_get_iterator(window);
|
|
for (; it != g_window_list.end(); it++)
|
|
{
|
|
auto w = it->get();
|
|
if (!(w->flags & WF_TRANSPARENT))
|
|
continue;
|
|
if (w->viewport == viewport)
|
|
continue;
|
|
|
|
if (viewport->x + viewport->width <= w->x)
|
|
continue;
|
|
if (w->x + w->width <= viewport->x)
|
|
continue;
|
|
|
|
if (viewport->y + viewport->height <= w->y)
|
|
continue;
|
|
if (w->y + w->height <= viewport->y)
|
|
continue;
|
|
|
|
auto left = w->x;
|
|
auto right = w->x + w->width;
|
|
auto top = w->y;
|
|
auto bottom = w->y + w->height;
|
|
|
|
if (left < viewport->x)
|
|
left = viewport->x;
|
|
if (right > viewport->x + viewport->width)
|
|
right = viewport->x + viewport->width;
|
|
|
|
if (top < viewport->y)
|
|
top = viewport->y;
|
|
if (bottom > viewport->y + viewport->height)
|
|
bottom = viewport->y + viewport->height;
|
|
|
|
if (left >= right)
|
|
continue;
|
|
if (top >= bottom)
|
|
continue;
|
|
|
|
window_draw_all(dpi, left, top, right, bottom);
|
|
}
|
|
|
|
viewport_redraw_after_shift(dpi, window, viewport, x_diff, y_diff);
|
|
}
|
|
|
|
static void viewport_move(int16_t x, int16_t y, rct_window* w, rct_viewport* viewport)
|
|
{
|
|
uint8_t zoom = (1 << viewport->zoom);
|
|
|
|
// Note: do not do the subtraction and then divide!
|
|
// Note: Due to arithmetic shift != /zoom a shift will have to be used
|
|
// hopefully when 0x006E7FF3 is finished this can be converted to /zoom.
|
|
int16_t x_diff = (viewport->view_x >> viewport->zoom) - (x >> viewport->zoom);
|
|
int16_t y_diff = (viewport->view_y >> viewport->zoom) - (y >> viewport->zoom);
|
|
|
|
viewport->view_x = x;
|
|
viewport->view_y = y;
|
|
|
|
// If no change in viewing area
|
|
if ((!x_diff) && (!y_diff))
|
|
return;
|
|
|
|
if (w->flags & WF_7)
|
|
{
|
|
int32_t left = std::max<int32_t>(viewport->x, 0);
|
|
int32_t top = std::max<int32_t>(viewport->y, 0);
|
|
int32_t right = std::min<int32_t>(viewport->x + viewport->width, context_get_width());
|
|
int32_t bottom = std::min<int32_t>(viewport->y + viewport->height, context_get_height());
|
|
|
|
if (left >= right)
|
|
return;
|
|
if (top >= bottom)
|
|
return;
|
|
|
|
if (drawing_engine_has_dirty_optimisations())
|
|
{
|
|
rct_drawpixelinfo* dpi = drawing_engine_get_dpi();
|
|
window_draw_all(dpi, left, top, right, bottom);
|
|
return;
|
|
}
|
|
}
|
|
|
|
rct_viewport view_copy;
|
|
std::memcpy(&view_copy, viewport, sizeof(rct_viewport));
|
|
|
|
if (viewport->x < 0)
|
|
{
|
|
viewport->width += viewport->x;
|
|
viewport->view_width += viewport->x * zoom;
|
|
viewport->view_x -= viewport->x * zoom;
|
|
viewport->x = 0;
|
|
}
|
|
|
|
int32_t eax = viewport->x + viewport->width - context_get_width();
|
|
if (eax > 0)
|
|
{
|
|
viewport->width -= eax;
|
|
viewport->view_width -= eax * zoom;
|
|
}
|
|
|
|
if (viewport->width <= 0)
|
|
{
|
|
std::memcpy(viewport, &view_copy, sizeof(rct_viewport));
|
|
return;
|
|
}
|
|
|
|
if (viewport->y < 0)
|
|
{
|
|
viewport->height += viewport->y;
|
|
viewport->view_height += viewport->y * zoom;
|
|
viewport->view_y -= viewport->y * zoom;
|
|
viewport->y = 0;
|
|
}
|
|
|
|
eax = viewport->y + viewport->height - context_get_height();
|
|
if (eax > 0)
|
|
{
|
|
viewport->height -= eax;
|
|
viewport->view_height -= eax * zoom;
|
|
}
|
|
|
|
if (viewport->height <= 0)
|
|
{
|
|
std::memcpy(viewport, &view_copy, sizeof(rct_viewport));
|
|
return;
|
|
}
|
|
|
|
if (drawing_engine_has_dirty_optimisations())
|
|
{
|
|
rct_drawpixelinfo* dpi = drawing_engine_get_dpi();
|
|
viewport_shift_pixels(dpi, w, viewport, x_diff, y_diff);
|
|
}
|
|
|
|
std::memcpy(viewport, &view_copy, sizeof(rct_viewport));
|
|
}
|
|
|
|
// rct2: 0x006E7A15
|
|
static void viewport_set_underground_flag(int32_t underground, rct_window* window, rct_viewport* viewport)
|
|
{
|
|
if (window->classification != WC_MAIN_WINDOW)
|
|
{
|
|
if (!underground)
|
|
{
|
|
int32_t bit = viewport->flags & VIEWPORT_FLAG_UNDERGROUND_INSIDE;
|
|
viewport->flags &= ~VIEWPORT_FLAG_UNDERGROUND_INSIDE;
|
|
if (!bit)
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
int32_t bit = viewport->flags & VIEWPORT_FLAG_UNDERGROUND_INSIDE;
|
|
viewport->flags |= VIEWPORT_FLAG_UNDERGROUND_INSIDE;
|
|
if (bit)
|
|
return;
|
|
}
|
|
window->Invalidate();
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
* rct2: 0x006E7A3A
|
|
*/
|
|
void viewport_update_position(rct_window* window)
|
|
{
|
|
window_event_resize_call(window);
|
|
|
|
rct_viewport* viewport = window->viewport;
|
|
if (!viewport)
|
|
return;
|
|
|
|
if (window->viewport_smart_follow_sprite != SPRITE_INDEX_NULL)
|
|
{
|
|
viewport_update_smart_sprite_follow(window);
|
|
}
|
|
|
|
if (window->viewport_target_sprite != SPRITE_INDEX_NULL)
|
|
{
|
|
viewport_update_sprite_follow(window);
|
|
return;
|
|
}
|
|
|
|
viewport_set_underground_flag(0, window, viewport);
|
|
|
|
int16_t x = window->saved_view_x + viewport->view_width / 2;
|
|
int16_t y = window->saved_view_y + viewport->view_height / 2;
|
|
|
|
auto mapCoord = viewport_coord_to_map_coord(x, y, 0);
|
|
|
|
// Clamp to the map minimum value
|
|
int32_t at_map_edge = 0;
|
|
if (mapCoord.x < MAP_MINIMUM_X_Y)
|
|
{
|
|
mapCoord.x = MAP_MINIMUM_X_Y;
|
|
at_map_edge = 1;
|
|
}
|
|
if (mapCoord.y < MAP_MINIMUM_X_Y)
|
|
{
|
|
mapCoord.y = MAP_MINIMUM_X_Y;
|
|
at_map_edge = 1;
|
|
}
|
|
|
|
// Clamp to the map maximum value (scenario specific)
|
|
if (mapCoord.x > gMapSizeMinus2)
|
|
{
|
|
mapCoord.x = gMapSizeMinus2;
|
|
at_map_edge = 1;
|
|
}
|
|
if (mapCoord.y > gMapSizeMinus2)
|
|
{
|
|
mapCoord.y = gMapSizeMinus2;
|
|
at_map_edge = 1;
|
|
}
|
|
|
|
if (at_map_edge)
|
|
{
|
|
int32_t _2d_x, _2d_y;
|
|
centre_2d_coordinates(mapCoord.x, mapCoord.y, 0, &_2d_x, &_2d_y, viewport);
|
|
|
|
window->saved_view_x = _2d_x;
|
|
window->saved_view_y = _2d_y;
|
|
}
|
|
|
|
x = window->saved_view_x;
|
|
y = window->saved_view_y;
|
|
if (window->flags & WF_SCROLLING_TO_LOCATION)
|
|
{
|
|
// Moves the viewport if focusing in on an item
|
|
uint8_t flags = 0;
|
|
x -= viewport->view_x;
|
|
if (x < 0)
|
|
{
|
|
x = -x;
|
|
flags |= 1;
|
|
}
|
|
y -= viewport->view_y;
|
|
if (y < 0)
|
|
{
|
|
y = -y;
|
|
flags |= 2;
|
|
}
|
|
x = (x + 7) / 8;
|
|
y = (y + 7) / 8;
|
|
|
|
// If we are at the final zoom position
|
|
if (!x && !y)
|
|
{
|
|
window->flags &= ~WF_SCROLLING_TO_LOCATION;
|
|
}
|
|
if (flags & 1)
|
|
{
|
|
x = -x;
|
|
}
|
|
if (flags & 2)
|
|
{
|
|
y = -y;
|
|
}
|
|
x += viewport->view_x;
|
|
y += viewport->view_y;
|
|
}
|
|
|
|
viewport_move(x, y, window, viewport);
|
|
}
|
|
|
|
void viewport_update_sprite_follow(rct_window* window)
|
|
{
|
|
if (window->viewport_target_sprite != SPRITE_INDEX_NULL && window->viewport)
|
|
{
|
|
rct_sprite* sprite = get_sprite(window->viewport_target_sprite);
|
|
|
|
int32_t height = (tile_element_height({ sprite->generic.x, sprite->generic.y })) - 16;
|
|
int32_t underground = sprite->generic.z < height;
|
|
|
|
viewport_set_underground_flag(underground, window, window->viewport);
|
|
|
|
int32_t centre_x, centre_y;
|
|
centre_2d_coordinates(sprite->generic.x, sprite->generic.y, sprite->generic.z, ¢re_x, ¢re_y, window->viewport);
|
|
|
|
window->saved_view_x = centre_x;
|
|
window->saved_view_y = centre_y;
|
|
viewport_move(centre_x, centre_y, window, window->viewport);
|
|
}
|
|
}
|
|
|
|
void viewport_update_smart_sprite_follow(rct_window* window)
|
|
{
|
|
rct_sprite* sprite = try_get_sprite(window->viewport_smart_follow_sprite);
|
|
if (sprite == nullptr)
|
|
{
|
|
window->viewport_smart_follow_sprite = SPRITE_INDEX_NULL;
|
|
window->viewport_target_sprite = SPRITE_INDEX_NULL;
|
|
}
|
|
else if (sprite->generic.sprite_identifier == SPRITE_IDENTIFIER_PEEP)
|
|
{
|
|
Peep* peep = GET_PEEP(window->viewport_smart_follow_sprite);
|
|
|
|
if (peep->type == PEEP_TYPE_GUEST)
|
|
viewport_update_smart_guest_follow(window, peep);
|
|
else if (peep->type == PEEP_TYPE_STAFF)
|
|
viewport_update_smart_staff_follow(window, peep);
|
|
}
|
|
else if (sprite->generic.sprite_identifier == SPRITE_IDENTIFIER_VEHICLE)
|
|
{
|
|
viewport_update_smart_vehicle_follow(window);
|
|
}
|
|
else if (
|
|
sprite->generic.sprite_identifier == SPRITE_IDENTIFIER_MISC
|
|
|| sprite->generic.sprite_identifier == SPRITE_IDENTIFIER_LITTER)
|
|
{
|
|
window->viewport_focus_sprite.sprite_id = window->viewport_smart_follow_sprite;
|
|
window->viewport_target_sprite = window->viewport_smart_follow_sprite;
|
|
}
|
|
else
|
|
{
|
|
window->viewport_smart_follow_sprite = SPRITE_INDEX_NULL;
|
|
window->viewport_target_sprite = SPRITE_INDEX_NULL;
|
|
}
|
|
}
|
|
|
|
viewport_focus viewport_update_smart_guest_follow(rct_window* window, Peep* peep)
|
|
{
|
|
viewport_focus focus{};
|
|
focus.type = VIEWPORT_FOCUS_TYPE_SPRITE;
|
|
focus.sprite.sprite_id = peep->sprite_index;
|
|
|
|
if (peep->state == PEEP_STATE_PICKED)
|
|
{
|
|
focus.sprite.sprite_id = SPRITE_INDEX_NULL;
|
|
window->viewport_smart_follow_sprite = SPRITE_INDEX_NULL;
|
|
window->viewport_target_sprite = SPRITE_INDEX_NULL;
|
|
return focus;
|
|
}
|
|
else
|
|
{
|
|
bool overallFocus = true;
|
|
if (peep->state == PEEP_STATE_ON_RIDE || peep->state == PEEP_STATE_ENTERING_RIDE
|
|
|| (peep->state == PEEP_STATE_LEAVING_RIDE && peep->x == LOCATION_NULL))
|
|
{
|
|
auto ride = get_ride(peep->current_ride);
|
|
if (ride != nullptr && (ride->lifecycle_flags & RIDE_LIFECYCLE_ON_TRACK))
|
|
{
|
|
auto train = GET_VEHICLE(ride->vehicles[peep->current_train]);
|
|
if (train != nullptr)
|
|
{
|
|
auto car = train->GetCar(peep->current_car);
|
|
if (car != nullptr)
|
|
{
|
|
focus.sprite.sprite_id = car->sprite_index;
|
|
overallFocus = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (peep->x == LOCATION_NULL && overallFocus)
|
|
{
|
|
auto ride = get_ride(peep->current_ride);
|
|
if (ride != nullptr)
|
|
{
|
|
auto x = (int32_t)ride->overall_view.x * 32 + 16;
|
|
auto y = (int32_t)ride->overall_view.y * 32 + 16;
|
|
focus.type = VIEWPORT_FOCUS_TYPE_COORDINATE;
|
|
focus.coordinate.x = x;
|
|
focus.coordinate.y = y;
|
|
focus.coordinate.z = tile_element_height({ x, y }) + 32;
|
|
focus.sprite.type |= VIEWPORT_FOCUS_TYPE_COORDINATE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
focus.sprite.type |= VIEWPORT_FOCUS_TYPE_SPRITE | VIEWPORT_FOCUS_TYPE_COORDINATE;
|
|
focus.sprite.pad_486 &= 0xFFFF;
|
|
}
|
|
focus.coordinate.rotation = get_current_rotation();
|
|
}
|
|
|
|
window->viewport_focus_sprite = focus.sprite;
|
|
window->viewport_target_sprite = window->viewport_focus_sprite.sprite_id;
|
|
return focus;
|
|
}
|
|
|
|
void viewport_update_smart_staff_follow(rct_window* window, Peep* peep)
|
|
{
|
|
sprite_focus focus = {};
|
|
|
|
focus.sprite_id = window->viewport_smart_follow_sprite;
|
|
|
|
if (peep->state == PEEP_STATE_PICKED)
|
|
{
|
|
// focus.sprite.sprite_id = SPRITE_INDEX_NULL;
|
|
window->viewport_smart_follow_sprite = SPRITE_INDEX_NULL;
|
|
window->viewport_target_sprite = SPRITE_INDEX_NULL;
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
focus.type |= VIEWPORT_FOCUS_TYPE_SPRITE | VIEWPORT_FOCUS_TYPE_COORDINATE;
|
|
}
|
|
|
|
window->viewport_focus_sprite = focus;
|
|
window->viewport_target_sprite = window->viewport_focus_sprite.sprite_id;
|
|
}
|
|
|
|
void viewport_update_smart_vehicle_follow(rct_window* window)
|
|
{
|
|
// Can be expanded in the future if needed
|
|
sprite_focus focus = {};
|
|
|
|
focus.sprite_id = window->viewport_smart_follow_sprite;
|
|
|
|
window->viewport_focus_sprite = focus;
|
|
window->viewport_target_sprite = window->viewport_focus_sprite.sprite_id;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* rct2: 0x00685C02
|
|
* ax: left
|
|
* bx: top
|
|
* dx: right
|
|
* esi: viewport
|
|
* edi: dpi
|
|
* ebp: bottom
|
|
*/
|
|
void viewport_render(
|
|
rct_drawpixelinfo* dpi, const rct_viewport* viewport, int32_t left, int32_t top, int32_t right, int32_t bottom,
|
|
std::vector<paint_session>* sessions)
|
|
{
|
|
if (right <= viewport->x)
|
|
return;
|
|
if (bottom <= viewport->y)
|
|
return;
|
|
if (left >= viewport->x + viewport->width)
|
|
return;
|
|
if (top >= viewport->y + viewport->height)
|
|
return;
|
|
|
|
#ifdef DEBUG_SHOW_DIRTY_BOX
|
|
int32_t l = left, t = top, r = right, b = bottom;
|
|
#endif
|
|
|
|
left = std::max<int32_t>(left - viewport->x, 0);
|
|
right = std::min<int32_t>(right - viewport->x, viewport->width);
|
|
top = std::max<int32_t>(top - viewport->y, 0);
|
|
bottom = std::min<int32_t>(bottom - viewport->y, viewport->height);
|
|
|
|
left <<= viewport->zoom;
|
|
right <<= viewport->zoom;
|
|
top <<= viewport->zoom;
|
|
bottom <<= viewport->zoom;
|
|
|
|
left += viewport->view_x;
|
|
right += viewport->view_x;
|
|
top += viewport->view_y;
|
|
bottom += viewport->view_y;
|
|
|
|
viewport_paint(viewport, dpi, left, top, right, bottom, sessions);
|
|
|
|
#ifdef DEBUG_SHOW_DIRTY_BOX
|
|
if (viewport != g_viewport_list)
|
|
{
|
|
gfx_fill_rect_inset(dpi, l, t, r - 1, b - 1, 0x2, INSET_RECT_F_30);
|
|
return;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
static void viewport_fill_column(paint_session* session)
|
|
{
|
|
paint_session_generate(session);
|
|
paint_session_arrange(session);
|
|
}
|
|
|
|
static void viewport_paint_column(paint_session* session)
|
|
{
|
|
if (session->ViewFlags
|
|
& (VIEWPORT_FLAG_HIDE_VERTICAL | VIEWPORT_FLAG_HIDE_BASE | VIEWPORT_FLAG_UNDERGROUND_INSIDE
|
|
| VIEWPORT_FLAG_CLIP_VIEW)
|
|
&& (~session->ViewFlags & VIEWPORT_FLAG_TRANSPARENT_BACKGROUND))
|
|
{
|
|
uint8_t colour = COLOUR_AQUAMARINE;
|
|
if (session->ViewFlags & VIEWPORT_FLAG_INVISIBLE_SPRITES)
|
|
{
|
|
colour = COLOUR_BLACK;
|
|
}
|
|
gfx_clear(&session->DPI, colour);
|
|
}
|
|
|
|
paint_draw_structs(session);
|
|
|
|
if (gConfigGeneral.render_weather_gloom && !gTrackDesignSaveMode && !(session->ViewFlags & VIEWPORT_FLAG_INVISIBLE_SPRITES)
|
|
&& !(session->ViewFlags & VIEWPORT_FLAG_HIGHLIGHT_PATH_ISSUES))
|
|
{
|
|
viewport_paint_weather_gloom(&session->DPI);
|
|
}
|
|
|
|
if (session->PSStringHead != nullptr)
|
|
{
|
|
paint_draw_money_structs(&session->DPI, session->PSStringHead);
|
|
}
|
|
|
|
paint_session_free(session);
|
|
}
|
|
|
|
/**
|
|
*
|
|
* rct2: 0x00685CBF
|
|
* eax: left
|
|
* ebx: top
|
|
* edx: right
|
|
* esi: viewport
|
|
* edi: dpi
|
|
* ebp: bottom
|
|
*/
|
|
void viewport_paint(
|
|
const rct_viewport* viewport, rct_drawpixelinfo* dpi, int16_t left, int16_t top, int16_t right, int16_t bottom,
|
|
std::vector<paint_session>* sessions)
|
|
{
|
|
uint32_t viewFlags = viewport->flags;
|
|
uint16_t width = right - left;
|
|
uint16_t height = bottom - top;
|
|
uint16_t bitmask = 0xFFFF & (0xFFFF << viewport->zoom);
|
|
|
|
width &= bitmask;
|
|
height &= bitmask;
|
|
left &= bitmask;
|
|
top &= bitmask;
|
|
right = left + width;
|
|
bottom = top + height;
|
|
|
|
int16_t x = (int16_t)(left - (int16_t)(viewport->view_x & bitmask));
|
|
x >>= viewport->zoom;
|
|
x += viewport->x;
|
|
|
|
int16_t y = (int16_t)(top - (int16_t)(viewport->view_y & bitmask));
|
|
y >>= viewport->zoom;
|
|
y += viewport->y;
|
|
|
|
rct_drawpixelinfo dpi1 = *dpi;
|
|
dpi1.bits = dpi->bits + (x - dpi->x) + ((y - dpi->y) * (dpi->width + dpi->pitch));
|
|
dpi1.x = left;
|
|
dpi1.y = top;
|
|
dpi1.width = width;
|
|
dpi1.height = height;
|
|
dpi1.pitch = (dpi->width + dpi->pitch) - (width >> viewport->zoom);
|
|
dpi1.zoom_level = viewport->zoom;
|
|
|
|
// make sure, the compare operation is done in int16_t to avoid the loop becoming an infiniteloop.
|
|
// this as well as the [x += 32] in the loop causes signed integer overflow -> undefined behaviour.
|
|
int16_t rightBorder = dpi1.x + dpi1.width;
|
|
|
|
std::vector<paint_session*> columns;
|
|
|
|
bool useMultithreading = gConfigGeneral.multithreading;
|
|
if (window_get_main() != nullptr && viewport != window_get_main()->viewport)
|
|
useMultithreading = false;
|
|
|
|
if (useMultithreading && _paintJobs == nullptr)
|
|
{
|
|
_paintJobs = std::make_unique<JobPool>();
|
|
}
|
|
else if (useMultithreading == false && _paintJobs != nullptr)
|
|
{
|
|
_paintJobs.reset();
|
|
}
|
|
|
|
// Splits the area into 32 pixel columns and renders them
|
|
size_t index = 0;
|
|
for (x = floor2(dpi1.x, 32); x < rightBorder; x += 32, index++)
|
|
{
|
|
paint_session* session = paint_session_alloc(&dpi1, viewFlags);
|
|
columns.push_back(session);
|
|
|
|
rct_drawpixelinfo& dpi2 = session->DPI;
|
|
if (x >= dpi2.x)
|
|
{
|
|
int16_t leftPitch = x - dpi2.x;
|
|
dpi2.width -= leftPitch;
|
|
dpi2.bits += leftPitch >> dpi2.zoom_level;
|
|
dpi2.pitch += leftPitch >> dpi2.zoom_level;
|
|
dpi2.x = x;
|
|
}
|
|
|
|
int16_t paintRight = dpi2.x + dpi2.width;
|
|
if (paintRight >= x + 32)
|
|
{
|
|
int16_t rightPitch = paintRight - x - 32;
|
|
paintRight -= rightPitch;
|
|
dpi2.pitch += rightPitch >> dpi2.zoom_level;
|
|
}
|
|
dpi2.width = paintRight - dpi2.x;
|
|
|
|
if (useMultithreading)
|
|
{
|
|
_paintJobs->AddTask([session]() -> void { viewport_fill_column(session); });
|
|
}
|
|
else
|
|
{
|
|
viewport_fill_column(session);
|
|
}
|
|
}
|
|
|
|
if (useMultithreading)
|
|
{
|
|
_paintJobs->Join();
|
|
}
|
|
|
|
for (auto&& column : columns)
|
|
{
|
|
viewport_paint_column(column);
|
|
}
|
|
}
|
|
|
|
static void viewport_paint_weather_gloom(rct_drawpixelinfo* dpi)
|
|
{
|
|
auto paletteId = climate_get_weather_gloom_palette_id(gClimateCurrent);
|
|
if (paletteId != PALETTE_NULL)
|
|
{
|
|
gfx_filter_rect(dpi, dpi->x, dpi->y, dpi->width + dpi->x - 1, dpi->height + dpi->y - 1, paletteId);
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
* rct2: 0x0068958D
|
|
*/
|
|
CoordsXY screen_pos_to_map_pos(ScreenCoordsXY screenCoords, int32_t* direction)
|
|
{
|
|
CoordsXY mapCoords = screen_get_map_xy(screenCoords, nullptr);
|
|
if (mapCoords.x == LOCATION_NULL)
|
|
return {};
|
|
|
|
int32_t my_direction;
|
|
int32_t dist_from_centre_x = abs(mapCoords.x % 32);
|
|
int32_t dist_from_centre_y = abs(mapCoords.y % 32);
|
|
if (dist_from_centre_x > 8 && dist_from_centre_x < 24 && dist_from_centre_y > 8 && dist_from_centre_y < 24)
|
|
{
|
|
my_direction = 4;
|
|
}
|
|
else
|
|
{
|
|
int16_t mod_x = mapCoords.x & 0x1F;
|
|
int16_t mod_y = mapCoords.y & 0x1F;
|
|
if (mod_x <= 16)
|
|
{
|
|
if (mod_y < 16)
|
|
{
|
|
my_direction = 2;
|
|
}
|
|
else
|
|
{
|
|
my_direction = 3;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (mod_y < 16)
|
|
{
|
|
my_direction = 1;
|
|
}
|
|
else
|
|
{
|
|
my_direction = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
mapCoords.x = mapCoords.x & ~0x1F;
|
|
mapCoords.y = mapCoords.y & ~0x1F;
|
|
if (direction != nullptr)
|
|
*direction = my_direction;
|
|
return mapCoords;
|
|
}
|
|
|
|
LocationXY16 screen_coord_to_viewport_coord(rct_viewport* viewport, ScreenCoordsXY screenCoords)
|
|
{
|
|
LocationXY16 ret;
|
|
ret.x = ((screenCoords.x - viewport->x) << viewport->zoom) + viewport->view_x;
|
|
ret.y = ((screenCoords.y - viewport->y) << viewport->zoom) + viewport->view_y;
|
|
return ret;
|
|
}
|
|
|
|
CoordsXY viewport_coord_to_map_coord(int32_t x, int32_t y, int32_t z)
|
|
{
|
|
CoordsXY ret{};
|
|
switch (get_current_rotation())
|
|
{
|
|
case 0:
|
|
ret.x = -x / 2 + y + z;
|
|
ret.y = x / 2 + y + z;
|
|
break;
|
|
case 1:
|
|
ret.x = -x / 2 - y - z;
|
|
ret.y = -x / 2 + y + z;
|
|
break;
|
|
case 2:
|
|
ret.x = x / 2 - y - z;
|
|
ret.y = -x / 2 - y - z;
|
|
break;
|
|
case 3:
|
|
ret.x = x / 2 + y + z;
|
|
ret.y = x / 2 - y - z;
|
|
break;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* rct2: 0x00664689
|
|
*/
|
|
void show_gridlines()
|
|
{
|
|
if (gShowGridLinesRefCount == 0)
|
|
{
|
|
rct_window* mainWindow = window_get_main();
|
|
if (mainWindow != nullptr)
|
|
{
|
|
if (!(mainWindow->viewport->flags & VIEWPORT_FLAG_GRIDLINES))
|
|
{
|
|
mainWindow->viewport->flags |= VIEWPORT_FLAG_GRIDLINES;
|
|
mainWindow->Invalidate();
|
|
}
|
|
}
|
|
}
|
|
gShowGridLinesRefCount++;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* rct2: 0x006646B4
|
|
*/
|
|
void hide_gridlines()
|
|
{
|
|
gShowGridLinesRefCount--;
|
|
if (gShowGridLinesRefCount == 0)
|
|
{
|
|
rct_window* mainWindow = window_get_main();
|
|
if (mainWindow != nullptr)
|
|
{
|
|
if (!gConfigGeneral.always_show_gridlines)
|
|
{
|
|
mainWindow->viewport->flags &= ~VIEWPORT_FLAG_GRIDLINES;
|
|
mainWindow->Invalidate();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
* rct2: 0x00664E8E
|
|
*/
|
|
void show_land_rights()
|
|
{
|
|
if (gShowLandRightsRefCount == 0)
|
|
{
|
|
rct_window* mainWindow = window_get_main();
|
|
if (mainWindow != nullptr)
|
|
{
|
|
if (!(mainWindow->viewport->flags & VIEWPORT_FLAG_LAND_OWNERSHIP))
|
|
{
|
|
mainWindow->viewport->flags |= VIEWPORT_FLAG_LAND_OWNERSHIP;
|
|
mainWindow->Invalidate();
|
|
}
|
|
}
|
|
}
|
|
gShowLandRightsRefCount++;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* rct2: 0x00664EB9
|
|
*/
|
|
void hide_land_rights()
|
|
{
|
|
gShowLandRightsRefCount--;
|
|
if (gShowLandRightsRefCount == 0)
|
|
{
|
|
rct_window* mainWindow = window_get_main();
|
|
if (mainWindow != nullptr)
|
|
{
|
|
if (mainWindow->viewport->flags & VIEWPORT_FLAG_LAND_OWNERSHIP)
|
|
{
|
|
mainWindow->viewport->flags &= ~VIEWPORT_FLAG_LAND_OWNERSHIP;
|
|
mainWindow->Invalidate();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
* rct2: 0x00664EDD
|
|
*/
|
|
void show_construction_rights()
|
|
{
|
|
if (gShowConstuctionRightsRefCount == 0)
|
|
{
|
|
rct_window* mainWindow = window_get_main();
|
|
if (mainWindow != nullptr)
|
|
{
|
|
if (!(mainWindow->viewport->flags & VIEWPORT_FLAG_CONSTRUCTION_RIGHTS))
|
|
{
|
|
mainWindow->viewport->flags |= VIEWPORT_FLAG_CONSTRUCTION_RIGHTS;
|
|
mainWindow->Invalidate();
|
|
}
|
|
}
|
|
}
|
|
gShowConstuctionRightsRefCount++;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* rct2: 0x00664F08
|
|
*/
|
|
void hide_construction_rights()
|
|
{
|
|
gShowConstuctionRightsRefCount--;
|
|
if (gShowConstuctionRightsRefCount == 0)
|
|
{
|
|
rct_window* mainWindow = window_get_main();
|
|
if (mainWindow != nullptr)
|
|
{
|
|
if (mainWindow->viewport->flags & VIEWPORT_FLAG_CONSTRUCTION_RIGHTS)
|
|
{
|
|
mainWindow->viewport->flags &= ~VIEWPORT_FLAG_CONSTRUCTION_RIGHTS;
|
|
mainWindow->Invalidate();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
* rct2: 0x006CB70A
|
|
*/
|
|
void viewport_set_visibility(uint8_t mode)
|
|
{
|
|
rct_window* window = window_get_main();
|
|
|
|
if (window != nullptr)
|
|
{
|
|
rct_viewport* vp = window->viewport;
|
|
uint32_t invalidate = 0;
|
|
|
|
switch (mode)
|
|
{
|
|
case 0:
|
|
{ // Set all these flags to 0, and invalidate if any were active
|
|
uint32_t mask = VIEWPORT_FLAG_UNDERGROUND_INSIDE | VIEWPORT_FLAG_SEETHROUGH_RIDES
|
|
| VIEWPORT_FLAG_SEETHROUGH_SCENERY | VIEWPORT_FLAG_SEETHROUGH_PATHS | VIEWPORT_FLAG_INVISIBLE_SUPPORTS
|
|
| VIEWPORT_FLAG_LAND_HEIGHTS | VIEWPORT_FLAG_TRACK_HEIGHTS | VIEWPORT_FLAG_PATH_HEIGHTS
|
|
| VIEWPORT_FLAG_INVISIBLE_PEEPS | VIEWPORT_FLAG_HIDE_BASE | VIEWPORT_FLAG_HIDE_VERTICAL;
|
|
|
|
invalidate += vp->flags & mask;
|
|
vp->flags &= ~mask;
|
|
break;
|
|
}
|
|
case 1: // 6CB79D
|
|
case 4: // 6CB7C4
|
|
// Set underground on, invalidate if it was off
|
|
invalidate += !(vp->flags & VIEWPORT_FLAG_UNDERGROUND_INSIDE);
|
|
vp->flags |= VIEWPORT_FLAG_UNDERGROUND_INSIDE;
|
|
break;
|
|
case 2: // 6CB7EB
|
|
// Set track heights on, invalidate if off
|
|
invalidate += !(vp->flags & VIEWPORT_FLAG_TRACK_HEIGHTS);
|
|
vp->flags |= VIEWPORT_FLAG_TRACK_HEIGHTS;
|
|
break;
|
|
case 3: // 6CB7B1
|
|
case 5: // 6CB7D8
|
|
// Set underground off, invalidate if it was on
|
|
invalidate += vp->flags & VIEWPORT_FLAG_UNDERGROUND_INSIDE;
|
|
vp->flags &= ~((uint16_t)VIEWPORT_FLAG_UNDERGROUND_INSIDE);
|
|
break;
|
|
}
|
|
if (invalidate != 0)
|
|
window->Invalidate();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Stores some info about the element pointed at, if requested for this particular type through the interaction mask.
|
|
* Originally checked 0x0141F569 at start
|
|
* rct2: 0x00688697
|
|
*/
|
|
static void store_interaction_info(paint_struct* ps)
|
|
{
|
|
if (ps->sprite_type == VIEWPORT_INTERACTION_ITEM_NONE
|
|
|| ps->sprite_type == 11 // 11 as a type seems to not exist, maybe part of the typo mentioned later on.
|
|
|| ps->sprite_type > VIEWPORT_INTERACTION_ITEM_BANNER)
|
|
return;
|
|
|
|
uint16_t mask;
|
|
if (ps->sprite_type == VIEWPORT_INTERACTION_ITEM_BANNER)
|
|
// I think CS made a typo here. Let's replicate the original behaviour.
|
|
mask = 1 << (ps->sprite_type - 3);
|
|
else
|
|
mask = 1 << (ps->sprite_type - 1);
|
|
|
|
if (!(_interactionFlags & mask))
|
|
{
|
|
_interactionSpriteType = ps->sprite_type;
|
|
_interactionMapX = ps->map_x;
|
|
_interactionMapY = ps->map_y;
|
|
_interaction_element = ps->tileElement;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* rct2: 0x00679236, 0x00679662, 0x00679B0D, 0x00679FF1
|
|
*/
|
|
static bool is_pixel_present_bmp(uint32_t imageType, const rct_g1_element* g1, const uint8_t* index, const uint8_t* palette)
|
|
{
|
|
// Probably used to check for corruption
|
|
if (!(g1->flags & G1_FLAG_BMP))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (imageType & IMAGE_TYPE_REMAP)
|
|
{
|
|
return palette[*index] != 0;
|
|
}
|
|
|
|
if (imageType & IMAGE_TYPE_TRANSPARENT)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return (*index != 0);
|
|
}
|
|
|
|
/**
|
|
* rct2: 0x0067933B, 0x00679788, 0x00679C4A, 0x0067A117
|
|
*/
|
|
static bool is_pixel_present_rle(const uint8_t* esi, int16_t x_start_point, int16_t y_start_point, int32_t round)
|
|
{
|
|
uint16_t start_offset = esi[y_start_point * 2] | (esi[y_start_point * 2 + 1] << 8);
|
|
const uint8_t* ebx = esi + start_offset;
|
|
|
|
uint8_t last_data_line = 0;
|
|
while (!last_data_line)
|
|
{
|
|
int32_t no_pixels = *ebx++;
|
|
uint8_t gap_size = *ebx++;
|
|
|
|
last_data_line = no_pixels & 0x80;
|
|
|
|
no_pixels &= 0x7F;
|
|
|
|
ebx += no_pixels;
|
|
|
|
if (round > 1)
|
|
{
|
|
if (gap_size % 2)
|
|
{
|
|
gap_size++;
|
|
no_pixels--;
|
|
if (no_pixels == 0)
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (round == 4)
|
|
{
|
|
if (gap_size % 4)
|
|
{
|
|
gap_size += 2;
|
|
no_pixels -= 2;
|
|
if (no_pixels <= 0)
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
int32_t x_start = gap_size - x_start_point;
|
|
if (x_start <= 0)
|
|
{
|
|
no_pixels += x_start;
|
|
if (no_pixels <= 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
x_start = 0;
|
|
}
|
|
else
|
|
{
|
|
// Do nothing?
|
|
}
|
|
|
|
x_start += no_pixels;
|
|
x_start--;
|
|
if (x_start > 0)
|
|
{
|
|
no_pixels -= x_start;
|
|
if (no_pixels <= 0)
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (round > 1)
|
|
{
|
|
// This matches the original implementation, but allows empty lines to cause false positives on zoom 0
|
|
if (ceil2(no_pixels, round) == 0)
|
|
continue;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* rct2: 0x00679074
|
|
*
|
|
* @param dpi (edi)
|
|
* @param imageId (ebx)
|
|
* @param x (cx)
|
|
* @param y (dx)
|
|
* @return value originally stored in 0x00141F569
|
|
*/
|
|
static bool is_sprite_interacted_with_palette_set(
|
|
rct_drawpixelinfo* dpi, int32_t imageId, int16_t x, int16_t y, const uint8_t* palette)
|
|
{
|
|
const rct_g1_element* g1 = gfx_get_g1_element(imageId & 0x7FFFF);
|
|
if (g1 == nullptr)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (dpi->zoom_level != 0)
|
|
{
|
|
if (g1->flags & G1_FLAG_NO_ZOOM_DRAW)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (g1->flags & G1_FLAG_HAS_ZOOM_SPRITE)
|
|
{
|
|
// TODO: SAR in dpi done with `>> 1`, in coordinates with `/ 2`
|
|
rct_drawpixelinfo zoomed_dpi = {
|
|
/* .bits = */ dpi->bits,
|
|
/* .x = */ (int16_t)(dpi->x >> 1),
|
|
/* .y = */ (int16_t)(dpi->y >> 1),
|
|
/* .height = */ dpi->height,
|
|
/* .width = */ dpi->width,
|
|
/* .pitch = */ dpi->pitch,
|
|
/* .zoom_level = */ (uint16_t)(dpi->zoom_level - 1),
|
|
};
|
|
|
|
return is_sprite_interacted_with_palette_set(&zoomed_dpi, imageId - g1->zoomed_offset, x / 2, y / 2, palette);
|
|
}
|
|
}
|
|
|
|
int32_t round = 1 << dpi->zoom_level;
|
|
|
|
if (g1->flags & G1_FLAG_RLE_COMPRESSION)
|
|
{
|
|
y -= (round - 1);
|
|
}
|
|
|
|
y += g1->y_offset;
|
|
int16_t yStartPoint = 0;
|
|
int16_t height = g1->height;
|
|
if (dpi->zoom_level != 0)
|
|
{
|
|
if (height % 2)
|
|
{
|
|
height--;
|
|
yStartPoint++;
|
|
}
|
|
|
|
if (dpi->zoom_level == 2)
|
|
{
|
|
if (height % 4)
|
|
{
|
|
height -= 2;
|
|
yStartPoint += 2;
|
|
}
|
|
}
|
|
|
|
if (height == 0)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
y = floor2(y, round);
|
|
int16_t yEndPoint = height;
|
|
y -= dpi->y;
|
|
if (y < 0)
|
|
{
|
|
yEndPoint += y;
|
|
if (yEndPoint <= 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
yStartPoint -= y;
|
|
y = 0;
|
|
}
|
|
|
|
y += yEndPoint;
|
|
y--;
|
|
if (y > 0)
|
|
{
|
|
yEndPoint -= y;
|
|
if (yEndPoint <= 0)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
int16_t xStartPoint = 0;
|
|
int16_t xEndPoint = g1->width;
|
|
|
|
x += g1->x_offset;
|
|
x = floor2(x, round);
|
|
x -= dpi->x;
|
|
if (x < 0)
|
|
{
|
|
xEndPoint += x;
|
|
if (xEndPoint <= 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
xStartPoint -= x;
|
|
x = 0;
|
|
}
|
|
|
|
x += xEndPoint;
|
|
x--;
|
|
if (x > 0)
|
|
{
|
|
xEndPoint -= x;
|
|
if (xEndPoint <= 0)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (g1->flags & G1_FLAG_RLE_COMPRESSION)
|
|
{
|
|
return is_pixel_present_rle(g1->offset, xStartPoint, yStartPoint, round);
|
|
}
|
|
|
|
uint8_t* offset = g1->offset + (yStartPoint * g1->width) + xStartPoint;
|
|
uint32_t imageType = _currentImageType;
|
|
|
|
if (!(g1->flags & G1_FLAG_1))
|
|
{
|
|
return is_pixel_present_bmp(imageType, g1, offset, palette);
|
|
}
|
|
|
|
Guard::Assert(false, "Invalid image type encountered.");
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* rct2: 0x00679023
|
|
*/
|
|
static bool is_sprite_interacted_with(rct_drawpixelinfo* dpi, int32_t imageId, int32_t x, int32_t y)
|
|
{
|
|
const uint8_t* palette = nullptr;
|
|
imageId &= ~IMAGE_TYPE_TRANSPARENT;
|
|
if (imageId & IMAGE_TYPE_REMAP)
|
|
{
|
|
_currentImageType = IMAGE_TYPE_REMAP;
|
|
int32_t index = (imageId >> 19) & 0x7F;
|
|
if (imageId & IMAGE_TYPE_REMAP_2_PLUS)
|
|
{
|
|
index &= 0x1F;
|
|
}
|
|
int32_t g1Index = palette_to_g1_offset[index];
|
|
const rct_g1_element* g1 = gfx_get_g1_element(g1Index);
|
|
if (g1 != nullptr)
|
|
{
|
|
palette = g1->offset;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
_currentImageType = 0;
|
|
}
|
|
return is_sprite_interacted_with_palette_set(dpi, imageId, x, y, palette);
|
|
}
|
|
|
|
/**
|
|
*
|
|
* rct2: 0x0068862C
|
|
*/
|
|
static void set_interaction_info_from_paint_session(paint_session* session)
|
|
{
|
|
paint_struct* ps = &session->PaintHead;
|
|
rct_drawpixelinfo* dpi = &session->DPI;
|
|
|
|
while ((ps = ps->next_quadrant_ps) != nullptr)
|
|
{
|
|
paint_struct* old_ps = ps;
|
|
paint_struct* next_ps = ps;
|
|
while (next_ps != nullptr)
|
|
{
|
|
ps = next_ps;
|
|
if (is_sprite_interacted_with(dpi, ps->image_id, ps->x, ps->y))
|
|
{
|
|
store_interaction_info(ps);
|
|
}
|
|
next_ps = ps->children;
|
|
}
|
|
|
|
for (attached_paint_struct* attached_ps = ps->attached_ps; attached_ps != nullptr; attached_ps = attached_ps->next)
|
|
{
|
|
if (is_sprite_interacted_with(
|
|
dpi, attached_ps->image_id, (attached_ps->x + ps->x) & 0xFFFF, (attached_ps->y + ps->y) & 0xFFFF))
|
|
{
|
|
store_interaction_info(ps);
|
|
}
|
|
}
|
|
|
|
ps = old_ps;
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
* rct2: 0x00685ADC
|
|
* screenX: eax
|
|
* screenY: ebx
|
|
* flags: edx
|
|
* x: ax
|
|
* y: cx
|
|
* interactionType: bl
|
|
* tileElement: edx
|
|
* viewport: edi
|
|
*/
|
|
void get_map_coordinates_from_pos(
|
|
int32_t screenX, int32_t screenY, int32_t flags, int16_t* x, int16_t* y, int32_t* interactionType,
|
|
TileElement** tileElement, rct_viewport** viewport)
|
|
{
|
|
rct_window* window = window_find_from_point(ScreenCoordsXY(screenX, screenY));
|
|
get_map_coordinates_from_pos_window(window, screenX, screenY, flags, x, y, interactionType, tileElement, viewport);
|
|
}
|
|
|
|
void get_map_coordinates_from_pos_window(
|
|
rct_window* window, int32_t screenX, int32_t screenY, int32_t flags, int16_t* x, int16_t* y, int32_t* interactionType,
|
|
TileElement** tileElement, rct_viewport** viewport)
|
|
{
|
|
ScreenCoordsXY screenCoords(screenX, screenY);
|
|
CoordsXY mapCoords;
|
|
|
|
get_map_coordinates_from_pos_window(window, screenCoords, flags, mapCoords, interactionType, tileElement, viewport);
|
|
|
|
if (x != nullptr)
|
|
*x = mapCoords.x;
|
|
if (y != nullptr)
|
|
*y = mapCoords.y;
|
|
}
|
|
|
|
void get_map_coordinates_from_pos_window(
|
|
rct_window* window, ScreenCoordsXY screenCoords, int32_t flags, CoordsXY& mapCoords, int32_t* interactionType,
|
|
TileElement** tileElement, rct_viewport** viewport)
|
|
{
|
|
_interactionFlags = flags & 0xFFFF;
|
|
_interactionSpriteType = 0;
|
|
if (window != nullptr && window->viewport != nullptr)
|
|
{
|
|
rct_viewport* myviewport = window->viewport;
|
|
screenCoords.x -= (int32_t)myviewport->x;
|
|
screenCoords.y -= (int32_t)myviewport->y;
|
|
if (screenCoords.x >= 0 && screenCoords.x < (int32_t)myviewport->width && screenCoords.y >= 0
|
|
&& screenCoords.y < (int32_t)myviewport->height)
|
|
{
|
|
screenCoords.x <<= myviewport->zoom;
|
|
screenCoords.y <<= myviewport->zoom;
|
|
screenCoords.x += (int32_t)myviewport->view_x;
|
|
screenCoords.y += (int32_t)myviewport->view_y;
|
|
_viewportDpi1.zoom_level = myviewport->zoom;
|
|
screenCoords.x &= (0xFFFF << myviewport->zoom) & 0xFFFF;
|
|
screenCoords.y &= (0xFFFF << myviewport->zoom) & 0xFFFF;
|
|
_viewportDpi1.x = screenCoords.x;
|
|
_viewportDpi1.y = screenCoords.y;
|
|
rct_drawpixelinfo* dpi = &_viewportDpi2;
|
|
dpi->y = _viewportDpi1.y;
|
|
dpi->height = 1;
|
|
dpi->zoom_level = _viewportDpi1.zoom_level;
|
|
dpi->x = _viewportDpi1.x;
|
|
dpi->width = 1;
|
|
|
|
paint_session* session = paint_session_alloc(dpi, myviewport->flags);
|
|
paint_session_generate(session);
|
|
paint_session_arrange(session);
|
|
set_interaction_info_from_paint_session(session);
|
|
paint_session_free(session);
|
|
}
|
|
if (viewport != nullptr)
|
|
*viewport = myviewport;
|
|
}
|
|
if (interactionType != nullptr)
|
|
*interactionType = _interactionSpriteType;
|
|
|
|
mapCoords.x = _interactionMapX;
|
|
mapCoords.y = _interactionMapY;
|
|
|
|
if (tileElement != nullptr)
|
|
*tileElement = _interaction_element;
|
|
}
|
|
|
|
/**
|
|
* Left, top, right and bottom represent 2D map coordinates at zoom 0.
|
|
*/
|
|
void viewport_invalidate(rct_viewport* viewport, int32_t left, int32_t top, int32_t right, int32_t bottom)
|
|
{
|
|
// if unknown viewport visibility, use the containing window to discover the status
|
|
if (viewport->visibility == VC_UNKNOWN)
|
|
{
|
|
auto windowManager = GetContext()->GetUiContext()->GetWindowManager();
|
|
auto owner = windowManager->GetOwner(viewport);
|
|
if (owner != nullptr && owner->classification != WC_MAIN_WINDOW)
|
|
{
|
|
// note, window_is_visible will update viewport->visibility, so this should have a low hit count
|
|
if (!window_is_visible(owner))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (viewport->visibility == VC_COVERED)
|
|
return;
|
|
|
|
int32_t viewportLeft = viewport->view_x;
|
|
int32_t viewportTop = viewport->view_y;
|
|
int32_t viewportRight = viewport->view_x + viewport->view_width;
|
|
int32_t viewportBottom = viewport->view_y + viewport->view_height;
|
|
if (right > viewportLeft && bottom > viewportTop)
|
|
{
|
|
left = std::max(left, viewportLeft);
|
|
top = std::max(top, viewportTop);
|
|
right = std::min(right, viewportRight);
|
|
bottom = std::min(bottom, viewportBottom);
|
|
|
|
uint8_t zoom = 1 << viewport->zoom;
|
|
left -= viewportLeft;
|
|
top -= viewportTop;
|
|
right -= viewportLeft;
|
|
bottom -= viewportTop;
|
|
left /= zoom;
|
|
top /= zoom;
|
|
right /= zoom;
|
|
bottom /= zoom;
|
|
left += viewport->x;
|
|
top += viewport->y;
|
|
right += viewport->x;
|
|
bottom += viewport->y;
|
|
gfx_set_dirty_blocks(left, top, right, bottom);
|
|
}
|
|
}
|
|
|
|
static rct_viewport* viewport_find_from_point(int32_t screenX, int32_t screenY)
|
|
{
|
|
rct_window* w = window_find_from_point(ScreenCoordsXY(screenX, screenY));
|
|
if (w == nullptr)
|
|
return nullptr;
|
|
|
|
rct_viewport* viewport = w->viewport;
|
|
if (viewport == nullptr)
|
|
return nullptr;
|
|
|
|
if (screenX < viewport->x || screenY < viewport->y)
|
|
return nullptr;
|
|
if (screenX >= viewport->x + viewport->width || screenY >= viewport->y + viewport->height)
|
|
return nullptr;
|
|
|
|
return viewport;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* rct2: 0x00688972
|
|
* In:
|
|
* screen_x: eax
|
|
* screen_y: ebx
|
|
* Out:
|
|
* x: ax
|
|
* y: bx
|
|
* tile_element: edx ?
|
|
* viewport: edi
|
|
*/
|
|
CoordsXY screen_get_map_xy(ScreenCoordsXY screenCoords, rct_viewport** viewport)
|
|
{
|
|
int16_t my_x, my_y;
|
|
int32_t interactionType;
|
|
rct_viewport* myViewport = nullptr;
|
|
get_map_coordinates_from_pos(
|
|
screenCoords.x, screenCoords.y, VIEWPORT_INTERACTION_MASK_TERRAIN, &my_x, &my_y, &interactionType, nullptr,
|
|
&myViewport);
|
|
if (interactionType == VIEWPORT_INTERACTION_ITEM_NONE)
|
|
{
|
|
return { LOCATION_NULL, 0 };
|
|
}
|
|
|
|
LocationXY16 start_vp_pos = screen_coord_to_viewport_coord(myViewport, screenCoords);
|
|
CoordsXY map_pos = { my_x + 16, my_y + 16 };
|
|
|
|
for (int32_t i = 0; i < 5; i++)
|
|
{
|
|
int32_t z = tile_element_height(map_pos);
|
|
map_pos = viewport_coord_to_map_coord(start_vp_pos.x, start_vp_pos.y, z);
|
|
map_pos.x = std::clamp<int16_t>(map_pos.x, my_x, my_x + 31);
|
|
map_pos.y = std::clamp<int16_t>(map_pos.y, my_y, my_y + 31);
|
|
}
|
|
|
|
if (viewport != nullptr)
|
|
*viewport = myViewport;
|
|
|
|
return map_pos;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* rct2: 0x006894D4
|
|
*/
|
|
void screen_get_map_xy_with_z(int16_t screenX, int16_t screenY, int16_t z, int16_t* mapX, int16_t* mapY)
|
|
{
|
|
rct_viewport* viewport = viewport_find_from_point(screenX, screenY);
|
|
if (viewport == nullptr)
|
|
{
|
|
*mapX = LOCATION_NULL;
|
|
return;
|
|
}
|
|
|
|
screenX = viewport->view_x + ((screenX - viewport->x) << viewport->zoom);
|
|
screenY = viewport->view_y + ((screenY - viewport->y) << viewport->zoom);
|
|
|
|
auto mapPosition = viewport_coord_to_map_coord(screenX, screenY + z, 0);
|
|
if (mapPosition.x < 0 || mapPosition.x >= (256 * 32) || mapPosition.y < 0 || mapPosition.y > (256 * 32))
|
|
{
|
|
*mapX = LOCATION_NULL;
|
|
return;
|
|
}
|
|
|
|
*mapX = mapPosition.x;
|
|
*mapY = mapPosition.y;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* rct2: 0x00689604
|
|
*/
|
|
CoordsXY screen_get_map_xy_quadrant(ScreenCoordsXY screenCoords, uint8_t* quadrant)
|
|
{
|
|
CoordsXY mapCoords = screen_get_map_xy(screenCoords, nullptr);
|
|
if (mapCoords.x == LOCATION_NULL)
|
|
return mapCoords;
|
|
|
|
*quadrant = map_get_tile_quadrant(mapCoords.x, mapCoords.y);
|
|
return { floor2(mapCoords.x, 32), floor2(mapCoords.y, 32) };
|
|
}
|
|
|
|
/**
|
|
*
|
|
* rct2: 0x0068964B
|
|
*/
|
|
void screen_get_map_xy_quadrant_with_z(
|
|
int16_t screenX, int16_t screenY, int16_t z, int16_t* mapX, int16_t* mapY, uint8_t* quadrant)
|
|
{
|
|
screen_get_map_xy_with_z(screenX, screenY, z, mapX, mapY);
|
|
if (*mapX == LOCATION_NULL)
|
|
return;
|
|
|
|
*quadrant = map_get_tile_quadrant(*mapX, *mapY);
|
|
*mapX = floor2(*mapX, 32);
|
|
*mapY = floor2(*mapY, 32);
|
|
}
|
|
|
|
/**
|
|
*
|
|
* rct2: 0x00689692
|
|
*/
|
|
CoordsXY screen_get_map_xy_side(ScreenCoordsXY screenCoords, uint8_t* side)
|
|
{
|
|
CoordsXY mapCoords = screen_get_map_xy(screenCoords, nullptr);
|
|
if (mapCoords.x == LOCATION_NULL)
|
|
return mapCoords;
|
|
|
|
*side = map_get_tile_side(mapCoords.x, mapCoords.y);
|
|
return { floor2(mapCoords.x, 32), floor2(mapCoords.y, 32) };
|
|
}
|
|
|
|
/**
|
|
*
|
|
* rct2: 0x006896DC
|
|
*/
|
|
void screen_get_map_xy_side_with_z(int16_t screenX, int16_t screenY, int16_t z, int16_t* mapX, int16_t* mapY, uint8_t* side)
|
|
{
|
|
screen_get_map_xy_with_z(screenX, screenY, z, mapX, mapY);
|
|
if (*mapX == LOCATION_NULL)
|
|
return;
|
|
|
|
*side = map_get_tile_side(*mapX, *mapY);
|
|
*mapX = floor2(*mapX, 32);
|
|
*mapY = floor2(*mapY, 32);
|
|
}
|
|
|
|
/**
|
|
* Get current viewport rotation.
|
|
*
|
|
* If an invalid rotation is detected and DEBUG_LEVEL_1 is enabled, an error
|
|
* will be reported.
|
|
*
|
|
* @returns rotation in range 0-3 (inclusive)
|
|
*/
|
|
uint8_t get_current_rotation()
|
|
{
|
|
uint8_t rotation = gCurrentRotation;
|
|
uint8_t rotation_masked = rotation & 3;
|
|
#if defined(DEBUG_LEVEL_1) && DEBUG_LEVEL_1
|
|
if (rotation != rotation_masked)
|
|
{
|
|
log_error("Found wrong rotation %d! Will return %d instead.", (uint32_t)rotation, (uint32_t)rotation_masked);
|
|
}
|
|
#endif // DEBUG_LEVEL_1
|
|
return rotation_masked;
|
|
}
|
|
|
|
int16_t get_height_marker_offset()
|
|
{
|
|
// Height labels in units
|
|
if (gConfigGeneral.show_height_as_units)
|
|
return 0;
|
|
|
|
// Height labels in feet
|
|
if (gConfigGeneral.measurement_format == MEASUREMENT_FORMAT_IMPERIAL)
|
|
return 1 * 256;
|
|
|
|
// Height labels in metres
|
|
return 2 * 256;
|
|
}
|
|
|
|
void viewport_set_saved_view()
|
|
{
|
|
rct_window* w = window_get_main();
|
|
if (w != nullptr)
|
|
{
|
|
rct_viewport* viewport = w->viewport;
|
|
|
|
gSavedViewX = viewport->view_width / 2 + viewport->view_x;
|
|
gSavedViewY = viewport->view_height / 2 + viewport->view_y;
|
|
|
|
gSavedViewZoom = viewport->zoom;
|
|
gSavedViewRotation = get_current_rotation();
|
|
}
|
|
}
|