mirror of
https://github.com/fallout2-ce/fallout2-ce.git
synced 2026-07-27 16:47:11 -07:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
53ebe3740c |
@@ -0,0 +1,79 @@
|
||||
#include "test_utils.h"
|
||||
|
||||
procedure start;
|
||||
|
||||
variable x, y, areaID;
|
||||
variable name, title;
|
||||
|
||||
procedure start begin
|
||||
// Tests for set_terrain_name and get_terrain_name
|
||||
// Valid coordinates
|
||||
x := 10;
|
||||
y := 20;
|
||||
name := "MyCustomTerrain";
|
||||
sfall_func3("set_terrain_name", x, y, name);
|
||||
debug_assert_string_equal(sfall_func2("get_terrain_name", x, y), name, "get_terrain_name valid coords");
|
||||
|
||||
// Test with different valid coordinates and name
|
||||
x := 0;
|
||||
y := 0;
|
||||
name := "AnotherTerrain";
|
||||
sfall_func3("set_terrain_name", x, y, name);
|
||||
debug_assert_string_equal(sfall_func2("get_terrain_name", x, y), name, "get_terrain_name valid coords 2");
|
||||
|
||||
// Test overwriting a terrain name
|
||||
name := "OverwrittenTerrain";
|
||||
sfall_func3("set_terrain_name", x, y, name);
|
||||
debug_assert_string_equal(sfall_func2("get_terrain_name", x, y), name, "get_terrain_name overwrite");
|
||||
|
||||
// Test get_terrain_name for coordinates not set - should rely on fallback in C++
|
||||
// This requires knowing what the C++ fallback returns or being able to mock it.
|
||||
// For now, we'll just check it doesn't crash and returns something.
|
||||
// A more robust test would involve setting up the game state for a known default terrain.
|
||||
debug_print("Terrain at (1000,1000): " + sfall_func2("get_terrain_name", 1000, 1000));
|
||||
|
||||
|
||||
// Test get_terrain_name without arguments (current player position)
|
||||
// This also relies on game state. We'll check it returns something.
|
||||
debug_print("Current terrain name: " + sfall_func0("get_terrain_name"));
|
||||
|
||||
|
||||
// Tests for set_town_title
|
||||
// Valid area ID
|
||||
areaID := 1; // Assuming Arroyo is Area 1 from city.txt
|
||||
title := "Arroyo Title";
|
||||
sfall_func2("set_town_title", areaID, title);
|
||||
// There's no direct sfall_func to get_town_title for assertion here.
|
||||
// This would typically be verified by observing game behavior (hovering over town).
|
||||
// For now, we ensure it doesn't crash.
|
||||
debug_print("Set town title for Area " + areaID + " to: " + title);
|
||||
|
||||
// Test clearing a town title
|
||||
title := ""; // Or pass a null equivalent if the C++ side handles it
|
||||
sfall_func2("set_town_title", areaID, title);
|
||||
debug_print("Cleared town title for Area " + areaID);
|
||||
|
||||
// Test with another area ID
|
||||
areaID := 2; // Assuming The Den
|
||||
title := "The Den's Custom Title";
|
||||
sfall_func2("set_town_title", areaID, title);
|
||||
debug_print("Set town title for Area " + areaID + " to: " + title);
|
||||
|
||||
|
||||
// Invalid coordinate tests for set_terrain_name (should trigger fatal error in C++)
|
||||
// These cannot be directly asserted in SSL if they cause a fatal error.
|
||||
// The C++ side should handle this. If it returned an error code, we could check that.
|
||||
// sfall_func3("set_terrain_name", -1, 0, "InvalidX");
|
||||
// sfall_func3("set_terrain_name", 0, -1, "InvalidY");
|
||||
|
||||
// Invalid coordinate tests for get_terrain_name (should return "Error" from C++)
|
||||
debug_assert_string_equal(sfall_func2("get_terrain_name", -1, 0), "Error", "get_terrain_name invalid X");
|
||||
debug_assert_string_equal(sfall_func2("get_terrain_name", 0, -1), "Error", "get_terrain_name invalid Y");
|
||||
|
||||
|
||||
// Invalid area ID for set_town_title (should trigger fatal error in C++)
|
||||
// sfall_func2("set_town_title", -1, "InvalidArea");
|
||||
|
||||
|
||||
debug_print("Worldmap tests completed.");
|
||||
end
|
||||
+79
-3
@@ -57,6 +57,82 @@ static void mf_string_to_case(Program* program, int args);
|
||||
static void mf_string_format(Program* program, int args);
|
||||
static void mf_floor2(Program* program, int args);
|
||||
|
||||
// New metarule function implementations
|
||||
static void mf_set_terrain_name(Program* program, int args) {
|
||||
// Args from script: x, y, name
|
||||
const char* name = programStackPopString(program);
|
||||
long y = programStackPopInteger(program);
|
||||
long x = programStackPopInteger(program);
|
||||
|
||||
if (x < 0 || y < 0) {
|
||||
programFatalError("set_terrain_name() - invalid x/y coordinates for the sub-tile. x=%ld, y=%ld", x, y);
|
||||
return; // programFatalError should halt, but return for safety
|
||||
}
|
||||
if (name == nullptr) {
|
||||
// Though programStackPopString might handle nulls, an explicit check after can be useful
|
||||
// Depending on how programStackPopString handles empty/null strings from script.
|
||||
// Assuming it can return nullptr if script provides a way to pass null.
|
||||
programFatalError("set_terrain_name() - name cannot be null.");
|
||||
return;
|
||||
}
|
||||
|
||||
wmSetTerrainTypeName(x, y, name);
|
||||
// No return value pushed to stack for void function
|
||||
}
|
||||
|
||||
static void mf_get_terrain_name(Program* program, int args) {
|
||||
const char* name = nullptr;
|
||||
if (args == 0) {
|
||||
name = wmGetCurrentTerrainName();
|
||||
if (name == nullptr) {
|
||||
programStackPushString(program, "Error");
|
||||
} else {
|
||||
programStackPushString(program, name);
|
||||
}
|
||||
} else if (args == 2) {
|
||||
// Args from script: x, y
|
||||
long y = programStackPopInteger(program);
|
||||
long x = programStackPopInteger(program);
|
||||
|
||||
if (x < 0 || y < 0) {
|
||||
programFatalError("get_terrain_name() - invalid x/y coordinates for the sub-tile. x=%ld, y=%ld", x, y);
|
||||
programStackPushString(program, "Error"); // Push "Error" as per requirement
|
||||
return;
|
||||
}
|
||||
name = wmGetTerrainTypeName(x, y);
|
||||
if (name == nullptr) {
|
||||
programStackPushString(program, "Error");
|
||||
} else {
|
||||
programStackPushString(program, name);
|
||||
}
|
||||
} else {
|
||||
// This case should ideally be caught by MetaruleInfo min/max args check in sfall_metarule dispatcher
|
||||
programFatalError("get_terrain_name: invalid number of arguments. Expected 0 or 2, got %d", args);
|
||||
programStackPushString(program, "Error"); // Push "Error" as per requirement
|
||||
}
|
||||
}
|
||||
|
||||
static void mf_set_town_title(Program* program, int args) {
|
||||
// Args from script: areaID, title
|
||||
const char* title = programStackPopString(program);
|
||||
long areaID = programStackPopInteger(program);
|
||||
|
||||
if (areaID < 0) {
|
||||
programFatalError("set_town_title: invalid area ID. areaID=%ld", areaID);
|
||||
return; // programFatalError should halt
|
||||
}
|
||||
// Allowing null title to be passed to wmSetCustomAreaTitle to potentially clear the title.
|
||||
// If null title itself is an error for this metarule, add:
|
||||
// if (title == nullptr) {
|
||||
// programFatalError("set_town_title: title cannot be null for areaID=%ld.", areaID);
|
||||
// return;
|
||||
// }
|
||||
|
||||
wmSetCustomAreaTitle(areaID, title);
|
||||
// No return value pushed to stack for void function
|
||||
}
|
||||
|
||||
|
||||
// ref. https://github.com/sfall-team/sfall/blob/42556141127895c27476cd5242a73739cbb0fade/sfall/Modules/Scripting/Handlers/Metarule.cpp#L72
|
||||
constexpr MetaruleInfo kMetarules[] = {
|
||||
// {"add_extra_msg_file", mf_add_extra_msg_file, 1, 2, -1, {ARG_STRING, ARG_INT}},
|
||||
@@ -95,7 +171,7 @@ constexpr MetaruleInfo kMetarules[] = {
|
||||
// {"get_stat_max", mf_get_stat_max, 1, 2, 0, {ARG_INT, ARG_INT}},
|
||||
// {"get_stat_min", mf_get_stat_min, 1, 2, 0, {ARG_INT, ARG_INT}},
|
||||
// {"get_string_pointer", mf_get_string_pointer, 1, 1, 0, {ARG_STRING}}, // note: deprecated; do not implement
|
||||
// {"get_terrain_name", mf_get_terrain_name, 0, 2, -1, {ARG_INT, ARG_INT}},
|
||||
{ "get_terrain_name", mf_get_terrain_name, 0, 2 },
|
||||
{ "get_text_width", mf_get_text_width, 1, 1 },
|
||||
// {"get_window_attribute", mf_get_window_attribute, 1, 2, -1, {ARG_INT, ARG_INT}},
|
||||
// {"has_fake_perk_npc", mf_has_fake_perk_npc, 2, 2, 0, {ARG_OBJECT, ARG_STRING}},
|
||||
@@ -144,8 +220,8 @@ constexpr MetaruleInfo kMetarules[] = {
|
||||
// {"set_rest_mode", mf_set_rest_mode, 1, 1, -1, {ARG_INT}},
|
||||
// {"set_scr_name", mf_set_scr_name, 0, 1, -1, {ARG_STRING}},
|
||||
// {"set_selectable_perk_npc", mf_set_selectable_perk_npc, 5, 5, -1, {ARG_OBJECT, ARG_STRING, ARG_INT, ARG_INT, ARG_STRING}},
|
||||
// {"set_terrain_name", mf_set_terrain_name, 3, 3, -1, {ARG_INT, ARG_INT, ARG_STRING}},
|
||||
// {"set_town_title", mf_set_town_title, 2, 2, -1, {ARG_INT, ARG_STRING}},
|
||||
{ "set_terrain_name", mf_set_terrain_name, 3, 3 },
|
||||
{ "set_town_title", mf_set_town_title, 2, 2 },
|
||||
// {"set_unique_id", mf_set_unique_id, 1, 2, -1, {ARG_OBJECT, ARG_INT}},
|
||||
// {"set_unjam_locks_time", mf_set_unjam_locks_time, 1, 1, -1, {ARG_INT}},
|
||||
// {"set_window_flag", mf_set_window_flag, 3, 3, -1, {ARG_INTSTR, ARG_INT, ARG_INT}},
|
||||
|
||||
@@ -9,6 +9,11 @@ void sfall_metarule(Program* program, int args);
|
||||
|
||||
void sprintf_lite(Program* program, int args, const char* infoOpcodeName);
|
||||
|
||||
// New static function declarations for metarules
|
||||
static void mf_set_terrain_name(Program* program, int args);
|
||||
static void mf_get_terrain_name(Program* program, int args);
|
||||
static void mf_set_town_title(Program* program, int args);
|
||||
|
||||
} // namespace fallout
|
||||
|
||||
#endif /* FALLOUT_SFALL_METARULES_H_ */
|
||||
|
||||
@@ -817,6 +817,10 @@ static int wmMaxEncBaseTypes;
|
||||
// 0x67303C
|
||||
static int wmMaxEncounterInfoTables;
|
||||
|
||||
// Definitions for new static global variables
|
||||
static std::vector<std::pair<long, std::string>> wmTerrainTypeNames;
|
||||
static std::unordered_map<long, std::string> wmAreaHotSpotTitle;
|
||||
|
||||
static bool gTownMapHotkeysFix;
|
||||
static double gGameTimeIncRemainder = 0.0;
|
||||
static FrmImage _backgroundFrmImage;
|
||||
@@ -1059,6 +1063,10 @@ int wmWorldMap_reset()
|
||||
wmWorldMapLoadTempData();
|
||||
wmMarkAllSubTiles(0);
|
||||
|
||||
// Clear new global variables
|
||||
wmTerrainTypeNames.clear();
|
||||
wmAreaHotSpotTitle.clear();
|
||||
|
||||
return wmGenDataReset();
|
||||
}
|
||||
|
||||
@@ -6672,4 +6680,87 @@ void wmForceEncounter(int map, unsigned int flags)
|
||||
}
|
||||
}
|
||||
|
||||
// Implementation of new functions
|
||||
|
||||
void wmSetTerrainTypeName(long x, long y, const char* name) {
|
||||
// x and y are subtile indices
|
||||
long subTileID = x + y * (wmNumHorizontalTiles * SUBTILE_GRID_WIDTH);
|
||||
wmTerrainTypeNames.push_back({subTileID, name});
|
||||
}
|
||||
|
||||
const char* wmGetTerrainTypeName(long x, long y) {
|
||||
// x and y are subtile indices
|
||||
long subTileID = x + y * (wmNumHorizontalTiles * SUBTILE_GRID_WIDTH);
|
||||
|
||||
for (auto it = wmTerrainTypeNames.crbegin(); it != wmTerrainTypeNames.crend(); ++it) {
|
||||
if (it->first == subTileID) {
|
||||
return it->second.c_str();
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to default terrain name
|
||||
SubtileInfo* subtile = nullptr;
|
||||
// Convert subtile indices (x, y) to world coordinates for wmFindCurSubTileFromPos
|
||||
int worldX = x * WM_SUBTILE_SIZE;
|
||||
int worldY = y * WM_SUBTILE_SIZE;
|
||||
|
||||
// Need to find the correct tile first to pass to wmFindCurSubTileFromPos,
|
||||
// or rather, wmFindCurSubTileFromPos might be enough if its x and y are world coordinates.
|
||||
// wmFindCurSubTileFromPos expects world coordinates.
|
||||
if (wmFindCurSubTileFromPos(worldX, worldY, &subtile) == 0 && subtile != nullptr) {
|
||||
int terrainId = subtile->terrain;
|
||||
// Ensure gWorldmapMessageListItem is properly handled or declare a local one if needed for getmsg
|
||||
MessageListItem item; // Using a local item for safety if gWorldmapMessageListItem has shared state concerns
|
||||
return getmsg(&wmMsgFile, &item, 1000 + terrainId)->text;
|
||||
}
|
||||
return "Unknown Terrain"; // Absolute fallback
|
||||
}
|
||||
|
||||
const char* wmGetCurrentTerrainName() {
|
||||
long subTileX = wmGenData.worldPosX / WM_SUBTILE_SIZE;
|
||||
long subTileY = wmGenData.worldPosY / WM_SUBTILE_SIZE;
|
||||
long subTileID = subTileX + subTileY * (wmNumHorizontalTiles * SUBTILE_GRID_WIDTH);
|
||||
|
||||
for (auto it = wmTerrainTypeNames.crbegin(); it != wmTerrainTypeNames.crend(); ++it) {
|
||||
if (it->first == subTileID) {
|
||||
return it->second.c_str();
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to default current terrain name
|
||||
int terrainId = -1;
|
||||
if (wmGenData.currentSubtile != nullptr) {
|
||||
terrainId = wmGenData.currentSubtile->terrain;
|
||||
} else {
|
||||
SubtileInfo* currentSubtilePtr = nullptr;
|
||||
if (wmFindCurSubTileFromPos(wmGenData.worldPosX, wmGenData.worldPosY, ¤tSubtilePtr) == 0 && currentSubtilePtr != nullptr) {
|
||||
wmGenData.currentSubtile = currentSubtilePtr; // Cache it
|
||||
terrainId = currentSubtilePtr->terrain;
|
||||
}
|
||||
}
|
||||
|
||||
if (terrainId != -1) {
|
||||
MessageListItem item; // Using a local item
|
||||
return getmsg(&wmMsgFile, &item, 1000 + terrainId)->text;
|
||||
}
|
||||
return "Unknown Current Terrain"; // Absolute fallback
|
||||
}
|
||||
|
||||
void wmSetCustomAreaTitle(long areaID, const char* title) {
|
||||
if (title != nullptr) {
|
||||
wmAreaHotSpotTitle[areaID] = title;
|
||||
} else {
|
||||
// Option: remove the entry if title is null, or store empty string
|
||||
wmAreaHotSpotTitle.erase(areaID);
|
||||
}
|
||||
}
|
||||
|
||||
const char* wmGetCustomAreaTitle(long areaID) {
|
||||
auto it = wmAreaHotSpotTitle.find(areaID);
|
||||
if (it != wmAreaHotSpotTitle.end()) {
|
||||
return it->second.c_str();
|
||||
}
|
||||
return nullptr; // Or an empty string
|
||||
}
|
||||
|
||||
} // namespace fallout
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
#define WORLD_MAP_H
|
||||
|
||||
#include "db.h"
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace fallout {
|
||||
|
||||
@@ -237,6 +240,10 @@ typedef enum Map {
|
||||
|
||||
extern unsigned char* circleBlendTable;
|
||||
|
||||
// Static global variables
|
||||
static std::vector<std::pair<long, std::string>> wmTerrainTypeNames;
|
||||
static std::unordered_map<long, std::string> wmAreaHotSpotTitle;
|
||||
|
||||
int wmWorldMap_init();
|
||||
void wmWorldMap_exit();
|
||||
int wmWorldMap_reset();
|
||||
@@ -287,6 +294,13 @@ void wmSetPartyWorldPos(int x, int y);
|
||||
void wmCarSetCurrentArea(int area);
|
||||
void wmForceEncounter(int map, unsigned int flags);
|
||||
|
||||
// New function declarations
|
||||
void wmSetTerrainTypeName(long x, long y, const char* name);
|
||||
const char* wmGetTerrainTypeName(long x, long y);
|
||||
const char* wmGetCurrentTerrainName();
|
||||
void wmSetCustomAreaTitle(long areaID, const char* title);
|
||||
const char* wmGetCustomAreaTitle(long areaID);
|
||||
|
||||
} // namespace fallout
|
||||
|
||||
#endif /* WORLD_MAP_H */
|
||||
|
||||
Reference in New Issue
Block a user