mirror of
https://github.com/fallout2-ce/fallout2-ce.git
synced 2026-07-27 16:47:11 -07:00
Compare commits
22
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
651bdff37e | ||
|
|
e3e976dd82 | ||
|
|
541af8f543 | ||
|
|
09abb57cab | ||
|
|
67f0dc5d36 | ||
|
|
7809dc0806 | ||
|
|
63d54f3259 | ||
|
|
11e726ba31 | ||
|
|
b8a091b7f5 | ||
|
|
31fedf1567 | ||
|
|
09de0c1845 | ||
|
|
51edb44be9 | ||
|
|
8691a99722 | ||
|
|
ba8cdf47ba | ||
|
|
03bb4f2e9c | ||
|
|
223471acaf | ||
|
|
6f3b42fe34 | ||
|
|
f8070fd58a | ||
|
|
07adef4622 | ||
|
|
d12a59202f | ||
|
|
b7324afc4e | ||
|
|
0afa3ed255 |
@@ -161,6 +161,8 @@ set(FALLOUT_ENGINE_SOURCES
|
||||
"src/map_defs.h"
|
||||
"src/map.cc"
|
||||
"src/map.h"
|
||||
"src/map_edge.cc"
|
||||
"src/map_edge.h"
|
||||
"src/memory_defs.h"
|
||||
"src/memory_manager.cc"
|
||||
"src/memory_manager.h"
|
||||
|
||||
@@ -4,6 +4,18 @@ This document tracks Fallout 2 CE compatibility with sfall. This is for modders
|
||||
|
||||
For now, this covers opcodes/metarules, and hooks. In the future, it will include other ways of modifying the engine (like ini files), and other Sfall-specific behaviour.
|
||||
|
||||
## HRP EDG Scroll-Blocker Support
|
||||
|
||||
CE supports the `.edg` file format from the HRP (High Resolution Patch), which defines per-map scroll boundaries and square-level render clipping.
|
||||
|
||||
**How it works in CE:**
|
||||
|
||||
- On map load, `maps/<mapname>.edg` is read if present. Missing file = silent fallback to the scroll-blocker object system.
|
||||
- The `.edg` file defines per-elevation rectangle boundary zones. Multiple chained zones per elevation are supported.
|
||||
- When loaded, these zones are used for both scroll blocking and visible area clipping (black bars), replacing both vanilla scroll blocking and CE hi-res stencil system.
|
||||
- v2 EDG files also contain a `SquareRect` that defines "Angled edges", or square-grid stencil. This is also supported.
|
||||
|
||||
|
||||
## Settings (ddraw.ini → fallout2.cfg / game.cfg)
|
||||
|
||||
Settings previously read from `ddraw.ini` have been moved into standard CE config files.
|
||||
|
||||
@@ -110,6 +110,8 @@ iface_bar_mode=0
|
||||
iface_bar_side_art=2
|
||||
iface_bar_sides_ori=0
|
||||
iface_bar_width=800
|
||||
;Whether to load EDG files (HRP format) when loading maps. If loaded, they override default edge clipping and scroll blocking behavior.
|
||||
edg_support=1
|
||||
ignore_map_edges=0
|
||||
;Set to 1 to display numbered dialogue options
|
||||
numbers_in_dialogue=0
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#include "interface.h"
|
||||
#include "item.h"
|
||||
#include "kb.h"
|
||||
#include "map_edge.h"
|
||||
#include "mouse.h"
|
||||
#include "object.h"
|
||||
#include "proto.h"
|
||||
@@ -931,6 +932,9 @@ void _gmouse_handle_event(int mouseX, int mouseY, int mouseState)
|
||||
if (gameMouseClickOnInterfaceBar()) {
|
||||
return;
|
||||
}
|
||||
if (mapEdgeIsOverClippedArea(mouseX, mouseY)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if we should block mouse button up events for inventoryOpenUseItemOn inventory window
|
||||
if (gBlockMouseUpEvent && (mouseState & MOUSE_EVENT_LEFT_BUTTON_UP) != 0) {
|
||||
@@ -2447,6 +2451,11 @@ int gameMouseHandleScrolling(int x, int y, int cursor)
|
||||
}
|
||||
|
||||
if (dx == 0 && dy == 0) {
|
||||
if (mapEdgeIsOverClippedArea(x, y)) {
|
||||
gameMouseObjectsHide();
|
||||
gameMouseSetCursor(MOUSE_CURSOR_ARROW);
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
+34
-8
@@ -26,6 +26,7 @@
|
||||
#include "item.h"
|
||||
#include "light.h"
|
||||
#include "loadsave.h"
|
||||
#include "map_edge.h"
|
||||
#include "memory.h"
|
||||
#include "object.h"
|
||||
#include "palette.h"
|
||||
@@ -225,7 +226,9 @@ int isoInit()
|
||||
colorCycleInit();
|
||||
debugPrint(">cycle_init\t\t");
|
||||
|
||||
tileScrollBlockingEnable();
|
||||
if (!settings.ui.ignore_map_edges) {
|
||||
tileScrollBlockingEnable();
|
||||
}
|
||||
tileScrollLimitingEnable();
|
||||
|
||||
if (interfaceInit() != 0) {
|
||||
@@ -734,6 +737,7 @@ int mapSetEnteringLocation(int elevation, int tile_num, int orientation)
|
||||
// 0x482938
|
||||
void mapNewMap()
|
||||
{
|
||||
mapEdgeFree();
|
||||
mapSetElevation(0);
|
||||
tileSetCenter(20100, TILE_SET_CENTER_FLAG_IGNORE_SCROLL_RESTRICTIONS);
|
||||
memset(&gMapTransition, 0, sizeof(gMapTransition));
|
||||
@@ -931,6 +935,10 @@ static int mapLoad(File* stream)
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (settings.ui.edg_support) {
|
||||
mapEdgeLoad(gMapHeader.name);
|
||||
}
|
||||
|
||||
error = "Error setting tile center";
|
||||
if (tileSetCenter(gEnteringTile, TILE_SET_CENTER_FLAG_IGNORE_SCROLL_RESTRICTIONS) != 0) {
|
||||
goto err;
|
||||
@@ -1061,7 +1069,7 @@ err:
|
||||
// NOTE: Uninline.
|
||||
mapSetEnteringLocation(-1, -1, -1);
|
||||
|
||||
tile_hires_stencil_init();
|
||||
tile_hires_stencil_on_map_load();
|
||||
|
||||
gameMovieFadeOut();
|
||||
|
||||
@@ -1532,7 +1540,7 @@ static void isoWindowRefreshRect(Rect* rect)
|
||||
windowRefreshRect(gIsoWindow, rect);
|
||||
}
|
||||
|
||||
// 0x483EE4
|
||||
// 0x483EE4 map_scroll_refresh_game
|
||||
static void isoWindowRefreshRectGame(Rect* rect)
|
||||
{
|
||||
Rect rectToUpdate;
|
||||
@@ -1540,23 +1548,31 @@ static void isoWindowRefreshRectGame(Rect* rect)
|
||||
return;
|
||||
}
|
||||
|
||||
// CE: Clear dirty rect to prevent most of the visual artifacts near map
|
||||
// edges.
|
||||
Rect visArea;
|
||||
bool hasVisArea = mapEdgeComputeVisibleArea(gElevation, &visArea);
|
||||
|
||||
// HRP rect_inside_bound_scroll_clip: always clear srcRect, then clip to mapVisibleArea.
|
||||
bufferFill(gIsoWindowBuffer + rectToUpdate.top * rectGetWidth(&gIsoWindowRect) + rectToUpdate.left,
|
||||
rectGetWidth(&rectToUpdate),
|
||||
rectGetHeight(&rectToUpdate),
|
||||
rectGetWidth(&gIsoWindowRect),
|
||||
0);
|
||||
|
||||
if (hasVisArea && rectIntersection(&rectToUpdate, &visArea, &rectToUpdate) == -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
tileRenderFloorsInRect(&rectToUpdate, gElevation);
|
||||
_obj_render_pre_roof(&rectToUpdate, gElevation);
|
||||
tileRenderRoofsInRect(&rectToUpdate, gElevation);
|
||||
_obj_render_post_roof(&rectToUpdate, gElevation);
|
||||
|
||||
tile_hires_stencil_draw(&rectToUpdate, gIsoWindowBuffer, rectGetWidth(&gIsoWindowRect), rectGetHeight(&gIsoWindowRect));
|
||||
if (!hasVisArea) {
|
||||
tile_hires_stencil_draw(&rectToUpdate, gIsoWindowBuffer, rectGetWidth(&gIsoWindowRect), rectGetHeight(&gIsoWindowRect));
|
||||
}
|
||||
}
|
||||
|
||||
// 0x483F44
|
||||
// 0x483F44 map_scroll_refresh_mapper
|
||||
static void isoWindowRefreshRectMapper(Rect* rect)
|
||||
{
|
||||
Rect rectToUpdate;
|
||||
@@ -1564,19 +1580,29 @@ static void isoWindowRefreshRectMapper(Rect* rect)
|
||||
return;
|
||||
}
|
||||
|
||||
Rect visArea;
|
||||
bool hasVisArea = mapEdgeComputeVisibleArea(gElevation, &visArea);
|
||||
|
||||
// HRP rect_inside_bound_scroll_clip: always clear srcRect, then clip to mapVisibleArea.
|
||||
bufferFill(gIsoWindowBuffer + rectToUpdate.top * rectGetWidth(&gIsoWindowRect) + rectToUpdate.left,
|
||||
rectGetWidth(&rectToUpdate),
|
||||
rectGetHeight(&rectToUpdate),
|
||||
rectGetWidth(&gIsoWindowRect),
|
||||
0);
|
||||
|
||||
if (hasVisArea && rectIntersection(&rectToUpdate, &visArea, &rectToUpdate) == -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
tileRenderFloorsInRect(&rectToUpdate, gElevation);
|
||||
_grid_render(&rectToUpdate, gElevation);
|
||||
_obj_render_pre_roof(&rectToUpdate, gElevation);
|
||||
tileRenderRoofsInRect(&rectToUpdate, gElevation);
|
||||
_obj_render_post_roof(&rectToUpdate, gElevation);
|
||||
|
||||
tile_hires_stencil_draw(&rectToUpdate, gIsoWindowBuffer, rectGetWidth(&gIsoWindowRect), rectGetHeight(&gIsoWindowRect));
|
||||
if (!hasVisArea) {
|
||||
tile_hires_stencil_draw(&rectToUpdate, gIsoWindowBuffer, rectGetWidth(&gIsoWindowRect), rectGetHeight(&gIsoWindowRect));
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: Inlined.
|
||||
|
||||
+462
@@ -0,0 +1,462 @@
|
||||
#include "map_edge.h"
|
||||
|
||||
#include "db.h"
|
||||
#include "debug.h"
|
||||
#include "geometry.h"
|
||||
#include "map.h"
|
||||
#include "map_defs.h"
|
||||
#include "platform_compat.h"
|
||||
#include "svga.h"
|
||||
#include "tile.h"
|
||||
#include "window_manager.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <memory>
|
||||
|
||||
namespace fallout {
|
||||
|
||||
// Tile size in pixels
|
||||
constexpr int kTileWidth = 32;
|
||||
constexpr int kTileHeight = 24;
|
||||
|
||||
static std::unique_ptr<EdgeZone> gEdgeZones[ELEVATION_COUNT];
|
||||
static bool gEdgeDataLoaded = false;
|
||||
static bool gEdgeVersion2 = false;
|
||||
static EdgeZone* gCurrentEdgeZone = nullptr;
|
||||
|
||||
// Boundary alignment mods (sfall mapModWidth/Height), set by clamp/check functions.
|
||||
static int currentTileXAlignment = 0;
|
||||
static int currentTileYAlignment = 0;
|
||||
static int maxTileXAlignment = 0;
|
||||
static int maxTileYAlignment = 0;
|
||||
|
||||
static Rect gMapVisibleArea;
|
||||
|
||||
// Convert tile index to pixel-offset coordinates.
|
||||
// Equivalent to sfall ViewMap::GetTileCoordOffset.
|
||||
void tileToPixelOffset(int tile, int& outX, int& outY)
|
||||
{
|
||||
int x = tile % HEX_GRID_WIDTH;
|
||||
int y = (tile / HEX_GRID_WIDTH) + (x / 2);
|
||||
y &= ~1; // force even row
|
||||
x = (2 * x) + 200 - y;
|
||||
outY = kTileHeight / 2 * y;
|
||||
outX = kTileWidth / 2 * x;
|
||||
}
|
||||
|
||||
// Convert pixel-offset to tile coord (in-place, like sfall GetCoordFromOffset).
|
||||
void pixelToTileCoord(int& inOutX, int& inOutY)
|
||||
{
|
||||
int y = inOutY / kTileHeight;
|
||||
int x = (inOutX / kTileWidth) + y - 100;
|
||||
inOutX = x;
|
||||
inOutY = (2 * y) - (x / 2);
|
||||
}
|
||||
|
||||
// Convert pixel-offset to tile number.
|
||||
static int pixelToTile(int px, int py)
|
||||
{
|
||||
pixelToTileCoord(px, py);
|
||||
return px + py * HEX_GRID_WIDTH;
|
||||
}
|
||||
|
||||
static Size getIsoWindowSize()
|
||||
{
|
||||
Rect winRect;
|
||||
assert(windowGetRect(gIsoWindow, &winRect) != -1);
|
||||
return { rectGetWidth(&winRect), rectGetHeight(&winRect) };
|
||||
}
|
||||
|
||||
// Fill pixel-space fields of a zone from its stored tileRect corners.
|
||||
// Screen-size dependent — must be re-run if resolution changes.
|
||||
static void calcEdgeData(EdgeZone* zone)
|
||||
{
|
||||
const auto [winWidth, winHeight] = getIsoWindowSize();
|
||||
const int winHalfWidth = winWidth / 2;
|
||||
const int winHalfHeight = winHeight / 2;
|
||||
|
||||
// Compute sub-tile alignment sizes (sfall mapWidthModSize / mapHeightModSize).
|
||||
maxTileXAlignment = winHalfWidth % kTileWidth;
|
||||
maxTileYAlignment = winHalfHeight % kTileHeight;
|
||||
|
||||
// Truncated half sizes for border contraction (matching sfall ViewMap::GetWinMapHalfSize).
|
||||
const int winHalfWidthSnapped = winHalfWidth - maxTileXAlignment;
|
||||
const int winHalfHeightSnapped = winHalfHeight - maxTileYAlignment;
|
||||
|
||||
// Convert tileRect corners to pixel offsets.
|
||||
// pixelRect = raw pixel-space rect before any contraction.
|
||||
int px, py;
|
||||
|
||||
tileToPixelOffset(zone->tileRect.left, px, py);
|
||||
zone->pixelRect.left = zone->scrollBorderRect.left = px;
|
||||
|
||||
tileToPixelOffset(zone->tileRect.top, px, py);
|
||||
zone->pixelRect.top = zone->scrollBorderRect.top = py;
|
||||
|
||||
tileToPixelOffset(zone->tileRect.right, px, py);
|
||||
zone->pixelRect.right = zone->scrollBorderRect.right = px;
|
||||
|
||||
tileToPixelOffset(zone->tileRect.bottom, px, py);
|
||||
zone->pixelRect.bottom = zone->scrollBorderRect.bottom = py;
|
||||
|
||||
// Contract scrollBorderRect inward by window half-size (or half its own size snapped to hex grid, if it's smaller than the window).
|
||||
// Narrows the scrollable area, with guards below collapsing to a point if it crosses.
|
||||
{
|
||||
long rectHalfWidth = (zone->scrollBorderRect.left - zone->scrollBorderRect.right) / 2;
|
||||
if (rectHalfWidth < winHalfWidthSnapped) {
|
||||
long remainder = rectHalfWidth % kTileWidth;
|
||||
long rectHalfWidthSnapped = rectHalfWidth - remainder;
|
||||
zone->scrollBorderRect.left -= rectHalfWidthSnapped;
|
||||
zone->scrollBorderRect.right += rectHalfWidthSnapped + (remainder ? kTileWidth : 0);
|
||||
} else {
|
||||
zone->scrollBorderRect.left -= winHalfWidthSnapped;
|
||||
zone->scrollBorderRect.right += winHalfWidthSnapped;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
long rectHalfHeight = (zone->scrollBorderRect.bottom - zone->scrollBorderRect.top) / 2;
|
||||
if (rectHalfHeight < winHalfHeightSnapped) {
|
||||
long remainder = rectHalfHeight % kTileHeight;
|
||||
long rectHalfHeightSnapped = rectHalfHeight - remainder;
|
||||
zone->scrollBorderRect.top += rectHalfHeightSnapped + (remainder ? kTileHeight : 0);
|
||||
zone->scrollBorderRect.bottom -= rectHalfHeightSnapped;
|
||||
} else {
|
||||
zone->scrollBorderRect.top += winHalfHeightSnapped;
|
||||
zone->scrollBorderRect.bottom -= winHalfHeightSnapped;
|
||||
}
|
||||
}
|
||||
|
||||
// Inverted-X guard: collapse to vertical line if left/right cross or nearly touch.
|
||||
if ((zone->scrollBorderRect.left < zone->scrollBorderRect.right) || (zone->scrollBorderRect.left - zone->scrollBorderRect.right) == kTileWidth) {
|
||||
zone->scrollBorderRect.left = zone->scrollBorderRect.right;
|
||||
}
|
||||
|
||||
// Inverted-Y guard
|
||||
if ((zone->scrollBorderRect.bottom < zone->scrollBorderRect.top) || (zone->scrollBorderRect.bottom - zone->scrollBorderRect.top) == kTileHeight) {
|
||||
zone->scrollBorderRect.bottom = zone->scrollBorderRect.top;
|
||||
}
|
||||
|
||||
debugPrint("EDG[%p] tileRect=(%d,%d,%d,%d) win=(%d,%d) half size snap=(%d,%d) pixelRect=(%d,%d,%d,%d) scrollBorderRect=(%d,%d,%d,%d)\n",
|
||||
static_cast<void*>(zone), zone->tileRect.left, zone->tileRect.top,
|
||||
zone->tileRect.right, zone->tileRect.bottom, winWidth, winHeight, winHalfWidthSnapped, winHalfHeightSnapped,
|
||||
zone->pixelRect.left, zone->pixelRect.top,
|
||||
zone->pixelRect.right, zone->pixelRect.bottom,
|
||||
zone->scrollBorderRect.left, zone->scrollBorderRect.top,
|
||||
zone->scrollBorderRect.right, zone->scrollBorderRect.bottom);
|
||||
}
|
||||
|
||||
// Multi-edge zone selection: pick the zone whose scrollBorderRect contains the pixel position.
|
||||
// Matches GetCenterTile logic: advance while target is outside current zone, use last if
|
||||
// none contains it.
|
||||
static EdgeZone* findZoneByPixel(int px, int py, int elevation)
|
||||
{
|
||||
EdgeZone* zone = gEdgeZones[elevation].get();
|
||||
if (zone == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Multi-edge: advance while target is outside current zone.
|
||||
// width/height in original are half-window values (winHalfWidth-1, winHalfHeight+1),
|
||||
// so window size cancels out of the condition — only pixelRect ± small constants remain.
|
||||
constexpr int kZoneMarginY = 2;
|
||||
|
||||
while (zone->next != nullptr) {
|
||||
// Point is inside current zone.
|
||||
if (px < zone->pixelRect.left && px > zone->pixelRect.right
|
||||
&& py > zone->pixelRect.top - kZoneMarginY && py < zone->pixelRect.bottom - kZoneMarginY) {
|
||||
break;
|
||||
}
|
||||
zone = zone->next.get();
|
||||
}
|
||||
|
||||
return zone;
|
||||
}
|
||||
|
||||
// Load EDG file, populate gEdgeZones, and compute pixel-space fields.
|
||||
// EDG files use big-endian byte order (like all Fallout 2 files).
|
||||
static bool mapEdgeLoadFromStream(File* stream)
|
||||
{
|
||||
int magic;
|
||||
if (fileReadInt32(stream, &magic) == -1 || magic != 'EDGE') return false;
|
||||
|
||||
int version;
|
||||
if (fileReadInt32(stream, &version) == -1 || (version != 1 && version != 2)) return false;
|
||||
|
||||
int reserved;
|
||||
if (fileReadInt32(stream, &reserved) == -1 || reserved != 0) return false;
|
||||
|
||||
gEdgeVersion2 = (version == 2);
|
||||
|
||||
int levelIndicator = 0;
|
||||
|
||||
for (int elev = 0; elev < ELEVATION_COUNT; elev++) {
|
||||
int sqLeft = 99, sqTop = 0, sqRight = 0, sqBottom = 99;
|
||||
int sqClipData = 0;
|
||||
|
||||
if (gEdgeVersion2) {
|
||||
int sqRect[4];
|
||||
if (fileReadInt32List(stream, sqRect, 4) == -1
|
||||
|| fileReadInt32(stream, &sqClipData) == -1) {
|
||||
return false;
|
||||
}
|
||||
sqLeft = sqRect[0];
|
||||
sqTop = sqRect[1];
|
||||
sqRight = sqRect[2];
|
||||
sqBottom = sqRect[3];
|
||||
}
|
||||
|
||||
if (levelIndicator != elev) {
|
||||
continue; // no tileRect data for this elevation
|
||||
}
|
||||
|
||||
auto tail = &gEdgeZones[elev];
|
||||
bool isFirstZone = true;
|
||||
|
||||
while (true) {
|
||||
int tileRect[4];
|
||||
if (fileReadInt32List(stream, tileRect, 4) == -1) {
|
||||
return elev == ELEVATION_COUNT - 1;
|
||||
}
|
||||
|
||||
auto zone = std::make_unique<EdgeZone>();
|
||||
// File stores RECT order: [0]=left, [1]=top, [2]=right, [3]=bottom.
|
||||
zone->tileRect.left = tileRect[0];
|
||||
zone->tileRect.top = tileRect[1];
|
||||
zone->tileRect.right = tileRect[2];
|
||||
zone->tileRect.bottom = tileRect[3];
|
||||
|
||||
zone->squareRect.left = sqLeft;
|
||||
zone->squareRect.top = sqTop;
|
||||
zone->squareRect.right = sqRight;
|
||||
zone->squareRect.bottom = sqBottom;
|
||||
int rawClip = isFirstZone ? sqClipData : 0;
|
||||
zone->clipSides.bottom = (rawClip & 1) != 0;
|
||||
zone->clipSides.right = ((rawClip >> 8) & 1) != 0;
|
||||
zone->clipSides.top = ((rawClip >> 16) & 1) != 0;
|
||||
zone->clipSides.left = ((rawClip >> 24) & 1) != 0;
|
||||
zone->next = nullptr;
|
||||
|
||||
calcEdgeData(zone.get());
|
||||
|
||||
*tail = std::move(zone);
|
||||
tail = &(*tail)->next;
|
||||
isFirstZone = false;
|
||||
|
||||
if (fileReadInt32(stream, &levelIndicator) == -1) {
|
||||
return elev == ELEVATION_COUNT - 1;
|
||||
}
|
||||
|
||||
if (levelIndicator != elev) {
|
||||
break; // next elevation begins
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void mapEdgeLoad(const char* mapName)
|
||||
{
|
||||
mapEdgeFree();
|
||||
|
||||
char fname[COMPAT_MAX_FNAME];
|
||||
compat_splitpath(mapName, nullptr, nullptr, fname, nullptr);
|
||||
|
||||
char edgPath[COMPAT_MAX_PATH];
|
||||
snprintf(edgPath, sizeof(edgPath), "MAPS\\%s.EDG", fname);
|
||||
|
||||
File* stream = fileOpen(edgPath, "rb");
|
||||
if (stream == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool ok = mapEdgeLoadFromStream(stream);
|
||||
fileClose(stream);
|
||||
|
||||
if (ok) {
|
||||
gEdgeDataLoaded = true;
|
||||
debugPrint("mapEdgeLoad: loaded %s\n", edgPath);
|
||||
} else {
|
||||
debugPrint("mapEdgeLoad: failed to parse %s\n", edgPath);
|
||||
mapEdgeFree();
|
||||
}
|
||||
}
|
||||
|
||||
void mapEdgeFree()
|
||||
{
|
||||
for (auto& gEdgeZone : gEdgeZones) {
|
||||
gEdgeZone.reset();
|
||||
}
|
||||
gEdgeDataLoaded = false;
|
||||
gCurrentEdgeZone = nullptr;
|
||||
currentTileXAlignment = 0;
|
||||
currentTileYAlignment = 0;
|
||||
maxTileXAlignment = 0;
|
||||
maxTileYAlignment = 0;
|
||||
gMapVisibleArea = {};
|
||||
}
|
||||
|
||||
bool mapEdgeIsLoaded()
|
||||
{
|
||||
return gEdgeDataLoaded;
|
||||
}
|
||||
|
||||
bool mapEdgeZoneIsSelected()
|
||||
{
|
||||
return gCurrentEdgeZone != nullptr;
|
||||
}
|
||||
|
||||
// Shared helper: set currentTileXAlignment/Height if the pixel is on a scrollBorderRect edge.
|
||||
static void updateTileAlignment(const EdgeZone* zone, int px, int py);
|
||||
|
||||
int mapEdgeSelectZoneAndClamp(int tile, int elevation)
|
||||
{
|
||||
int px, py;
|
||||
tileToPixelOffset(tile, px, py);
|
||||
|
||||
// Set current zone for subsequent scroll blocking checks.
|
||||
EdgeZone* zone = gCurrentEdgeZone = findZoneByPixel(px, py, elevation);
|
||||
if (zone == nullptr) {
|
||||
return tile;
|
||||
}
|
||||
|
||||
// Clamp pixel position to scrollBorderRect (matching sfall GetCenterTile).
|
||||
// Note: X is inverted (left > right), so the valid range is [right, left].
|
||||
int cx = (px <= zone->scrollBorderRect.left)
|
||||
? ((px >= zone->scrollBorderRect.right) ? px : zone->scrollBorderRect.right)
|
||||
: zone->scrollBorderRect.left;
|
||||
|
||||
int cy = (py <= zone->scrollBorderRect.bottom)
|
||||
? ((py >= zone->scrollBorderRect.top) ? py : zone->scrollBorderRect.top)
|
||||
: zone->scrollBorderRect.bottom;
|
||||
|
||||
updateTileAlignment(zone, cx, cy);
|
||||
|
||||
return pixelToTile(cx, cy);
|
||||
}
|
||||
|
||||
bool mapEdgeTileInBounds(int tile)
|
||||
{
|
||||
int px, py;
|
||||
tileToPixelOffset(tile, px, py);
|
||||
|
||||
EdgeZone* zone = gCurrentEdgeZone;
|
||||
if (zone == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Note: X is inverted (left > right), so check px in [right, left].
|
||||
return px >= zone->scrollBorderRect.right && px <= zone->scrollBorderRect.left
|
||||
&& py >= zone->scrollBorderRect.top && py <= zone->scrollBorderRect.bottom;
|
||||
}
|
||||
|
||||
// Shared helper: set currentTileXAlignment if the pixel is on a scrollBorderRect edge.
|
||||
static void updateTileAlignment(const EdgeZone* zone, int px, int py)
|
||||
{
|
||||
currentTileXAlignment = 0;
|
||||
currentTileYAlignment = 0;
|
||||
if (px == zone->scrollBorderRect.left) {
|
||||
currentTileXAlignment = -maxTileXAlignment;
|
||||
} else if (px == zone->scrollBorderRect.right) {
|
||||
currentTileXAlignment = maxTileXAlignment;
|
||||
}
|
||||
if (py == zone->scrollBorderRect.top) {
|
||||
currentTileYAlignment = -maxTileYAlignment;
|
||||
} else if (py == zone->scrollBorderRect.bottom) {
|
||||
currentTileYAlignment = maxTileYAlignment;
|
||||
}
|
||||
}
|
||||
|
||||
bool mapEdgeSetBoundaryMods(int tile)
|
||||
{
|
||||
int px, py;
|
||||
tileToPixelOffset(tile, px, py);
|
||||
|
||||
EdgeZone* zone = gCurrentEdgeZone;
|
||||
if (zone == nullptr) {
|
||||
currentTileXAlignment = 0;
|
||||
currentTileYAlignment = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
const int oldModW = currentTileXAlignment;
|
||||
const int oldModH = currentTileYAlignment;
|
||||
updateTileAlignment(zone, px, py);
|
||||
return currentTileXAlignment != oldModW || currentTileYAlignment != oldModH;
|
||||
}
|
||||
|
||||
int mapEdgeGetTileXAlignment() { return currentTileXAlignment; }
|
||||
|
||||
int mapEdgeGetTileYAlignment() { return currentTileYAlignment; }
|
||||
|
||||
bool mapEdgeHasSquareRect(int elevation)
|
||||
{
|
||||
const auto& zone = gEdgeZones[elevation];
|
||||
return gEdgeVersion2 && zone != nullptr && zone->squareRect.left >= 0;
|
||||
}
|
||||
|
||||
void mapEdgeGetSquareRect(int elevation, Rect* outRect)
|
||||
{
|
||||
const auto& zone = gEdgeZones[elevation];
|
||||
*outRect = zone->squareRect;
|
||||
}
|
||||
|
||||
EdgeZone::ClipSides mapEdgeGetClipSides(int elevation)
|
||||
{
|
||||
const auto& zone = gEdgeZones[elevation];
|
||||
return zone ? zone->clipSides : EdgeZone::ClipSides{};
|
||||
}
|
||||
|
||||
void mapEdgeRecalc()
|
||||
{
|
||||
for (auto& gEdgeZone : gEdgeZones) {
|
||||
const auto* zone = &gEdgeZone;
|
||||
while (zone != nullptr) {
|
||||
calcEdgeData(zone->get());
|
||||
zone = &zone->get()->next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool mapEdgeComputeVisibleArea(int elevation, Rect* outRect)
|
||||
{
|
||||
if (!gEdgeDataLoaded) return false;
|
||||
|
||||
int px, py;
|
||||
tileToPixelOffset(gCenterTile, px, py);
|
||||
|
||||
px += currentTileXAlignment;
|
||||
py -= currentTileYAlignment;
|
||||
|
||||
EdgeZone* zone = findZoneByPixel(px, py, elevation);
|
||||
if (zone == nullptr) return false;
|
||||
|
||||
// Convert pixel-offset space to screen space via screen origin.
|
||||
// screenOrigin = pixel-space coord of the screen's top-left corner.
|
||||
// X is inverted in pixel-space: screenX = screenOriginX - pixelX.
|
||||
// Matches sfall EdgeClipping::rect_inside_bound_clip mapVisibleArea computation.
|
||||
const auto [winWidth, winHeight] = getIsoWindowSize();
|
||||
const int screenOriginX = px + winWidth / 2 - 1;
|
||||
const int screenOriginY = py - (winHeight / 2 - 1);
|
||||
|
||||
outRect->left = screenOriginX - zone->pixelRect.left;
|
||||
outRect->right = screenOriginX - zone->pixelRect.right;
|
||||
outRect->top = zone->pixelRect.top - screenOriginY;
|
||||
outRect->bottom = zone->pixelRect.bottom - screenOriginY;
|
||||
|
||||
gMapVisibleArea = *outRect;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool mapEdgeIsOverClippedArea(int screenX, int screenY)
|
||||
{
|
||||
if (!gEdgeDataLoaded) return false;
|
||||
|
||||
if (screenX >= gMapVisibleArea.left && screenX <= gMapVisibleArea.right && screenY >= gMapVisibleArea.top && screenY < gMapVisibleArea.bottom) return false;
|
||||
|
||||
return windowGetAtPoint(screenX, screenY) == gIsoWindow;
|
||||
}
|
||||
|
||||
} // namespace fallout
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
#ifndef MAP_EDGE_H
|
||||
#define MAP_EDGE_H
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "geometry.h"
|
||||
|
||||
namespace fallout {
|
||||
|
||||
struct EdgeZone {
|
||||
struct ClipSides {
|
||||
bool bottom = false;
|
||||
bool right = false;
|
||||
bool top = false;
|
||||
bool left = false;
|
||||
};
|
||||
|
||||
// Raw tile indices from EDG file (tileRect in tile space, 0-39999).
|
||||
Rect tileRect;
|
||||
|
||||
// Pixel-space rect from tileRect corner conversion (before contraction).
|
||||
Rect pixelRect;
|
||||
|
||||
// Pixel-space boundary for center-tile scroll blocking (screen-size dependent).
|
||||
// Derived from pixelRect by contracting inward by window half-size and snapping to hex grid.
|
||||
// X axis is inverted: left > right (smaller tile index → larger pixel X).
|
||||
// Y axis is normal: bottom > top.
|
||||
Rect scrollBorderRect;
|
||||
|
||||
// Square-grid clip rect for floor/roof rendering (v2 EDG only).
|
||||
// Valid when left >= 0.
|
||||
Rect squareRect;
|
||||
|
||||
// Per-side clip flags unpacked from EDG v2. True means the black square overlay
|
||||
// for that side is drawn on top of (after) non-flat objects.
|
||||
ClipSides clipSides;
|
||||
|
||||
std::unique_ptr<EdgeZone> next;
|
||||
};
|
||||
|
||||
// Load EDG file for a map. mapName is the raw map filename e.g. "ARROYO.MAP".
|
||||
// Silently does nothing if no .edg file is found.
|
||||
void mapEdgeLoad(const char* mapName);
|
||||
|
||||
// Free all loaded EDG data. Safe to call when nothing is loaded.
|
||||
void mapEdgeFree();
|
||||
|
||||
// Returns true if EDG data was successfully loaded for the current map.
|
||||
bool mapEdgeIsLoaded();
|
||||
|
||||
// Returns true if a zone was selected on last tileSetCenter call.
|
||||
bool mapEdgeZoneIsSelected();
|
||||
|
||||
// Clamps tile to the EDG boundary for the given elevation.
|
||||
// Returns the clamped tile (may equal tile if already in bounds).
|
||||
int mapEdgeSelectZoneAndClamp(int tile, int elevation);
|
||||
|
||||
// Returns true if tile is within the EDG boundary for the given elevation.
|
||||
// Used by the stencil flood-fill as a traversal gate.
|
||||
bool mapEdgeTileInBounds(int tile);
|
||||
|
||||
// Returns true if squareRect data (v2 EDG) is available for this elevation.
|
||||
bool mapEdgeHasSquareRect(int elevation);
|
||||
|
||||
// Convert tile index to pixel-offset coordinates.
|
||||
// Equivalent to sfall ViewMap::GetTileCoordOffset.
|
||||
void tileToPixelOffset(int tile, int& outX, int& outY);
|
||||
|
||||
// Convert pixel-offset to tile coord (in-place, like sfall GetCoordFromOffset).
|
||||
void pixelToTileCoord(int& inOutX, int& inOutY);
|
||||
|
||||
// Fills the squareRect for the given elevation.
|
||||
// Only valid when mapEdgeHasSquareRect(elevation) returns true.
|
||||
void mapEdgeGetSquareRect(int elevation, Rect* outRect);
|
||||
|
||||
// Returns the clipSides for the given elevation (default-constructed if no EDG data).
|
||||
EdgeZone::ClipSides mapEdgeGetClipSides(int elevation);
|
||||
|
||||
// Recalculate all pixel-space fields using current screen dimensions.
|
||||
// Call when resolution changes while a map is loaded.
|
||||
void mapEdgeRecalc();
|
||||
|
||||
// Pixel-offset adjustments for sub-tile boundary alignment (sfall mapModWidth/Height).
|
||||
// Used when camera is exactly at one of the scroll border edges.
|
||||
int mapEdgeGetTileXAlignment();
|
||||
int mapEdgeGetTileYAlignment();
|
||||
|
||||
// For the blocking (flags == 0) scroll path: sets boundary mod values if the given
|
||||
// tile's pixel position is exactly on a scrollBorderRect edge.
|
||||
// Returns true if the tile is on a boundary edge (mods changed) — caller should set
|
||||
// TILE_SET_CENTER_REFRESH_WINDOW for full redraw, matching sfall CheckBorder result==1.
|
||||
bool mapEdgeSetBoundaryMods(int tile);
|
||||
|
||||
// Computes the screen-space visible area rectangle.
|
||||
// Equivalent to sfall EdgeClipping's mapVisibleArea. Returns false if no EDG loaded.
|
||||
bool mapEdgeComputeVisibleArea(int elevation, Rect* outRect);
|
||||
|
||||
// Returns true if the screen coordinate is over the map window but outside the EDG-visible area.
|
||||
bool mapEdgeIsOverClippedArea(int screenX, int screenY);
|
||||
|
||||
} // namespace fallout
|
||||
|
||||
#endif /* MAP_EDGE_H */
|
||||
@@ -590,12 +590,12 @@ static void mp_run_placement_loop(PlaceFn place)
|
||||
}
|
||||
|
||||
switch (keyCode) {
|
||||
case 327: // Home
|
||||
case KEY_HOME: // Home
|
||||
if (gDude != nullptr) {
|
||||
if (gElevation != gDude->elevation) {
|
||||
mapSetElevation(gDude->elevation);
|
||||
}
|
||||
tileSetCenter(gDude->tile, 1);
|
||||
tileSetCenter(gDude->tile, TILE_SET_CENTER_REFRESH_WINDOW);
|
||||
}
|
||||
break;
|
||||
case KEY_ARROW_UP:
|
||||
|
||||
@@ -2749,9 +2749,9 @@ static void mapper_enter_play_mode(Object** pHlObj1)
|
||||
}
|
||||
scriptsExecMapEnterProc();
|
||||
scriptsExecMapUpdateProc();
|
||||
tileSetCenter(gDude->tile, 1);
|
||||
tileSetCenter(gDude->tile, TILE_SET_CENTER_REFRESH_WINDOW);
|
||||
} else {
|
||||
tileSetCenter(savedCenterTile, 1);
|
||||
tileSetCenter(savedCenterTile, TILE_SET_CENTER_REFRESH_WINDOW);
|
||||
}
|
||||
|
||||
isoEnable();
|
||||
|
||||
@@ -826,6 +826,8 @@ void _obj_render_pre_roof(Rect* rect, int elevation)
|
||||
}
|
||||
}
|
||||
|
||||
tileRenderEdgeBlackSquares(&updatedRect, elevation, false);
|
||||
|
||||
for (int i = 0; i < renderCount; i++) {
|
||||
int lightIntensity;
|
||||
|
||||
@@ -856,6 +858,8 @@ void _obj_render_pre_roof(Rect* rect, int elevation)
|
||||
objectListNode = objectListNode->next;
|
||||
}
|
||||
}
|
||||
|
||||
tileRenderEdgeBlackSquares(&updatedRect, elevation, true);
|
||||
}
|
||||
|
||||
// 0x4897EC obj_render_post_roof
|
||||
|
||||
@@ -153,6 +153,7 @@ void initSettingsRegistry(bool isMapper)
|
||||
SETTING_P(iface_bar_side_art, clamp(0, 999));
|
||||
SETTING(iface_bar_sides_ori);
|
||||
SETTING_P(splash_screen_size, clamp(0, 2));
|
||||
SETTING(edg_support);
|
||||
SETTING(ignore_map_edges);
|
||||
SETTING(quick_toolbar_visible);
|
||||
SETTING_P(anim_speed, clamp(0.1, 100.0));
|
||||
|
||||
@@ -56,6 +56,10 @@ struct UISettings {
|
||||
|
||||
int splash_screen_size = 0;
|
||||
|
||||
// Whether to load EDG files (HRP format) when loading maps. If loaded, they override default edge clipping and scroll blocking behavior.
|
||||
bool edg_support = true;
|
||||
|
||||
// Disables map edges, including vanilla scroll blockers and CE hi-res stencil.
|
||||
bool ignore_map_edges = false;
|
||||
|
||||
// iOS quick-actions toolbar above the interface bar. No-op on other platforms.
|
||||
|
||||
+156
-18
@@ -14,6 +14,7 @@
|
||||
#include "game_mouse.h"
|
||||
#include "light.h"
|
||||
#include "map.h"
|
||||
#include "map_edge.h"
|
||||
#include "object.h"
|
||||
#include "platform_compat.h"
|
||||
#include "settings.h"
|
||||
@@ -440,6 +441,8 @@ int tileInit(TileData** squareGrid, int squareGridWidth, int squareGridHeight, i
|
||||
gTileWindowWidth = ORIGINAL_ISO_WINDOW_WIDTH;
|
||||
gTileWindowHeight = ORIGINAL_ISO_WINDOW_HEIGHT;
|
||||
|
||||
tile_hires_stencil_init();
|
||||
|
||||
tileSetCenter(hexGridWidth * (hexGridHeight / 2) + hexGridWidth / 2, TILE_SET_CENTER_FLAG_IGNORE_SCROLL_RESTRICTIONS);
|
||||
tileSetBorder(windowWidth, windowHeight, hexGridWidth, hexGridHeight);
|
||||
|
||||
@@ -540,6 +543,30 @@ int tileSetCenter(int tile, int flags)
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool boundaryModsSet = false;
|
||||
if (mapEdgeIsLoaded() && !settings.ui.ignore_map_edges) {
|
||||
bool isScroll = flags == 0;
|
||||
if (!isScroll) {
|
||||
// Forced positioning (teleport, initial load, etc.): clamp to edge boundary.
|
||||
tile = mapEdgeSelectZoneAndClamp(tile, gElevation);
|
||||
if (!tileIsValid(tile)) return -1;
|
||||
} else if (mapEdgeZoneIsSelected()) {
|
||||
// Normal scroll: block if tile is outside boundary (matching sfall CheckBorder).
|
||||
// Clamping here would move the center slightly and cause mapScroll's buffer
|
||||
// copy to produce visual artifacts; blocking preserves the current view.
|
||||
if (!mapEdgeTileInBounds(tile)) {
|
||||
return -1;
|
||||
}
|
||||
// Tile is in bounds; set sub-tile boundary mods if tile is on the edge.
|
||||
// If the tile is exactly on a boundary edge, force a full redraw
|
||||
// (matching sfall's CheckBorder returning 1 → modeFlags |= 1).
|
||||
if (mapEdgeSetBoundaryMods(tile)) {
|
||||
boundaryModsSet = true;
|
||||
flags |= TILE_SET_CENTER_REFRESH_WINDOW;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ((flags & TILE_SET_CENTER_FLAG_IGNORE_SCROLL_RESTRICTIONS) == 0) {
|
||||
if (gTileScrollLimitingEnabled) {
|
||||
int tileScreenX;
|
||||
@@ -561,7 +588,9 @@ int tileSetCenter(int tile, int flags)
|
||||
}
|
||||
}
|
||||
|
||||
if (gTileScrollBlockingEnabled && !settings.ui.ignore_map_edges) {
|
||||
// Scroll-blocker object check only runs when no EDG is loaded.
|
||||
// EDG clamping above already enforces the boundary.
|
||||
if ((!mapEdgeIsLoaded() || !mapEdgeZoneIsSelected()) && gTileScrollBlockingEnabled) {
|
||||
if (_obj_scroll_blocking_at(tile, gElevation) == 0) {
|
||||
return -1;
|
||||
}
|
||||
@@ -571,16 +600,17 @@ int tileSetCenter(int tile, int flags)
|
||||
int tile_x = gHexGridWidth - 1 - tile % gHexGridWidth;
|
||||
int tile_y = tile / gHexGridWidth;
|
||||
|
||||
if (gTileBorderInitialized && !settings.ui.ignore_map_edges) {
|
||||
// Global tile borders are always checked, unless scroll blocking is disabled.
|
||||
if (gTileBorderInitialized && gTileScrollBlockingEnabled) {
|
||||
if (tile_x <= gTileBorderMinX || tile_x >= gTileBorderMaxX || tile_y <= gTileBorderMinY || tile_y >= gTileBorderMaxY) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
_tile_y = tile_y;
|
||||
_tile_offx = (gTileWindowWidth - 32) / 2;
|
||||
_tile_offx = mapEdgeGetTileXAlignment() + (gTileWindowWidth - 32) / 2;
|
||||
_tile_x = tile_x;
|
||||
_tile_offy = (gTileWindowHeight - 16) / 2;
|
||||
_tile_offy = mapEdgeGetTileYAlignment() + (gTileWindowHeight - 16) / 2;
|
||||
|
||||
if (tile_x & 1) {
|
||||
_tile_x -= 1;
|
||||
@@ -604,11 +634,49 @@ int tileSetCenter(int tile, int flags)
|
||||
if ((flags & TILE_SET_CENTER_REFRESH_WINDOW) != 0) {
|
||||
// NOTE: Uninline.
|
||||
tileWindowRefresh();
|
||||
if (boundaryModsSet)
|
||||
return -1; // necessary for the correct rendering of the map
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Port of HRP EdgeClipping::CheckRect.
|
||||
// Returns true if any corner of the screen-space rect maps to a tile outside the 200x200 grid.
|
||||
bool checkRectNeedsClear(const Rect* rect, int elevation)
|
||||
{
|
||||
(void)elevation;
|
||||
|
||||
int cX, cY;
|
||||
tileToPixelOffset(gCenterTile, cX, cY);
|
||||
|
||||
const int halfW = gTileWindowWidth / 2;
|
||||
const int halfH = gTileWindowHeight / 2;
|
||||
|
||||
// Convert screen-space rect corners to pixel-offset space (HRP formula).
|
||||
// xLeft = (cX + width) - rect->left, yTop = (cY + rect->top) - height
|
||||
// xRight = (cX + width) - rect->right, yBottom = (cY + rect->bottom) - height
|
||||
struct {
|
||||
int x, y;
|
||||
} corners[4] = {
|
||||
{ (cX + halfW) - rect->left, (cY + rect->top) - halfH },
|
||||
{ (cX + halfW) - rect->right, (cY + rect->top) - halfH },
|
||||
{ (cX + halfW) - rect->left, (cY + rect->bottom) - halfH },
|
||||
{ (cX + halfW) - rect->right, (cY + rect->bottom) - halfH }
|
||||
};
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
int x = corners[i].x;
|
||||
int y = corners[i].y;
|
||||
pixelToTileCoord(x, y);
|
||||
if (x < 0 || x >= HEX_GRID_WIDTH || y < 0 || y >= HEX_GRID_HEIGHT) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: these two functions are exact copies of isoWindowRefreshRect*. gTileWindowBuffer == gIsoWindowBuffer, these are the same window!
|
||||
// 0x4B1554 refresh_mapper
|
||||
static void tileRefreshMapper(Rect* rect, int elevation)
|
||||
{
|
||||
@@ -618,11 +686,21 @@ static void tileRefreshMapper(Rect* rect, int elevation)
|
||||
return;
|
||||
}
|
||||
|
||||
bufferFill(gTileWindowBuffer + gTileWindowPitch * rectToUpdate.top + rectToUpdate.left,
|
||||
rectToUpdate.right - rectToUpdate.left + 1,
|
||||
rectToUpdate.bottom - rectToUpdate.top + 1,
|
||||
gTileWindowPitch,
|
||||
0);
|
||||
Rect visArea;
|
||||
bool hasVisArea = mapEdgeComputeVisibleArea(elevation, &visArea);
|
||||
|
||||
// HRP EdgeClipping: when clipped, clear only if CheckRect; otherwise always clear.
|
||||
if (!hasVisArea || checkRectNeedsClear(&rectToUpdate, elevation)) {
|
||||
bufferFill(gTileWindowBuffer + gTileWindowPitch * rectToUpdate.top + rectToUpdate.left,
|
||||
rectGetWidth(&rectToUpdate),
|
||||
rectGetHeight(&rectToUpdate),
|
||||
gTileWindowPitch,
|
||||
0);
|
||||
}
|
||||
|
||||
if (hasVisArea && rectIntersection(&rectToUpdate, &visArea, &rectToUpdate) == -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
tileRenderFloorsInRect(&rectToUpdate, elevation);
|
||||
_grid_render(&rectToUpdate, elevation);
|
||||
@@ -630,7 +708,9 @@ static void tileRefreshMapper(Rect* rect, int elevation)
|
||||
tileRenderRoofsInRect(&rectToUpdate, elevation);
|
||||
_obj_render_post_roof(&rectToUpdate, elevation);
|
||||
|
||||
tile_hires_stencil_draw(&rectToUpdate, gTileWindowBuffer, gTileWindowWidth, gTileWindowHeight);
|
||||
if (!hasVisArea) {
|
||||
tile_hires_stencil_draw(&rectToUpdate, gTileWindowBuffer, gTileWindowWidth, gTileWindowHeight);
|
||||
}
|
||||
|
||||
gTileWindowRefreshProc(&rectToUpdate);
|
||||
}
|
||||
@@ -644,20 +724,30 @@ static void tileRefreshGame(Rect* rect, int elevation)
|
||||
return;
|
||||
}
|
||||
|
||||
// CE: Clear dirty rect to prevent most of the visual artifacts near map
|
||||
// edges.
|
||||
bufferFill(gTileWindowBuffer + rectToUpdate.top * gTileWindowPitch + rectToUpdate.left,
|
||||
rectGetWidth(&rectToUpdate),
|
||||
rectGetHeight(&rectToUpdate),
|
||||
gTileWindowPitch,
|
||||
0);
|
||||
Rect visArea;
|
||||
bool hasVisArea = mapEdgeComputeVisibleArea(elevation, &visArea);
|
||||
|
||||
// HRP EdgeClipping: when clipped, clear only if CheckRect; otherwise always clear.
|
||||
if (!hasVisArea || checkRectNeedsClear(&rectToUpdate, elevation)) {
|
||||
bufferFill(gTileWindowBuffer + rectToUpdate.top * gTileWindowPitch + rectToUpdate.left,
|
||||
rectGetWidth(&rectToUpdate),
|
||||
rectGetHeight(&rectToUpdate),
|
||||
gTileWindowPitch,
|
||||
0);
|
||||
}
|
||||
|
||||
if (hasVisArea && rectIntersection(&rectToUpdate, &visArea, &rectToUpdate) == -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
tileRenderFloorsInRect(&rectToUpdate, elevation);
|
||||
_obj_render_pre_roof(&rectToUpdate, elevation);
|
||||
tileRenderRoofsInRect(&rectToUpdate, elevation);
|
||||
_obj_render_post_roof(&rectToUpdate, elevation);
|
||||
|
||||
tile_hires_stencil_draw(&rectToUpdate, gTileWindowBuffer, gTileWindowWidth, gTileWindowHeight);
|
||||
if (!hasVisArea) {
|
||||
tile_hires_stencil_draw(&rectToUpdate, gTileWindowBuffer, gTileWindowWidth, gTileWindowHeight);
|
||||
}
|
||||
|
||||
gTileWindowRefreshProc(&rectToUpdate);
|
||||
}
|
||||
@@ -1437,6 +1527,54 @@ void tileRenderFloorsInRect(Rect* rect, int elevation)
|
||||
}
|
||||
}
|
||||
|
||||
// Port of sfall HRP ViewMap::square_obj_render
|
||||
void tileRenderEdgeBlackSquares(Rect* rect, int elevation, bool drawOnTop)
|
||||
{
|
||||
if (!mapEdgeHasSquareRect(elevation)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Rect squareRect;
|
||||
mapEdgeGetSquareRect(elevation, &squareRect);
|
||||
EdgeZone::ClipSides clipSides = mapEdgeGetClipSides(elevation);
|
||||
|
||||
int maxX, minY, minX, maxY, temp;
|
||||
|
||||
squareTileScreenToCoord(rect->left, rect->bottom, elevation, &maxX, &temp);
|
||||
squareTileScreenToCoord(rect->left, rect->top, elevation, &temp, &minY);
|
||||
squareTileScreenToCoord(rect->right, rect->top, elevation, &minX, &temp);
|
||||
squareTileScreenToCoord(rect->right, rect->bottom, elevation, &temp, &maxY);
|
||||
|
||||
if (++maxX > gSquareGridWidth) maxX = gSquareGridWidth - 1;
|
||||
if (--minX < 0) minX = 0;
|
||||
if (--minY < 0) minY = 0;
|
||||
if (++maxY > gSquareGridHeight) maxY = gSquareGridHeight - 1;
|
||||
|
||||
if (minY >= maxY || minX >= maxX) return;
|
||||
|
||||
bool drawLeft = clipSides.left == drawOnTop;
|
||||
bool drawTop = clipSides.top == drawOnTop;
|
||||
bool drawRight = clipSides.right == drawOnTop;
|
||||
bool drawBottom = clipSides.bottom == drawOnTop;
|
||||
|
||||
const int kEdgeFid = buildFid(OBJ_TYPE_TILE, 1, 0, 0, 0);
|
||||
int baseSquareTile = gSquareGridWidth * minY;
|
||||
|
||||
for (int y = minY; y < maxY; y++) {
|
||||
for (int x = minX; x < maxX; x++) {
|
||||
if ((drawLeft && x > squareRect.left)
|
||||
|| (drawTop && y < squareRect.top)
|
||||
|| (drawRight && x < squareRect.right)
|
||||
|| (drawBottom && y > squareRect.bottom)) {
|
||||
int sx, sy;
|
||||
squareTileToScreenXY(baseSquareTile + x, &sx, &sy, elevation);
|
||||
tileRenderFloor(kEdgeFid, sx, sy, rect);
|
||||
}
|
||||
}
|
||||
baseSquareTile += gSquareGridWidth;
|
||||
}
|
||||
}
|
||||
|
||||
// 0x4B2B10 square_roof_intersect
|
||||
bool _square_roof_intersect(int x, int y, int elevation)
|
||||
{
|
||||
|
||||
@@ -58,6 +58,7 @@ void squareTileScreenToCoordRoof(int screenX, int screenY, int elevation, int* c
|
||||
void tileRenderRoofsInRect(Rect* rect, int elevation);
|
||||
void tile_fill_roof(int x, int y, int elevation, bool on);
|
||||
void tileRenderFloorsInRect(Rect* rect, int elevation);
|
||||
void tileRenderEdgeBlackSquares(Rect* rect, int elevation, bool drawOnTop);
|
||||
bool _square_roof_intersect(int x, int y, int elevation);
|
||||
void _grid_render(Rect* rect, int elevation);
|
||||
int _tile_scroll_to(int tile, int flags);
|
||||
@@ -67,6 +68,10 @@ static bool tileIsValid(int tile)
|
||||
return tile >= 0 && tile < gHexGridSize;
|
||||
}
|
||||
|
||||
// Returns true if the rect's screen-space corners map to tiles outside the 200x200 grid.
|
||||
// Port of HRP EdgeClipping::CheckRect — used to decide whether to clear (blacken) a rect.
|
||||
bool checkRectNeedsClear(const Rect* rect, int elevation);
|
||||
|
||||
} // namespace fallout
|
||||
|
||||
#endif /* TILE_H */
|
||||
|
||||
+10
-14
@@ -2,6 +2,7 @@
|
||||
#include "debug.h"
|
||||
#include "draw.h"
|
||||
#include "geometry.h"
|
||||
#include "map_edge.h"
|
||||
#include "settings.h"
|
||||
#include "stdio.h"
|
||||
#include "tile.h"
|
||||
@@ -210,20 +211,11 @@ void tile_hires_stencil_on_center_tile_or_elevation_change()
|
||||
if (!gIsTileHiresStencilEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!gTileBorderInitialized) {
|
||||
// With EDG loaded, the EdgeClipping path handles blackening via ClearRect/CheckRect.
|
||||
// The stencil used as a backup when no EDG is present.
|
||||
if (mapEdgeIsLoaded() || !gTileBorderInitialized || visited_tiles[gElevation][gCenterTile]) {
|
||||
return;
|
||||
};
|
||||
|
||||
if (visited_tiles[gElevation][gCenterTile]) {
|
||||
// debugPrint("tile_hires_stencil_on_center_tile_or_elevation_change tile was visited gElevation=%i gCenterTile=%i so doing nothing\n",
|
||||
// gElevation, gCenterTile);
|
||||
|
||||
return;
|
||||
};
|
||||
|
||||
// debugPrint("tile_hires_stencil_on_center_tile_or_elevation_change non-visited tile gElevation=%i gCenterTile=%i\n",
|
||||
// gElevation, gCenterTile);
|
||||
}
|
||||
|
||||
clean_cache_for_elevation(gElevation);
|
||||
|
||||
@@ -256,7 +248,6 @@ void tile_hires_stencil_on_center_tile_or_elevation_change()
|
||||
if (_obj_scroll_blocking_at(tileInfo.tile, gElevation) == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// TODO: Maybe create new function in tile.cc and use it here
|
||||
int tile_x = HEX_GRID_WIDTH - 1 - tileInfo.tile % HEX_GRID_WIDTH;
|
||||
int tile_y = tileInfo.tile / HEX_GRID_WIDTH;
|
||||
@@ -421,9 +412,14 @@ void tile_hires_stencil_init()
|
||||
}
|
||||
|
||||
debugPrint("tile_hires_stencil_init\n");
|
||||
}
|
||||
|
||||
void tile_hires_stencil_on_map_load()
|
||||
{
|
||||
clean_cache();
|
||||
tile_hires_stencil_on_center_tile_or_elevation_change();
|
||||
tileWindowRefresh();
|
||||
debugPrint("tile_hires_stencil_on_map_load\n");
|
||||
}
|
||||
|
||||
} // namespace fallout
|
||||
|
||||
@@ -7,6 +7,8 @@ namespace fallout {
|
||||
|
||||
void tile_hires_stencil_init();
|
||||
|
||||
void tile_hires_stencil_on_map_load();
|
||||
|
||||
void tile_hires_stencil_on_center_tile_or_elevation_change();
|
||||
|
||||
void tile_hires_stencil_draw(Rect* rect, unsigned char* buffer, int windowWidth, int windowHeight);
|
||||
|
||||
Reference in New Issue
Block a user