Compare commits

...
Author SHA1 Message Date
Vasilii Rogin 8751a2551b Call tileSetCenter before window refresh 2026-03-16 20:21:59 +02:00
Vasilii Rogin 2bf0aecff3 Use elevation param 2026-03-16 20:21:32 +02:00
Vasilii Rogin 2b0c1deebf Merge branch 'add_scroll_limits_to_stencil_rebased' into add_scroll_limits_to_stencil_rebased_add_scroll 2026-03-16 20:18:15 +02:00
Vasilii Rogin 1aa36af8b4 Rename funcs and struct 2026-03-16 20:17:33 +02:00
Vasilii Rogin d672346a64 Add explicit initialized prop 2026-03-16 20:16:29 +02:00
Vasilii Rogin 20e8ff3725 fix tile lookup 2026-03-16 19:49:06 +02:00
Vasilii Rogin 811d5de9ac Add flags for tileSetCenter 2026-03-16 19:46:58 +02:00
Vasilii Rogin 14300f3f97 Add auto-scrolling for hires stencil 2026-03-16 19:44:07 +02:00
Vasilii RoginandCopilot Autofix powered by AI 3a3675d846 Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-03-16 18:47:16 +02:00
Vasilii Rogin 036f63ffa8 Fix spelling 2026-03-16 18:26:42 +02:00
Vasilii Rogin 60bf86231a Add get_tile_neighbors 2026-03-16 18:26:00 +02:00
Vasilii Rogin 608d758864 Merge branch 'fix/tile-from-screen-xy-signature' into add_scroll_limits_to_stencil_rebased 2026-03-16 18:21:19 +02:00
Vasilii Rogin 65922b8dd2 Remove debugPrint 2026-03-16 18:12:15 +02:00
Vasilii Rogin 7c1c9d287e Merge remote-tracking branch 'origin/main' into add_scroll_limits_to_stencil_rebased 2026-03-16 18:11:28 +02:00
Vasilii Rogin 9baea460c9 Update comment 2026-03-15 20:20:21 +02:00
Vasilii RoginandCopilot Autofix powered by AI 9b182e4c25 Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-03-15 20:19:14 +02:00
Vasilii RoginandCopilot Autofix powered by AI 9b490e9627 Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-03-15 20:17:49 +02:00
Vasilii Rogin f5d976d199 Add scroll limits to hi-res stencil 2026-03-15 19:56:04 +02:00
7 changed files with 367 additions and 53 deletions
+1 -1
View File
@@ -3153,7 +3153,7 @@ static void opFloatMessage(Program* program)
color = _colorTable[31744];
backgroundColor = _colorTable[0];
font = 103;
tileSetCenter(gDude->tile, TILE_SET_CENTER_REFRESH_WINDOW);
tileSetCenter(gDude->tile, TILE_SET_CENTER_REFRESH_WINDOW | TILE_SET_CENTER_FLAG_ALLOW_HIRES_TWEAK);
break;
case FLOATING_MESSAGE_TYPE_NORMAL:
case FLOATING_MESSAGE_TYPE_YELLOW:
+2 -2
View File
@@ -932,7 +932,7 @@ static int mapLoad(File* stream)
}
error = "Error setting tile center";
if (tileSetCenter(gEnteringTile, TILE_SET_CENTER_FLAG_IGNORE_SCROLL_RESTRICTIONS) != 0) {
if (tileSetCenter(gEnteringTile, TILE_SET_CENTER_FLAG_IGNORE_SCROLL_RESTRICTIONS | TILE_SET_CENTER_FLAG_ALLOW_HIRES_TWEAK) != 0) {
goto err;
}
@@ -1293,7 +1293,7 @@ int mapHandleTransition()
objectSetRotation(gDude, gMapTransition.rotation, nullptr);
}
if (tileSetCenter(gDude->tile, TILE_SET_CENTER_REFRESH_WINDOW) == -1) {
if (tileSetCenter(gDude->tile, TILE_SET_CENTER_REFRESH_WINDOW | TILE_SET_CENTER_FLAG_ALLOW_HIRES_TWEAK) == -1) {
debugPrint("\nError: map: attempt to center out-of-bounds!");
}
+1 -1
View File
@@ -3723,7 +3723,7 @@ int _obj_load_dude(File* stream)
return -1;
}
tileSetCenter(tile, TILE_SET_CENTER_REFRESH_WINDOW | TILE_SET_CENTER_FLAG_IGNORE_SCROLL_RESTRICTIONS);
tileSetCenter(tile, TILE_SET_CENTER_REFRESH_WINDOW | TILE_SET_CENTER_FLAG_IGNORE_SCROLL_RESTRICTIONS | TILE_SET_CENTER_FLAG_ALLOW_HIRES_TWEAK);
return rc;
}
+11
View File
@@ -568,6 +568,10 @@ int tileSetCenter(int tile, int flags)
if (_obj_scroll_blocking_at(tile, gElevation) == 0) {
return -1;
}
if (!tile_hires_stencil_allows_scrolling_to_tile(tile, gCenterTile, gElevation, gTileWindowWidth, gTileWindowHeight)) {
return -1;
}
}
}
@@ -604,6 +608,13 @@ int tileSetCenter(int tile, int flags)
tile_hires_stencil_on_center_tile_or_elevation_change();
if (flags & TILE_SET_CENTER_FLAG_ALLOW_HIRES_TWEAK) {
auto tweaked_center_tile = tile_hires_stencil_get_tweaked_center_tile(gCenterTile, gElevation, gTileWindowWidth, gTileWindowHeight);
if (tileIsValid(tweaked_center_tile) && tweaked_center_tile != gCenterTile) {
return tileSetCenter(tweaked_center_tile, flags & ~TILE_SET_CENTER_FLAG_ALLOW_HIRES_TWEAK);
}
}
if ((flags & TILE_SET_CENTER_REFRESH_WINDOW) != 0) {
// NOTE: Uninline.
tileWindowRefresh();
+1
View File
@@ -8,6 +8,7 @@ namespace fallout {
#define TILE_SET_CENTER_REFRESH_WINDOW 0x01
#define TILE_SET_CENTER_FLAG_IGNORE_SCROLL_RESTRICTIONS 0x02
#define TILE_SET_CENTER_FLAG_ALLOW_HIRES_TWEAK 0x04
typedef void(TileWindowRefreshProc)(Rect* rect);
typedef void(TileWindowRefreshElevationProc)(Rect* rect, int elevation);
+348 -49
View File
@@ -25,6 +25,15 @@ namespace fallout {
/** This holds hex tiles which can be center tile */
static bool visited_tiles[ELEVATION_COUNT][HEX_GRID_SIZE];
struct StencilScreenLimits {
int minX;
int maxX;
int minY;
int maxY;
bool initialized;
};
static struct StencilScreenLimits screen_xy_limits[ELEVATION_COUNT];
static constexpr int pixels_per_horizontal_move = 32;
static constexpr int pixels_per_vertical_move = 24;
@@ -69,11 +78,13 @@ static bool gIsTileHiresStencilEnabled = true;
static void clean_cache()
{
memset(visited_tiles, 0, sizeof(visited_tiles));
memset(screen_xy_limits, 0, sizeof(screen_xy_limits));
memset(visible_squares, 0, sizeof(visible_squares));
}
static void clean_cache_for_elevation(int elevation)
{
memset(visited_tiles[elevation], 0, sizeof(visited_tiles[elevation]));
memset(&screen_xy_limits[elevation], 0, sizeof(screen_xy_limits[elevation]));
memset(visible_squares[elevation], 0, sizeof(visible_squares[elevation]));
}
@@ -205,6 +216,95 @@ static void mark_screen_tiles_around_as_visible(int center_tile, const Point& sc
}
}
static void update_screen_xy_limits(int tileScreenX, int tileScreenY, const Point& screen_diff, int elevation)
{
auto& limits = screen_xy_limits[elevation];
auto candidateMinX = tileScreenX - screen_view_width / 2 - screen_diff.x;
auto candidateMaxX = tileScreenX + screen_view_width / 2 - screen_diff.x;
auto candidateMinY = tileScreenY - screen_view_height / 2 - screen_diff.y;
auto candidateMaxY = tileScreenY + screen_view_height / 2 - screen_diff.y;
if (!limits.initialized) {
limits.minX = candidateMinX;
limits.maxX = candidateMaxX;
limits.minY = candidateMinY;
limits.maxY = candidateMaxY;
limits.initialized = true;
return;
}
if (candidateMinX < limits.minX) {
limits.minX = candidateMinX;
}
if (candidateMaxX > limits.maxX) {
limits.maxX = candidateMaxX;
}
if (candidateMinY < limits.minY) {
limits.minY = candidateMinY;
}
if (candidateMaxY > limits.maxY) {
limits.maxY = candidateMaxY;
}
}
struct TileScrollNeighbors {
int left;
int right;
int up;
int down;
};
static struct TileScrollNeighbors get_tile_scroll_neighbors(int tileScreenX, int tileScreenY)
{
// tile size is 32 x 18
//
// / \ ^
// | | | 18
// \ / v
//
// <-32->
//
// Scrolling left-right changes x by 32
// But scrolling top-bottom changes y by 24
//
//
// / \
// | | <----- tiles on vertical change, 24 px
// / \ / \ |
// | | | <------ | ----- tiles on horizontal change, 32 px
// \ / \ / |
// | | <--/
// \ /
//
constexpr int tile_center_offset_x = 16;
constexpr int tile_center_offset_y = 8;
struct TileScrollNeighbors r;
r.left = tileFromScreenXY(
tileScreenX - pixels_per_horizontal_move + tile_center_offset_x,
tileScreenY + tile_center_offset_y,
true);
r.right = tileFromScreenXY(
tileScreenX + pixels_per_horizontal_move + tile_center_offset_x,
tileScreenY + tile_center_offset_y,
true);
r.up = tileFromScreenXY(
tileScreenX + tile_center_offset_x,
tileScreenY - pixels_per_vertical_move + tile_center_offset_y,
true);
r.down = tileFromScreenXY(
tileScreenX + tile_center_offset_x,
tileScreenY + pixels_per_vertical_move + tile_center_offset_y,
true);
return r;
}
void tile_hires_stencil_on_center_tile_or_elevation_change()
{
if (!gIsTileHiresStencilEnabled) {
@@ -216,15 +316,9 @@ void tile_hires_stencil_on_center_tile_or_elevation_change()
};
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);
struct TileToVisit {
@@ -245,14 +339,16 @@ void tile_hires_stencil_on_center_tile_or_elevation_change()
auto tileInfo = tiles_to_visit.back();
tiles_to_visit.pop_back();
// Ensure tile index is within valid bounds before any array access.
if (tileInfo.tile < 0 || tileInfo.tile >= HEX_GRID_SIZE) {
continue;
}
if (visited_tiles[gElevation][tileInfo.tile]) {
continue;
}
if (tileInfo.tile != gCenterTile) [[unlikely]] {
if (tileInfo.tile < 0 || tileInfo.tile >= HEX_GRID_SIZE) {
continue;
}
if (_obj_scroll_blocking_at(tileInfo.tile, gElevation) == 0) {
continue;
}
@@ -274,53 +370,19 @@ void tile_hires_stencil_on_center_tile_or_elevation_change()
int tileScreenY;
tileToScreenXY(tileInfo.tile, &tileScreenX, &tileScreenY);
// tile size is 32 x 18
//
// / \ ^
// | | | 18
// \ / v
//
// <-32->
//
// Scrolling left-right changes x by 32
// But scrolling top-bottom changes y by 24
//
//
// / \
// | | <----- tiles on vertical change, 24 px
// / \ / \ |
// | | | <------ | ----- tiles on horizontal change, 32 px
// \ / \ / |
// | | <--/
// \ /
//
update_screen_xy_limits(tileScreenX, tileScreenY, screen_diff, gElevation);
constexpr int tile_center_offset_x = 16;
constexpr int tile_center_offset_y = 8;
auto neighbors = get_tile_scroll_neighbors(tileScreenX, tileScreenY);
tiles_to_visit.push_back({ tileFromScreenXY(
tileScreenX - pixels_per_horizontal_move + tile_center_offset_x,
tileScreenY + tile_center_offset_y,
true),
tiles_to_visit.push_back({ neighbors.left,
MarkOnlyPart::LEFT });
tiles_to_visit.push_back({ tileFromScreenXY(
tileScreenX + pixels_per_horizontal_move + tile_center_offset_x,
tileScreenY + tile_center_offset_y,
true),
tiles_to_visit.push_back({ neighbors.right,
MarkOnlyPart::RIGHT });
tiles_to_visit.push_back({ tileFromScreenXY(
tileScreenX + tile_center_offset_x,
tileScreenY - pixels_per_vertical_move + tile_center_offset_y,
true),
tiles_to_visit.push_back({ neighbors.up,
MarkOnlyPart::UP });
tiles_to_visit.push_back({ tileFromScreenXY(
tileScreenX + tile_center_offset_x,
tileScreenY + pixels_per_vertical_move + tile_center_offset_y,
true),
tiles_to_visit.push_back({ neighbors.down,
MarkOnlyPart::DOWN });
}
debugPrint("tile_hires_stencil_on_center_tile_or_elevation_change visited_tiles_count=%i\n", visited_tiles_count);
}
void tile_hires_stencil_draw(Rect* rect, unsigned char* buffer, int windowWidth, int windowHeight)
@@ -403,6 +465,243 @@ void tile_hires_stencil_draw(Rect* rect, unsigned char* buffer, int windowWidth,
}
}
/**
* Even when we can scroll to the tile according to scrollblocker tiles,
* we also want to disallow scrolling on big resolutions if there is nothing to show there
*/
bool tile_hires_stencil_allows_scrolling_to_tile(int newCenterTile, int currentCenterTile, int elevation, int windowWidth, int windowHeight)
{
if (!gIsTileHiresStencilEnabled) {
return true;
}
if (!gTileBorderInitialized) {
return true;
};
auto& limits = screen_xy_limits[elevation];
if (!limits.initialized) {
return true;
}
int currentTileScreenX;
int currentTileScreenY;
tileToScreenXY(currentCenterTile, &currentTileScreenX, &currentTileScreenY);
int newTileScreenX;
int newTileScreenY;
tileToScreenXY(newCenterTile, &newTileScreenX, &newTileScreenY);
auto xDiff = newTileScreenX - currentTileScreenX;
auto yDiff = newTileScreenY - currentTileScreenY;
auto screen_diff = get_screen_diff();
auto newScreenMinX = newTileScreenX - windowWidth / 2 - screen_diff.x;
auto newScreenMaxX = newTileScreenX + windowWidth / 2 - screen_diff.x;
auto newScreenMinY = newTileScreenY - windowHeight / 2 - screen_diff.y;
auto newScreenMaxY = newTileScreenY + windowHeight / 2 - screen_diff.y;
if (xDiff < 0) { // Moving left
if (newScreenMinX >= limits.minX) {
// Scrolling left when there is still something to show is always allowed
// [------------------------] <- possible visible area border (limits.maxX and limits.minX)
// ( x ) <- hi-res screen with center tile (newScreenMinX, newScreenMaxX)
} else if (newScreenMaxX <= limits.maxX) {
// Disallow if map is bigger than screen
// [------------------------] <- possible visible area border (limits.maxX and limits.minX)
// ( x ) <- this clearly disallow
return false;
} else {
// Now we have two cases: when moving left will make map to be more in the center or not
// [-----------] <- possible visible area border (limits.maxX and limits.minX)
// ( x ) <- allow this, it will move map to the center
// ( x ) <- disallow this, it will move map from the center
auto leftDiff = limits.minX - newScreenMinX;
auto rightDiff = newScreenMaxX - limits.maxX;
if (leftDiff > rightDiff) {
return false;
}
}
}
if (xDiff > 0) {
if (newScreenMaxX <= limits.maxX) {
// Allow
} else if (newScreenMinX > limits.minX) {
return false;
} else {
auto leftDiff = limits.minX - newScreenMinX;
auto rightDiff = newScreenMaxX - limits.maxX;
if (leftDiff < rightDiff) {
return false;
}
}
}
if (yDiff < 0) { // Moving up
if (newScreenMinY >= limits.minY) {
// Scrolling up when there is still something to show is always allowed
} else if (newScreenMaxY <= limits.maxY) {
// Disallow if map is taller than screen
return false;
} else {
// Allow only if moving up makes the visible area more centered vertically
auto topDiff = limits.minY - newScreenMinY;
auto bottomDiff = newScreenMaxY - limits.maxY;
if (topDiff > bottomDiff) {
return false;
}
}
}
if (yDiff > 0) { // Moving down
if (newScreenMaxY <= limits.maxY) {
// Allow
} else if (newScreenMinY > limits.minY) {
return false;
} else {
auto topDiff = limits.minY - newScreenMinY;
auto bottomDiff = newScreenMaxY - limits.maxY;
if (topDiff < bottomDiff) {
return false;
}
}
}
return true;
}
/**
* In case of hi-res we automatically scroll a little bit until we get overlay hidden
*/
int tile_hires_stencil_get_tweaked_center_tile(int initialCenterTile, int elevation, int windowWidth, int windowHeight)
{
if (!gIsTileHiresStencilEnabled || !gTileBorderInitialized) {
return initialCenterTile;
}
auto& limits = screen_xy_limits[elevation];
if (limits.maxX == 0 && limits.maxY == 0) {
return initialCenterTile;
}
auto candidateTile = initialCenterTile;
int loopBreaker = 1000; // Just in case, to avoid infinite loop
for (; loopBreaker > 0; loopBreaker--) {
int candidateTileScreenX;
int candidateTileScreenY;
tileToScreenXY(candidateTile, &candidateTileScreenX, &candidateTileScreenY);
auto screen_diff = get_screen_diff();
auto candidateScreenMinX = candidateTileScreenX - windowWidth / 2 - screen_diff.x;
auto candidateScreenMaxX = candidateTileScreenX + windowWidth / 2 - screen_diff.x;
auto candidateScreenMinY = candidateTileScreenY - windowHeight / 2 - screen_diff.y;
auto candidateScreenMaxY = candidateTileScreenY + windowHeight / 2 - screen_diff.y;
auto leftIsOutOfLimits = candidateScreenMinX < limits.minX;
auto rightIsOutOfLimits = candidateScreenMaxX > limits.maxX;
auto upIsOutOfLimits = candidateScreenMinY < limits.minY;
auto downIsOutOfLimits = candidateScreenMaxY > limits.maxY;
auto neighbors = get_tile_scroll_neighbors(candidateTileScreenX, candidateTileScreenY);
auto canScrollLeft = tile_hires_stencil_allows_scrolling_to_tile(
neighbors.left, candidateTile, elevation, windowWidth, windowHeight)
&& _obj_scroll_blocking_at(neighbors.left, elevation) != 0;
auto canScrollRight = tile_hires_stencil_allows_scrolling_to_tile(
neighbors.right, candidateTile, elevation, windowWidth, windowHeight)
&& _obj_scroll_blocking_at(neighbors.right, elevation) != 0;
auto canScrollUp = tile_hires_stencil_allows_scrolling_to_tile(
neighbors.up, candidateTile, elevation, windowWidth, windowHeight)
&& _obj_scroll_blocking_at(neighbors.up, elevation) != 0;
auto canScrollDown = tile_hires_stencil_allows_scrolling_to_tile(
neighbors.down, candidateTile, elevation, windowWidth, windowHeight)
&& _obj_scroll_blocking_at(neighbors.down, elevation) != 0;
if (leftIsOutOfLimits && canScrollRight) {
candidateTile = neighbors.right;
} else if (rightIsOutOfLimits && canScrollLeft) {
candidateTile = neighbors.left;
} else if (upIsOutOfLimits && canScrollDown) {
candidateTile = neighbors.down;
} else if (downIsOutOfLimits && canScrollUp) {
candidateTile = neighbors.up;
} else {
return candidateTile;
}
};
debugPrint("Warning! tile_hires_stencil_get_tweaked_center_tile: loop breaker reached, something went wrong\n");
return -1;
}
/**
Scrolling in Fallout 2 is controlled by special scrollblocker tiles.
The game has state variable "gCenterTile" which defines current window scroll position.
When player scrolls, game checks if there is a scrollblocker tile in the direction of scroll
and if there is, it does not change gCenterTile and does not scroll.
So maps place scrollblocker tiles not exactly on the edge but on the half of default screen size (640x480).
Example (x = current gCenterTile; + = scrollblocker tile on map):
<- this is the border of possible visible area
(usually also border of the map)
+++++++++++++++++++++++++
+x +
+ + <-------- Scrollblocker tile are inside
+ +
+ +
640x480window +
+ +
+ +
+ +
+ +
+ +
+++++++++++++++++++++++++
1920x1080window
<-------> this distance is half of default screen width, i.e. 640/2=320 pixels
But not every map is rectangular.
So on every map or elevation change we do a search for all possible accessible
center tile values starting from the current one, and for each such possible
center tile we mark the 640x380 tile viewport area around as visible (within the
overall 640x480 screen). The rest gets covered by a black overlay.
++++++++++
+ +
+ +
+ +
+ +
+ +
+ +++++++++++++++++++++++++++++
+
+ x +
+ +
+ x x+
+++++++++++++++++++++++++
*/
void tile_hires_stencil_init()
{
configGetBool(&gSfallConfig, SFALL_CONFIG_MAIN_KEY, SFALL_CONFIG_ENABLE_HIRES_STENCIL, &gIsTileHiresStencilEnabled);
+3
View File
@@ -11,6 +11,9 @@ void tile_hires_stencil_on_center_tile_or_elevation_change();
void tile_hires_stencil_draw(Rect* rect, unsigned char* buffer, int windowWidth, int windowHeight);
bool tile_hires_stencil_allows_scrolling_to_tile(int newCenterTile, int currentCenterTile, int elevation, int windowWidth, int windowHeight);
int tile_hires_stencil_get_tweaked_center_tile(int initialCenterTile, int elevation, int windowWidth, int windowHeight);
} // namespace fallout
#endif