Compare commits

...
Author SHA1 Message Date
Mike Klaas 80bdd7fdb0 remove isIniatlized() 2026-05-29 18:51:53 -07:00
Mike Klaas 4eb9e869df outdent one more place 2026-05-27 08:34:15 -07:00
Mike Klaas 49a24f6925 Use ScopeConfig for function-scoped Config uses
Eliminates the free, which sometimes required gotos.

Left configRead in a couple places that return different values if the file is missing (0) vs hard error (-1). With more careful analysis it's possible we could fold the read into the ScopeConfig constructor in these cases too.

Also, doing this makes it feel like a ScopeConfig::get(section, value, &out) function would feel nice.
2026-05-27 08:19:52 -07:00
Mike Klaas 9423a2c289 Sfall: save/restore last savegame page/slot (#464)
* Sfall: save/restore last savegame page/slot

* ScopedConfig for RAII convenience
2026-05-26 23:38:02 -07:00
Mike Klaas dc4753cde9 Improve music/sound system to allow .ogg/.wav map music + 44.1 kHz output (#465)
* Change output sound buffer to 44.1 kHz

This allow CE to play 44.1 kHz input files without loss of precision.  In particular, it plays HQ music that ships with RPU.

There is a very similar fix in Sfall

* use generic sound loading path to allow .ogg/.wav music files

* rm a few dangling references

* PR feedback

* rm references to audio file

* rm audio_file.cc/h

* restore default audio io

* PR fixes
2026-05-26 23:12:42 -07:00
Mike Klaas cd3adf1621 Implement set_unique_id (#466)
* Implement `set_unique_id`

* save/load watermark, including compat with previous saves (would be `0`)
* metarule to set and reset unique_id
* inventory stacking rules
2026-05-26 22:38:40 -07:00
Mike Klaas d42c8456dd Custom iface tags + metarules (#463)
* custom iface tag

* add_iface_tag, set_iface_tag_text

Used by FO2Tweaks and other mods
2026-05-26 22:30:42 -07:00
Vlad K c574812f1d EDG: fix getIsoWindowSize returning (1, 1) in release builds (#471) 2026-05-26 18:46:08 +02:00
Mike Klaas 8afe4b3061 Use in-game gamma when generating screenshots (#469)
This fixes the slight darkening effect that currently exists
2026-05-26 09:23:14 -07:00
Vlad Kandgithub-actions[bot] c423d475c5 EDG support (#450)
* HRP's edg file support (WIP)

* Update docs and fix bug

* chore: auto-format with clang-format

* EDG: proper scroll blocking based on pre-calculated rects, exactly as in HRP

* chore: auto-format with clang-format

* EDG: fix scrolling past pixel-space bounds with sub-hex map edge mod

* EDG: review fixes

* EDG: proper edge clipping matching HRP logic

* chore: auto-format with clang-format

* EDG: code review fixes and refactoring

* chore: auto-format with clang-format

* EDG: fix clamping

* EDG: show arrow cursor and block events when over clipped area

* chore: auto-format with clang-format

* EDG: option to disable

* EDG: deobfuscate code

* chore: auto-format with clang-format

* EDG: get rid of rect2 and simplify algebra

* EDG: update docs, clarify that squareRect is not implemented

* EDG: square mask support

* chore: auto-format with clang-format

* Code review fixes

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-05-26 07:25:40 +00:00
Mike Klaas 06d7f09728 Implement HOOK_USESKILL, HOOK_USESKILLON (#456)
* Implement HOOK_USESKILL, HOOK_USESKILLON

* make comment more accurate
2026-05-19 22:40:33 -07:00
Mike Klaas 81164465cf Load f2_res.dat after master.dat (#462)
This bring CE much closer to sfall's load order, and fixes RPU install.

 master_patches > critter_patches > mods_order.txt > PatchFileXX > patchXXX.dat > sfall.dat > critter_dat > f2_res_patches > f2_res_dat > master_dat

Also fixed typo in `showMessageBox`, and added a debugPrint in that function, so errors are visible in the log (LLMs otherwise find it hard to figure out what happened)
2026-05-19 22:20:13 -07:00
IronKing24 0a7834f81b Merge pull request #459 from fallout2-ce/mingw
Add Mingw build system
2026-05-17 01:15:07 +03:00
IronKing24 f76e310f10 Fix mapper compilation 2026-05-16 19:45:00 +03:00
IronKing24 303e7573ca Merge branch 'main' into mingw 2026-05-16 18:30:39 +03:00
Mike Klaas 16ae69fa5a HOOK_ENCOUNTER (#453)
* HOOK_ENCOUNTER

Pretty much 1:1 with Sfall except adding extra validation that the mapId returns belongs to an area
2026-05-13 15:09:43 -07:00
Mike Klaas 6d2d9d6ef3 Add "trade/loot" context menu for party members. (#449)
* Add "trade/loot" context menu for party members.

This was an idea I saw listed on NMA. Veterans of classic Fallout know you can trade with your companions much more easily using the steal skill, But this would be difficult for most new players to discover. This replaces the dialogue option in the long press (which is redundant since it is the default action) with the hand, which is normally used to loot from containers and corpses.

This does introduce a slight inconsistency in the default action, not being present when you long press. how almost everyone will be single-clicking to talk to NPCs.

Also, "you plant..." and "you steal..." messages when trading with companions this way. Put this behind the same flag in case someone wants the original experience.

* Full context menu

* container -> target

* PR feedback

* rebase on main

* rename function
2026-05-12 23:03:43 -07:00
Mike Klaas 7ae0452e1a Add original symbol beside all function and symbol offsets (#332)
* Add original symbol beside all function offsets

e.g.

```
// 0x414E98 register_object_call
int animationRegisterCallback(

// 0x414E20 register_object_must_erase
int animationRegisterHideObjectForced(
```

This was programmatically generated so there could be errors.  Spot checking looks pretty good though
2026-05-11 19:49:15 -07:00
Vlad Kandgithub-actions[bot] f63c113d77 Mapper: Playmode & misc features (#448)
* Mapper: fixed playmode, added some mapper features

* mapper: save map corrections + some stubs

* mapper: fix playmode and cleanup discrepancy

* mapper: edit_mapper inacuracies and tidying up some code

* mapper: corrected Delete Spatial, minor fixes

* mapper: implement debug.show_tile_num, Mark Exit Grid, toolbar name drawing fix and refactoring

* chore: auto-format with clang-format

* mapper: fix inaccuracies after AI review, round 1

* mapper: fix inaccuracies after AI review, round 2

* chore: auto-format with clang-format

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-05-12 03:00:19 +02:00
EvgeniyandZoschukEN f8ad77061b Refactoring datafile: (#445)
* Refactoring datafile:

Description

1. Removed unused extern variables from the header file.
2. Replaced magic numbers with constants.
3. Added stdint types

* Emty commit for start clang formatter

* Used clang format

---------

Co-authored-by: ZoschukEN <ZoschukEN@RTK-CD.RU>
2026-05-11 10:10:23 -07:00
IronKing24 471ebbf417 Merge branch 'main' into mingw 2026-05-11 10:37:26 +03:00
Mike Klaas 924e12b94c [HRP] Enable HR dialog screen (#441)
* [HRP] Enable HR dialog screen

(It's not really "hi resolution", just adds a border so it looks less funny.)

Also, picked up @NovaRain's suggestion of centering dialog on the play area if it is large enough, which matches Sfall.
2026-05-10 22:35:35 -07:00
Mike Klaas 706785d60e Implement a few AP opcodes (#443)
* get/set_unspent_ap{_perk}_bonus

* get/set_inven_ap_cost
2026-05-10 21:48:22 -07:00
Mike Klaas 1175efd379 Implement HOOK_STEAL (#442)
* Implement HOOK_STEAL
2026-05-10 10:15:50 -07:00
IronKing24 bc7ab1ecb2 Merge branch 'main' into mingw 2026-05-03 11:04:19 +03:00
IronKing24 a0a6091bd0 add package dependency fix 2026-05-02 20:56:29 +03:00
IronKing24 f2b2127709 fix processor name typo 2026-05-02 20:53:19 +03:00
IronKing24 ec6647968b Merge branch 'main' into mingw 2026-05-02 19:15:08 +03:00
IronKing24 f97ceff645 add x86 build and move presets to the public 2026-05-02 19:07:27 +03:00
IronKing24 4247d7ac3f - Set correct checks for msvc or win
- change SDL2 cmake for mingw to link correctly
2026-05-02 15:56:41 +03:00
IronKing24 bd3358709f add toolchain file 2026-05-01 20:28:30 +03:00
155 changed files with 7821 additions and 4506 deletions
+59 -4
View File
@@ -47,8 +47,6 @@ set(FALLOUT_ENGINE_SOURCES
"src/animation.h"
"src/art.cc"
"src/art.h"
"src/audio_file.cc"
"src/audio_file.h"
"src/audio.cc"
"src/audio.h"
"src/automap.cc"
@@ -161,6 +159,8 @@ set(FALLOUT_ENGINE_SOURCES
"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"
@@ -372,14 +372,33 @@ if(WIN32)
)
endif()
if(MINGW)
target_compile_definitions(${EXECUTABLE_NAME} PUBLIC
_USE_MATH_DEFINES
)
endif()
if(WIN32)
target_link_libraries(${EXECUTABLE_NAME}
winmm
)
endif()
if(MSVC)
target_link_libraries(${EXECUTABLE_NAME}
debug libcpmtd
optimized libcpmt
)
endif()
if(MINGW)
target_link_options(${EXECUTABLE_NAME} PRIVATE
-static
-static-libgcc
-static-libstdc++
)
endif()
if(WIN32)
target_sources(${EXECUTABLE_NAME} PUBLIC
"os/windows/fallout2-ce.ico"
@@ -496,6 +515,7 @@ target_link_libraries(${EXECUTABLE_NAME} ${ZLIB_LIBRARIES})
target_include_directories(${EXECUTABLE_NAME} PRIVATE ${ZLIB_INCLUDE_DIRS})
target_link_libraries(${EXECUTABLE_NAME} ${SDL2_LIBRARIES})
target_link_libraries(${EXECUTABLE_NAME} ${SDL2_MAIN_LIBRARIES})
target_include_directories(${EXECUTABLE_NAME} PRIVATE ${SDL2_INCLUDE_DIRS})
if((NOT ANDROID) AND (NOT IOS) AND (NOT CMAKE_SYSTEM_NAME MATCHES "Emscripten"))
@@ -522,6 +542,13 @@ if((NOT ANDROID) AND (NOT IOS) AND (NOT CMAKE_SYSTEM_NAME MATCHES "Emscripten"))
if(WIN32)
target_link_libraries(ce-dat-tool winmm)
endif()
if(MINGW)
target_link_options(ce-dat-tool PRIVATE
-static
-static-libgcc
-static-libstdc++
)
endif()
target_include_directories(ce-dat-tool PRIVATE "src")
target_include_directories(ce-dat-tool PRIVATE ${ZLIB_INCLUDE_DIRS})
target_include_directories(ce-dat-tool PRIVATE ${SDL2_INCLUDE_DIRS})
@@ -545,6 +572,14 @@ if((NOT ANDROID) AND (NOT IOS) AND (NOT CMAKE_SYSTEM_NAME MATCHES "Emscripten"))
target_include_directories(ce-frm2png PRIVATE ${ZLIB_INCLUDE_DIRS})
target_link_libraries(ce-frm2png ${SDL2_LIBRARIES})
target_link_libraries(ce-frm2png ${ZLIB_LIBRARIES})
if(MINGW)
target_link_options(ce-frm2png PRIVATE
-static
-static-libgcc
-static-libstdc++
)
endif()
if(APPLE)
file(GLOB_RECURSE CE_DAT_INPUTS CONFIGURE_DEPENDS "${CMAKE_SOURCE_DIR}/files/ce.dat/*")
@@ -605,11 +640,30 @@ if((NOT ANDROID) AND (NOT IOS) AND (NOT CMAKE_SYSTEM_NAME MATCHES "Emscripten"))
WIN32_LEAN_AND_MEAN
_STATIC_CPPLIB
)
if(MINGW)
target_compile_definitions(mapper-ce PUBLIC
_USE_MATH_DEFINES
)
target_link_options(mapper-ce PRIVATE
-static
-static-libgcc
-static-libstdc++
)
endif()
target_link_libraries(mapper-ce
winmm
debug libcpmtd
optimized libcpmt
)
if(MSVC)
target_link_libraries(mapper-ce
debug libcpmtd
optimized libcpmt
)
endif()
target_sources(mapper-ce PUBLIC
"os/windows/fallout2-ce.rc"
)
@@ -633,6 +687,7 @@ if((NOT ANDROID) AND (NOT IOS) AND (NOT CMAKE_SYSTEM_NAME MATCHES "Emscripten"))
fpattern_windows::fpattern_windows
${ZLIB_LIBRARIES}
${SDL2_LIBRARIES}
${SDL2_MAIN_LIBRARIES}
)
endif()
+91
View File
@@ -139,6 +139,77 @@
"cacheVariables": {
"CMAKE_SYSTEM_NAME": "iOS"
}
},
{
"name": "Mingw-base",
"hidden": true,
"description": "Base preset for all MinGW builds.",
"inherits":[
"base"
],
"generator": "Ninja",
"toolchainFile": "${sourceDir}/cmake/toolchain/mingw.cmake",
"cacheVariables": {
"CMAKE_SYSTEM_NAME": "Windows"
}
},
{
"name": "Mingw-x86-base",
"hidden": true,
"description": "Base preset for all MinGW x86 builds.",
"inherits": [
"Mingw-base"
],
"cacheVariables": {
"CMAKE_C_FLAGS": "-m32",
"CMAKE_CXX_FLAGS": "-m32",
"CMAKE_SYSTEM_PROCESSOR": "i686"
}
},
{
"name": "Mingw-x86-debug",
"displayName": "MinGW x86 Debug",
"description": "Cross-compile a Debug Windows x86 build with MinGW-w64 and Ninja.",
"inherits": [
"Mingw-x86-base"
],
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug"
}
},
{
"name": "Mingw-x86-release",
"displayName": "MinGW x86 Release",
"description": "Cross-compile a RelWithDebInfo Windows x86 build with MinGW-w64 and Ninja.",
"inherits": [
"Mingw-x86-debug"
],
"cacheVariables": {
"CMAKE_BUILD_TYPE": "RelWithDebInfo"
}
},
{
"name": "Mingw-x64-debug",
"displayName": "MinGW x64 Debug",
"description": "Cross-compile a Debug Windows x64 build with MinGW-w64 and Ninja.",
"inherits": [
"Mingw-base"
],
"cacheVariables": {
"CMAKE_SYSTEM_PROCESSOR": "x86_64",
"CMAKE_BUILD_TYPE": "Debug"
}
},
{
"name": "Mingw-x64-release",
"displayName": "MinGW x64 Release",
"description": "Cross-compile a RelWithDebInfo Windows x64 build with MinGW-w64 and Ninja.",
"inherits": [
"Mingw-x64-debug"
],
"cacheVariables": {
"CMAKE_BUILD_TYPE": "RelWithDebInfo"
}
}
],
"buildPresets": [
@@ -197,6 +268,26 @@
"name": "ios-release",
"configurePreset": "ios",
"configuration": "RelWithDebInfo"
},
{
"name": "Mingw-x86-Debug",
"displayName": "MinGW x86 Debug",
"configurePreset": "Mingw-x86-debug"
},
{
"name": "Mingw-x86-Release",
"displayName": "MinGW x86 Release",
"configurePreset": "Mingw-x86-release"
},
{
"name": "Mingw-x64-Debug",
"displayName": "MinGW x64 Debug",
"configurePreset": "Mingw-x64-debug"
},
{
"name": "Mingw-x64-Release",
"displayName": "MinGW x64 Release",
"configurePreset": "Mingw-x64-release"
}
]
}
+3
View File
@@ -143,6 +143,7 @@ In time this stuff will receive in-game interface, right now you have to do it m
* Expanded 2-column inventory
* Expanded 4-row barter screen
* Expanded AP bar
* Party members can loot and barter in place of PC
* Increased pathfinding nodes 5x for more accurate pathfinding
* Ctrl-click to quickly move items when bartering, looting, or stealing
* _a_ to select "all" when selecting item quantity
@@ -150,6 +151,8 @@ In time this stuff will receive in-game interface, right now you have to do it m
* When bartering, caps default to the right amount to balance the trade (if possible)
* Music continues playing between maps
* Auto open doors
* 44.1 kHz stereo sound/music supported, in .ogg and .wav format as well as legacy .acm
* Last used save slot is remembered
## Contributing
+19 -7
View File
@@ -4,6 +4,18 @@ This document tracks Fallout 2 CE compatibility with sfall. This is for modders
For now, this covers opcodes/metarules, and hooks. In the future, it will include other ways of modifying the engine (like ini files), and other Sfall-specific behaviour.
## HRP EDG Scroll-Blocker Support
CE supports the `.edg` file format from the HRP (High Resolution Patch), which defines per-map scroll boundaries and square-level render clipping.
**How it works in CE:**
- On map load, `maps/<mapname>.edg` is read if present. Missing file = silent fallback to the scroll-blocker object system.
- The `.edg` file defines per-elevation rectangle boundary zones. Multiple chained zones per elevation are supported.
- When loaded, these zones are used for both scroll blocking and visible area clipping (black bars), replacing both vanilla scroll blocking and CE hi-res stencil system.
- v2 EDG files also contain a `SquareRect` that defines "Angled edges", or square-grid stencil. This is also supported.
## Settings (ddraw.ini → fallout2.cfg / game.cfg)
Settings previously read from `ddraw.ini` have been moved into standard CE config files.
@@ -55,7 +67,7 @@ See [`https://sfall-team.github.io/sfall/`](https://sfall-team.github.io/sfall/)
| Tiles and paths | get_tile_fid<br>tile_under_cursor<br>tile_light<br>tile_get_objs<br>tile_refresh_display<br>obj_blocking_tile<br>tile_by_position<br>get_tile_ground_fid<br>get_tile_roof_fid<br>obj_blocking_line<br>path_find_to<br>objects_in_radius | âś… | - |
| Utility | sprintf<br>typeof<br>atoi<br>atof | âś… | - |
| Utility / Strings | string_split<br>substr<br>strlen<br>charcode<br>get_string_pointer<br>string_find<br>string_find_from<br>string_format<br>string_format_array<br>string_replace<br>string_to_case<br>string_compare | âś… | `get_string_pointer` is deprecated and intentionally omitted. |
| 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. |
| Interface / Tags | show_iface_tag<br>hide_iface_tag<br>is_iface_tag_active<br>set_iface_tag_text<br>add_iface_tag | âś… | Legacy `BoxBarCount`, `BoxBarColors` ddraw.ini settings not supported. |
| 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 | âś… | - |
@@ -70,9 +82,9 @@ See [`https://sfall-team.github.io/sfall/`](https://sfall-team.github.io/sfall/)
| 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. |
| 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, obj_is_openable, get_proto_data, set_proto_data | - |
| 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/<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 | - |
| 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. |
@@ -97,8 +109,8 @@ See [`https://sfall-team.github.io/sfall/`](https://sfall-team.github.io/sfall/)
| 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` | âś… | - |
| UseSkill | `HOOK_USESKILL` | đźš« | - |
| Steal | `HOOK_STEAL` | đźš« | Et tu |
| UseSkill | `HOOK_USESKILL` | âś… | - |
| Steal | `HOOK_STEAL` | âś… | - |
| WithinPerception | `HOOK_WITHINPERCEPTION` | âś… | - |
| InventoryMove | `HOOK_INVENTORYMOVE` | âś… | - |
| InvenWield | `HOOK_INVENWIELD` | âś… | - |
@@ -113,13 +125,13 @@ See [`https://sfall-team.github.io/sfall/`](https://sfall-team.github.io/sfall/)
| UseAnimObj | `HOOK_USEANIMOBJ` | đźš« | Et tu; (maybe) |
| ExplosiveTimer | `HOOK_EXPLOSIVETIMER` | âś… | - |
| DescriptionObj | `HOOK_DESCRIPTIONOBJ` | đźš« | Et tu |
| UseSkillOn | `HOOK_USESKILLON` | đźš« | Et tu |
| UseSkillOn | `HOOK_USESKILLON` | âś… | - |
| OnExplosion | `HOOK_ONEXPLOSION` | đźš« | (maybe) |
| SubCombatDamage | `HOOK_SUBCOMBATDAMAGE` | đźš« | (maybe) |
| SetLighting | `HOOK_SETLIGHTING` | đźš« | Et tu; (maybe) |
| Sneak | `HOOK_SNEAK` | đźš« | - |
| TargetObject | `HOOK_TARGETOBJECT` | đźš« | (maybe) |
| Encounter | `HOOK_ENCOUNTER` | đźš« | Et tu |
| Encounter | `HOOK_ENCOUNTER` | âś… | - |
| AdjustPoison | `HOOK_ADJUSTPOISON` | đźš« | (maybe) |
| AdjustRads | `HOOK_ADJUSTRADS` | đźš« | (maybe) |
| RollCheck | `HOOK_ROLLCHECK` | đźš« | - |
+18
View File
@@ -0,0 +1,18 @@
# specify the cross compiler
set(CMAKE_C_COMPILER ${CMAKE_SYSTEM_PROCESSOR}-w64-mingw32-gcc)
set(CMAKE_CXX_COMPILER ${CMAKE_SYSTEM_PROCESSOR}-w64-mingw32-g++)
set(CMAKE_RC_COMPILER ${CMAKE_SYSTEM_PROCESSOR}-w64-mingw32-windres)
# where is the target environment
set(CMAKE_FIND_ROOT_PATH /usr/${CMAKE_SYSTEM_PROCESSOR}-w64-mingw32)
# search for programs in the build host directories
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
# for libraries and headers in the target directories
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
# CMake determines how to examine dependencies based on the *host* system, leading to
# a `file unknown error` unless the target platform is explicitly specified.
set(CMAKE_GET_RUNTIME_DEPENDENCIES_PLATFORM "windows+pe")
Binary file not shown.
+6
View File
@@ -98,6 +98,8 @@ auto_quick_save=3
display_bonus_damage=1
;Set to 1 to get notification of karma changes in the notification window
display_karma_changes=0
;Set to 1 to use the hi-res dialog border/background at resolutions above 640x480. Requires art\intrface\HR_ALLTLK.FRM (for example from f2_res.dat)
dialog_border=1
;Set to one to hide areas outside map bounds when using higher than 640x480 resolution, and to zero to disable
enable_high_resolution_stencil=1
;Set to 1 to extend the action points bar to show up to 16 AP instead of 10 (requires iface_apbar_e.frm)
@@ -108,6 +110,8 @@ iface_bar_mode=0
iface_bar_side_art=2
iface_bar_sides_ori=0
iface_bar_width=800
;Whether to load EDG files (HRP format) when loading maps. If loaded, they override default edge clipping and scroll blocking behavior.
edg_support=1
ignore_map_edges=0
;Set to 1 to display numbered dialogue options
numbers_in_dialogue=0
@@ -124,5 +128,7 @@ inventory_columns=2
auto_open_doors=0
; Overrides distance at which walk animation is used when using an object.
use_walk_distance=5
; Allow opening party member inventory from the long-press world menu.
party_trade_from_menu=1
; Allow switching to companions' inventories in loot screens (and barter, in the future)
party_loot_and_barter=1
+100
View File
@@ -1,5 +1,63 @@
#include "test_utils.h"
#include "sfall.h"
#include "dik.h"
variable manual_extra_tag := 0;
variable manual_tag5_color := 2;
procedure print_custom_tag_state(variable tag, variable label) begin
display_msg(label + ": " + is_iface_tag_active(tag));
end
procedure toggle_custom_tag(variable tag, variable label) begin
if (is_iface_tag_active(tag)) then begin
hide_iface_tag(tag);
display_msg(label + " hidden");
end else begin
show_iface_tag(tag);
display_msg(label + " shown");
end
call print_custom_tag_state(tag, label);
end
procedure apply_tag5_color begin
set_iface_tag_text(5, "TAG FIVE", manual_tag5_color);
display_msg(string_format1("custom tag 5 color -> %d", manual_tag5_color));
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_B) then begin
call toggle_custom_tag(5, "custom tag 5");
end else if (key == DIK_C) then begin
manual_tag5_color := manual_tag5_color + 1;
if (manual_tag5_color > 7) then begin
manual_tag5_color := 0;
end
call apply_tag5_color;
end else if (key == DIK_N) then begin
call toggle_custom_tag(6, "custom tag 6");
end else if (key == DIK_M) then begin
if (manual_extra_tag == 0) then begin
display_msg("manual extra tag is not initialized");
return;
end
call toggle_custom_tag(manual_extra_tag, "manual extra tag");
end else if (key == DIK_V) then begin
call print_custom_tag_state(5, "custom tag 5");
call print_custom_tag_state(6, "custom tag 6");
if (manual_extra_tag != 0) then begin
call print_custom_tag_state(manual_extra_tag, "manual extra tag");
end
end
end
procedure start begin
display_msg("Testing iface_tag functions...");
@@ -29,5 +87,47 @@ procedure start begin
// RADIATED
call assertFalse("hide_iface_tag(RADIATED)", is_iface_tag_active(2));
// Default custom box from BoxBarCount.
call apply_tag5_color;
call assertFalse("custom tag 5 starts inactive", is_iface_tag_active(5));
show_iface_tag(5);
call assertTrue("show_iface_tag(custom 5)", is_iface_tag_active(5));
show_iface_tag(5);
call assertTrue("show_iface_tag(custom 5 again)", is_iface_tag_active(5));
hide_iface_tag(5);
call assertFalse("hide_iface_tag(custom 5)", is_iface_tag_active(5));
hide_iface_tag(5);
call assertFalse("hide_iface_tag(custom 5 again)", is_iface_tag_active(5));
set_iface_tag_text(6, "TAG SIX", 6);
call assertFalse("custom tag 6 starts inactive", is_iface_tag_active(6));
show_iface_tag(6);
call assertTrue("show_iface_tag(custom 6)", is_iface_tag_active(6));
show_iface_tag(5);
call assertTrue("custom tag 5 still active", is_iface_tag_active(5));
call assertTrue("custom tag 6 still active", is_iface_tag_active(6));
hide_iface_tag(5);
call assertFalse("hide_iface_tag(custom 5 with 6 active)", is_iface_tag_active(5));
call assertTrue("custom tag 6 remains active", is_iface_tag_active(6));
hide_iface_tag(6);
call assertFalse("hide_iface_tag(custom 6)", is_iface_tag_active(6));
// Dynamically added custom box.
variable extra_tag := add_iface_tag;
call assertTrue("add_iface_tag lower bound", extra_tag >= 10);
call assertTrue("add_iface_tag upper bound", extra_tag <= 126);
manual_extra_tag := extra_tag;
set_iface_tag_text(extra_tag, "EXTRA TAG", 3);
call assertFalse("added custom tag starts inactive", is_iface_tag_active(extra_tag));
show_iface_tag(extra_tag);
call assertTrue("show_iface_tag(added custom)", is_iface_tag_active(extra_tag));
hide_iface_tag(extra_tag);
call assertFalse("hide_iface_tag(added custom)", is_iface_tag_active(extra_tag));
variable next_tag := add_iface_tag;
call assertEquals("add_iface_tag increments", next_tag, extra_tag + 1);
call report_test_results("iface_tag");
display_msg("iface_tag manual test ready: B toggle tag 5, C cycle tag 5 color, N toggle tag 6, M toggle extra tag, V print states");
register_hook_proc(HOOK_KEYPRESS, keypress_handler);
end
+136
View File
@@ -0,0 +1,136 @@
#include "define_lite.h"
#include "sfall.h"
#include "dik.h"
#include "define_extra.h"
#include "lib.inven.h"
#include "test_utils.h"
#define TEST_ITEM_PID 40
#define PARTY_UNIQUE_ID_LOWER_BOUND 18000
#define UNIQUE_ID_LOWER_BOUND 0x10000000
procedure find_random_inventory_item_for_unique_id begin
variable items;
variable length;
variable start;
variable scan;
variable index;
variable item;
variable item_id;
items := inven_as_array(dude_obj);
length := len_array(items);
if (length <= 0) then return 0;
start := random(0, length - 1);
scan := 0;
while (scan < length) do begin
index := start + scan;
if (index >= length) then index -= length;
item := items[index];
item_id := get_object_data(item, OBJ_DATA_ID);
if (item_id < PARTY_UNIQUE_ID_LOWER_BOUND) then begin
return item;
end
scan += 1;
end
return items[start];
end
procedure assign_unique_id_to_random_inventory_item begin
variable item;
variable old_id;
variable new_id;
item := find_random_inventory_item_for_unique_id;
if (item == 0) then begin
display_msg("unique_id manual test: inventory is empty");
return;
end
old_id := get_object_data(item, OBJ_DATA_ID);
new_id := set_unique_id(item);
if (new_id == old_id) then begin
display_msg(string_format3("unique_id manual U: %s pid=%d id unchanged=%d", obj_name(item), obj_pid(item), new_id));
end else begin
display_msg(string_format4("unique_id manual U: %s pid=%d %d->%d", obj_name(item), obj_pid(item), old_id, new_id));
end
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_U) then begin
call assign_unique_id_to_random_inventory_item();
end
end
procedure start begin
variable item_a;
variable item_b;
variable item_c;
variable baseline_total;
variable unique_id_a;
variable unique_id_a_again;
variable unique_id_c;
variable engine_id_a;
variable regular_count;
variable total;
if (not game_loaded) then return;
display_msg("Testing set_unique_id...");
call assertEquals("metarule exists", metarule_exist("set_unique_id"), 1);
baseline_total := obj_is_carrying_obj_pid(dude_obj, TEST_ITEM_PID);
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);
unique_id_a := set_unique_id(item_a);
call assertTrue("unique id lower bound", unique_id_a >= UNIQUE_ID_LOWER_BOUND);
call assertEquals("unique id stored on object", get_object_data(item_a, OBJ_DATA_ID), unique_id_a);
unique_id_a_again := set_unique_id(item_a);
call assertEquals("existing unique id reused", unique_id_a_again, unique_id_a);
add_obj_to_inven(dude_obj, item_a);
add_obj_to_inven(dude_obj, item_b);
call assertEquals("unique item stays separate", obj_is_carrying_obj(dude_obj, item_a), 1);
regular_count := obj_is_carrying_obj(dude_obj, item_b);
call assertTrue("regular item still exists after unique item add", regular_count >= 1);
total := obj_is_carrying_obj_pid(dude_obj, TEST_ITEM_PID);
call assertEquals("unique and regular items add two total items", total, baseline_total + 2);
unique_id_c := set_unique_id(item_c);
call assertNotEquals("second unique item gets new id", unique_id_c, unique_id_a);
add_obj_to_inven(dude_obj, item_c);
call assertEquals("second unique item stays separate", obj_is_carrying_obj(dude_obj, item_c), 1);
total := obj_is_carrying_obj_pid(dude_obj, TEST_ITEM_PID);
call assertEquals("pid total counts all three added items", total, baseline_total + 3);
engine_id_a := unset_unique_id(item_a);
call assertTrue("unset returns engine id", engine_id_a < UNIQUE_ID_LOWER_BOUND);
call assertNotEquals("unset replaces unique id", engine_id_a, unique_id_a);
call assertEquals("engine id stored on object", get_object_data(item_a, OBJ_DATA_ID), engine_id_a);
call assertEquals("remove unique item a", rm_mult_objs_from_inven(dude_obj, item_a, 1), 1);
call assertEquals("remove regular item b", rm_mult_objs_from_inven(dude_obj, item_b, 1), 1);
call assertEquals("remove unique item c", rm_mult_objs_from_inven(dude_obj, item_c, 1), 1);
destroy_object(item_a);
destroy_object(item_b);
destroy_object(item_c);
call report_test_results("unique_id");
display_msg("unique_id manual test ready: press U to assign/print a unique id for a random inventory item");
register_hook_proc(HOOK_KEYPRESS, keypress_handler);
end
+93
View File
@@ -0,0 +1,93 @@
#include "sfall.h"
#include "dik.h"
#include "../test_utils.h"
#define AREA_ARROYO (0)
#define AREA_RND_CITY (28)
#define MAP_RND_CITY1 (68)
variable mode := 0;
variable pending_event := -1;
variable expected_map := -1;
variable arroyo_known_before := 0;
variable car_town_before := 0;
procedure mode_name(variable value) begin
if (value == 0) then return "observe";
if (value == 1) then return "reroute_worldmap_enter";
if (value == 2) then return "cancel_encounters";
return "unknown";
end
procedure log_mode begin
display_msg(string_format2("encounter mode=%d (%s)", mode, mode_name(mode)));
end
procedure begin_pending_assertions(variable event_type) begin
pending_event := event_type;
expected_map := MAP_RND_CITY1;
arroyo_known_before := town_known(AREA_ARROYO);
car_town_before := car_current_town;
end
procedure encounter_handler begin
variable
event_type := get_sfall_arg_at(0),
map_id := get_sfall_arg_at(1),
is_special := get_sfall_arg_at(2),
table_num := get_sfall_arg_at(3),
entry_num := get_sfall_arg_at(4);
display_msg(string_format5("encounter event=%d map=%d special=%d table=%d entry=%d", event_type, map_id, is_special, table_num, entry_num));
if (mode == 1 and event_type == 1) then begin
call begin_pending_assertions(event_type);
display_msg(string_format1("encounter rerouting worldmap entry to map %d", expected_map));
set_sfall_return(expected_map);
end else if (mode == 2 and event_type == 0) then begin
display_msg("canceling encounter");
set_sfall_return(-1);
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_X) then return;
mode := mode + 1;
if (mode > 2) then mode := 0;
call log_mode;
end
procedure start begin
if (not game_loaded) then return;
display_msg("encounter manual test ready:");
display_msg("press X to cycle mode: observe, reroute worldmap enter, cancel encounters");
display_msg("mode 1: enter any location from the world map");
display_msg("mode 2: cancel encounters");
call log_mode;
register_hook_proc(HOOK_KEYPRESS, keypress_handler);
register_hook_proc(HOOK_ENCOUNTER, encounter_handler);
end
procedure map_enter_p_proc begin
if (expected_map == -1) then return;
call assertEquals("encounter reroute loaded expected map", cur_map_index, expected_map);
call assertEquals("encounter reroute load area matches rerouted map", map_get_load_area, AREA_RND_CITY);
call assertEquals("encounter reroute does not mutate Arroyo known state", town_known(AREA_ARROYO), arroyo_known_before);
call assertEquals("encounter reroute preserves car town", car_current_town, car_town_before);
if (pending_event == 1) then
call report_test_results("hook_encounter_worldmap_enter");
pending_event := -1;
expected_map := -1;
end
+153
View File
@@ -0,0 +1,153 @@
#include "sfall.h"
#include "dik.h"
#include "define_lite.h"
#include "../test_utils.h"
variable seen_adjustfid := 0;
variable seen_invenwield := 0;
variable seen_invenwield_remove := 0;
variable seen_canuseweapon_player := 0;
variable seen_canuseweapon_ai := 0;
variable temp_container := 0;
variable invenwield_override_enabled := false;
variable canuseweapon_override := -1;
procedure invenwield_mode_name begin
if (invenwield_override_enabled) then return "suppress";
return "engine";
end
procedure canuseweapon_mode_name(variable value) begin
if (value == -1) then return "engine";
if (value == 1) then return "on";
if (value == 0) then return "off";
return "unknown";
end
procedure cycle_canuseweapon_override begin
if (canuseweapon_override == -1) then canuseweapon_override := 1;
else if (canuseweapon_override == 1) then canuseweapon_override := 0;
else canuseweapon_override := -1;
display_msg(string_format1("hook_item_compat canuseweapon mode=%s", canuseweapon_mode_name(canuseweapon_override)));
end
procedure adjustfid_handler begin
variable args := get_sfall_args;
call assertEquals("adjustfid arg count", len_array(args), 2);
call assertEquals("adjustfid arg0 type", typeof(args[0]), VALTYPE_INT);
call assertEquals("adjustfid arg1 type", typeof(args[1]), VALTYPE_INT);
seen_adjustfid := seen_adjustfid + 1;
display_msg(string_format2("adjustfid vanilla=%d modified=%d", args[0], args[1]));
end
procedure invenwield_handler begin
variable
args := get_sfall_args,
item := args[1],
slot := args[2],
is_wield := args[3],
is_remove := args[4];
call assertEquals("invenwield arg count", len_array(args), 5);
call assertTrue("invenwield slot range", slot >= 0 and slot <= 2);
call assertTrue("invenwield is_wield flag", is_wield == 0 or is_wield == 1);
call assertTrue("invenwield is_remove flag", is_remove == 0 or is_remove == 1);
seen_invenwield := seen_invenwield + 1;
if (is_remove) then begin
seen_invenwield_remove := seen_invenwield_remove + 1;
end
display_msg(string_format4("invenwield item=%s slot=%d wield=%d remove=%d", obj_name(item), slot, is_wield, is_remove));
if (invenwield_override_enabled) then begin
set_sfall_return(0);
end
end
procedure canuseweapon_handler begin
variable
args := get_sfall_args,
hit_mode := args[2],
result := args[3],
weapon := args[1];
call assertEquals("canuseweapon arg count", len_array(args), 4);
call assertTrue("canuseweapon result flag", result == 0 or result == 1);
if (hit_mode == -1) then begin
seen_canuseweapon_player := seen_canuseweapon_player + 1;
end else begin
seen_canuseweapon_ai := seen_canuseweapon_ai + 1;
end
display_msg(string_format3("canuseweapon item=%s hitmode=%d result=%d", obj_name(weapon), hit_mode, result));
if (canuseweapon_override != -1) then begin
set_sfall_return(canuseweapon_override);
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_Z) then begin
display_msg("hook_item_compat: unwield armor");
unwield_slot(dude_obj, INVEN_TYPE_WORN);
end else if (key == DIK_X) then begin
display_msg("hook_item_compat: unwield right hand");
unwield_slot(dude_obj, INVEN_TYPE_RIGHT_HAND);
end else if (key == DIK_C) then begin
display_msg("hook_item_compat: unwield left hand");
unwield_slot(dude_obj, INVEN_TYPE_LEFT_HAND);
end else if (key == DIK_V) then begin
display_msg("hook_item_compat: toggle active hand");
toggle_active_hand;
end else if (key == DIK_N) then begin
display_msg("hook_item_compat: move inventory to temp container");
if (temp_container == 0) then begin
temp_container := create_object_sid(467, 0, 0, -1);
end
move_obj_inven_to_obj(dude_obj, temp_container);
end else if (key == DIK_M) then begin
invenwield_override_enabled := not invenwield_override_enabled;
display_msg(string_format1("hook_item_compat invenwield mode=%s", invenwield_mode_name));
end else if (key == DIK_G) then begin
call cycle_canuseweapon_override;
end else if (key == DIK_B) then begin
display_msg(string_format7("hook_item_compat summary inven_mode=%s canuse_mode=%s adjust=%d inven=%d remove=%d player=%d ai=%d",
invenwield_mode_name,
canuseweapon_mode_name(canuseweapon_override),
seen_adjustfid,
seen_invenwield,
seen_invenwield_remove,
seen_canuseweapon_player,
seen_canuseweapon_ai));
call assertTrue("adjustfid observed", seen_adjustfid > 0);
call assertTrue("invenwield observed", seen_invenwield > 0);
call assertTrue("player canuseweapon observed", seen_canuseweapon_player > 0);
call report_test_results("hook_item_compat");
end
end
procedure start begin
if (not game_loaded) then return;
display_msg("hook_item_compat ready:");
display_msg("open inventory/barter to trigger adjustfid");
display_msg("press Z armor, X right, C left, V swap hand");
display_msg("press N disposable-save inventory move, M invenwield mode");
display_msg("press G canuseweapon mode (engine/on/off), B report");
register_hook_proc(HOOK_KEYPRESS, keypress_handler);
register_hook_proc(HOOK_INVENWIELD, invenwield_handler);
register_hook_proc(HOOK_ADJUSTFID, adjustfid_handler);
register_hook_proc(HOOK_CANUSEWEAPON, canuseweapon_handler);
end
+83
View File
@@ -0,0 +1,83 @@
#include "sfall.h"
#include "dik.h"
#include "lib.arrays.h"
variable steal_mode := 0;
procedure steal_mode_name(variable mode) begin
if (mode == 1) then return "force_success";
if (mode == 2) then return "caught_fail";
if (mode == 3) then return "silent_fail";
if (mode == 4) then return "xp_override";
return "vanilla";
end
procedure steal_handler begin
variable
args := get_sfall_args,
thief := args[0],
target := args[1],
item := args[2],
is_planting := args[3],
quantity := args[4],
thief_name := "<null>",
target_name := "<null>",
item_name := "<null>";
if (thief) then thief_name := obj_name(thief);
if (target) then target_name := obj_name(target);
if (item) then item_name := obj_name(item);
display_msg(string_format6("steal mode=%s thief=%s target=%s item=%s planting=%d qty=%d",
steal_mode_name(steal_mode),
thief_name,
target_name,
item_name,
is_planting,
quantity));
display_msg(string_format1("steal args=%s", debug_array_str(args)));
if (thief != dude_obj) then return;
if (steal_mode == 1) then begin
display_msg("steal forcing success");
display_msg(sprintf(mstr_skill(571 + is_planting * 2), item_name));
set_sfall_return(1);
end else if (steal_mode == 2) then begin
display_msg("steal forcing caught failure");
display_msg(sprintf(mstr_skill(570 + is_planting * 2), item_name));
set_sfall_return(0);
end else if (steal_mode == 3) then begin
display_msg("steal forcing silent failure");
set_sfall_return(2);
end else if (steal_mode == 4) then begin
display_msg("steal forcing success with xp override 77");
display_msg(sprintf(mstr_skill(571 + is_planting * 2), item_name));
set_sfall_return(1);
set_sfall_return(77);
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_X) then return;
steal_mode += 1;
if (steal_mode > 4) then steal_mode := 0;
display_msg(string_format1("steal mode -> %s", steal_mode_name(steal_mode)));
end
procedure start begin
if (not game_loaded) then return;
display_msg("steal manual test ready: press X to cycle vanilla / force_success / caught_fail / silent_fail / xp_override");
display_msg("open a steal screen and move an item in either direction; mode 4 should award 77 XP for a successful action");
register_hook_proc(HOOK_KEYPRESS, keypress_handler);
register_hook_proc(HOOK_STEAL, steal_handler);
end
+145
View File
@@ -0,0 +1,145 @@
#include "sfall.h"
#include "dik.h"
#include "lib.arrays.h"
#include "../test_utils.h"
variable useskill_mode := 0;
variable useskillon_calls := 0;
variable useskill_calls := 0;
variable last_useskillon_skill := -1;
variable last_useskill_skill := -1;
procedure skill_name(variable skill) begin
if (skill == SKILL_FIRST_AID) then return "first_aid";
if (skill == SKILL_DOCTOR) then return "doctor";
if (skill == SKILL_LOCKPICK) then return "lockpick";
if (skill == SKILL_STEAL) then return "steal";
if (skill == SKILL_TRAPS) then return "traps";
if (skill == SKILL_SCIENCE) then return "science";
if (skill == SKILL_REPAIR) then return "repair";
if (skill == SKILL_SNEAK) then return "sneak";
return string_format1("skill_%d", skill);
end
procedure obj_name_safe(variable obj) begin
if (obj == 0) then return "<null>";
return obj_name(obj);
end
procedure useskill_mode_name(variable mode) begin
if (mode == 1) then return "useskillon_cancel";
if (mode == 2) then return "useskillon_user_override";
if (mode == 3) then return "useskillon_allow_combat";
if (mode == 4) then return "useskill_override";
if (mode == 5) then return "useskill_cancel_steal";
return "vanilla";
end
procedure print_mode begin
display_msg(string_format1("useskill mode -> %s", useskill_mode_name(useskill_mode)));
end
procedure useskillon_handler begin
variable
args := get_sfall_args,
user := args[0],
target := args[1],
skill := args[2];
useskillon_calls++;
last_useskillon_skill := skill;
display_msg(string_format("useskillon mode=%s user=%s target=%s skill=%s",
useskill_mode_name(useskill_mode),
obj_name_safe(user),
obj_name_safe(target),
skill_name(skill)));
display_msg(string_format1("useskillon args=%s", debug_array_str(args)));
call assertEquals("useskillon arg count", len_array(args), 3);
call assertTrue("useskillon user is set", user != 0);
call assertTrue("useskillon target is set", target != 0);
if (useskill_mode == 1) then begin
display_msg("useskillon cancelling skill action");
set_sfall_return(-1);
end else if (useskill_mode == 2) then begin
if (skill == SKILL_STEAL) then begin
display_msg("useskillon returning dude_obj for Steal; engine should ignore this return value");
end else begin
display_msg("useskillon returning dude_obj as user override");
end
set_sfall_return(dude_obj);
end else if (useskill_mode == 3) then begin
display_msg("useskillon allowing combat use");
set_sfall_return(0);
set_sfall_return(1);
end
end
procedure useskill_handler begin
variable
args := get_sfall_args,
user := args[0],
target := args[1],
skill := args[2],
skill_bonus := args[3];
useskill_calls++;
last_useskill_skill := skill;
display_msg(string_format("useskill mode=%s user=%s target=%s skill=%s bonus=%d",
useskill_mode_name(useskill_mode),
obj_name_safe(user),
obj_name_safe(target),
skill_name(skill),
skill_bonus));
display_msg(string_format1("useskill args=%s", debug_array_str(args)));
call assertEquals("useskill arg count", len_array(args), 4);
call assertTrue("useskill user is set", user != 0);
call assertTrue("useskill target is set", target != 0);
if (useskill_mode == 4) then begin
display_msg("useskill overriding hard-coded handler with result 1");
set_sfall_return(1);
end else if (useskill_mode == 5) then begin
if (skill == SKILL_STEAL) then begin
display_msg("useskill canceling steal by overriding hard-coded handler with result 0");
set_sfall_return(0);
end
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_U) then begin
useskill_mode += 1;
if (useskill_mode > 5) then useskill_mode := 0;
call print_mode;
end else if (key == DIK_Y) then begin
display_msg(string_format("useskill stats: on=%d skill=%s, handler=%d skill=%s",
useskillon_calls,
skill_name(last_useskillon_skill),
useskill_calls,
skill_name(last_useskill_skill)));
call report_test_results("hook_useskill");
end
end
procedure start begin
if (not game_loaded) then return;
display_msg("useskill manual test ready");
display_msg("press U to cycle modes: vanilla / useskillon_cancel / useskillon_user_override / useskillon_allow_combat / useskill_override / useskill_cancel_steal");
display_msg("use a Skilldex skill or a medical kit on a valid target; press Y to report assertions");
register_hook_proc(HOOK_KEYPRESS, keypress_handler);
register_hook_proc(HOOK_USESKILLON, useskillon_handler);
register_hook_proc(HOOK_USESKILL, useskill_handler);
end
+30 -27
View File
@@ -46,13 +46,13 @@ typedef enum ScienceRepairTargetType {
SCIENCE_REPAIR_TARGET_TYPE_ANYONE,
} ScienceRepairTargetType;
// 0x5106D0
// 0x5106D0 action_in_explode
static bool _action_in_explode = false;
// 0x5106D4
// 0x5106D4 rotation
int rotation;
// 0x5106E0
// 0x5106E0 death_2
static const int gNormalDeathAnimations[DAMAGE_TYPE_COUNT] = {
ANIM_DANCING_AUTOFIRE,
ANIM_SLICED_IN_HALF,
@@ -63,7 +63,7 @@ static const int gNormalDeathAnimations[DAMAGE_TYPE_COUNT] = {
ANIM_BIG_HOLE,
};
// 0x5106FC
// 0x5106FC death_3
static const int gMaximumBloodDeathAnimations[DAMAGE_TYPE_COUNT] = {
ANIM_CHUNKS_OF_FLESH,
ANIM_SLICED_IN_HALF,
@@ -519,7 +519,7 @@ int _show_death(Object* obj, int anim)
// Animates damage to extras in a given attack (secondary targets).
//
// 0x410FEC _show_damage_extras
// 0x410FEC show_damage_extras
int showDamageToExtras(Attack* attack)
{
for (int index = 0; index < attack->extrasLength; index++) {
@@ -1262,20 +1262,18 @@ int actionPickUp(Object* critter, Object* item)
return reg_anim_end();
}
// TODO: Looks like the name is a little misleading, container can only be a
// critter, which is enforced by this function as well as at the call sites.
// Used to loot corpses, so probably should be something like actionLootCorpse.
// Check if it can be called with a living critter.
// This is primarily used to loot critters, namely corpses. I don't believe it can
// be called with a living creature, but am not 100% certain.
//
// 0x4123E8
int _action_loot_container(Object* critter, Object* container)
// 0x4123E8 was _action_loot_container
int actionLootCritter(Object* critter, Object* target)
{
if (FID_TYPE(container->fid) != OBJ_TYPE_CRITTER) {
if (FID_TYPE(target->fid) != OBJ_TYPE_CRITTER) {
return -1;
}
// SFALL: Fix for trying to loot corpses with the "NoSteal" flag.
if (critterFlagCheck(container->pid, CRITTER_NO_STEAL)) {
if (critterFlagCheck(target->pid, CRITTER_NO_STEAL)) {
return -1;
}
@@ -1288,20 +1286,20 @@ int _action_loot_container(Object* critter, Object* container)
if (isInCombat()) {
reg_anim_begin(ANIMATION_REQUEST_RESERVED);
animationRegisterMoveToObject(critter, container, critter->data.critter.combat.ap, 0);
animationRegisterMoveToObject(critter, target, critter->data.critter.combat.ap, 0);
} else {
reg_anim_begin(critter == gDude ? ANIMATION_REQUEST_RESERVED : ANIMATION_REQUEST_UNRESERVED);
if (objectWithinWalkDistance(critter, container)) {
animationRegisterMoveToObject(critter, container, -1, 0);
if (objectWithinWalkDistance(critter, target)) {
animationRegisterMoveToObject(critter, target, -1, 0);
} else {
animationRegisterRunToObject(critter, container, -1, 0);
animationRegisterRunToObject(critter, target, -1, 0);
}
}
animationRegisterCallbackForced(critter, container, (AnimationCallback*)_is_next_to, -1);
animationRegisterCallback(critter, container, (AnimationCallback*)checkSceneryUseActionPointCost, -1);
animationRegisterCallback(critter, container, (AnimationCallback*)scriptsRequestLooting, -1);
animationRegisterCallbackForced(critter, target, (AnimationCallback*)_is_next_to, -1);
animationRegisterCallback(critter, target, (AnimationCallback*)checkSceneryUseActionPointCost, -1);
animationRegisterCallback(critter, target, (AnimationCallback*)scriptsRequestLooting, -1);
return reg_anim_end();
}
@@ -1338,10 +1336,15 @@ static int _action_use_skill_in_combat_error(Object* critter)
// 0x41255C
int actionUseSkill(Object* user, Object* target, int skill)
{
UseSkillOnHookResult hookResult = scriptHooks_UseSkillOn(&user, target, skill);
if (!hookResult.shouldContinue) {
return -1;
}
switch (skill) {
case SKILL_FIRST_AID:
case SKILL_DOCTOR:
if (isInCombat()) {
if (isInCombat() && !hookResult.allowInCombat) {
// NOTE: Uninline.
return _action_use_skill_in_combat_error(user);
}
@@ -1351,7 +1354,7 @@ int actionUseSkill(Object* user, Object* target, int skill)
}
break;
case SKILL_LOCKPICK:
if (isInCombat()) {
if (isInCombat() && !hookResult.allowInCombat) {
// NOTE: Uninline.
return _action_use_skill_in_combat_error(user);
}
@@ -1362,7 +1365,7 @@ int actionUseSkill(Object* user, Object* target, int skill)
break;
case SKILL_STEAL:
if (isInCombat()) {
if (isInCombat() && !hookResult.allowInCombat) {
// NOTE: Uninline.
return _action_use_skill_in_combat_error(user);
}
@@ -1377,7 +1380,7 @@ int actionUseSkill(Object* user, Object* target, int skill)
break;
case SKILL_TRAPS:
if (isInCombat()) {
if (isInCombat() && !hookResult.allowInCombat) {
// NOTE: Uninline.
return _action_use_skill_in_combat_error(user);
}
@@ -1389,7 +1392,7 @@ int actionUseSkill(Object* user, Object* target, int skill)
break;
case SKILL_SCIENCE:
case SKILL_REPAIR:
if (isInCombat()) {
if (isInCombat() && !hookResult.allowInCombat) {
// NOTE: Uninline.
return _action_use_skill_in_combat_error(user);
}
@@ -1430,9 +1433,9 @@ int actionUseSkill(Object* user, Object* target, int skill)
// Performer is either dude, or party member who's best at the specified
// skill in entire party, and this skill is his/her own best.
Object* performer = gDude;
Object* performer = hookResult.userOverridden ? user : gDude;
if (user == gDude) {
if (user == gDude && !hookResult.userOverridden) {
Object* partyMember = partyMemberGetBestInSkill(skill);
if (partyMember == gDude) {
+1 -1
View File
@@ -12,7 +12,7 @@ int _action_attack(Attack* attack);
int _action_use_an_item_on_object(Object* user, Object* targetObj, Object* item);
int _action_use_an_object(Object* user, Object* targetObj);
int actionPickUp(Object* critter, Object* item);
int _action_loot_container(Object* critter, Object* container);
int actionLootCritter(Object* critter, Object* target);
int _action_skill_use(int skill);
int actionUseSkill(Object* user, Object* target, int skill);
bool _is_hit_from_front(const Object* attacker, const Object* defender);
+11 -11
View File
@@ -309,39 +309,39 @@ static unsigned int animationComputeTicksPerFrame(Object* object, int fid);
static void reportOverloaded(Object* critter);
// 0x510718
// 0x510718 curr_sad
static int gAnimationCurrentSad = 0;
// 0x51071C
// 0x51071C curr_anim_set
static int gAnimationSequenceCurrentIndex = -1;
// 0x510720
// 0x510720 anim_in_init
static bool gAnimationInInit = false;
// 0x510724
// 0x510724 anim_in_anim_stop
static bool gAnimationInStop = false;
// 0x510728
// 0x510728 anim_in_bk
static bool _anim_in_bk = false;
// 0x530014
// 0x530014 sad
static AnimationSad gAnimationSads[ANIMATION_SAD_LIST_CAPACITY];
#define PATH_NODE_CAPACITY 10000
// 0x542FD4
// 0x542FD4 dad
static PathNode gClosedPathNodeList[PATH_NODE_CAPACITY];
// 0x54CC14
// 0x54CC14 anim_set
static AnimationSequence gAnimationSequences[32];
// 0x561814
// 0x561814 seen_tile
static unsigned char gPathfinderProcessedTiles[5000];
// 0x562B9C
// 0x562B9C child_path
static PathNode gOpenPathNodeList[PATH_NODE_CAPACITY];
// 0x56C7DC
// 0x56C7DC curr_anim_counter
static int gAnimationDescriptionCurrentIndex;
// anim_init
+17 -17
View File
@@ -44,19 +44,19 @@ static int artReadHeader(Art* art, File* stream);
static int artGetDataSize(const Art* art);
static int paddingForSize(int size);
// 0x5002D8
// 0x5002D8 str2
static char gDefaultJumpsuitMaleFileName[] = "hmjmps";
// 0x05002E0
// 0x05002E0 aHfjmps
static char gDefaultJumpsuitFemaleFileName[] = "hfjmps";
// 0x5002E8
// 0x5002E8 aHmwarr
static char gDefaultTribalMaleFileName[] = "hmwarr";
// 0x5002F0
// 0x5002F0 aHfprim
static char gDefaultTribalFemaleFileName[] = "hfprim";
// 0x510738
// 0x510738 art
static ArtListDescription gArtListDescriptions[OBJ_TYPE_COUNT] = {
{ 0, "items", nullptr, nullptr, 0 },
{ 0, "critters", nullptr, nullptr, 0 },
@@ -74,18 +74,18 @@ static ArtListDescription gArtListDescriptions[OBJ_TYPE_COUNT] = {
// This flag denotes that localized arts should be looked up first. Used
// together with [gArtLanguage].
//
// 0x510898
// 0x510898 darn_foreigners
static bool gArtLanguageInitialized = false;
// 0x51089C
// 0x51089C head1
static const char* _head1 = "gggnnnbbbgnb";
// 0x5108A0
// 0x5108A0 head2
static const char* _head2 = "vfngfbnfvppp";
// Current native look base fid.
//
// 0x5108A4
// 0x5108A4 art_vault_guy_num
int _art_vault_guy_num = 0;
// Base fids for unarmored dude.
@@ -99,37 +99,37 @@ int _art_vault_guy_num = 0;
// been accessed differently in 0x49F984, which clearly uses look type as an
// index, not gender.
//
// 0x5108A8
// 0x5108A8 art_vault_person_nums
int _art_vault_person_nums[DUDE_NATIVE_LOOK_COUNT][GENDER_COUNT];
// Index of "grid001.frm" in tiles.lst.
//
// 0x5108B8
// 0x5108B8 art_mapper_blank_tile
static int _art_mapper_blank_tile = 1;
// Non-english language name.
//
// This value is used as a directory name to display localized arts.
//
// 0x56C970
// 0x56C970 darn_foreign_sub_path
static char gArtLanguage[32];
// 0x56C990
// 0x56C990 art_cache
Cache gArtCache;
// 0x56C9E4
// 0x56C9E4 art_name
static char _art_name[COMPAT_MAX_PATH];
// head_info
// 0x56CAE8
// 0x56CAE8 head_info
static HeadDescription* gHeadDescriptions;
// anon_alias
// 0x56CAEC
// 0x56CAEC anon_alias
static int* _anon_alias;
// artCritterFidShouldRunData
// 0x56CAF0
// 0x56CAF0 artCritterFidShouldRunData
static int* gArtCritterFidShoudRunData;
static std::unordered_map<std::string, std::shared_ptr<NamedCacheEntry>> gNamedArtCache;
+6 -3
View File
@@ -47,13 +47,13 @@ static int audioSoundDecoderReadHandler(void* data, void* buf, unsigned int size
static bool audioIsWavePath(const char* filePath);
static bool audioDecodeWave(File* stream, AudioFileInfo* info, unsigned char** dataPtr, int* sizePtr);
// 0x5108BC
// 0x5108BC queryCompressedFunc
static AudioQueryCompressedFunc* queryCompressedFunc = defaultCompressionFunc;
// 0x56CB00
// 0x56CB00 numAudio
static int gAudioListLength;
// 0x56CB04
// 0x56CB04 audio
static Audio* gAudioList;
// 0x41A2B0
@@ -263,6 +263,9 @@ int audioOpen(const char* fname, AudioFileInfo* info, bool* isMemoryBackedPtr)
audioFile->bitsPerSample = 16;
if (info != nullptr) {
// Do not propagate decoder-reported channels by default. Legacy
// speech/lips paths rely on the caller's existing mono/stereo
// choice unless they explicitly opt into stereo before soundLoad.
info->sampleRate = audioFile->sampleRate;
info->bitsPerSample = audioFile->bitsPerSample;
}
+7 -2
View File
@@ -10,6 +10,8 @@ namespace fallout {
#define AUDIO_ENGINE_SOUND_BUFFERS 8
static constexpr int kAudioEngineTargetSampleRate = 44100;
struct AudioEngineSoundBuffer {
bool active;
unsigned int size;
@@ -96,14 +98,17 @@ static void audioEngineMixin(void* userData, Uint8* stream, int length)
bool audioEngineInit()
{
SDL_AudioSpec desiredSpec;
desiredSpec.freq = 22050;
// Request 44.1 kHz output so 44.1 kHz ACM music can play without being
// downsampled by the mixer on the common path.
desiredSpec.freq = kAudioEngineTargetSampleRate;
desiredSpec.format = AUDIO_S16;
desiredSpec.channels = 2;
desiredSpec.samples = 1024;
desiredSpec.callback = audioEngineMixin;
gAudioEngineDeviceId = SDL_OpenAudioDevice(nullptr, 0, &desiredSpec, &gAudioEngineSpec, SDL_AUDIO_ALLOW_ANY_CHANGE);
if (gAudioEngineDeviceId == -1) {
if (gAudioEngineDeviceId == 0) {
gAudioEngineDeviceId = -1;
return false;
}
-262
View File
@@ -1,262 +0,0 @@
#include "audio_file.h"
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include "debug.h"
#include "memory_manager.h"
#include "platform_compat.h"
#include "sound.h"
#include "sound_decoder.h"
namespace fallout {
typedef enum AudioFileFlags {
AUDIO_FILE_IN_USE = 0x01,
AUDIO_FILE_COMPRESSED = 0x02,
} AudioFileFlags;
typedef struct AudioFile {
int flags;
FILE* stream;
SoundDecoder* soundDecoder;
int fileSize;
int sampleRate;
int channels;
int position;
} AudioFile;
static bool defaultCompressionFunc(char* filePath);
static int audioFileSoundDecoderReadHandler(void* data, void* buffer, unsigned int size);
// 0x5108C0
static AudioFileQueryCompressedFunc* queryCompressedFunc = defaultCompressionFunc;
// 0x56CB10
static AudioFile* gAudioFileList;
// 0x56CB14
static int gAudioFileListLength;
// 0x41A850
static bool defaultCompressionFunc(char* filePath)
{
char* pch = strrchr(filePath, '.');
if (pch != nullptr) {
strcpy(pch + 1, "raw");
}
return false;
}
// 0x41A870
static int audioFileSoundDecoderReadHandler(void* data, void* buffer, unsigned int size)
{
return fread(buffer, 1, size, reinterpret_cast<FILE*>(data));
}
// 0x41A88C
int audioFileOpen(const char* fname, AudioFileInfo* info, bool* isMemoryBackedPtr)
{
char path[COMPAT_MAX_PATH];
strcpy(path, fname);
int compression;
if (queryCompressedFunc(path)) {
compression = 2;
} else {
compression = 0;
}
FILE* stream = compat_fopen(path, "rb");
if (stream == nullptr) {
return -1;
}
int index;
for (index = 0; index < gAudioFileListLength; index++) {
if ((gAudioFileList[index].flags & AUDIO_FILE_IN_USE) == 0) {
break;
}
}
if (index == gAudioFileListLength) {
if (gAudioFileList != nullptr) {
gAudioFileList = (AudioFile*)internal_realloc_safe(gAudioFileList, sizeof(*gAudioFileList) * (gAudioFileListLength + 1), __FILE__, __LINE__); // "..\int\audiof.c", 207
} else {
gAudioFileList = (AudioFile*)internal_malloc_safe(sizeof(*gAudioFileList), __FILE__, __LINE__); // "..\int\audiof.c", 209
}
gAudioFileListLength++;
}
AudioFile* audioFile = &(gAudioFileList[index]);
audioFile->flags = AUDIO_FILE_IN_USE;
audioFile->stream = stream;
if (compression == 2) {
audioFile->flags |= AUDIO_FILE_COMPRESSED;
audioFile->soundDecoder = soundDecoderInit(audioFileSoundDecoderReadHandler, audioFile->stream, &(audioFile->channels), &(audioFile->sampleRate), &(audioFile->fileSize));
audioFile->fileSize *= 2;
if (info != nullptr) {
info->sampleRate = audioFile->sampleRate;
info->bitsPerSample = 16;
}
if (isMemoryBackedPtr != nullptr) {
*isMemoryBackedPtr = false;
}
} else {
audioFile->fileSize = getFileSize(stream);
if (info != nullptr) {
info->bitsPerSample = 8;
}
if (isMemoryBackedPtr != nullptr) {
*isMemoryBackedPtr = false;
}
}
audioFile->position = 0;
return index + 1;
}
// 0x41AAA0
int audioFileClose(int handle)
{
AudioFile* audioFile = &(gAudioFileList[handle - 1]);
fclose(audioFile->stream);
if ((audioFile->flags & AUDIO_FILE_COMPRESSED) != 0) {
soundDecoderFree(audioFile->soundDecoder);
}
// Reset audio file (which also resets it's use flag).
memset(audioFile, 0, sizeof(*audioFile));
return 0;
}
// 0x41AB08
int audioFileRead(int handle, void* buffer, unsigned int size)
{
AudioFile* ptr = &(gAudioFileList[handle - 1]);
int bytesRead;
if ((ptr->flags & AUDIO_FILE_COMPRESSED) != 0) {
bytesRead = soundDecoderDecode(ptr->soundDecoder, buffer, size);
} else {
bytesRead = fread(buffer, 1, size, ptr->stream);
}
ptr->position += bytesRead;
return bytesRead;
}
// 0x41AB74
long audioFileSeek(int handle, long offset, int origin)
{
void* buf;
int remaining;
int targetPosition;
AudioFile* audioFile = &(gAudioFileList[handle - 1]);
switch (origin) {
case SEEK_SET:
targetPosition = offset;
break;
case SEEK_CUR:
targetPosition = audioFile->fileSize + offset;
break;
case SEEK_END:
targetPosition = audioFile->position + offset;
break;
default:
assert(false && "Should be unreachable");
}
if ((audioFile->flags & AUDIO_FILE_COMPRESSED) != 0) {
if (targetPosition <= audioFile->position) {
soundDecoderFree(audioFile->soundDecoder);
fseek(audioFile->stream, 0, 0);
audioFile->soundDecoder = soundDecoderInit(audioFileSoundDecoderReadHandler, audioFile->stream, &(audioFile->channels), &(audioFile->sampleRate), &(audioFile->fileSize));
audioFile->fileSize *= 2;
audioFile->position = 0;
if (targetPosition != 0) {
buf = internal_malloc_safe(4096, __FILE__, __LINE__); // "..\int\audiof.c", 364
while (targetPosition > 4096) {
audioFileRead(handle, buf, 4096);
targetPosition -= 4096;
}
if (targetPosition != 0) {
audioFileRead(handle, buf, targetPosition);
}
internal_free_safe(buf, __FILE__, __LINE__); // "..\int\audiof.c", 370
}
} else {
buf = internal_malloc_safe(0x400, __FILE__, __LINE__); // "..\int\audiof.c", 316
remaining = audioFile->position - targetPosition;
while (remaining > 1024) {
audioFileRead(handle, buf, 1024);
remaining -= 1024;
}
if (remaining != 0) {
audioFileRead(handle, buf, remaining);
}
// TODO: Obiously leaks memory.
}
return audioFile->position;
}
return fseek(audioFile->stream, offset, origin);
}
// 0x41AD20
long audioFileGetSize(int handle)
{
AudioFile* audioFile = &(gAudioFileList[handle - 1]);
return audioFile->fileSize;
}
// 0x41AD3C
long audioFileTell(int handle)
{
AudioFile* audioFile = &(gAudioFileList[handle - 1]);
return audioFile->position;
}
// AudiofWrite
// 0x41AD58
int audioFileWrite(int handle, const void* buffer, unsigned int size)
{
debugPrint("AudiofWrite shouldn't be ever called\n");
return 0;
}
// 0x41AD68
int audioFileInit(AudioFileQueryCompressedFunc* func)
{
queryCompressedFunc = func;
gAudioFileList = nullptr;
gAudioFileListLength = 0;
return soundSetDefaultFileIO(audioFileOpen, audioFileClose, audioFileRead, audioFileWrite, audioFileSeek, audioFileTell, audioFileGetSize);
}
// 0x41ADAC
void audioFileExit()
{
if (gAudioFileList != nullptr) {
internal_free_safe(gAudioFileList, __FILE__, __LINE__); // "..\int\audiof.c", 405
}
gAudioFileListLength = 0;
gAudioFileList = nullptr;
}
} // namespace fallout

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