446 lines
14 KiB
C++
446 lines
14 KiB
C++
#include "ViewportManager.h"
|
|
#include "Config.h"
|
|
#include "Console.h"
|
|
#include "Station.h"
|
|
#include "Ui.h"
|
|
#include "Window.h"
|
|
#include "interop/interop.hpp"
|
|
#include "map/tile.h"
|
|
#include "map/tilemgr.h"
|
|
#include "things/thing.h"
|
|
#include "things/thingmgr.h"
|
|
#include <algorithm>
|
|
#include <cassert>
|
|
#include <memory>
|
|
|
|
using namespace openloco::ui;
|
|
using namespace openloco::interop;
|
|
|
|
namespace openloco::ui::viewportmgr
|
|
{
|
|
static std::vector<std::unique_ptr<viewport>> _viewports;
|
|
|
|
static loco_global<int32_t, 0x00E3F0B8> currentRotation;
|
|
|
|
static viewport* create(registers regs, int index);
|
|
|
|
void init()
|
|
{
|
|
_viewports.clear();
|
|
}
|
|
|
|
// 0x004CEC25
|
|
void collectGarbage()
|
|
{
|
|
_viewports.erase(
|
|
std::remove_if(
|
|
_viewports.begin(),
|
|
_viewports.end(),
|
|
[](std::unique_ptr<viewport>& viewport) {
|
|
return viewport->width == 0;
|
|
}),
|
|
_viewports.end());
|
|
}
|
|
|
|
static viewport* initViewport(gfx::point_t origin, gfx::ui_size_t size, ZoomLevel zoom)
|
|
{
|
|
auto vp = _viewports.emplace_back(std::make_unique<viewport>()).get();
|
|
|
|
vp->x = origin.x;
|
|
vp->y = origin.y;
|
|
vp->width = size.width;
|
|
vp->height = size.height;
|
|
|
|
vp->view_width = size.width << static_cast<uint8_t>(zoom);
|
|
vp->view_height = size.height << static_cast<uint8_t>(zoom);
|
|
vp->zoom = static_cast<uint8_t>(zoom);
|
|
vp->flags = 0;
|
|
|
|
auto& cfg = openloco::config::get();
|
|
if ((cfg.flags & config::flags::gridlines_on_landscape) != 0)
|
|
{
|
|
vp->flags |= viewport_flags::gridlines_on_landscape;
|
|
}
|
|
|
|
return vp;
|
|
}
|
|
|
|
static void focusViewportOn(window* w, int index, thing_id_t dx)
|
|
{
|
|
assert(index >= 0 && index < viewportsPerWindow);
|
|
viewport* viewport = w->viewports[index];
|
|
|
|
w->viewport_configurations[index].viewport_target_sprite = dx;
|
|
|
|
auto t = thingmgr::get<Thing>(dx);
|
|
|
|
int16_t dest_x, dest_y;
|
|
viewport->centre2dCoordinates(t->x, t->y, t->z, &dest_x, &dest_y);
|
|
w->viewport_configurations[index].saved_view_x = dest_x;
|
|
w->viewport_configurations[index].saved_view_y = dest_y;
|
|
viewport->view_x = dest_x;
|
|
viewport->view_y = dest_y;
|
|
}
|
|
|
|
static void focusViewportOn(window* w, int index, map::map_pos3 tile)
|
|
{
|
|
assert(index >= 0 && index < viewportsPerWindow);
|
|
viewport* viewport = w->viewports[index];
|
|
|
|
w->viewport_configurations[index].viewport_target_sprite = 0xFFFF;
|
|
|
|
int16_t dest_x, dest_y;
|
|
viewport->centre2dCoordinates(tile.x, tile.y, tile.z, &dest_x, &dest_y);
|
|
w->viewport_configurations[index].saved_view_x = dest_x;
|
|
w->viewport_configurations[index].saved_view_y = dest_y;
|
|
viewport->view_x = dest_x;
|
|
viewport->view_y = dest_y;
|
|
}
|
|
|
|
static viewport* create(registers regs, int index)
|
|
{
|
|
ui::window* window = (ui::window*)regs.esi;
|
|
ZoomLevel zoom = ZoomLevel::full;
|
|
if (regs.edx & (1 << 30))
|
|
{
|
|
regs.edx &= ~(1 << 30);
|
|
zoom = (ZoomLevel)regs.cl;
|
|
}
|
|
|
|
int16_t x = regs.ax;
|
|
int16_t y = regs.eax >> 16;
|
|
uint16_t width = regs.bx;
|
|
uint16_t height = regs.ebx >> 16;
|
|
|
|
if (regs.edx & (1 << 31))
|
|
{
|
|
thing_id_t id = regs.dx;
|
|
return create(window, index, { x, y }, { width, height }, zoom, id);
|
|
}
|
|
else
|
|
{
|
|
map::map_pos3 tile;
|
|
tile.x = regs.dx;
|
|
tile.y = regs.edx >> 16;
|
|
tile.z = regs.ecx >> 16;
|
|
return create(window, index, { x, y }, { width, height }, zoom, tile);
|
|
}
|
|
}
|
|
|
|
/* 0x004CA2D0
|
|
* ax : x
|
|
* eax >> 16 : y
|
|
* bx : width
|
|
* ebx >> 16 : height
|
|
* cl : zoom
|
|
* edx >> 14 : flags (bit 30 zoom related, bit 31 set if thing_id used)
|
|
* Optional one of 2
|
|
* 1.
|
|
* ecx >> 16 : tile_z
|
|
* dx : tile_x
|
|
* edx >> 16 : tile_y
|
|
* 2.
|
|
* dx : thing_id
|
|
*/
|
|
viewport* create(window* window, int viewportIndex, gfx::point_t origin, gfx::ui_size_t size, ZoomLevel zoom, thing_id_t thing_id)
|
|
{
|
|
viewport* viewport = initViewport(origin, size, zoom);
|
|
|
|
if (viewport == nullptr)
|
|
return nullptr;
|
|
|
|
window->viewports[viewportIndex] = viewport;
|
|
focusViewportOn(window, viewportIndex, thing_id);
|
|
|
|
collectGarbage();
|
|
return viewport;
|
|
}
|
|
|
|
/* 0x004CA2D0
|
|
* ax : x
|
|
* eax >> 16 : y
|
|
* bx : width
|
|
* ebx >> 16 : height
|
|
* cl : zoom
|
|
* edx >> 14 : flags (bit 30 zoom related, bit 31 set if thing_id used)
|
|
* Optional one of 2
|
|
* 1.
|
|
* ecx >> 16 : tile_z
|
|
* dx : tile_x
|
|
* edx >> 16 : tile_y
|
|
* 2.
|
|
* dx : thing_id
|
|
*/
|
|
viewport* create(window* window, int viewportIndex, gfx::point_t origin, gfx::ui_size_t size, ZoomLevel zoom, map::map_pos3 tile)
|
|
{
|
|
viewport* viewport = initViewport(origin, size, zoom);
|
|
|
|
if (viewport == nullptr)
|
|
return nullptr;
|
|
|
|
window->viewports[viewportIndex] = viewport;
|
|
focusViewportOn(window, viewportIndex, tile);
|
|
|
|
collectGarbage();
|
|
return viewport;
|
|
}
|
|
|
|
static void invalidate(const ViewportRect& rect, ZoomLevel zoom)
|
|
{
|
|
bool doGarbageCollect = false;
|
|
|
|
for (auto& viewport : _viewports)
|
|
{
|
|
// TODO: check for invalid viewports
|
|
if (viewport->width == 0)
|
|
{
|
|
doGarbageCollect = true;
|
|
continue;
|
|
}
|
|
|
|
// Skip if zoomed out further than zoom argument
|
|
if (viewport->zoom > (uint8_t)zoom)
|
|
continue;
|
|
|
|
if (!viewport->intersects(rect))
|
|
continue;
|
|
|
|
auto intersection = viewport->getIntersection(rect);
|
|
|
|
// offset rect by (negative) viewport origin
|
|
int16_t left = intersection.left - viewport->view_x;
|
|
int16_t right = intersection.right - viewport->view_x;
|
|
int16_t top = intersection.top - viewport->view_y;
|
|
int16_t bottom = intersection.bottom - viewport->view_y;
|
|
|
|
// apply zoom
|
|
left = left >> viewport->zoom;
|
|
right = right >> viewport->zoom;
|
|
top = top >> viewport->zoom;
|
|
bottom = bottom >> viewport->zoom;
|
|
|
|
// offset calculated area by viewport offset
|
|
left += viewport->x;
|
|
right += viewport->x;
|
|
top += viewport->y;
|
|
bottom += viewport->y;
|
|
|
|
gfx::setDirtyBlocks(left, top, right, bottom);
|
|
}
|
|
|
|
if (doGarbageCollect)
|
|
{
|
|
collectGarbage();
|
|
}
|
|
}
|
|
|
|
// 0x004CBA2D
|
|
void invalidate(station* station)
|
|
{
|
|
bool doGarbageCollect = false;
|
|
|
|
for (auto& viewport : _viewports)
|
|
{
|
|
// TODO: check for invalid viewports
|
|
if (viewport->width == 0)
|
|
{
|
|
doGarbageCollect = true;
|
|
continue;
|
|
}
|
|
|
|
ViewportRect rect;
|
|
rect.left = station->label_left[viewport->zoom];
|
|
rect.top = station->label_top[viewport->zoom];
|
|
rect.right = station->label_right[viewport->zoom] + 1;
|
|
rect.bottom = station->label_bottom[viewport->zoom] + 1;
|
|
|
|
rect.left <<= viewport->zoom;
|
|
rect.top <<= viewport->zoom;
|
|
rect.right <<= viewport->zoom;
|
|
rect.bottom <<= viewport->zoom;
|
|
|
|
if (!viewport->intersects(rect))
|
|
continue;
|
|
|
|
auto intersection = viewport->getIntersection(rect);
|
|
|
|
// offset rect by (negative) viewport origin
|
|
int16_t left = intersection.left - viewport->view_x;
|
|
int16_t right = intersection.right - viewport->view_x;
|
|
int16_t top = intersection.top - viewport->view_y;
|
|
int16_t bottom = intersection.bottom - viewport->view_y;
|
|
|
|
// apply zoom
|
|
left = left >> viewport->zoom;
|
|
right = right >> viewport->zoom;
|
|
top = top >> viewport->zoom;
|
|
bottom = bottom >> viewport->zoom;
|
|
|
|
// offset calculated area by viewport offset
|
|
left += viewport->x;
|
|
right += viewport->x;
|
|
top += viewport->y;
|
|
bottom += viewport->y;
|
|
|
|
gfx::setDirtyBlocks(left, top, right, bottom);
|
|
}
|
|
|
|
if (doGarbageCollect)
|
|
{
|
|
collectGarbage();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 0x004CBB01 (eight)
|
|
* 0x004CBBD2 (quarter)
|
|
* 0x004CBCAC (half)
|
|
* 0x004CBD86 (full)
|
|
*
|
|
* @param t @<esi>
|
|
* @param zoom
|
|
*/
|
|
void invalidate(Thing* t, ZoomLevel zoom)
|
|
{
|
|
if (t->sprite_left == location::null)
|
|
return;
|
|
|
|
ViewportRect rect;
|
|
rect.left = t->sprite_left;
|
|
rect.top = t->sprite_top;
|
|
rect.right = t->sprite_right;
|
|
rect.bottom = t->sprite_bottom;
|
|
|
|
auto level = (ZoomLevel)std::min(config::get().vehicles_min_scale, (uint8_t)zoom);
|
|
invalidate(rect, level);
|
|
}
|
|
|
|
void invalidate(const map::map_pos pos, coord_t zMin, coord_t zMax, ZoomLevel zoom, int radius)
|
|
{
|
|
auto axbx = map::coordinate3dTo2d(pos.x + 16, pos.y + 16, zMax, currentRotation);
|
|
axbx.x -= radius;
|
|
axbx.y -= radius;
|
|
auto dxbp = map::coordinate3dTo2d(pos.x + 16, pos.y + 16, zMin, currentRotation);
|
|
dxbp.x += radius;
|
|
dxbp.y += radius;
|
|
|
|
ViewportRect rect = {};
|
|
rect.left = axbx.x;
|
|
rect.top = axbx.y;
|
|
rect.right = dxbp.x;
|
|
rect.bottom = dxbp.y;
|
|
|
|
invalidate(rect, zoom);
|
|
}
|
|
|
|
void registerHooks()
|
|
{
|
|
registerHook(
|
|
0x0046112C,
|
|
[](registers& regs) FORCE_ALIGN_ARG_POINTER -> uint8_t {
|
|
registers backup = regs;
|
|
map::tilemgr::mapInvalidateMapSelectionTiles();
|
|
regs = backup;
|
|
return 0;
|
|
});
|
|
registerHook(
|
|
0x004CA2D0,
|
|
[](registers& regs) FORCE_ALIGN_ARG_POINTER -> uint8_t {
|
|
registers backup = regs;
|
|
auto viewport = create(regs, 0);
|
|
regs = backup;
|
|
regs.edi = reinterpret_cast<uint32_t>(viewport);
|
|
return 0;
|
|
});
|
|
registerHook(
|
|
0x004CA38A,
|
|
[](registers& regs) FORCE_ALIGN_ARG_POINTER -> uint8_t {
|
|
registers backup = regs;
|
|
auto viewport = create(regs, 1);
|
|
regs = backup;
|
|
regs.edi = reinterpret_cast<uint32_t>(viewport);
|
|
return 0;
|
|
});
|
|
registerHook(
|
|
0x004CBA2D,
|
|
[](registers& regs) FORCE_ALIGN_ARG_POINTER -> uint8_t {
|
|
invalidate((station*)regs.esi);
|
|
return 0;
|
|
});
|
|
registerHook(
|
|
0x004CBB01,
|
|
[](registers& regs) FORCE_ALIGN_ARG_POINTER -> uint8_t {
|
|
invalidate((Thing*)regs.esi, ZoomLevel::eighth);
|
|
return 0;
|
|
});
|
|
registerHook(
|
|
0x004CBBD2,
|
|
[](registers& regs) FORCE_ALIGN_ARG_POINTER -> uint8_t {
|
|
invalidate((Thing*)regs.esi, ZoomLevel::quarter);
|
|
return 0;
|
|
});
|
|
registerHook(
|
|
0x004CBCAC,
|
|
[](registers& regs) FORCE_ALIGN_ARG_POINTER -> uint8_t {
|
|
invalidate((Thing*)regs.esi, ZoomLevel::half);
|
|
return 0;
|
|
});
|
|
registerHook(
|
|
0x004CBD86,
|
|
[](registers& regs) FORCE_ALIGN_ARG_POINTER -> uint8_t {
|
|
invalidate((Thing*)regs.esi, ZoomLevel::full);
|
|
return 0;
|
|
});
|
|
registerHook(
|
|
0x004CBE5F,
|
|
[](registers& regs) FORCE_ALIGN_ARG_POINTER -> uint8_t {
|
|
auto pos = map::map_pos(regs.ax, regs.cx);
|
|
map::tilemgr::mapInvalidateTileFull(pos);
|
|
return 0;
|
|
});
|
|
registerHook(
|
|
0x004CBFBF,
|
|
[](registers& regs) FORCE_ALIGN_ARG_POINTER -> uint8_t {
|
|
auto pos = map::map_pos(regs.ax, regs.cx);
|
|
invalidate(pos, regs.di, regs.si, ZoomLevel::eighth, 56);
|
|
return 0;
|
|
});
|
|
registerHook(
|
|
0x004CC098,
|
|
[](registers& regs) FORCE_ALIGN_ARG_POINTER -> uint8_t {
|
|
auto pos = map::map_pos(regs.ax, regs.cx);
|
|
invalidate(pos, regs.di, regs.si, ZoomLevel::eighth);
|
|
return 0;
|
|
});
|
|
registerHook(
|
|
0x004CC20F,
|
|
[](registers& regs) FORCE_ALIGN_ARG_POINTER -> uint8_t {
|
|
auto pos = map::map_pos(regs.ax, regs.cx);
|
|
invalidate(pos, regs.di, regs.si, ZoomLevel::full);
|
|
return 0;
|
|
});
|
|
registerHook(
|
|
0x004CC390,
|
|
[](registers& regs) FORCE_ALIGN_ARG_POINTER -> uint8_t {
|
|
auto pos = map::map_pos(regs.ax, regs.cx);
|
|
invalidate(pos, regs.di, regs.si, ZoomLevel::half);
|
|
return 0;
|
|
});
|
|
registerHook(
|
|
0x004CC511,
|
|
[](registers& regs) FORCE_ALIGN_ARG_POINTER -> uint8_t {
|
|
auto pos = map::map_pos(regs.ax, regs.cx);
|
|
invalidate(pos, regs.di, regs.si, ZoomLevel::quarter);
|
|
return 0;
|
|
});
|
|
registerHook(
|
|
0x004CEC25,
|
|
[](registers& regs) FORCE_ALIGN_ARG_POINTER -> uint8_t {
|
|
registers backup = regs;
|
|
collectGarbage();
|
|
regs = backup;
|
|
return 0;
|
|
});
|
|
}
|
|
}
|