Compare commits

..
Author SHA1 Message Date
Mike Klaas 9e3e94ffd9 PR findings 2026-06-21 22:05:31 -07:00
github-actions[bot] 25e006cffd chore: auto-format with clang-format 2026-06-22 04:56:15 +00:00
Mike Klaas 3edd00d85f clarify which are transient 2026-06-21 21:53:16 -07:00
Mike Klaas 2bfa10411c better cleaup 2026-06-21 21:43:39 -07:00
Mike Klaas a9eddc1705 Expose transient message lists.
Some message lists are only loaded while a given screen is active.  Sfall still lets scripts access them, though scripts need to be careful to only do so when the windows are active.
2026-06-21 21:22:35 -07:00
Mike Klaas 26d7b632fc HOOK_MOVECOST (#496) 2026-06-17 06:11:08 +00:00
c6818f0849 Fix ddraw.ini -> game.cfg migration on unix-y systems (#497)
* Fix ddraw.ini -> game.cfg migration on unix-y systems

We were using `compat_splitpath` on a windows path.  We want to work on windows paths, so that's good.  This fixes compat_splitpath so that it works with both folder separators.  Also tweak `compat_makepath` to recognize both, for consistency.

This means that filenames with `\` will be broken, but I think we can (and should) live with that)

* Apply suggestions from code review

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* chore: auto-format with clang-format

* fix

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-06-16 22:48:46 -07:00
Mike Klaas 84cac43da1 HOOK_ITEMDAMAGE (#494)
* HOOK_ITEMDAMAGE
2026-06-16 22:22:53 -07:00
Mike Klaas 261b54c603 Implement unwield_slot (#488)
* Implement `unwield_slot`

This one was surprisingly subtle.  Supports many game modes and handles things differently depending on game mode.
2026-06-08 22:25:46 -07:00
Vlad K 46b89abbd8 Merge pull request #490 from fallout2-ce/feature/mapper-edg
Mapper EDG support
2026-06-08 23:17:25 +02:00
phobos2077 1ee64988bc Guard against saving EDG when saving SAV maps in gameplay 2026-06-07 20:53:35 +02:00
Vlad KandCopilot Autofix powered by AI c78ba885d7 Guard against using dev_path out of mapper
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-06-07 20:44:34 +02:00
Vlad K 8c98a15ef6 sfall: get_ini_config + config cache (#489)
* sfall: get_ini_config + config cache

* Code review fixes and comment standardization
2026-06-06 22:34:37 +00:00
63 changed files with 1099 additions and 3698 deletions
-2
View File
@@ -614,8 +614,6 @@ if((NOT ANDROID) AND (NOT IOS) AND (NOT CMAKE_SYSTEM_NAME MATCHES "Emscripten"))
target_sources(mapper-ce PUBLIC
${FALLOUT_ENGINE_SOURCES}
${FALLOUT_PLATFORM_SOURCES}
"src/proto_txt.cc"
"src/proto_txt.h"
"src/mapper/map_edge_setup.cc"
"src/mapper/map_edge_setup.h"
"src/mapper/map_func.cc"
+6 -4
View File
@@ -81,12 +81,14 @@ See [`https://sfall-team.github.io/sfall/`](https://sfall-team.github.io/sfall/)
| Interface / Inventory | display_stats<br>inventory_redraw<br>critter_inven_obj2<br>get_current_inven_size<br>item_weight | implemented: critter_inven_obj2, item_weight | - |
| Interface / Cursor | get/set_cursor_mode | âś… | - |
| Locks | lock_is_jammed<br>unjam_lock<br>set_unjam_locks_time | not implemented | - |
| INI settings | get_ini_setting<br>get_ini_string<br>get_ini_section<br>get_ini_sections<br>get_ini_config<br>get_ini_config_db<br>set_ini_setting | âś… except get_ini_config, get_ini_config_db | `modified_ini` is intentionally omitted as deprecated. |
| INI settings | get_ini_setting<br>get_ini_string<br>get_ini_section<br>get_ini_sections<br>get_ini_config<br>get_ini_config_db<br>set_ini_setting | âś… | `modified_ini` is intentionally omitted as deprecated. |
| Objects and scripts | set_self<br>set_dude_obj<br>real_dude_obj<br>remove_script<br>get/set_script<br>obj_is_carrying_obj<br>loot_obj<br>dialog_obj<br>obj_under_cursor<br>get/set_object_data<br>get/set_flags<br>set_unique_id<br>set_scr_name<br>obj_is_openable<br>get/set_proto_data<br>get_object_ai_data | implemented: set_self, get/set/remove_script, obj_is_carrying_obj, loot_obj, dialog_obj, obj_under_cursor, get_object_data, get_flags, set_flags, set_unique_id, obj_is_openable, get_proto_data, set_proto_data | - |
| Other / Game management | set_movie_path<br>stop/resume_game<br>mark_movie_played<br>game_loaded<br>get_game_mode<br>get_uptime<br>signal_close_game | implemented: game_loaded, get_game_mode, get_uptime, signal_close_game | - |
| Gameplay tweaks | set_pickpocket_max<br>set_hit_chance_max<br>set_xp_mod<br>set_critter_hit_chance_mod<br>set_base_hit_chance_mod<br>set_hp_per_level_mod<br>gdialog_get_barter_mod<br>get/set_unspent_ap_bonus<br>get/set_unspent_ap_perk_bonus<br>set_base_pickpocket_mod<br>set_critter_pickpocket_mod<br>get/set_inven_ap_cost<br>set_drugs_data<br>get_kill_counter<br>mod_kill_counter<br>set_pipboy_available | implemented: gdialog_get_barter_mod, get/set_unspent_ap{_perk}_bonus, get/set_inven_ap_cost | - |
| NPCs | inc_npc_level<br>get_npc_level<br>npc_engine_level_up | not implemented | - |
| Other | get_year<br>set_dm/df_model<br>active_hand<br>toggle_active_hand<br>get/set_viewport_x/y<br>hero_select_win<br>get_light_level<br>message_str_game<br>sneak_success<br>create_spatial<br>unwield_slot<br>add_g_timer_event<br>add_extra_msg_file<br>get_metarule_table<br>metarule_exist<br>remove_timer_event<br>spatial_radius | implemented: get_year, active_hand, toggle_active_hand, get_light_level, 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. |
| Hero Appearange | set_dm/df_model<br>hero_select_win | not implemented | - |
| Events | add_g_timer_event<br>remove_timer_event<br>create_spatial<br>spatial_radius | not implemented | - |
| Other | get_year<br>active_hand<br>toggle_active_hand<br>get/set_viewport_x/y<br>get_light_level<br>message_str_game<br>sneak_success<br>unwield_slot<br>add_extra_msg_file<br>get_metarule_table<br>metarule_exist<br> | âś… except get/set_viewport_x/y, sneak_success | `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. |
## Hooks
@@ -104,8 +106,8 @@ See [`https://sfall-team.github.io/sfall/`](https://sfall-team.github.io/sfall/)
| UseObj | `HOOK_USEOBJ` | âś… | CE notes an sfall-matching inconsistency around return code `2` behavior between interface contexts. |
| RemoveInvenObj | `HOOK_REMOVEINVENOBJ` | đźš« | - |
| BarterPrice | `HOOK_BARTERPRICE` | âś… | - |
| MoveCost | `HOOK_MOVECOST` | đźš« | - |
| ItemDamage | `HOOK_ITEMDAMAGE` | đźš« | - |
| ItemDamage | `HOOK_ITEMDAMAGE` | âś… | - |
| MoveCost | `HOOK_MOVECOST` | âś… | - |
| AmmoCost | `HOOK_AMMOCOST` | âś… | Requires `check_weapon_ammo_cost=1` if you want pre-attack ammo validation to respect per-shot/per-round overrides. |
| KeyPress | `HOOK_KEYPRESS` | âś… | Third hook arg is currently `0`; CE doesn't use VK codes. |
| MouseClick | `HOOK_MOUSECLICK` | âś… | - |
+6
View File
@@ -65,6 +65,12 @@ procedure ini_test_suite begin
call assertEquals("TC7 get_ini_sections size", len_array(result_array), 3);
call assertTrue("TC7 get_ini_sections", scan_array(result_array, "TestMisc") >= 0);
// get_ini_config
result_array := get_ini_config_db("config\\config_from_db.cfg");
call assertEquals("TC8 get_ini_config_db size", len_array(result_array), 2);
call assertTrue("TC8 get_ini_config ValidSection", result_array.ValidSection != 0);
call assertEquals("TC8 get_ini_config_db ValidValue", result_array.ValidSection.ValidKey, "ValidValue");
call report_test_results("ini");
end
+43
View File
@@ -0,0 +1,43 @@
#include "sfall.h"
#include "dik.h"
#include "define_lite.h"
#include "define_extra.h"
#include "test_utils.h"
procedure run_unwield_slot_test(variable slot, variable slotName) begin
variable equippedItem := critter_inven_obj2(dude_obj, slot);
if (equippedItem == 0) then begin
display_msg("unwield_slot " + slotName + ": slot is empty");
return;
end
unwield_slot(dude_obj, slot);
call assertTrue("unwield_slot clears " + slotName + " slot", critter_inven_obj2(dude_obj, slot) == 0);
call report_test_results("unwield_slot");
end
procedure keypress_handler begin
variable pressed := get_sfall_arg_at(0);
variable key := get_sfall_arg_at(1);
if (not pressed) then return;
if (key == DIK_J) then begin
call run_unwield_slot_test(INVEN_TYPE_WORN, "armor");
end else if (key == DIK_K) then begin
call run_unwield_slot_test(INVEN_TYPE_RIGHT_HAND, "right");
end else if (key == DIK_L) then begin
call run_unwield_slot_test(INVEN_TYPE_LEFT_HAND, "left");
end
end
procedure start begin
if (not game_loaded) then return;
call assertEquals("unwield_slot metarule exists", metarule_exist("unwield_slot"), 1);
call assertEquals("critter_inven_obj2 metarule exists", metarule_exist("critter_inven_obj2"), 1);
display_msg("unwield_slot manual test ready: open inventory or barter, then press J armor, K right, L left");
register_hook_proc(HOOK_KEYPRESS, keypress_handler);
end
+98
View File
@@ -0,0 +1,98 @@
#include "sfall.h"
#include "dik.h"
#include "lib.arrays.h"
#include "../test_utils.h"
variable itemdamage_mode := 0;
variable itemdamage_seen := 0;
variable itemdamage_last_min := 0;
variable itemdamage_last_max := 0;
variable itemdamage_last_hit_mode := 0;
variable itemdamage_last_is_melee_weapon := 0;
variable itemdamage_last_weapon_pid := -1;
variable itemdamage_last_weapon_name := "<none>";
procedure itemdamage_mode_name begin
if (itemdamage_mode == 1) then return "fixed-42";
if (itemdamage_mode == 2) then return "range-30-30";
return "engine";
end
procedure itemdamage_weapon_name(variable weapon) begin
if (weapon == 0) then return "<unarmed>";
return obj_name(weapon);
end
procedure itemdamage_handler begin
variable
args := get_sfall_args,
weapon := args[2];
call assertEquals("itemdamage arg count", len_array(args), 6);
call assertTrue("itemdamage min <= max", args[0] <= args[1]);
call assertTrue("itemdamage melee flag", args[5] == 0 or args[5] == 1);
itemdamage_seen := itemdamage_seen + 1;
itemdamage_last_min := args[0];
itemdamage_last_max := args[1];
itemdamage_last_hit_mode := args[4];
itemdamage_last_is_melee_weapon := args[5];
itemdamage_last_weapon_name := itemdamage_weapon_name(weapon);
if (weapon == 0) then
itemdamage_last_weapon_pid := -1;
else
itemdamage_last_weapon_pid := obj_pid(weapon);
display_msg(string_format6("itemdamage mode=%s min=%d max=%d weapon=%s hit=%d melee=%d",
itemdamage_mode_name,
args[0],
args[1],
itemdamage_last_weapon_name,
itemdamage_last_hit_mode,
itemdamage_last_is_melee_weapon));
if (itemdamage_mode == 1) then begin
set_sfall_return(42);
end else if (itemdamage_mode == 2) then begin
set_sfall_return(30);
set_sfall_return(30);
end
end
procedure keypress_handler begin
variable
pressed := get_sfall_arg_at(0),
key := get_sfall_arg_at(1);
if (not pressed) then return;
if (key == DIK_J) then begin
itemdamage_mode := itemdamage_mode + 1;
if (itemdamage_mode > 2) then itemdamage_mode := 0;
display_msg(string_format1("itemdamage mode -> %s", itemdamage_mode_name));
end else if (key == DIK_K) then begin
display_msg(string_format7("itemdamage summary mode=%s seen=%d last=%d-%d weapon=%s pid=%d melee=%d",
itemdamage_mode_name,
itemdamage_seen,
itemdamage_last_min,
itemdamage_last_max,
itemdamage_last_weapon_name,
itemdamage_last_weapon_pid,
itemdamage_last_is_melee_weapon));
call assertTrue("itemdamage observed", itemdamage_seen > 0);
call report_test_results("itemdamage");
end
end
procedure start begin
if (not game_loaded) then return;
display_msg("itemdamage manual test ready:");
display_msg("press J to cycle engine/fixed-42/range-30-30");
display_msg("attack with fists, melee weapons, and ranged weapons");
display_msg("press K to report after HOOK_ITEMDAMAGE fires");
register_hook_proc(HOOK_KEYPRESS, keypress_handler);
register_hook_proc(HOOK_ITEMDAMAGE, itemdamage_handler);
end
+70
View File
@@ -0,0 +1,70 @@
#include "sfall.h"
#include "dik.h"
variable movecost_mode := 0;
procedure movecost_mode_name(variable mode) begin
variable name := "unknown";
if (mode == 0) then name := "engine";
else if (mode == 1) then name := "plus_per_hex";
else if (mode == 2) then name := "double";
return name;
end
procedure cycle_movecost_mode begin
if (movecost_mode == 0) then movecost_mode := 1;
else if (movecost_mode == 1) then movecost_mode := 2;
else movecost_mode := 0;
display_msg(string_format1("movecost mode=%s", movecost_mode_name(movecost_mode)));
end
procedure movecost_handler begin
variable
args := get_sfall_args,
critter := args[0],
distance := args[1],
defaultCost := args[2],
modeName := movecost_mode_name(movecost_mode),
newCost := defaultCost;
if (critter != dude_obj) then return;
display_msg(string_format3("movecost dist=%d default=%d mode=%s",
distance,
defaultCost,
modeName));
if (movecost_mode == 1) then begin
newCost := defaultCost + distance;
end else if (movecost_mode == 2) then begin
newCost := defaultCost * 2;
end else begin
return;
end
set_sfall_return(newCost);
end
procedure keypress_handler begin
variable
pressed := get_sfall_arg_at(0),
key := get_sfall_arg_at(1);
if (not pressed) then return;
if (key != DIK_M) then return;
call cycle_movecost_mode;
end
procedure start begin
if (not game_loaded) then return;
display_msg("movecost manual test ready: enter combat, hover a tile, then move");
display_msg("press M to cycle hook override: engine -> plus_per_hex -> double");
register_hook_proc(HOOK_KEYPRESS, keypress_handler);
register_hook_proc(HOOK_MOVECOST, movecost_handler);
end
@@ -0,0 +1,7 @@
[ValidSection]
ValidKey=ValidValue
Key2=2
[Section2]
Key1=AA
Key2=BB
+2
View File
@@ -0,0 +1,2 @@
A test mod for sfall scripting features testing.
Add it to your mods_order.txt
@@ -0,0 +1 @@
Put compiled scripts in here
-6
View File
@@ -367,12 +367,6 @@ void artToggleObjectTypeHidden(int objectType)
}
}
// art_total
int art_total(int objectType)
{
return objectType >= 0 && objectType < OBJ_TYPE_COUNT ? gArtListDescriptions[objectType].fileNamesLength : 0;
}
// 0x418F7C
int artGetFidgetCount(int headFid)
{
-1
View File
@@ -123,7 +123,6 @@ void artExit();
char* artGetObjectTypeName(int objectType);
int artIsObjectTypeHidden(int objectType);
void artToggleObjectTypeHidden(int objectType);
int art_total(int objectType);
int artGetFidgetCount(int headFid);
void artRender(int fid, unsigned char* dest, int width, int height, int pitch);
int art_list_str(int fid, char* name);
+30 -7
View File
@@ -112,6 +112,8 @@ typedef enum EditorFolder {
EDITOR_FOLDER_KILLS,
} EditorFolder;
static void characterEditorMessageListReset();
enum {
EDITOR_DERIVED_STAT_ARMOR_CLASS,
EDITOR_DERIVED_STAT_ACTION_POINTS,
@@ -587,6 +589,12 @@ static int gCharacterEditorOptionalTraitBtns[TRAIT_COUNT];
// 0x5700E8 mesg
static MessageListItem gCharacterEditorMessageListItem;
static void characterEditorMessageListReset()
{
messageListRepositorySetStandardMessageList(STANDARD_MESSAGE_LIST_EDITOR, nullptr);
messageListFree(&gCharacterEditorMessageList);
}
// 0x5700F8 old_str1
static char gCharacterEditorCardTitle[48];
@@ -1284,18 +1292,35 @@ static int characterEditorWindowInit()
if (!messageListLoad(&gCharacterEditorMessageList, path)) {
return -1;
}
messageListRepositorySetStandardMessageList(STANDARD_MESSAGE_LIST_EDITOR, &gCharacterEditorMessageList);
fid = buildFid(OBJ_TYPE_INTERFACE, (gCharacterEditorIsCreationMode ? 169 : 177), 0, 0, 0);
if (!_editorBackgroundFrmImage.lock(fid)) {
messageListFree(&gCharacterEditorMessageList);
characterEditorMessageListReset();
return -1;
}
if (karmaInit() == -1) {
_editorBackgroundFrmImage.unlock();
characterEditorMessageListReset();
if (gCharacterEditorIsoWasEnabled) {
isoEnable();
}
colorCycleEnable();
gameMouseSetCursor(MOUSE_CURSOR_ARROW);
return -1;
}
if (genericReputationInit() == -1) {
karmaFree();
_editorBackgroundFrmImage.unlock();
characterEditorMessageListReset();
if (gCharacterEditorIsoWasEnabled) {
isoEnable();
}
colorCycleEnable();
gameMouseSetCursor(MOUSE_CURSOR_ARROW);
return -1;
}
@@ -1318,11 +1343,9 @@ static int characterEditorWindowInit()
while (--i >= 0) {
_editorFrmImages[i].unlock();
}
return -1;
_editorBackgroundFrmImage.unlock();
messageListFree(&gCharacterEditorMessageList);
characterEditorMessageListReset();
if (gCharacterEditorIsoWasEnabled) {
isoEnable();
@@ -1360,7 +1383,7 @@ static int characterEditorWindowInit()
_editorBackgroundFrmImage.unlock();
messageListFree(&gCharacterEditorMessageList);
characterEditorMessageListReset();
if (gCharacterEditorIsoWasEnabled) {
isoEnable();
}
@@ -1389,7 +1412,7 @@ static int characterEditorWindowInit()
_editorBackgroundFrmImage.unlock();
messageListFree(&gCharacterEditorMessageList);
characterEditorMessageListReset();
if (gCharacterEditorIsoWasEnabled) {
isoEnable();
}
@@ -1869,7 +1892,7 @@ static void characterEditorWindowFree()
// SFALL: Custom town reputation.
customTownReputationFree();
messageListFree(&gCharacterEditorMessageList);
characterEditorMessageListReset();
interfaceBarRefresh();
+6 -3
View File
@@ -1359,13 +1359,16 @@ int critterGetMovementPointCostAdjustedForCrippledLegs(Object* critter, int dist
}
int flags = critter->data.critter.combat.results;
int actionPoints = 0;
if ((flags & DAM_CRIP_LEG_LEFT) != 0 && (flags & DAM_CRIP_LEG_RIGHT) != 0) {
return 8 * distance;
actionPoints = 8 * distance;
} else if ((flags & DAM_CRIP_LEG_ANY) != 0) {
return 4 * distance;
actionPoints = 4 * distance;
} else {
return distance;
actionPoints = distance;
}
return scriptHooks_MoveCost(critter, distance, actionPoints);
}
// 0x42E66C critterIsOverloaded
-24
View File
@@ -144,30 +144,6 @@ File* fileOpen(const char* filename, const char* mode)
return xfileOpen(filename, mode);
}
File* fileOpenDirect(const char* filename, const char* mode)
{
return xfileOpenDirect(filename, mode);
}
const char* buildDataPath(const char* root, const char* relative)
{
static char path[COMPAT_MAX_PATH];
size_t rootLen = strlen(root);
bool rootHasSeparator = rootLen > 0 && (root[rootLen - 1] == '\\' || root[rootLen - 1] == '/');
snprintf(path, sizeof(path), "%s%s%s", root, rootHasSeparator ? "" : "\\", relative);
char dir[COMPAT_MAX_PATH];
snprintf(dir, sizeof(dir), "%s", path);
char* separator = strrchr(dir, '\\');
if (separator != nullptr) {
*separator = '\0';
compat_mkdir_recursive(dir);
}
return path;
}
// 0x4C5ED0 db_fprintf
int filePrintFormatted(File* stream, const char* format, ...)
{
-5
View File
@@ -18,7 +18,6 @@ int dbGetFileSize(const char* filePath, int* sizePtr);
int dbGetFileContents(const char* filePath, void* ptr);
int fileClose(File* stream);
File* fileOpen(const char* filename, const char* mode);
File* fileOpenDirect(const char* filename, const char* mode);
int filePrintFormatted(File* stream, const char* format, ...);
int fileReadChar(File* stream);
char* fileReadString(char* str, size_t size, File* stream);
@@ -112,10 +111,6 @@ void fileNameListFree(char*** fileNames, int unused);
int fileGetSize(File* stream);
void fileSetReadProgressHandler(FileReadProgressHandler* handler, int size);
// Joins [root] and [relative] into a save path and creates its directory.
// Returns a pointer to a static buffer.
const char* buildDataPath(const char* root, const char* relative);
} // namespace fallout
#endif /* DB_H */
+1
View File
@@ -448,6 +448,7 @@ void gameReset()
scriptHooksReset();
sfallArraysReset();
sfall_gl_scr_reset();
sfall_ini_cache_clear();
sfallOnGameReset();
gGameLoaded = false;
}
+12 -1
View File
@@ -86,6 +86,8 @@ typedef enum GameDialogReviewWindowButtonFrm {
GAME_DIALOG_REVIEW_WINDOW_BUTTON_FRM_COUNT,
} GameDialogReviewWindowButtonFrm;
static void partyMemberCustomizationMessageListReset();
typedef enum GameDialogReaction {
GAME_DIALOG_REACTION_GOOD = 49,
GAME_DIALOG_REACTION_NEUTRAL = 50,
@@ -583,6 +585,12 @@ static int gGameDialogReviewWindowOldFont;
// 0x58F470 gdialog_buttons
static int _gdialog_buttons[9];
static void partyMemberCustomizationMessageListReset()
{
messageListRepositorySetStandardMessageList(STANDARD_MESSAGE_LIST_CUSTOM, nullptr);
messageListFree(&gCustomMessageList);
}
// 0x58F4C8 oldFont
static int _oldFont;
@@ -3966,10 +3974,12 @@ int partyMemberCustomizationWindowInit()
if (!messageListLoad(&gCustomMessageList, "game\\custom.msg")) {
return -1;
}
messageListRepositorySetStandardMessageList(STANDARD_MESSAGE_LIST_CUSTOM, &gCustomMessageList);
FrmImage backgroundFrmImage;
int backgroundFid = buildFid(OBJ_TYPE_INTERFACE, 391, 0, 0, 0);
if (!backgroundFrmImage.lock(backgroundFid)) {
partyMemberCustomizationMessageListReset();
return -1;
}
@@ -4081,6 +4091,7 @@ int partyMemberCustomizationWindowInit()
void partyMemberCustomizationWindowFree()
{
if (gGameDialogWindow == -1) {
partyMemberCustomizationMessageListReset();
return;
}
@@ -4118,7 +4129,7 @@ void partyMemberCustomizationWindowFree()
windowDestroy(gGameDialogWindow);
gGameDialogWindow = -1;
messageListFree(&gCustomMessageList);
partyMemberCustomizationMessageListReset();
}
// 0x449B3C
+4 -4
View File
@@ -151,7 +151,7 @@ typedef enum OpRegAnimFunc {
static void scriptPredefinedError(Program* program, const char* name, int error);
static void scriptError(const char* format, ...);
static int tileIsVisible(int tile);
static int _correctFidForRemovedItem(Object* critter, Object* item, int flags);
int correctFidForRemovedItem(Object* critter, Object* item, int flags);
static void opGiveExpPoints(Program* program);
static void opScrReturn(Program* program);
static void opPlaySfx(Program* program);
@@ -407,7 +407,7 @@ static int tileIsVisible(int tile)
}
// 0x45409C correctFidForRemovedItem
static int _correctFidForRemovedItem(Object* critter, Object* item, int flags)
int correctFidForRemovedItem(Object* critter, Object* item, int flags)
{
InvenSlot invenSlot = InvenSlot::Armor;
if ((flags & OBJECT_IN_RIGHT_HAND) != 0) {
@@ -1683,7 +1683,7 @@ static void opRemoveObjectFromInventory(Program* program)
tileWindowRefreshRect(&rect, item->elevation);
if (updateFlags) {
_correctFidForRemovedItem(owner, item, flags);
correctFidForRemovedItem(owner, item, flags);
}
}
}
@@ -4641,7 +4641,7 @@ static void opMoveObjectInventoryToObject(Program* program)
flags |= OBJECT_IN_RIGHT_HAND;
}
_correctFidForRemovedItem(object1, oldWeapon, flags);
correctFidForRemovedItem(object1, oldWeapon, flags);
}
itemMoveAll(object1, object2);
+4 -1
View File
@@ -2,10 +2,11 @@
#define INTERPRETER_EXTRA_H
#include "interpreter.h"
#include "obj_types.h"
namespace fallout {
enum class InvenSlot {
enum class InvenSlot : int {
Armor = 0, // INVEN_TYPE_WORN
RightHand = 1, // INVEN_TYPE_RIGHT_HAND
LeftHand = 2, // INVEN_TYPE_LEFT_HAND
@@ -13,6 +14,8 @@ enum class InvenSlot {
constexpr int kInvenSlotInvCount = -2;
int correctFidForRemovedItem(Object* critter, Object* item, int flags);
void _intExtraClose_();
void _initIntExtra();
void intExtraUpdate();
+138 -3
View File
@@ -937,6 +937,7 @@ static int inventoryMessageListInit()
if (!messageListLoad(&gInventoryMessageList, path))
return -1;
messageListRepositorySetStandardMessageList(STANDARD_MESSAGE_LIST_INVENTORY, &gInventoryMessageList);
return 0;
}
@@ -944,6 +945,7 @@ static int inventoryMessageListInit()
// 0x46E7A0
static int inventoryMessageListFree()
{
messageListRepositorySetStandardMessageList(STANDARD_MESSAGE_LIST_INVENTORY, nullptr);
messageListFree(&gInventoryMessageList);
return 0;
}
@@ -3643,7 +3645,6 @@ int inventoryUnequipFunc(Object* critter, int hand, bool animate)
{
int activeHand;
Object* item;
int fid;
if (critter == gDude) {
activeHand = interfaceGetCurrentHand();
@@ -3678,13 +3679,13 @@ int inventoryUnequipFunc(Object* critter, int hand, bool animate)
animationRegisterAnimate(critter, ANIM_PUT_AWAY, 0);
fid = buildFid(OBJ_TYPE_CRITTER, critter->fid & 0xFFF, 0, 0, critter->rotation + 1);
int fid = buildFid(OBJ_TYPE_CRITTER, critter->fid & 0xFFF, 0, 0, critter->rotation + 1);
animationRegisterSetFid(critter, fid, -1);
return reg_anim_end();
}
fid = buildFid(OBJ_TYPE_CRITTER, critter->fid & 0xFFF, 0, 0, critter->rotation + 1);
int fid = buildFid(OBJ_TYPE_CRITTER, critter->fid & 0xFFF, 0, 0, critter->rotation + 1);
_dude_stand(critter, critter->rotation, fid);
}
@@ -6344,4 +6345,138 @@ Object* inventoryGetTargetObject()
return _target_stack[_target_curr_stack];
}
int inventoryUnwieldSlot(Object* critter, InvenSlot slot)
{
if (critter == nullptr) {
return -1;
}
bool isDude = critter == gDude;
bool uiModeActive = GameMode::isInGameMode(GameMode::kInventory)
|| GameMode::isInGameMode(GameMode::kUseOn)
|| GameMode::isInGameMode(GameMode::kLoot)
|| GameMode::isInGameMode(GameMode::kBarter);
bool update = false;
if (slot != InvenSlot::Armor && !uiModeActive) {
int rc = inventoryUnequipFunc(critter, slot == InvenSlot::LeftHand ? HAND_LEFT : HAND_RIGHT, false);
if (rc == 0) {
update = isDude;
}
if (update) {
interfaceUpdateItems(false, INTERFACE_ITEM_ACTION_DEFAULT, INTERFACE_ITEM_ACTION_DEFAULT);
}
return rc;
}
bool forceAdd = false;
Object* item = nullptr;
Object** itemSlot = nullptr;
if (slot != InvenSlot::Armor) {
if (!isDude || critter != _inven_dude) {
return 0;
}
if (slot == InvenSlot::RightHand) {
item = gInventoryRightHandItem;
itemSlot = &gInventoryRightHandItem;
} else {
item = gInventoryLeftHandItem;
itemSlot = &gInventoryLeftHandItem;
}
if (item != nullptr) {
if (!scriptHooks_InvenWield(critter, item, slot, 0, 0, false)) {
return -1;
}
int oldFlags = item->flags;
bool clearBothHandSlots = gInventoryLeftHandItem == item && gInventoryRightHandItem == item;
if (clearBothHandSlots) {
gInventoryLeftHandItem = nullptr;
gInventoryRightHandItem = nullptr;
} else {
*itemSlot = nullptr;
}
item->flags &= ~OBJECT_IN_ANY_HAND;
if (itemAdd(critter, item, 1) != 0) {
// item will be in an invalid state, but this can only happen on malloc failure
return -1;
}
update = true;
}
} else {
if (isDude && critter == _inven_dude) {
item = gInventoryArmor;
}
if (item == nullptr) {
item = critterGetArmor(critter);
} else {
gInventoryArmor = nullptr;
forceAdd = true;
}
if (item != nullptr) {
item->flags &= ~OBJECT_WORN;
if (correctFidForRemovedItem(critter, item, OBJECT_WORN) != 0) {
if (forceAdd) {
gInventoryArmor = item;
item->flags |= OBJECT_WORN;
}
return -1;
}
if (forceAdd && itemAdd(critter, item, 1) != 0) {
// improper cleanup, but this can only happen on malloc failure
return -1;
}
if (isDude) {
interfaceRenderArmorClass(false);
}
}
}
if (!uiModeActive) {
return 0;
}
InventoryWindowType inventoryWindowType = INVENTORY_WINDOW_TYPE_NORMAL;
if (GameMode::isInGameMode(GameMode::kBarter)) {
inventoryWindowType = INVENTORY_WINDOW_TYPE_TRADE;
} else if (GameMode::isInGameMode(GameMode::kLoot)) {
inventoryWindowType = INVENTORY_WINDOW_TYPE_LOOT;
} else if (GameMode::isInGameMode(GameMode::kUseOn)) {
inventoryWindowType = INVENTORY_WINDOW_TYPE_USE_ITEM_ON;
}
if (inventoryWindowType == INVENTORY_WINDOW_TYPE_NORMAL) {
inventoryRenderSummary();
}
int targetFid = -1;
if (inventoryWindowType == INVENTORY_WINDOW_TYPE_LOOT
|| inventoryWindowType == INVENTORY_WINDOW_TYPE_TRADE) {
targetFid = _target_stack[_target_curr_stack]->fid;
_display_target_inventory(_target_stack_offset[_target_curr_stack], -1, _target_pud, inventoryWindowType);
}
_adjust_fid();
_display_inventory(_stack_offset[_curr_stack], -1, inventoryWindowType);
_display_body(targetFid, inventoryWindowType);
if (inventoryWindowType == INVENTORY_WINDOW_TYPE_TRADE) {
barterDisplayTables(gInventoryBarterBackgroundWindow, gPlayerTableObj, gBartererTableObj, -1);
}
if (update) {
interfaceUpdateItems(false, INTERFACE_ITEM_ACTION_DEFAULT, INTERFACE_ITEM_ACTION_DEFAULT);
}
return 0;
}
} // namespace fallout

Some files were not shown because too many files have changed in this diff Show More