[Et tu] get/set_terrain_name (#593)

* [Et tu] get/set_terrain_name
This commit is contained in:
Mike Klaas
2026-07-24 21:42:30 -07:00
committed by GitHub
parent 7604061e1c
commit dd21129464
5 changed files with 159 additions and 3 deletions
+1 -1
View File
@@ -59,7 +59,7 @@ See [`https://sfall-team.github.io/sfall/`](https://sfall-team.github.io/sfall/)
| Perks and traits | set_perk_image<br>set_perk_*<br>set_pyromaniac_mod<br>apply_heaveho_fix<br>set_swiftlearner_mod<br>has/set_fake_perk<br>has/set_fake_trait<br>set_selectable_perk<br>set_perkbox_title<br>show/hide_real_perks<br>perk_add_mode<br>clear_selectable_perks<br>add/remove_trait<br>seq_perk_freq | not implemented | - |
| Virtual file system | fs_create<br>fs_copy<br>fs_find<br>fs_read/write_*<br>fs_delete<br>fs_size<br>fs_pos<br>fs_seek<br>fs_resize | 🚫 | Open an issue if you have a use case for these |
| Combat / Knockback | set_weapon_knockback<br>set_target_knockback<br>set_attacker_knockback<br>remove_weapon_knockback<br>remove_target_knockback<br>remove_attacker_knockback | not implemented | - |
| Maps and encounters | in_world_map<br>force_encounter{_with_flags}<br>set_map_time_multi<br>get/set_map_enter_position<br>exec_map_update_scripts<br>get/set_terrain_name<br>set_town_title<br>get/set_can_rest_on_map<br>set_rest_heal_time<br>set_rest_mode<br>set_worldmap_heal_time | implemented: in_world_map, force_encounter, force_encounter_with_flags, set_map_time_multi | - |
| Maps and encounters | in_world_map<br>force_encounter{_with_flags}<br>set_map_time_multi<br>get/set_map_enter_position<br>exec_map_update_scripts<br>get/set_terrain_name<br>set_town_title<br>get/set_can_rest_on_map<br>set_rest_heal_time<br>set_rest_mode<br>set_worldmap_heal_time | implemented: in_world_map, force_encounter, force_encounter_with_flags, set_map_time_multi, get_terrain_name, set_terrain_name | - |
| Maps and encounters / Worldmap | get_world_map_x/y_pos<br>set_world_map_pos | ✅ | - |
| Audio | play_sfall_sound<br>stop_sfall_sound | ✅ | `play_sfall_sound` currently supports `.acm`, `.wav`, `.ogg` formats, and can load from `.dat` archives. `.mp3` is not yet supported. |
| Combat / Weapons and ammo | get/set_weapon_ammo_pid<br>get/set_weapon_ammo_count | ✅ | - |
+27
View File
@@ -0,0 +1,27 @@
#include "test_utils.h"
#include "sfall.h"
procedure start begin
variable defaultName;
display_msg("Testing terrain name metarules...");
call assertEquals("get metarule exists", metarule_exist("get_terrain_name"), 1);
call assertEquals("set metarule exists", metarule_exist("set_terrain_name"), 1);
defaultName := get_terrain_name(0, 0);
call assertNotEquals("default terrain name is not error", defaultName, "Error");
call assertNotEquals("default terrain name is not empty", defaultName, "");
set_terrain_name(0, 0, "CE Terrain Override");
call assertEquals("terrain override is returned", get_terrain_name(0, 0), "CE Terrain Override");
set_terrain_name(0, 0, "CE Terrain Override 2");
call assertEquals("latest terrain override wins", get_terrain_name(0, 0), "CE Terrain Override 2");
call assertEquals("one argument get returns error", sfall_func1("get_terrain_name", 0), -1);
call assertEquals("invalid get returns error", get_terrain_name(-1, 0), "Error");
call assertNotEquals("current terrain name is available", get_current_terrain_name, "Error");
call report_test_results("terrain_name");
signal_close_game;
end
+44 -2
View File
@@ -779,6 +779,7 @@ static void mf_get_object_ai_data(OpcodeContext& ctx);
static void mf_get_object_data(OpcodeContext& ctx);
static void mf_get_outline(OpcodeContext& ctx);
static void mf_get_sfall_arg_at(OpcodeContext& ctx);
static void mf_get_terrain_name(OpcodeContext& ctx);
static void mf_get_text_width(OpcodeContext& ctx);
static void mf_get_window_attribute(OpcodeContext& ctx);
static void mf_hide_window(OpcodeContext& ctx);
@@ -805,6 +806,7 @@ static void mf_set_flags(OpcodeContext& ctx);
static void mf_set_iface_tag_text(OpcodeContext& ctx);
static void mf_set_object_data(OpcodeContext& ctx);
static void mf_set_outline(OpcodeContext& ctx);
static void mf_set_terrain_name(OpcodeContext& ctx);
static void mf_set_window_flag(OpcodeContext& ctx);
static void mf_set_unique_id(OpcodeContext& ctx);
static void mf_show_window(OpcodeContext& ctx);
@@ -859,7 +861,7 @@ const 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, -1, { ARG_INT, ARG_INT } },
{ "get_text_width", mf_get_text_width, 1, 1, 0, { ARG_STRING } },
{ "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}},
@@ -908,7 +910,7 @@ const 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_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_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}},
@@ -1984,6 +1986,46 @@ void mf_unwield_slot(OpcodeContext& ctx)
}
}
static const char* invalidSubtilePos = "%s() - invalid x/y coordinates for the sub-tile.";
static void mf_get_terrain_name(OpcodeContext& ctx)
{
if (ctx.numArgs() == 0) {
ctx.setReturn(wmGetCurrentTerrainName());
return;
}
if (ctx.numArgs() == 1) {
ctx.printError("%s() - invalid number of arguments (%d), must be 0 or 2.", ctx.name(), ctx.numArgs());
ctx.setReturn(ctx.metaruleInfo()->errorReturn);
return;
}
int x = ctx.arg(0).asInt();
int y = ctx.arg(1).asInt();
if (!wmTerrainNameIsValidSubtile(x, y)) {
ctx.printError(invalidSubtilePos, ctx.name());
ctx.setReturn("Error");
return;
}
ctx.setReturn(wmGetTerrainName(x, y));
}
static void mf_set_terrain_name(OpcodeContext& ctx)
{
int x = ctx.arg(0).asInt();
int y = ctx.arg(1).asInt();
if (!wmTerrainNameIsValidSubtile(x, y)) {
ctx.printError(invalidSubtilePos, ctx.name());
return;
}
wmSetTerrainName(x, y, ctx.stringArg(2));
}
void mf_tile_by_position(OpcodeContext& ctx)
{
int x = ctx.arg(0).asInt();
+83
View File
@@ -7,6 +7,9 @@
#include <string.h>
#include <algorithm>
#include <string>
#include <utility>
#include <vector>
#include "animation.h"
#include "art.h"
@@ -472,6 +475,8 @@ void wmSetScriptWorldMapMulti(float value)
gScriptWorldMapMulti = value;
}
static std::vector<std::pair<int, std::string>> wmTerrainNameOverrides;
static void wmSetFlags(int* flagsPtr, int flag, int value);
static int wmGenDataInit();
static int wmGenDataReset();
@@ -822,6 +827,80 @@ static WmGenData wmGenData;
// 0x672FB0 wmMsgFile
static MessageList wmMsgFile;
static bool wmSubtileCoordsValid(int x, int y)
{
if (wmNumHorizontalTiles <= 0 || wmMaxTileNum <= 0) {
return false;
}
return x >= 0
&& x < SUBTILE_GRID_WIDTH * wmNumHorizontalTiles
&& y >= 0
&& y < SUBTILE_GRID_HEIGHT * (wmMaxTileNum / wmNumHorizontalTiles);
}
static int wmSubtileNameOverrideKey(int x, int y)
{
return x + y * (wmNumHorizontalTiles * SUBTILE_GRID_WIDTH);
}
static const char* wmGetTerrainNameOverride(int x, int y)
{
if (wmTerrainNameOverrides.empty()) {
return nullptr;
}
const int key = wmSubtileNameOverrideKey(x, y);
auto it = std::find_if(wmTerrainNameOverrides.crbegin(), wmTerrainNameOverrides.crend(),
[key](const std::pair<int, std::string>& terrainNameOverride) {
return terrainNameOverride.first == key;
});
return it != wmTerrainNameOverrides.crend() ? it->second.c_str() : nullptr;
}
bool wmTerrainNameIsValidSubtile(int x, int y)
{
return wmSubtileCoordsValid(x, y);
}
void wmSetTerrainName(int x, int y, const char* name)
{
assert(wmSubtileCoordsValid(x, y));
assert(name != nullptr);
wmTerrainNameOverrides.emplace_back(wmSubtileNameOverrideKey(x, y), name);
}
const char* wmGetTerrainName(int x, int y)
{
assert(wmSubtileCoordsValid(x, y));
const char* override = wmGetTerrainNameOverride(x, y);
if (override != nullptr) {
return override;
}
SubtileInfo* subtile;
if (wmFindCurSubTileFromPos(x * WM_SUBTILE_SIZE, y * WM_SUBTILE_SIZE, &subtile) == -1) {
return "Error";
}
MessageListItem messageListItem;
return getmsg(&wmMsgFile, &messageListItem, 1000 + subtile->terrain);
}
const char* wmGetCurrentTerrainName()
{
int x = wmGenData.worldPosX / WM_SUBTILE_SIZE;
int y = wmGenData.worldPosY / WM_SUBTILE_SIZE;
if (!wmSubtileCoordsValid(x, y)) {
return "Error";
}
return wmGetTerrainName(x, y);
}
// 0x672FB8 wmFreqValues
static int wmFreqValues[6];
@@ -975,6 +1054,7 @@ static int wmGenDataInit()
wmForceEncounterMapId = -1;
wmForceEncounterFlags = 0;
wmTerrainNameOverrides.clear();
return 0;
}
@@ -1028,6 +1108,7 @@ static int wmGenDataReset()
wmForceEncounterMapId = -1;
wmForceEncounterFlags = 0;
wmTerrainNameOverrides.clear();
return 0;
}
@@ -1035,6 +1116,8 @@ static int wmGenDataReset()
// 0x4BCE00 wmWorldMap_exit
void wmWorldMap_exit()
{
wmTerrainNameOverrides.clear();
if (wmTerrainTypeList != nullptr) {
internal_free(wmTerrainTypeList);
wmTerrainTypeList = nullptr;
+4
View File
@@ -289,6 +289,10 @@ void wmSetPartyWorldPos(int x, int y);
void wmCarSetCurrentArea(int area);
void wmForceEncounter(int map, unsigned int flags);
void wmSetScriptWorldMapMulti(float value);
bool wmTerrainNameIsValidSubtile(int x, int y);
void wmSetTerrainName(int x, int y, const char* name);
const char* wmGetTerrainName(int x, int y);
const char* wmGetCurrentTerrainName();
int worldmapGetWindow();
} // namespace fallout