OpenLoco/src/openloco/map/tilemgr.cpp

229 lines
7.0 KiB
C++

#include "tilemgr.h"
#include "../Input.h"
#include "../ViewportManager.h"
#include "../interop/interop.hpp"
using namespace openloco::interop;
namespace openloco::map::tilemgr
{
static loco_global<tile_element* [0x30004], 0x00E40134> _tiles;
static loco_global<coord_t, 0x00F24486> _mapSelectionAX;
static loco_global<coord_t, 0x00F24488> _mapSelectionBX;
static loco_global<coord_t, 0x00F2448A> _mapSelectionAY;
static loco_global<coord_t, 0x00F2448C> _mapSelectionBY;
constexpr uint16_t mapSelectedTilesSize = 300;
static loco_global<map_pos[mapSelectedTilesSize], 0x00F24490> _mapSelectedTiles;
tile get(map_pos pos)
{
return get(pos.x, pos.y);
}
tile get(coord_t x, coord_t y)
{
tile_coord_t tileX = x / 32;
tile_coord_t tileY = y / 32;
size_t index = ((y << 9) | x) >> 5;
auto data = _tiles[index];
if (data == (tile_element*)0xFFFFFFFF)
{
data = nullptr;
}
return tile(tileX, tileY, data);
}
/**
* Return the absolute height of an element, given its (x,y) coordinates
*
* ax: x
* cx: y
* return dx: height
* return edx >> 16: waterHeight
* loco: 0x0067297 rct2: 0x00662783 (numbers different)
*/
std::tuple<int16_t, int16_t> getHeight(coord_t x, coord_t y)
{
// Off the map
if ((unsigned)x >= 12287 || (unsigned)y >= 12287)
return std::make_tuple(16, 0);
// Truncate subtile coordinates
auto xTile = x & 0xFFE0;
auto yTile = y & 0xFFE0;
// Get the surface element for the tile
auto surfaceEl = get(xTile, yTile).surface();
if (surfaceEl == nullptr)
{
return std::make_tuple(16, 0);
}
int16_t waterHeight = surfaceEl->water() * 16;
int16_t height = surfaceEl->baseZ() * 4;
auto slope = surfaceEl->slopeCorners();
int8_t quad = 0, quad_extra = 0; // which quadrant the element is in?
// quad_extra is for extra height tiles
uint8_t TILE_SIZE = 31;
// Subtile coords
auto xl = x & 0x1f;
auto yl = y & 0x1f;
// Slope logic:
// Each of the four bits in slope represents that corner being raised
// slope == 15 (all four bits) is not used and slope == 0 is flat
// If the extra_height bit is set, then the slope goes up two z-levels
// We arbitrarily take the SW corner to be closest to the viewer
// One corner up
if (slope == surface_slope::n_corner_up || slope == surface_slope::e_corner_up || slope == surface_slope::s_corner_up || slope == surface_slope::w_corner_up)
{
switch (slope)
{
case surface_slope::n_corner_up:
quad = xl + yl - TILE_SIZE;
break;
case surface_slope::e_corner_up:
quad = xl - yl;
break;
case surface_slope::s_corner_up:
quad = TILE_SIZE - yl - xl;
break;
case surface_slope::w_corner_up:
quad = yl - xl;
break;
}
// If the element is in the quadrant with the slope, raise its height
if (quad > 0)
{
height += quad / 2;
}
}
// One side up
switch (slope)
{
case surface_slope::ne_side_up:
height += xl / 2 + 1;
break;
case surface_slope::se_side_up:
height += (TILE_SIZE - yl) / 2;
break;
case surface_slope::nw_side_up:
height += yl / 2;
height++;
break;
case surface_slope::sw_side_up:
height += (TILE_SIZE - xl) / 2;
break;
}
// One corner down
if ((slope == surface_slope::w_corner_dn) || (slope == surface_slope::s_corner_dn) || (slope == surface_slope::e_corner_dn) || (slope == surface_slope::n_corner_dn))
{
switch (slope)
{
case surface_slope::w_corner_dn:
quad_extra = xl + TILE_SIZE - yl;
quad = xl - yl;
break;
case surface_slope::s_corner_dn:
quad_extra = xl + yl;
quad = xl + yl - TILE_SIZE - 1;
break;
case surface_slope::e_corner_dn:
quad_extra = TILE_SIZE - xl + yl;
quad = yl - xl;
break;
case surface_slope::n_corner_dn:
quad_extra = (TILE_SIZE - xl) + (TILE_SIZE - yl);
quad = TILE_SIZE - yl - xl - 1;
break;
}
if (surfaceEl->isSlopeDoubleHeight())
{
height += quad_extra / 2;
height++;
return std::make_tuple(height, waterHeight);
}
// This tile is essentially at the next height level
height += 0x10;
// so we move *down* the slope
if (quad < 0)
{
height += quad / 2;
}
}
// Valleys
if ((slope == surface_slope::w_e_valley) || (slope == surface_slope::n_s_valley))
{
switch (slope)
{
case surface_slope::w_e_valley:
if (xl + yl <= TILE_SIZE + 1)
{
return std::make_tuple(height, waterHeight);
}
quad = TILE_SIZE - xl - yl;
break;
case surface_slope::n_s_valley:
quad = xl - yl;
break;
}
if (quad > 0)
{
height += quad / 2;
}
}
return std::make_tuple(height, waterHeight);
}
// 0x004610F2
void mapInvalidateSelectionRect()
{
if ((input::getMapSelectionFlags() & MapSelectFlag::enable) != 0)
{
for (coord_t x = _mapSelectionAX; x <= _mapSelectionBX; x += 32)
{
for (coord_t y = _mapSelectionAY; y <= _mapSelectionBY; y += 32)
{
mapInvalidateTileFull({ x, y });
}
}
}
}
// 0x004CBE5F
// regs.ax: pos.x
// regs.cx: pos.y
void mapInvalidateTileFull(map::map_pos pos)
{
ui::viewportmgr::invalidate(pos, 0, 1120, ZoomLevel::eighth);
}
// 0x0046112C
void mapInvalidateMapSelectionTiles()
{
if ((input::getMapSelectionFlags() & MapSelectFlag::enableConstruct) == 0)
return;
for (uint16_t index = 0; index < mapSelectedTilesSize; ++index)
{
auto& position = _mapSelectedTiles[index];
if (position.x == -1)
break;
mapInvalidateTileFull(position);
}
}
}