mirror of
https://github.com/fallout2-ce/fallout2-ce.git
synced 2026-07-27 16:47:11 -07:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
43a991a5ee | ||
|
|
cd3dd7c854 | ||
|
|
831c3a50fd | ||
|
|
39a6ed4a06 | ||
|
|
85344665dc | ||
|
|
f13ce3c9a0 | ||
|
|
7579532a5d | ||
|
|
3dc5bb4f82 |
@@ -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"
|
||||
|
||||
@@ -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 | - |
|
||||
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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
|
||||
@@ -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
File diff suppressed because it is too large
Load Diff
@@ -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_ */
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user