From 1680e885f2ea22150bbcd430499a9eb1547fa8d5 Mon Sep 17 00:00:00 2001 From: Mike Klaas Date: Wed, 8 Apr 2026 20:52:02 -0700 Subject: [PATCH] Implement obj_is_carrying_obj Sfall opcode (#362) Note: does not include the sfall fix to fix stack counts --- SFALL_COMPATIBILITY.md | 10 ++++---- sfall_testing/gl_test_inventory.ssl | 38 +++++++++++++++++++++++++++++ src/sfall_opcodes.cc | 29 ++++++++++++++++++++++ 3 files changed, 72 insertions(+), 5 deletions(-) create mode 100644 sfall_testing/gl_test_inventory.ssl diff --git a/SFALL_COMPATIBILITY.md b/SFALL_COMPATIBILITY.md index b65e58bc..32acda14 100644 --- a/SFALL_COMPATIBILITY.md +++ b/SFALL_COMPATIBILITY.md @@ -32,10 +32,10 @@ See [`https://sfall-team.github.io/sfall/`](https://sfall-team.github.io/sfall/) | Art and appearance | art_exists
refresh_pc_art
art_cache_clear
set_hero_race
set_hero_style | implemented: art_exists, refresh_pc_art, art_cache_clear | - | | Tiles and paths | get_tile_fid
tile_under_cursor
tile_light
tile_get_objs
tile_refresh_display
obj_blocking_tile
tile_by_position
get_tile_ground_fid
get_tile_roof_fid
obj_blocking_line
path_find_to
objects_in_radius | ✅ except objects_in_radius | - | | Utility | sprintf
typeof
atoi
atof | ✅ | - | -| Utility / Strings | string_split
substr
strlen
charcode
get_string_pointer
string_find
string_find_from
string_format
string_format_array
string_replace
string_to_case
string_compare | ✅ except get_string_pointer | `get_string_pointer` is deprecated and intentionally omitted. | -| Interface / Tags | show_iface_tag
hide_iface_tag
is_iface_tag_active
set_iface_tag_text
add_iface_tag | implemented: all 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
get_sfall_global_int
get_sfall_global_float | implemented: all except get_sfall_global_float | Current CE storage is int-backed; `set_sfall_global` stores integer values and there is no float getter yet. | -| Hooks / Hook functions | init_hook
get_sfall_arg
get_sfall_args
get_sfall_arg_at
set_sfall_return
set_sfall_arg
register_hook
register_hook_proc
register_hook_proc_spec | ✅ except init_hook | 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. | +| Utility / Strings | string_split
substr
strlen
charcode
get_string_pointer
string_find
string_find_from
string_format
string_format_array
string_replace
string_to_case
string_compare | ✅ | `get_string_pointer` is deprecated and intentionally omitted. | +| Interface / Tags | show_iface_tag
hide_iface_tag
is_iface_tag_active
set_iface_tag_text
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
get_sfall_global_int
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
get_sfall_arg
get_sfall_args
get_sfall_arg_at
set_sfall_return
set_sfall_arg
register_hook
register_hook_proc
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
temp_array
fix_array
get/set_array
resize_array
free_array
scan_array
len_array
save/load_array
array_key
arrayexpr | ✅ except save_array, load_array | - | | Perks and traits / NPC perks | set_fake_perk_npc
set_fake_trait_npc
set_selectable_perk_npc
has_fake_perk_npc
has_fake_trait_npc | not implemented | - | | Global scripts / Global script functions | set_global_script_repeat
set_global_script_type
available_global_script_types | implemented: all except available_global_script_types | - | @@ -48,7 +48,7 @@ See [`https://sfall-team.github.io/sfall/`](https://sfall-team.github.io/sfall/) | Interface / Cursor | get/set_cursor_mode | ✅ | - | | Locks | lock_is_jammed
unjam_lock
set_unjam_locks_time | not implemented | - | | INI settings | modified_ini
get_ini_setting
get_ini_string
get_ini_section
get_ini_sections
get_ini_config
get_ini_config_db
set_ini_setting | ✅ except modified_ini, get_ini_config, get_ini_config_db | `modified_ini` is intentionally omitted as deprecated. | -| Objects and scripts | set_self
set_dude_obj
real_dude_obj
remove_script
set_script
get_script
obj_is_carrying_obj
loot_obj
dialog_obj
obj_under_cursor
get_object_data
set_object_data
get_flags
set_flags
set_unique_id
set_scr_name
obj_is_openable
get/set_proto_data
get_object_ai_data | implemented: set_self, get_script, loot_obj, dialog_obj, obj_under_cursor, get_object_data, get_flags, set_flags, get_proto_data, set_proto_data | - | +| Objects and scripts | set_self
set_dude_obj
real_dude_obj
remove_script
set_script
get_script
obj_is_carrying_obj
loot_obj
dialog_obj
obj_under_cursor
get_object_data
set_object_data
get_flags
set_flags
set_unique_id
set_scr_name
obj_is_openable
get/set_proto_data
get_object_ai_data | implemented: set_self, get_script, obj_is_carrying_obj, loot_obj, dialog_obj, obj_under_cursor, get_object_data, get_flags, set_flags, get_proto_data, set_proto_data | - | | Other / Game management | set_movie_path
stop_game
resume_game
mark_movie_played
game_loaded
get_game_mode
get_uptime
signal_close_game | implemented: game_loaded, get_game_mode, get_uptime | - | | Other | input_funcs_available
get_year
set_dm_model
set_df_model
set_pipboy_available
get_kill_counter
mod_kill_counter
active_hand
toggle_active_hand
set_pickpocket_max
set_hit_chance_max
set_xp_mod
set_critter_hit_chance_mod
set_base_hit_chance_mod
inc_npc_level
get_npc_level
get/set_viewport_x/y
set_hp_per_level_mod
get/set_unspent_ap_bonus
get/set_unspent_ap_perk_bonus
hero_select_win
create_message_window
get_light_level
gdialog_get_barter_mod
set_inven_ap_cost
set_base_pickpocket_mod
set_critter_pickpocket_mod
message_str_game
sneak_success
create_spatial
unwield_slot
get_inven_ap_cost
add_g_timer_event
add_extra_msg_file
get_metarule_table
metarule_exist
npc_engine_level_up
remove_timer_event
set_drugs_data
spatial_radius | implemented: get_year, active_hand, toggle_active_hand, create_message_window, message_str_game, add_extra_msg_file, metarule_exist | `input_funcs_available`, `nb_create_char` are deprecated in sfall and intentionally absent in CE. `add_extra_msg_file` does not support the explicit `fileNumber` form in CE. | diff --git a/sfall_testing/gl_test_inventory.ssl b/sfall_testing/gl_test_inventory.ssl new file mode 100644 index 00000000..a8ace6d1 --- /dev/null +++ b/sfall_testing/gl_test_inventory.ssl @@ -0,0 +1,38 @@ +#include "define_lite.h" +#include "sfall.h" +#include "test_utils.h" + +#define TEST_ITEM_PID 40 + +procedure start begin + variable item_a; + variable item_b; + variable item_c; + variable pid_total; + + display_msg("Testing inventory functions..."); + + item_a := create_object_sid(TEST_ITEM_PID, 0, 0, -1); + item_b := create_object_sid(TEST_ITEM_PID, 0, 0, -1); + item_c := create_object_sid(TEST_ITEM_PID, 0, 0, -1); + + add_obj_to_inven(dude_obj, item_a); + call assertEquals("single carried object count", obj_is_carrying_obj(dude_obj, item_a), 1); + add_obj_to_inven(dude_obj, item_b); + + call assertEquals("merged stack no longer matches original object pointer", obj_is_carrying_obj(dude_obj, item_a), 0); + call assertEquals("merged stack matches replacement object pointer", obj_is_carrying_obj(dude_obj, item_b), 2); + + pid_total := obj_is_carrying_obj_pid(dude_obj, TEST_ITEM_PID); + call assertTrue("pid total covers both stacks", pid_total >= 2); + call assertEquals("pid total matches merged carried stack quantity", pid_total, obj_is_carrying_obj(dude_obj, item_b)); + + call assertEquals("object not in inventory returns zero", obj_is_carrying_obj(dude_obj, item_c), 0); + call assertEquals("null inventory object returns zero", obj_is_carrying_obj(0, item_a), 0); + call assertEquals("null item object returns zero", obj_is_carrying_obj(dude_obj, 0), 0); + + call assertEquals("remove merged stack quantity", rm_mult_objs_from_inven(dude_obj, item_b, 2), 2); + destroy_object(item_c); + + call report_test_results("inventory"); +end diff --git a/src/sfall_opcodes.cc b/src/sfall_opcodes.cc index 4130260a..b4975d70 100644 --- a/src/sfall_opcodes.cc +++ b/src/sfall_opcodes.cc @@ -64,6 +64,34 @@ static void op_art_exists(Program* program) programStackPushInteger(program, artExists(fid)); } +static void op_obj_is_carrying_obj(Program* program) +{ + Object* itemObj = static_cast(programStackPopPointer(program)); + Object* invenObj = static_cast(programStackPopPointer(program)); + + int count = 0; + if (invenObj != nullptr && itemObj != nullptr) { + Inventory* inventory = &(invenObj->data.inventory); + for (int index = 0; index < inventory->length; index++) { + InventoryItem* inventoryItem = &(inventory->items[index]); + if (inventoryItem->item == itemObj) { + if (inventoryItem->quantity <= 0) { + debugPrint("%s: obj_is_carrying_obj found non-positive inventory quantity for item %p in owner %p", + program->name, + itemObj, + invenObj); + count = 1; + } else { + count = inventoryItem->quantity; + } + break; + } + } + } + + programStackPushInteger(program, count); +} + // read_byte static void op_read_byte(Program* program) { @@ -1948,6 +1976,7 @@ void sfallOpcodesInit() // 0x8274 - int art_exists(int artFID) interpreterRegisterOpcode(0x8274, op_art_exists); // 0x8275 - int obj_is_carrying_obj(object invenObj, object itemObj) + interpreterRegisterOpcode(0x8275, op_obj_is_carrying_obj); // 0x8276 - any sfall_func0(string funcName) interpreterRegisterOpcode(0x8276, op_sfall_func0);