Compare commits

...
14 changed files with 762 additions and 118 deletions
+2
View File
@@ -158,6 +158,8 @@ target_sources(${EXECUTABLE_NAME} PUBLIC
"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"
+1 -1
View File
@@ -58,7 +58,7 @@ See [`https://sfall-team.github.io/sfall/`](https://sfall-team.github.io/sfall/)
| Interface / Tags | show_iface_tag<br>hide_iface_tag<br>is_iface_tag_active<br>set_iface_tag_text<br>add_iface_tag | âś… except set_iface_tag_text, add_iface_tag | CE only handles built-in interface tags here; custom tag creation/text is not supported yet. |
| Global variables | set_sfall_global<br>get_sfall_global_int<br>get_sfall_global_float | âś… except get_sfall_global_float | Current CE storage is int-backed; `set_sfall_global` stores integer values |
| Hooks / Hook functions | init_hook<br>get_sfall_arg<br>get_sfall_args<br>get_sfall_arg_at<br>set_sfall_return<br>set_sfall_arg<br>register_hook<br>register_hook_proc<br>register_hook_proc_spec | âś… | See below for implemented hooks. `init_hook` is deprecated and will not be implemented. register_hook_proc and register_hook_proc_spec both add hooks to the *end* of the hook list, instead of beginning and end, respectively. |
| Arrays / Array functions | create_array<br>temp_array<br>fix_array<br>get/set_array<br>resize_array<br>free_array<br>scan_array<br>len_array<br>save/load_array<br>array_key<br>arrayexpr | âś… except save_array, load_array | - |
| Arrays / Array functions | create_array<br>temp_array<br>fix_array<br>get/set_array<br>resize_array<br>free_array<br>scan_array<br>len_array<br>save/load_array<br>array_key<br>arrayexpr | âś… | - |
| Perks and traits / NPC perks | set_fake_perk_npc<br>set_fake_trait_npc<br>set_selectable_perk_npc<br>has_fake_perk_npc<br>has_fake_trait_npc | not implemented | - |
| Global scripts / Global script functions | set_global_script_repeat<br>set_global_script_type<br>available_global_script_types | âś… except available_global_script_types | - |
| Combat | attack_is_aimed<br>block_combat<br>force_aimed_shots<br>disable_aimed_shots<br>get_attack_type<br>get/set_bodypart_hit_modifier<br>combat_data<br>get/set/reset_critical_table<br>get_last_target<br>get_last_attacker<br>set_critter_burst_disable<br>get/set_critter_current_ap<br>set_spray_settings<br>get/set_combat_free_move | implemented: get_attack_type, get/set_bodypart_hit_modifier, combat_data, get/set_critter_current_ap, get/set_combat_free_move | - |
-3
View File
@@ -141,8 +141,6 @@ procedure array_test_suite begin
end
call assertEquals("foreach 2", s, "ar2=name:John;hp:25;0:5.50000;");
/*
display_msg("Testing save/load...");
arr := [2,1];
arr2 := {1:2};
@@ -163,7 +161,6 @@ procedure array_test_suite begin
call assertEquals("unsave array 1", load_array(0.1), 0);
call assertEquals("unsave array 2", len_array(arr), 2);
call assertEquals("saved arrays 3", len_array(list_saved_arrays), len_array(arr2) - 1);
*/
display_msg("Testing nested expressions...");
arr := [["one", "two"]];
+3 -67
View File
@@ -48,6 +48,7 @@
#include "scripts.h"
#include "settings.h"
#include "sfall_callbacks.h"
#include "sfall_ext.h"
#include "sfall_global_scripts.h"
#include "sfall_global_vars.h"
#include "skill.h"
@@ -1884,64 +1885,7 @@ static int lsgPerformSaveGame()
_flptr = fileOpen(_gmpath, "wb");
if (_flptr != nullptr) {
do {
if (!sfall_gl_vars_save(_flptr)) {
debugPrint("LOADSAVE (SFALL): ** Error saving global vars **\n");
break;
}
// TODO: For now fill remaining sections with zeros to that Sfall
// can successfully read our global vars and skip the rest.
int nextObjectId = 0;
if (fileWrite(&nextObjectId, sizeof(nextObjectId), 1, _flptr) != 1) {
debugPrint("LOADSAVE (SFALL): ** Error saving next object id **\n");
break;
}
int addedYears = 0;
if (fileWrite(&addedYears, sizeof(addedYears), 1, _flptr) != 1) {
debugPrint("LOADSAVE (SFALL): ** Error saving added years **\n");
break;
}
int fakeTraitsCount = 0;
if (fileWrite(&fakeTraitsCount, sizeof(fakeTraitsCount), 1, _flptr) != 1) {
debugPrint("LOADSAVE (SFALL): ** Error saving fake traits **\n");
break;
}
int fakePerksCount = 0;
if (fileWrite(&fakePerksCount, sizeof(fakePerksCount), 1, _flptr) != 1) {
debugPrint("LOADSAVE (SFALL): ** Error saving fake perks **\n");
break;
}
int fakeSelectablePerksCount = 0;
if (fileWrite(&fakeSelectablePerksCount, sizeof(fakeSelectablePerksCount), 1, _flptr) != 1) {
debugPrint("LOADSAVE (SFALL): ** Error saving fake selectable perks **\n");
break;
}
int arraysCountOld = 0;
if (fileWrite(&arraysCountOld, sizeof(arraysCountOld), 1, _flptr) != 1) {
debugPrint("LOADSAVE (SFALL): ** Error saving arrays (old fmt) **\n");
break;
}
int arraysCountNew = 0;
if (fileWrite(&arraysCountNew, sizeof(arraysCountNew), 1, _flptr) != 1) {
debugPrint("LOADSAVE (SFALL): ** Error saving arrays (new fmt) **\n");
break;
}
int drugPidsCount = 0;
if (fileWrite(&drugPidsCount, sizeof(drugPidsCount), 1, _flptr) != 1) {
debugPrint("LOADSAVE (SFALL): ** Error saving drug pids **\n");
break;
}
} while (0);
sfallSaveGameData(_flptr);
fileClose(_flptr);
}
@@ -2038,15 +1982,7 @@ static int lsgLoadGameInSlot(int slot)
_flptr = fileOpen(_gmpath, "rb");
if (_flptr != nullptr) {
do {
if (!sfall_gl_vars_load(_flptr)) {
debugPrint("LOADSAVE (SFALL): ** Error loading global vars **\n");
break;
}
// TODO: For now silently ignore remaining sections.
} while (0);
sfallLoadGameData(_flptr);
fileClose(_flptr);
}
+3
View File
@@ -42,6 +42,7 @@
#include "text_object.h"
#include "tile.h"
#include "tile_hires_stencil.h"
#include "map_edge.h"
#include "window_manager.h"
#include "window_manager_private.h"
#include "worldmap.h"
@@ -1059,6 +1060,8 @@ err:
// NOTE: Uninline.
mapSetEnteringLocation(-1, -1, -1);
mapEdgeFree();
mapEdgeInit(gMapHeader.name);
tile_hires_stencil_init();
gameMovieFadeOut();
+203
View File
@@ -0,0 +1,203 @@
#include "map_edge.h"
#include <algorithm>
#include "db.h"
#include "debug.h"
#include "map_defs.h"
#include "platform_compat.h"
namespace fallout {
static EdgeZone* gEdgeZones[ELEVATION_COUNT];
static bool gEdgeDataLoaded = false;
// Find the appropriate zone for a tile, or return the last zone as fallback
// (matches sfall behavior: use last zone when tile is outside all zones).
static EdgeZone* findZone(int tile, int elevation)
{
int tile_x = HEX_GRID_WIDTH - 1 - tile % HEX_GRID_WIDTH;
int tile_y = tile / HEX_GRID_WIDTH;
EdgeZone* zone = gEdgeZones[elevation];
EdgeZone* last = zone;
while (zone != nullptr) {
last = zone;
if (tile_x >= zone->minTileX && tile_x <= zone->maxTileX
&& tile_y >= zone->minTileY && tile_y <= zone->maxTileY) {
return zone;
}
zone = zone->next;
}
return last;
}
// Parse the EDG file and populate gEdgeZones. Returns true on success.
// EDG files use big-endian byte order (same as other Fallout 2 files).
static bool parseEdgFile(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;
int levelIndicator = 0;
for (int elev = 0; elev < ELEVATION_COUNT; elev++) {
int sqLeft = -1, sqTop = -1, sqRight = -1, sqBottom = -1;
if (version == 2) {
int sqRect[4], clipData;
if (fileReadInt32List(stream, sqRect, 4) == -1
|| fileReadInt32(stream, &clipData) == -1) {
return false;
}
sqLeft = sqRect[0];
sqTop = sqRect[1];
sqRight = sqRect[2];
sqBottom = sqRect[3];
// clipData skipped: CE's black fill is equivalent to sfall's blank-tile render
}
if (levelIndicator != elev) {
// No tileRect data for this elevation in the file
continue;
}
EdgeZone** tail = &gEdgeZones[elev];
bool isFirstZone = true;
while (true) {
int tileRect[4];
if (fileReadInt32List(stream, tileRect, 4) == -1) {
// EOF during read
return elev == ELEVATION_COUNT - 1;
}
EdgeZone* zone = new EdgeZone();
// tileRect[0]=left, [1]=right: left tile has smaller tile_x than right in CE coords
zone->minTileX = HEX_GRID_WIDTH - 1 - (tileRect[0] % HEX_GRID_WIDTH);
zone->maxTileX = HEX_GRID_WIDTH - 1 - (tileRect[1] % HEX_GRID_WIDTH);
zone->minTileY = tileRect[2] / HEX_GRID_WIDTH;
zone->maxTileY = tileRect[3] / HEX_GRID_WIDTH;
// squareRect is per-elevation (stored in first zone only)
zone->sqLeft = isFirstZone ? sqLeft : -1;
zone->sqTop = isFirstZone ? sqTop : -1;
zone->sqRight = isFirstZone ? sqRight : -1;
zone->sqBottom = isFirstZone ? sqBottom : -1;
zone->next = nullptr;
*tail = zone;
tail = &zone->next;
isFirstZone = false;
if (fileReadInt32(stream, &levelIndicator) == -1) {
// EOF after tileRect is valid for the last elevation
return elev == ELEVATION_COUNT - 1;
}
if (levelIndicator != elev) {
break; // next elevation begins
}
}
}
return true;
}
void mapEdgeInit(const char* mapName)
{
mapEdgeFree();
// Build "MAPS\<basename>.EDG" from e.g. "ARROYO.MAP"
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 = parseEdgFile(stream);
fileClose(stream);
if (ok) {
gEdgeDataLoaded = true;
debugPrint("mapEdgeInit: loaded %s\n", edgPath);
} else {
debugPrint("mapEdgeInit: failed to parse %s\n", edgPath);
mapEdgeFree();
}
}
void mapEdgeFree()
{
for (int elev = 0; elev < ELEVATION_COUNT; elev++) {
EdgeZone* zone = gEdgeZones[elev];
while (zone != nullptr) {
EdgeZone* next = zone->next;
delete zone;
zone = next;
}
gEdgeZones[elev] = nullptr;
}
gEdgeDataLoaded = false;
}
bool mapEdgeIsLoaded()
{
return gEdgeDataLoaded;
}
int mapEdgeClampTile(int tile, int elevation)
{
EdgeZone* zone = findZone(tile, elevation);
if (zone == nullptr) {
return tile;
}
int tile_x = HEX_GRID_WIDTH - 1 - tile % HEX_GRID_WIDTH;
int tile_y = tile / HEX_GRID_WIDTH;
int cx = std::clamp(tile_x, zone->minTileX, zone->maxTileX);
int cy = std::clamp(tile_y, zone->minTileY, zone->maxTileY);
return HEX_GRID_WIDTH * cy + (HEX_GRID_WIDTH - 1 - cx);
}
bool mapEdgeTileInBounds(int tile, int elevation)
{
EdgeZone* zone = findZone(tile, elevation);
if (zone == nullptr) {
return false;
}
int tile_x = HEX_GRID_WIDTH - 1 - tile % HEX_GRID_WIDTH;
int tile_y = tile / HEX_GRID_WIDTH;
return tile_x >= zone->minTileX && tile_x <= zone->maxTileX
&& tile_y >= zone->minTileY && tile_y <= zone->maxTileY;
}
bool mapEdgeHasSquareRect(int elevation)
{
EdgeZone* zone = gEdgeZones[elevation];
return zone != nullptr && zone->sqLeft >= 0;
}
void mapEdgeGetSquareRect(int elevation, int* left, int* top, int* right, int* bottom)
{
EdgeZone* zone = gEdgeZones[elevation];
*left = zone->sqLeft;
*top = zone->sqTop;
*right = zone->sqRight;
*bottom = zone->sqBottom;
}
} // namespace fallout
+53
View File
@@ -0,0 +1,53 @@
#ifndef MAP_EDGE_H
#define MAP_EDGE_H
namespace fallout {
struct EdgeZone {
// Scroll boundary in CE tile_x/tile_y coordinates (inclusive).
// CE tile_x = HEX_GRID_WIDTH - 1 - tile % HEX_GRID_WIDTH
// CE tile_y = tile / HEX_GRID_WIDTH
int minTileX;
int maxTileX;
int minTileY;
int maxTileY;
// squareRect in sfall native format (v2 only).
// valid range: squareTile%100 in [sqRight, sqLeft], squareTile/100 in [sqTop, sqBottom]
// All fields are -1 when not present (v1 EDG).
int sqLeft;
int sqTop;
int sqRight;
int sqBottom;
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 mapEdgeInit(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();
// Clamps tile to the EDG boundary for the given elevation.
// Returns the clamped tile (may equal tile if already in bounds).
int mapEdgeClampTile(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, int elevation);
// Returns true if squareRect data (v2 EDG) is available for this elevation.
bool mapEdgeHasSquareRect(int elevation);
// Fills the squareRect fields for the given elevation.
// Only valid when mapEdgeHasSquareRect(elevation) returns true.
void mapEdgeGetSquareRect(int elevation, int* left, int* top, int* right, int* bottom);
} // namespace fallout
#endif /* MAP_EDGE_H */
+346 -27
View File
File diff suppressed because it is too large Load Diff
+8
View File
@@ -3,6 +3,9 @@
#include "interpreter.h"
struct XFile;
typedef XFile File;
namespace fallout {
#define SFALL_ARRAYFLAG_ASSOC (1) // is map
@@ -33,6 +36,11 @@ ArrayId ListAsArray(int type);
ArrayId StringSplit(const char* str, const char* split);
void SaveArray(const ProgramValue& key, ArrayId arrayId, Program* program);
ArrayId LoadArray(const ProgramValue& key, Program* program);
bool sfallArraysSave(File* stream);
bool sfallArraysLoad(File* stream);
} // namespace fallout
#endif /* FALLOUT_SFALL_ARRAYS_H_ */
+61
View File
@@ -6,6 +6,8 @@
#include "db.h"
#include "debug.h"
#include "platform_compat.h"
#include "sfall_arrays.h"
#include "sfall_global_vars.h"
namespace fallout {
@@ -88,4 +90,63 @@ void sfallLoadMods()
}
}
// Binary layout of sfallgv.sav (must match sfall's SaveGame2 / LoadGame_Before order):
// global vars | nextObjectId(4) | addedYears(4) | fakeTraitsCount(4) |
// fakePerksCount(4) | fakeSelectablePerksCount(4) | arrays | drugPidsCount(4)
//
// Sections that CE doesn't implement are written/read as zero.
bool sfallSaveGameData(File* stream)
{
if (!sfall_gl_vars_save(stream)) {
debugPrint("LOADSAVE (SFALL): ** Error saving global vars **\n");
return false;
}
// Write zeros for CE-unimplemented fields: nextObjectId, addedYears,
// fakeTraitsCount, fakePerksCount, fakeSelectablePerksCount
int zero = 0;
for (int i = 0; i < 5; i++) {
if (fileWrite(&zero, sizeof(zero), 1, stream) != 1) {
debugPrint("LOADSAVE (SFALL): ** Error saving stub fields **\n");
return false;
}
}
if (!sfallArraysSave(stream)) {
debugPrint("LOADSAVE (SFALL): ** Error saving arrays **\n");
return false;
}
if (fileWrite(&zero, sizeof(zero), 1, stream) != 1) { // drugPidsCount
debugPrint("LOADSAVE (SFALL): ** Error saving drug pids **\n");
return false;
}
return true;
}
bool sfallLoadGameData(File* stream)
{
if (!sfall_gl_vars_load(stream)) {
debugPrint("LOADSAVE (SFALL): ** Error loading global vars **\n");
return false;
}
// Skip sections CE doesn't implement: nextObjectId, addedYears,
// fakeTraitsCount, fakePerksCount, fakeSelectablePerksCount
int ignored;
for (int i = 0; i < 5; i++) {
if (fileRead(&ignored, sizeof(ignored), 1, stream) != 1) return true; // old save, stop gracefully
}
if (!sfallArraysLoad(stream)) {
// Not fatal - may be an old save written before array persistence was implemented,
// or the file simply ended before the arrays section.
debugPrint("LOADSAVE (SFALL): ** Error loading arrays **, skipping\n");
}
return true;
}
} // namespace fallout
+5
View File
@@ -1,9 +1,14 @@
#ifndef SFALL_EXT_H
#define SFALL_EXT_H
struct XFile;
typedef XFile File;
namespace fallout {
void sfallLoadMods();
bool sfallSaveGameData(File* stream);
bool sfallLoadGameData(File* stream);
} // namespace fallout
+17
View File
@@ -1095,6 +1095,21 @@ static void op_get_message(Program* program)
programStackPushString(program, text);
}
// save_array
static void op_save_array(Program* program)
{
auto arrayId = static_cast<ArrayId>(programStackPopInteger(program));
auto key = programStackPopValue(program);
SaveArray(key, arrayId, program);
}
// load_array
static void op_load_array(Program* program)
{
auto key = programStackPopValue(program);
programStackPushInteger(program, static_cast<int>(LoadArray(key, program)));
}
// array_key
static void op_get_array_key(Program* program)
{
@@ -1840,7 +1855,9 @@ void sfallOpcodesInit()
// 0x8239 - int scan_array(int array, int/float var)
interpreterRegisterOpcode(0x8239, op_scan_array);
// 0x8254 - void save_array(any key, int array)
interpreterRegisterOpcode(0x8254, op_save_array);
// 0x8255 - int load_array(any key)
interpreterRegisterOpcode(0x8255, op_load_array);
// 0x8256 - int array_key(int array, int index)
interpreterRegisterOpcode(0x8256, op_get_array_key);
// 0x8257 - int arrayexpr(any key, any value)
+15 -2
View File
@@ -19,6 +19,7 @@
#include "settings.h"
#include "svga.h"
#include "tile_hires_stencil.h"
#include "map_edge.h"
namespace fallout {
@@ -540,6 +541,15 @@ int tileSetCenter(int tile, int flags)
return -1;
}
// When EDG data is loaded, clamp to map boundary (like sfall's GetCenterTile).
// This handles starting positions outside the defined boundary.
if (mapEdgeIsLoaded() && !settings.ui.ignore_map_edges) {
tile = mapEdgeClampTile(tile, gElevation);
if (!tileIsValid(tile)) {
return -1;
}
}
if ((flags & TILE_SET_CENTER_FLAG_IGNORE_SCROLL_RESTRICTIONS) == 0) {
if (gTileScrollLimitingEnabled) {
int tileScreenX;
@@ -561,7 +571,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() && gTileScrollBlockingEnabled && !settings.ui.ignore_map_edges) {
if (_obj_scroll_blocking_at(tile, gElevation) == 0) {
return -1;
}
@@ -571,7 +583,8 @@ 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) {
// Auto-computed border check only runs when no EDG is loaded.
if (!mapEdgeIsLoaded() && gTileBorderInitialized && !settings.ui.ignore_map_edges) {
if (tile_x <= gTileBorderMinX || tile_x >= gTileBorderMaxX || tile_y <= gTileBorderMinY || tile_y >= gTileBorderMaxY) {
return -1;
}
+45 -18
View File
@@ -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"
@@ -211,22 +212,44 @@ void tile_hires_stencil_on_center_tile_or_elevation_change()
return;
}
if (!gTileBorderInitialized) {
// EDG-based stencil does not need the auto-computed border to be ready.
if (!mapEdgeIsLoaded() && !gTileBorderInitialized) {
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);
// Branch A: v2 EDG — use squareRect directly to fill visible_squares.
// This avoids the flood-fill and matches sfall's squareRect clipping behavior.
if (mapEdgeIsLoaded() && mapEdgeHasSquareRect(gElevation)) {
visited_tiles[gElevation][gCenterTile] = true;
int sqLeft, sqTop, sqRight, sqBottom;
mapEdgeGetSquareRect(gElevation, &sqLeft, &sqTop, &sqRight, &sqBottom);
auto diff = get_screen_diff();
for (int cx = 0; cx < square_grid_width; cx++) {
for (int cy = 0; cy < square_grid_height; cy++) {
int screenX = cx * square_width + diff.x;
int screenY = cy * square_height + diff.y;
int squareTile = squareTileFromScreenXY(screenX, screenY, gElevation);
if (squareTile >= 0) {
int x = squareTile % SQUARE_GRID_WIDTH;
int y = squareTile / SQUARE_GRID_WIDTH;
if (x >= sqRight && x <= sqLeft && y >= sqTop && y <= sqBottom) {
visible_squares[gElevation][cx][cy] = true;
}
}
}
}
return;
}
// Branch B: flood-fill. Uses EDG tileRect bounds (v1 EDG) or scroll-blocker
// objects + auto-computed border (no EDG) as the traversal gate.
struct TileToVisit {
int tile;
MarkOnlyPart part;
@@ -253,15 +276,19 @@ void tile_hires_stencil_on_center_tile_or_elevation_change()
if (tileInfo.tile < 0 || tileInfo.tile >= HEX_GRID_SIZE) {
continue;
}
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;
if (tile_x <= gTileBorderMinX || tile_x >= gTileBorderMaxX || tile_y <= gTileBorderMinY || tile_y >= gTileBorderMaxY) {
continue;
if (mapEdgeIsLoaded()) {
if (!mapEdgeTileInBounds(tileInfo.tile, gElevation)) {
continue;
}
} else {
if (_obj_scroll_blocking_at(tileInfo.tile, gElevation) == 0) {
continue;
}
int tile_x = HEX_GRID_WIDTH - 1 - tileInfo.tile % HEX_GRID_WIDTH;
int tile_y = tileInfo.tile / HEX_GRID_WIDTH;
if (tile_x <= gTileBorderMinX || tile_x >= gTileBorderMaxX || tile_y <= gTileBorderMinY || tile_y >= gTileBorderMaxY) {
continue;
}
}
}