Compare commits

...
Author SHA1 Message Date
Mike Klaas 5cd34a745c Harden android startup path 2026-04-04 08:18:51 -07:00
Mike Klaas a98d9d6c26 Implement HOOK_INVENTORYMOVE (#351) 2026-04-03 22:08:25 -07:00
Mike Klaas 0a64cf4f6b Partially deobfuscate worldmap.cc (#349)
I did this as part of looking into `WorldMapSlots=21` support.  I don't fully understand that one yet, but this semantics preserving cleanup is worth committing
2026-04-04 04:23:14 +00:00
Mike Klaas b97c9864aa Fix global script loading on non-Windows (#347)
* Fix global script loading on non-Windows

Previously, global scripts in .dats and in local FS didn't work due to path separator confusion.

Now:
 * Hardcode global script path to "scripts\gl*.int" (windows separator)
 * Use Windows fpattern matching inside of dFile (.dats, which always use windows separators)
 * Use \-paths when listing found global scripts

The awkward part is having to vendor a copy of fpattern to force it to use "windows mode".  It's possible that we could use this everywhere since windows allows / as separator, but that could cause bugs where we're using fpattern on the native FS.  For now keeping the dFile implementation separate makes sense.
2026-04-01 20:20:57 -07:00
Vlad Kandgithub-actions[bot] 8d0009aa45 Nested array expressions (#346)
* Nested array expressions

* chore: auto-format with clang-format

* Code review fixes

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-03-31 20:18:38 +00:00
Vlad K 511d53a615 fix set_proto_data leftover value on stack in case of invalid args + use programPrintError (#344) 2026-03-31 17:03:50 +02:00
Mike Klaas f17dd19116 Fix parameter order in metarules set_flags and set_outline. (#341)
* Fix parameter order in metarules set_flags and set_outline.

Also fix missing return val in message_box error path
2026-03-30 08:35:35 -07:00
Mike Klaas c28c62727f Implement sfall animation opcodes and metarule (#326) 2026-03-30 08:24:57 -07:00
Mike Klaas b14f518714 Comment out hires_stencil debug logs (#339)
These are a bit too much when debugging other features.  Could make this controlled by a bool too if people prefer
2026-03-30 07:53:57 -07:00
Vlad K 6a0bed4433 Death Anim script hook + fall anim fix + related refactoring (#336)
* actions: refactor code around death anim selection

* add Death Anim hook from sfall

* AI review fixes

* Rename constants for force field PID/FID
2026-03-30 00:23:53 +02:00
Mike Klaas ce27eb55a1 Sfall compatibility tracker (#334)
The idea is that modders / contributors could track what is implemented, what is planned/unplanned, &c.  There's a risk that we won't keep it up to date, but I think it's a document that we'll need eventually.
2026-03-29 13:32:45 -07:00
Mike Klaas c240657d0d Fix two metarule argument popping order errors (#335)
* Fix two metarule argument popping order errors

Also, backfill all errorReturn and argument type validation for metarules
2026-03-29 13:26:34 -07:00
Vlad Kandgithub-actions[bot] 0a73abe67d Barter hook refactoring and Keypress hook repeat fix (#333)
* Barter hook refactor: weight compute in separate fn, names and comments for related vars, consistent value args

* Hook scripts: use initializer_list for defining args

* Keypress Hook: don't trigger on repeat, to match sfall

* chore: auto-format with clang-format

* Attempt to fix formatting after clang-format

* Don't use references for return values, for better readability

* chore: auto-format with clang-format

* Code review fixes

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-03-29 13:57:30 +02:00
Vlad K 54228248ef Add barter hook from sfall (#331)
* Add barter hook from sfall

* Code review fixes
2026-03-28 10:43:47 +01:00
Mike Klaas 4eba423399 Use GameMode::getCurrentGameMode() instead of custom game mode detection (#328)
* Use `GameMode::getCurrentGameMode()` instead of custom game mode detection
2026-03-28 00:41:33 -07:00
Mike Klaas de98136699 Implement sfall tile/path opcodes (#330)
* Implementation of tile and path opcodes
2026-03-27 22:58:29 -07:00
Mike Klaas b27b0f03fc Fix get_ini_setting (#329)
Previously it was returning `0` if the key is missing from the ini file, but the spec is to return -1: https://sfall-team.github.io/sfall/ini-settings/#get_ini_setting

Note that if the key _exists_, but is the value is empty (`key=`), sfall returns `0`.
2026-03-26 07:51:00 -07:00
Mike Klaas 9d6f86424e Implement sfall add_extra_message_file (#327)
https://sfall-team.github.io/sfall/sfall-funcx-macros/#add_extra_msg_file

For now we're choosing to not support the 2-arg form.  We could return `-1` if the script tries to do this, but if so and it doesn't check for an error, that's likely worse than just crashing at this point.  It's essentially an unimplemented opcode.
2026-03-25 21:19:35 -07:00
Mike Klaas bb65d8d32a sfall opcode refresh_pc_art (#324)
Needed for RPU
2026-03-24 05:30:08 +00:00
Mike Klaas c9139f762d Deobfuscate scripts.cc/h (#323)
* Deobfuscate `scripts.cc/h`

Mostly renaming, but also includes:
 * Some straightforward uninling
 * Function extraction of a pretty large chunk (elevators)
 * Simplification of the script loading loop

Tested manually
2026-03-22 04:25:46 +00:00
Mike Klaas beb3c537a4 Fix several compile warnings (#316)
* Fix several compile warnings

Main change is making display functions accept const char* instead of mutating their arguments.
2026-03-21 21:42:15 +00:00
NovaRain b71f1e7d18 Merge pull request #313 from NovaRain/wmenc_table_fix
Fix crash/glitched map when the encounter table has no available entries
2026-03-21 18:33:15 +08:00
Vlad K b7f2b32b92 Debug window size setting, start debug output earlier and log number of loaded mods (#321)
* Debug window size setting, start debug output earlier and log total number of mods loaded

* Code review fixes
2026-03-21 10:54:32 +01:00
Vlad K 75776e4b5b HOOK_COMBATDAMAGE (#322)
* HOOK_COMBATDAMAGE

* Fix compilation on 64bit systems

* Fix incorrect hit location var
2026-03-20 10:53:03 -07:00
Mike Klaas 5304823f8f Fix argument order in mapper: mapSetEnteringLocation (#315)
Mapper's call of this function uses the wrong order compared to all other uses

Fixes #294
2026-03-20 13:08:09 +01:00
Vlad K 5860ee2ffd Merge pull request #319 from fallout2-ce/feature/hook-ongamemodechange-rebase
Implement sfall HOOK_ONGAMEMODECHANGE
2026-03-20 11:23:44 +01:00
Mike Klaas 550e6f035a Implement sfall HOOK_ONGAMEMODECHANGE
Pretty straight forward one
2026-03-20 11:12:28 +01:00
Vlad Kandgithub-actions[bot] 26978dedc5 Hook scripts, related opcodes and some deobfuscation (#312)
* hooks: an empty cc and a list of hook types with useful comments

* Deobfuscate some script-related procedures and flags

* Script hooks WIP

* Interpreter: add programPrintError(), Program->procedureCount(), rename string-related functions

* ScriptHookCall refactoring and all basic hook script opcode implementation

* HOOK_KEYPRESS and first working hooks

* mf_obj_under_cursor + some test script

* HOOK_TOHIT

* chore: auto-format with clang-format

* Add hooks: useobj, useobjon

* chore: auto-format with clang-format

* opcode: get_sfall_args

* Code review fixes

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-03-20 11:08:49 +01:00
Mike Klaas 47434c4038 Starting game_dialogc/h deobfuscation (#307) 2026-03-18 22:50:11 -07:00
NovaRain 23e9eab72d Fix crash/glitched map when the encounter table has no available entries (#311) 2026-03-19 09:13:43 +08:00
Vlad K ed23b0f502 Fix crash when trying to run missing combat_p_proc and start is not the first in the script (#310) 2026-03-17 07:38:04 +00:00
Vlad K f0404596d0 Add fallout2.cfg->preferences->ui_anim_speed setting (#305)
* Use ui_anim_speed for fading, dialog panel, combat panel, pipboy rest
2026-03-17 08:33:16 +01:00
Vasilii Rogin 72da5eb879 Merge pull request #308 from fallout2-ce/fix/tile-from-screen-xy-signature
Remove unused elevation from tileFromScreenXY call sites
2026-03-16 22:14:21 +02:00
Vasilii Rogin e0384a37b5 Fix tileFromScreenXY signature cleanup 2026-03-16 18:18:02 +02:00
Vasilii Rogin c8cf50540f Merge pull request #304 from fallout2-ce/remove_unused_elevation_from_tileToScreenXY
Remove unused elevation in tileToScreenXY
2026-03-16 18:09:29 +02:00
Mike KlaasandCopilot Autofix powered by AI ebcd124ba4 Add a few more proto extendedFlags names (#306)
* Add a few more proto extendedFlags

Added a few names based on the list in fallout-tools: https://github.com/rotators/fallout-tools/blob/4a54255f3403e90cff8a20e657df152cb2602a65/F2_ProtoManager/ProtoManager/SetFlagsForm.Designer.vb#L883

Follow up to https://github.com/fallout2-ce/fallout2-ce/pull/301

* Potential fix for pull request finding

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

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-03-15 23:51:23 +00:00
Mike Klaas 07c55842b4 Enums for proto flags (#301) 2026-03-15 22:43:52 +00:00
Vasilii Rogin 7ad7c43ce6 Remove unused elevation in tileToScreenXY 2026-03-15 20:42:42 +02:00
Mike Klaas c352107b09 Dobfuscate region, game_sound, art (#300) 2026-03-14 22:05:30 -07:00
Mike Klaas b6bfcff9b3 Deobfuscate window_manager, inventory, string_parsers (#299)
Also fix bug in _adjust_fid
2026-03-14 04:45:16 +00:00
Mike Klaas 1df5841810 Update preferences window slider when +/- is pressed (#298)
* Update preferences window slider when +/- is pressed

* do not save brightness when in perferences window
2026-03-13 20:54:31 -07:00
Mike Klaas 98ff03c0b9 Deobfuscations of draw,endgame,interface,preferences (#297)
* Deobfuscations of draw,endgame,interface,preferences

This is _mostly_ mechanical renames.

There are a few other changes:
* Extracted two helper functions
* Added a couple constants that virtually eliminated "magic numbers" in a couple functions
* Switched one int -> bool
* Made sure interfaceGetCurrentHand() is compared against HAND_RIGHT/LEFT instead of treated as boolean

* comment dev by zero issues
2026-03-13 04:14:22 +00:00
Mike Klaas 404236c0cb Deobfuscate audio_file, character_selector, options, dictionary, map, elevator, movie.cc/h (#293)
* Debobfuscate audio_file, character_selector, options, and others

* dictionary, font_manager, nevs

* elevator, map, movie

* item, mp_proto, skill

* proto_instance

* proto, proto_types

* revert dictionary, step -> i

* PR feedback

* criticalChanceModifier -> skillBonus

* skillOrCritSuccessBonus
2026-03-09 05:14:20 +00:00
Mike Klaas 3cd817d4b5 Deobfuscate export,game_movie,random,select_file_list,widget and more (#292)
* Deobfuscate export,game_movie,random,select_file_list,widget

* add automap,cache,display_monitor,game_mouse,geometry,window_manager_pricate

* add static to forward-declared static functions for c++ tidiness
2026-03-01 09:23:04 -08:00
Mike Klaas e8d85dbdd8 Revert to original behaviour in window.cc window existence checks (#291)
revert to original behaviour in window.cc window existence checks

* flagging probably mistaken function
2026-02-28 16:39:56 -08:00
Mike Klaas 3633652884 Rename window* -> scriptWindow* (#288)
* Rename window* -> scriptWindow*

This disambiguates the function names from those in managed_window.cc (which also used window*)

Plus a few items of PR feedback from https://github.com/fallout2-ce/fallout2-ce/pull/286
2026-02-27 05:05:49 +00:00
Mike Klaas e86dce3266 more deobfuscation (#286)
Deobfuscation of several files that were close to complete. There are mechanical renames for the most part. A couple exceptions noted in the changeset
2026-02-22 21:55:37 -08:00
a738c38a81 Function renaming part 2 (#285)
* Rename functions with RE-based names for consistency with new scheme

* Rename sub_* that have equivalents in original source

sub_* names pulled from https://raw.githubusercontent.com/sfall-team/sfall/master/sfall/FalloutEngine/FunctionOffsets_def.h if possible

Per discussion in Discord, sticking with camelCase names

* build fix

* window*

* Rename several more window* functions, add explanatory comment

---------

Co-authored-by: phobos2077 <phobos2077@gmail.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Vlad K <phobos2077@users.noreply.github.com>
2026-02-22 16:58:01 +00:00
Vlad Kandgithub-actions[bot] be4569315f Function naming consistency, part 1 (#284)
* Rename functions with RE-based names for consistency with new scheme

* chore: auto-format with clang-format

* Fix typo

* critterHealByHours - better comment

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-02-22 17:47:45 +01:00
Mike Klaas a2505f8412 Deobfuscate audio/critter/heap.cc (#282)
Mechanical renames only
2026-02-17 22:35:36 -08:00
Mike Klaas 018d3dd48b Deobfuscate datafile.cc/h (#281)
Also, removed unused functions from the .h file

This PR contains only mechanical renames
2026-02-17 22:35:05 -08:00
Vlad 41eafedf87 Fix release standard library used in debug config (#280)
* Fix release standard library used in debug config

* Github Actions: Update MSVC build tools version
2026-02-17 17:54:07 +01:00
Vasilii Rogin c914e62f44 Merge pull request #277 from ololoken/emscripten-mouse-touch
[emscripten] do not lock mouse; improve mobile browsers compatibility;
2026-02-17 10:23:20 +02:00
cambragol a478e24c8a Merge pull request #279 from fallout2-ce/context-menu-fix
Update game_mouse.cc to fix context menu
2026-01-31 09:19:39 +09:00
cambragol 2703af882c Update game_mouse.cc to fix context menu
Don't set mouse to new position, set it to the original click point.
2026-01-30 22:27:18 +09:00
Roman Turchin 36e8b90831 [emscripten] do not lock mouse; improve mobile browsers compatibility; 2026-01-27 13:24:20 +01:00
170 changed files with 7614 additions and 3891 deletions
+4 -4
View File
@@ -328,16 +328,16 @@ jobs:
- name: Clone
uses: actions/checkout@v4
- name: Install Visual Studio 2019 Build Tools with v141_xp
- name: Install Visual Studio 2022 Build Tools with v141_xp
shell: cmd
run: |
choco install visualstudio2019buildtools -y --package-parameters="'--add Microsoft.VisualStudio.Workload.VCTools --add Microsoft.VisualStudio.Component.VC.v141.x86.x64 --add Microsoft.VisualStudio.Component.VC.v141.xp --add Microsoft.VisualStudio.Component.VC.Tools.x86.x64 --add Microsoft.VisualStudio.Component.Windows10SDK.10240 --add Microsoft.VisualStudio.ComponentGroup.NativeDesktop.WinXP --add Microsoft.VisualStudio.Component.WinXP'"
choco install visualstudio2022buildtools -y --package-parameters="'--add Microsoft.VisualStudio.Workload.VCTools --add Microsoft.VisualStudio.Component.VC.v141.x86.x64 --add Microsoft.VisualStudio.Component.VC.v141.xp --add Microsoft.VisualStudio.Component.VC.Tools.x86.x64 --add Microsoft.VisualStudio.Component.Windows10SDK.10240 --add Microsoft.VisualStudio.ComponentGroup.NativeDesktop.WinXP --add Microsoft.VisualStudio.Component.WinXP'"
- name: Cache cmake build
uses: actions/cache@v4
with:
path: out
key: windows-${{ matrix.arch }}-cmake-v101
key: windows-${{ matrix.arch }}-cmake-v102
- name: Configure
shell: cmd
@@ -432,4 +432,4 @@ jobs:
tag_name: continious
draft: false
prerelease: true
name: Fallout2-CE Continuous Build
name: Fallout2-CE Continuous Build
+6 -1
View File
@@ -286,8 +286,11 @@ target_sources(${EXECUTABLE_NAME} PUBLIC
"src/sfall_metarules.h"
"src/sfall_opcodes.cc"
"src/sfall_opcodes.h"
"src/sfall_script_hooks.cc"
"src/sfall_arrays.cc"
"src/sfall_arrays.h"
"src/sfall_animation.cc"
"src/sfall_animation.h"
"src/sfall_callbacks.cc"
"src/sfall_callbacks.h"
"src/touch.cc"
@@ -318,7 +321,8 @@ endif()
if(WIN32)
target_link_libraries(${EXECUTABLE_NAME}
winmm
libcpmt
debug libcpmtd
optimized libcpmt
)
endif()
@@ -430,6 +434,7 @@ endif()
add_subdirectory("third_party/fpattern")
target_link_libraries(${EXECUTABLE_NAME} fpattern::fpattern)
target_link_libraries(${EXECUTABLE_NAME} fpattern_windows::fpattern_windows)
target_link_libraries(${EXECUTABLE_NAME} ${ZLIB_LIBRARIES})
target_include_directories(${EXECUTABLE_NAME} PRIVATE ${ZLIB_INCLUDE_DIRS})
+2 -2
View File
@@ -28,7 +28,7 @@
"inherits": [
"windows-base"
],
"generator": "Visual Studio 16 2019",
"generator": "Visual Studio 17 2022",
"architecture": "Win32",
"cacheVariables": {
"CMAKE_GENERATOR_TOOLSET": "v141_xp",
@@ -42,7 +42,7 @@
"inherits": [
"windows-base"
],
"generator": "Visual Studio 16 2019",
"generator": "Visual Studio 17 2022",
"architecture": "x64",
"cacheVariables": {
"CMAKE_MSVC_RUNTIME_LIBRARY": "MultiThreaded$<$<CONFIG:Debug>:Debug>"
+104
View File
@@ -0,0 +1,104 @@
# Sfall Compatibility
This document tracks Fallout 2 CE compatibility with sfall. This is for modders who need to know which Sfall features work in CE.
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.
## Opcodes / Metarules
See [`https://sfall-team.github.io/sfall/`](https://sfall-team.github.io/sfall/) for documentation on specific functions.
| Group | Opcodes In Group | Compatibility | Notes |
| --- | --- | --- | --- |
| Direct memory access| read_byte,short,int,string<br>write_byte,short,int,string<br>call_offset_vX | đźš« | Not possible. Open an issue if you need functionality not covered by other opcodes. |
| Stats | get/set_pc_base_stat<br>get/set_pc_extra_stat<br>get/set_critter_base_stat<br>get/set_critter_extra_stat | âś… | CE uses engine stat helpers here instead of sfall's direct proto-field behavior, so derived-stat update behavior can differ. |
| Stats / Alter min/max | get/set_stat_min/max<br>set_pc_stat_min/max<br>set_npc_stat_min/max | not implemented | - |
| Skills | get/set_critter_skill_points<br>get/set_available_skill_points<br>set_skill_max<br>set_critter_skill_mod<br>set_base_skill_mod<br>mod_skill_points_per_level | not implemented | - |
| Graphics | graphics_funcs_available<br>force_graphics_refresh<br>get_screen_width<br>get_screen_height<br>set_palette | implemented: only get_screen_width, get_screen_height | - |
| Shaders | load_shader<br>free_shader<br>activate_shader<br>deactivate_shader<br>set/get_shader_* | đźš« | likely will not implement direct compatibility
| Perks and traits | set_perk_image<br>set_perk_*<br>set_pyromaniac_mod<br>apply_heaveho_fix<br>set_swiftlearner_mod<br>set_fake_perk<br>set_fake_trait<br>set_selectable_perk<br>set_perkbox_title<br>hide_real_perks<br>show_real_perks<br>perk_add_mode<br>clear_selectable_perks<br>has_fake_perk<br>has_fake_trait<br>add_trait<br>remove_trait<br>seq_perk_freq | not implemented | - |
| Virtual file system | fs_create<br>fs_copy<br>fs_find<br>fs_read/write_*<br>fs_delete<br>fs_size<br>fs_pos<br>fs_seek<br>fs_resize | đźš« | Open an issue if you have a use case for these |
| Combat / Knockback | set_weapon_knockback<br>set_target_knockback<br>set_attacker_knockback<br>remove_weapon_knockback<br>remove_target_knockback<br>remove_attacker_knockback | not implemented | - |
| Maps and encounters | in_world_map<br>force_encounter<br>force_encounter_with_flags<br>set_map_time_multi<br>get/set_map_enter_position<br>exec_map_update_scripts<br>get/set_terrain_name<br>set_town_title<br>get/set_can_rest_on_map<br>set_rest_heal_time<br>set_rest_mode<br>set_worldmap_heal_time | implemented: in_world_map, force_encounter, force_encounter_with_flags, set_map_time_multi | - |
| Maps and encounters / Worldmap | get_world_map_x/y_pos<br>set_world_map_pos | âś… | - |
| Audio | eax_available<br>set_eax_environment<br>play_sfall_sound<br>stop_sfall_sound | not implemented | *eax* opcodes will not be implemented |
| Combat / Weapons and ammo | get/set_weapon_ammo_pid<br>get/set_weapon_ammo_count | âś… | - |
| Sfall / Version | sfall_ver_major<br>sfall_ver_minor<br>sfall_ver_build | âś… | CE currently reports `4.3.4` |
| Utility / Math | log, exponent, round, sqrt, abs, sin, cos, tan, arctan, ceil, ^, floor2, div | âś… | - |
| Keyboard and mouse | key_pressed<br>tap_key<br>get_mouse_x/y<br>get_mouse_buttons | âś… | - |
| Lists | list_begin<br>list_next<br>list_end<br>list_as_array<br>party_member_list | âś… | - |
| Explosions | metarule2_explosions<br>set_attack_explosion_pattern<br>set_attack_explosion_art<br>set_attack_explosion_radius<br>set_attack_is_explosion_fire<br>set_explosion_radius<br>set_dynamite_damage<br>set_plastic_damage<br>get_explosion_damage<br>set_explosion_max_targets<br>item_make_explosive | âś… except item_make_explosive | - |
| Animations | reg_anim_combat_check<br>reg_anim_destroy<br>reg_anim_animate_and_hide<br>reg_anim_light<br>reg_anim_change_fid<br>reg_anim_take_out<br>reg_anim_turn_towards<br>reg_anim_callback<br>reg_anim_animate_and_move | âś… except reg_anim_callback | - |
| Art and appearance | art_exists<br>refresh_pc_art<br>art_cache_clear<br>set_hero_race<br>set_hero_style | implemented: art_exists, refresh_pc_art, art_cache_clear | - |
| 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 | âś… except 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 | âś… except get_string_pointer | `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 | implemented: all except set_iface_tag_text, add_iface_tag | CE only handles built-in interface tags here; custom tag creation/text is not supported yet. |
| Global variables | set_sfall_global<br>get_sfall_global_int<br>get_sfall_global_float | implemented: all except get_sfall_global_float | Current CE storage is int-backed; `set_sfall_global` stores integer values and there is no float getter yet. |
| Hooks / Hook functions | init_hook<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 | âś… except init_hook | See below for implemented hooks. `init_hook` is deprecated and will not be implemented. register_hook_proc and register_hook_proc_spec both add hooks to the *end* of the hook list, instead of beginning and end, respectively. |
| 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 | âś… except save_array, load_array | - |
| Perks and traits / NPC perks | set_fake_perk_npc<br>set_fake_trait_npc<br>set_selectable_perk_npc<br>has_fake_perk_npc<br>has_fake_trait_npc | not implemented | - |
| Global scripts / Global script functions | set_global_script_repeat<br>set_global_script_type<br>available_global_script_types | implemented: all except available_global_script_types | - |
| Combat | attack_is_aimed<br>block_combat<br>force_aimed_shots<br>disable_aimed_shots<br>get_attack_type<br>get/set_bodypart_hit_modifier<br>combat_data<br>get/set/reset_critical_table<br>get_last_target<br>get_last_attacker<br>set_critter_burst_disable<br>get/set_critter_current_ap<br>set_spray_settings<br>get/set_combat_free_move | implemented: only get_attack_type, get_bodypart_hit_modifier, combat_data, set_bodypart_hit_modifier | - |
| Car | set_car_current_town<br>car_gas_amount<br>set_car_intface_art | implemented: all except set_car_intface_art | - |
| Interface / Windows and images | art_frame_data<br>interface_art_draw<br>interface_print<br>draw_image<br>draw_image_scaled<br>get_window_under_mouse<br>create_win<br>get_window_attribute<br>message_box<br>set_window_flag<br>win_fill_color<br>interface_overlay<br>dialog_message<br>get_text_width<br>hide_window<br>show_window | implemented: only message_box, get_text_width, show_window | - |
| Interface / Outline | outlined_object<br>get_outline<br>set_outline | implemented: all except get_outline | - |
| Interface / Main interface | intface_is_hidden<br>intface_redraw<br>intface_hide<br>intface_show<br>set_quest_failure_value | implemented: only intface_redraw | `intface_redraw` only supports the zero-argument form; the optional-argument path is explicitly unimplemented. |
| Interface / Inventory | display_stats<br>inventory_redraw<br>critter_inven_obj2<br>get_current_inven_size<br>item_weight | implemented: only critter_inven_obj2 | - |
| Interface / Cursor | get/set_cursor_mode | âś… | - |
| Locks | lock_is_jammed<br>unjam_lock<br>set_unjam_locks_time | not implemented | - |
| INI settings | modified_ini<br>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 modified_ini, 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>set_script<br>get_script<br>obj_is_carrying_obj<br>loot_obj<br>dialog_obj<br>obj_under_cursor<br>get_object_data<br>set_object_data<br>get_flags<br>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_script, loot_obj, dialog_obj, obj_under_cursor, get_object_data, get_flags, set_flags, get_proto_data, set_proto_data | - |
| Other / Game management | set_movie_path<br>stop_game<br>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 | - |
| Other | input_funcs_available<br>get_year<br>set_dm_model<br>set_df_model<br>set_pipboy_available<br>get_kill_counter<br>mod_kill_counter<br>active_hand<br>toggle_active_hand<br>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>inc_npc_level<br>get_npc_level<br>get/set_viewport_x/y<br>set_hp_per_level_mod<br>get/set_unspent_ap_bonus<br>get/set_unspent_ap_perk_bonus<br>hero_select_win<br>create_message_window<br>get_light_level<br>gdialog_get_barter_mod<br>set_inven_ap_cost<br>set_base_pickpocket_mod<br>set_critter_pickpocket_mod<br>message_str_game<br>sneak_success<br>create_spatial<br>unwield_slot<br>get_inven_ap_cost<br>add_g_timer_event<br>add_extra_msg_file<br>get_metarule_table<br>metarule_exist<br>npc_engine_level_up<br>remove_timer_event<br>set_drugs_data<br>spatial_radius | implemented: get_year, active_hand, toggle_active_hand, create_message_window, message_str_game, add_extra_msg_file, metarule_exist | `input_funcs_available`, `nb_create_char` are deprecated in sfall and intentionally absent in CE. `add_extra_msg_file` does not support the explicit `fileNumber` form in CE. |
## Hooks
| Hook | ID | Compatibility | Notes |
| --- | --- | --- | --- |
| ToHit | `HOOK_TOHIT` | âś… | - |
| AfterHitRoll | `HOOK_AFTERHITROLL` | đźš« | Et tu |
| CalcAPCost | `HOOK_CALCAPCOST` | đźš« | - |
| DeathAnim1 | `HOOK_DEATHANIM1` | đźš« | Use DEATHANIM2 instead |
| DeathAnim2 | `HOOK_DEATHANIM2` | âś… | - |
| CombatDamage | `HOOK_COMBATDAMAGE` | âś… | - |
| OnDeath | `HOOK_ONDEATH` | đźš« | - |
| FindTarget | `HOOK_FINDTARGET` | đźš« | (maybe) |
| UseObjOn | `HOOK_USEOBJON` | âś… | - |
| 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` | đźš« | - |
| AmmoCost | `HOOK_AMMOCOST` | đźš« | Et tu |
| 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 |
| WithinPerception | `HOOK_WITHINPERCEPTION` | đźš« | Et tu |
| InventoryMove | `HOOK_INVENTORYMOVE` | âś… | - |
| InvenWield | `HOOK_INVENWIELD` | đźš« | - |
| AdjustFID | `HOOK_ADJUSTFID` | đźš« | - |
| CombatTurn | `HOOK_COMBATTURN` | đźš« | - |
| StdProcedure | `HOOK_STDPROCEDURE` | đźš« | Et tu |
| StdProcedureEnd | `HOOK_STDPROCEDURE_END` | đźš« | - |
| CarTravel | `HOOK_CARTRAVEL` | đźš« | - |
| SetGlobalVar | `HOOK_SETGLOBALVAR` | đźš« | - |
| RestTimer | `HOOK_RESTTIMER` | đźš« | Et tu |
| GameModeChange | `HOOK_GAMEMODECHANGE` | âś… | - |
| UseAnimObj | `HOOK_USEANIMOBJ` | đźš« | Et tu; (maybe) |
| ExplosiveTimer | `HOOK_EXPLOSIVETIMER` | đźš« | - |
| DescriptionObj | `HOOK_DESCRIPTIONOBJ` | đźš« | Et tu |
| UseSkillOn | `HOOK_USESKILLON` | đźš« | Et tu |
| 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 |
| AdjustPoison | `HOOK_ADJUSTPOISON` | đźš« | (maybe) |
| AdjustRads | `HOOK_ADJUSTRADS` | đźš« | (maybe) |
| RollCheck | `HOOK_ROLLCHECK` | đźš« | - |
| BestWeapon | `HOOK_BESTWEAPON` | đźš« | - |
| CanUseWeapon | `HOOK_CANUSEWEAPON` | đźš« | - |
| BuildSfxWeapon | `HOOK_BUILDSFXWEAPON` | đźš« | - |
-5
View File
@@ -197,11 +197,6 @@ NumbersInDialogue=0
;XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
[Scripts]
;Comma-separated list of masked paths to load global scripts from
;Only use single backslash \ as the directory separator
;Paths outside of scripts folder are supported
;GlobalScriptPaths=scripts\gl_*.int,scripts\sfall\gl*.int
;Uncomment the option to specify an additional directory for ini files used by scripts
;The game will search for ini files first relative to this directory and then relative to the root directory if not found
;The path length is limited to 61 characters
@@ -13,18 +13,23 @@ import java.io.OutputStream;
public class FileUtils {
static boolean copyRecursively(ContentResolver contentResolver, DocumentFile src, File dest) {
if (dest == null || (!dest.exists() && !dest.mkdirs())) {
return false;
}
final DocumentFile[] documentFiles = src.listFiles();
for (final DocumentFile documentFile : documentFiles) {
final String name = documentFile.getName();
if (name == null || name.isEmpty()) {
return false;
}
if (documentFile.isFile()) {
if (!copyFile(contentResolver, documentFile, new File(dest, documentFile.getName()))) {
if (!copyFile(contentResolver, documentFile, new File(dest, name))) {
return false;
}
} else if (documentFile.isDirectory()) {
final File subdirectory = new File(dest, documentFile.getName());
if (!subdirectory.exists()) {
subdirectory.mkdir();
}
final File subdirectory = new File(dest, name);
if (!copyRecursively(contentResolver, documentFile, subdirectory)) {
return false;
}
@@ -34,18 +39,22 @@ public class FileUtils {
}
private static boolean copyFile(ContentResolver contentResolver, DocumentFile src, File dest) {
try {
final InputStream inputStream = contentResolver.openInputStream(src.getUri());
final OutputStream outputStream = new FileOutputStream(dest);
final File parent = dest.getParentFile();
if (parent != null && !parent.exists() && !parent.mkdirs()) {
return false;
}
try (final InputStream inputStream = contentResolver.openInputStream(src.getUri());
final OutputStream outputStream = new FileOutputStream(dest)) {
if (inputStream == null) {
return false;
}
final byte[] buffer = new byte[16384];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}
inputStream.close();
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
return false;
@@ -2,10 +2,12 @@ package com.alexbatalov.fallout2ce;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.ActivityNotFoundException;
import android.content.ContentResolver;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.widget.Toast;
import androidx.documentfile.provider.DocumentFile;
@@ -17,18 +19,18 @@ public class ImportActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
startActivityForResult(intent, IMPORT_REQUEST_CODE);
launchImportPicker();
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent resultData) {
if (requestCode == IMPORT_REQUEST_CODE) {
if (resultCode == Activity.RESULT_OK) {
final Uri treeUri = resultData.getData();
final Uri treeUri = resultData != null ? resultData.getData() : null;
if (treeUri != null) {
grantImportPermissions(treeUri, resultData);
final DocumentFile treeDocument = DocumentFile.fromTreeUri(this, treeUri);
if (treeDocument != null) {
if (treeDocument != null && treeDocument.isDirectory()) {
copyFiles(treeDocument);
return;
}
@@ -42,20 +44,59 @@ public class ImportActivity extends Activity {
}
private void copyFiles(DocumentFile treeDocument) {
ProgressDialog dialog = createProgressDialog();
final ProgressDialog dialog = createProgressDialog();
dialog.show();
new Thread(() -> {
ContentResolver contentResolver = getContentResolver();
File externalFilesDir = getExternalFilesDir(null);
FileUtils.copyRecursively(contentResolver, treeDocument, externalFilesDir);
final ContentResolver contentResolver = getContentResolver();
final File externalFilesDir = getExternalFilesDir(null);
final boolean success = externalFilesDir != null
&& FileUtils.copyRecursively(contentResolver, treeDocument, externalFilesDir);
startMainActivity();
dialog.dismiss();
finish();
runOnUiThread(() -> {
dialog.dismiss();
if (success) {
startMainActivity();
} else {
Toast.makeText(this, R.string.import_failed, Toast.LENGTH_LONG).show();
}
finish();
});
}).start();
}
private void launchImportPicker() {
final Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
| Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
| Intent.FLAG_GRANT_PREFIX_URI_PERMISSION);
try {
startActivityForResult(intent, IMPORT_REQUEST_CODE);
} catch (ActivityNotFoundException e) {
Toast.makeText(this, R.string.import_picker_unavailable, Toast.LENGTH_LONG).show();
finish();
}
}
private void grantImportPermissions(Uri treeUri, Intent resultData) {
int flags = Intent.FLAG_GRANT_READ_URI_PERMISSION;
if (resultData != null) {
flags = resultData.getFlags()
& (Intent.FLAG_GRANT_READ_URI_PERMISSION
| Intent.FLAG_GRANT_WRITE_URI_PERMISSION
| Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
}
try {
getContentResolver().takePersistableUriPermission(treeUri, flags);
} catch (SecurityException ignored) {
// Some providers do not offer persistable grants. One-shot access is enough for import.
}
}
private void startMainActivity() {
Intent intent = new Intent(this, MainActivity.class);
startActivity(intent);
@@ -2,4 +2,6 @@
<string name="app_name">Fallout 2</string>
<string name="loading_dialog_title">PLEASE STAND BY</string>
<string name="loading_dialog_message">Copying files…</string>
<string name="import_failed">Failed to import game files. Please pick a valid Fallout 2 folder and try again.</string>
<string name="import_picker_unavailable">No compatible folder picker is available on this device.</string>
</resources>
+1
View File
@@ -1 +1,2 @@
*.int
sfall
+2 -14
View File
@@ -30,22 +30,10 @@ compile.exe -q -p -l -O2 -d -s -n -I<sfall_headers_id> <script_name.ssl>
- Install [VSCode Extension](https://marketplace.visualstudio.com/items?itemName=BGforge.bgforge-mls)
## Run test script
1. Move compiled `.int` file into game folder as `data/scripts/gl_<script_name>.int`
1. Move compiled `.int` file into game folder as `scripts/gl_<script_name>.int`
2. Change `ddraw.ini` and add this section:
```ini
[Scripts]
GlobalScriptPaths=data/scripts/gl*.int
```
(or add new path using comma as separator)
Note that on non-Windows it have to be `/` as folder separator
3. Run game, check that game displays message about tests
2. Run game, check that game displays message about tests
+56
View File
@@ -0,0 +1,56 @@
#include "sfall.h"
#include "dik.h"
#include "define_lite.h"
#include "define_extra.h"
#include "test_utils.h"
variable refresh_pc_art_test_done := false;
procedure run_refresh_pc_art_test begin
variable old_gender := get_critter_stat(dude_obj, STAT_gender);
variable new_gender := 1 - old_gender;
variable dude_pid := obj_pid(dude_obj);
variable old_proto_fid := get_proto_data(dude_pid, PROTO_FID);
set_pc_base_stat(STAT_gender, new_gender);
call assertEquals("gender changes immediately", get_critter_stat(dude_obj, STAT_gender), new_gender);
call assertEquals("proto fid unchanged before refresh", get_proto_data(dude_pid, PROTO_FID), old_proto_fid);
refresh_pc_art;
variable refreshed_proto_fid := get_proto_data(dude_pid, PROTO_FID);
call assertNotEquals("refresh_pc_art updates dude proto fid", refreshed_proto_fid, old_proto_fid);
if (not critter_inven_obj(dude_obj, INVEN_TYPE_WORN)) then begin
call assertEquals("refresh_pc_art updates unarmored dude art", obj_art_fid(dude_obj) bwand 0xFFF, refreshed_proto_fid bwand 0xFFF);
end
set_pc_base_stat(STAT_gender, old_gender);
refresh_pc_art;
call assertEquals("refresh_pc_art restores original proto fid", get_proto_data(dude_pid, PROTO_FID), old_proto_fid);
refresh_pc_art_test_done := true;
call report_test_results("hook_refresh_pc_art");
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_X) then return;
if (refresh_pc_art_test_done) then begin
display_msg("refresh_pc_art test already ran");
return;
end
call run_refresh_pc_art_test;
end
procedure start begin
if (not game_loaded) then return;
display_msg("refresh_pc_art test ready: press X");
register_hook_proc(HOOK_KEYPRESS, keypress_handler);
end
@@ -0,0 +1,26 @@
#include "test_utils.h"
#include "sfall.h"
// to test, copy test_extra_msg_file.msg into <game folder>/text/English/game/
procedure start begin
variable auto_id;
variable duplicate_auto_id;
display_msg("Testing add_extra_msg_file...");
call assertEquals("metarule exists", metarule_exist("add_extra_msg_file"), 1);
auto_id := add_extra_msg_file("test_extra_msg_file.msg");
call assertNotEquals("auto id is not load error", auto_id, -2);
call assertTrue("auto id lower bound", auto_id >= 0x3000);
call assertTrue("auto id upper bound", auto_id <= 0x3FFF);
call assertEquals("auto message", message_str_game(auto_id, 100), "auto extra msg");
duplicate_auto_id := add_extra_msg_file("TEST_EXTRA_MSG_FILE.MSG");
call assertEquals("duplicate file returns existing id", duplicate_auto_id, auto_id);
call assertEquals("missing file", add_extra_msg_file("missing_extra_msg_file.msg"), -2);
call report_test_results("add_extra_msg_file");
end
+6
View File
@@ -164,6 +164,12 @@ procedure array_test_suite begin
call assertEquals("saved arrays 3", len_array(list_saved_arrays), len_array(arr2) - 1);
*/
display_msg("Testing nested expressions...");
arr := [["one", "two"]];
call assertEquals("nested 1", arr[0][1], "two");
arr := [["one", "two"], {"three": ["four", -1]}];
call assertEquals("nested 2", arr[1].three[0], "four");
display_msg("All tests finished with "+test_suite_errors+" errors.");
call report_test_results("arrays");
+10 -6
View File
@@ -9,9 +9,10 @@ procedure ini_test_suite begin
// Test Case 1: Valid file, valid section
result_array := get_ini_section("test.ini", "ValidSection");
expected_array := {"Key1": "Value1", "Key2": "2"};
call assertEquals("TC1 Size", len_array(result_array), 2);
call assertEquals("TC1 Content", arrays_equal(result_array, expected_array), true);
call assertEquals("TC1 Size", len_array(result_array), 3);
call assertEquals("TC1 Key1", result_array["Key1"], "Value1");
call assertEquals("TC1 Key2", result_array["Key2"], "2");
call assertEquals("TC1 EmptyValue", result_array["EmptyValue"], "");
count := 0;
foreach key_val_pair in result_array begin
count += 1;
@@ -25,19 +26,22 @@ procedure ini_test_suite begin
call assertEquals("TC2 get_ini_string string", get_ini_string("test.ini|ValidSection|Key1"), "Value1");
call assertEquals("TC2 get_ini_setting int", get_ini_setting("test.ini|ValidSection|Key2"), 2);
call assertEquals("TC2 get_ini_string int", get_ini_string("test.ini|ValidSection|Key2"), "2");
call assertEquals("TC2 get_ini_setting empty int", get_ini_setting("test.ini|ValidSection|EmptyValue"), 0);
call assertEquals("TC2 get_ini_string empty string", get_ini_string("test.ini|ValidSection|EmptyValue"), "");
// INI file not found
result_array := get_ini_section("nonexistent.ini", "AnySection");
call assertEquals("TC3 Size", len_array(result_array), 0);
call assertEquals("TC3 Is Map", array_key(result_array, -1), 1); // Should still be an associative array
call assertEquals("TC3 get_ini_setting", get_ini_setting("notexist.ini|ValidSection|Key2"), 0);
call assertEquals("TC3 get_ini_setting missing file", get_ini_setting("notexist.ini|ValidSection|Key2"), -1);
call assertEquals("TC3 get_ini_setting missing key", get_ini_setting("test.ini|ValidSection|MissingKey"), -1);
call assertEquals("TC3 get_ini_string", get_ini_string("notexist.ini|ValidSection|Key2"), "");
// Valid file, section not found
result_array := get_ini_section("test.ini", "NonExistentSection");
call assertEquals("TC4 Size", len_array(result_array), 0);
call assertEquals("TC4 Is Map", array_key(result_array, -1), 1);
call assertEquals("TC4 get_ini_setting", get_ini_setting("test.ini|NonExistentSection|Key2"), 0);
call assertEquals("TC4 get_ini_setting", get_ini_setting("test.ini|NonExistentSection|Key2"), -1);
call assertEquals("TC4 get_ini_string", get_ini_string("test.ini|NonExistentSection|Key2"), "");
@@ -45,7 +49,7 @@ procedure ini_test_suite begin
result_array := get_ini_section("test.ini", "EmptySection");
call assertEquals("TC5 Size", len_array(result_array), 0);
call assertEquals("TC5 Is Map", array_key(result_array, -1), 1);
call assertEquals("TC5 get_ini_setting", get_ini_setting("test.ini|EmptySection|Key2"), 0);
call assertEquals("TC5 get_ini_setting", get_ini_setting("test.ini|EmptySection|Key2"), -1);
call assertEquals("TC5 get_ini_string", get_ini_string("test.ini|EmptySection|Key2"), "");
// set ini setting
@@ -0,0 +1,234 @@
#include "sfall.h"
#include "dik.h"
#include "command_lite.h"
#include "define_extra.h"
#include "test_utils.h"
#define REG_ANIM_BEGIN 1
#define REG_ANIM_END 3
#define ANIMATION_REQUEST_UNRESERVED 1
#define TEST_ITEM_PID 40
#define TEST_FID_SOURCE_PID 76
#define TEST_LIGHT_VALUE 0x12340002
#define TEST_TAKE_OUT_FRAME WPN_ANIM_PISTOL
#define TEST_STAND_ANIM 0
#define TEST_WALK_ANIM 1
#define reg_anim_begin() reg_anim_func(REG_ANIM_BEGIN, ANIMATION_REQUEST_UNRESERVED)
#define reg_anim_end() reg_anim_func(REG_ANIM_END, 0)
variable hook_registered := false;
variable pending_test := 0;
variable pending_frames := 0;
variable test_move_critter_obj := 0;
variable test_takeout_critter_obj := 0;
variable test_light_obj := 0;
variable test_fid_obj := 0;
variable test_fid_source_obj := 0;
variable test_hide_obj := 0;
variable test_destroy_obj := 0;
variable expected_turn_tile := 0;
variable expected_move_tile := 0;
variable initial_rotation := 0;
variable initial_tile := 0;
variable initial_fid := 0;
variable expected_fid := 0;
variable initial_hide_visibility := 0;
variable initial_destroy_visibility := 0;
variable test_objects_ready := false;
variable pending_timeout_frames := 0;
variable noncombat_test_completed := false;
// Manual checks:
// X:
// - No registered animation should run in combat.
// - The temp critters should not move or draw a weapon.
// V:
// - `reg_anim_combat_check(0)` should allow the registered animations in combat.
// N:
// - Outside combat, the full opcode surface should run and be checked.
procedure setup_test_objects begin
variable elev := elevation(dude_obj);
test_move_critter_obj := create_object_sid(obj_pid(dude_obj), tile_num_in_direction(dude_tile, dude_cur_rot, 3), elev, -1);
test_takeout_critter_obj := create_object_sid(obj_pid(dude_obj), tile_num_in_direction(dude_tile, (dude_cur_rot + 1) % 6, 3), elev, -1);
test_light_obj := create_object_sid(TEST_ITEM_PID, tile_num_in_direction(dude_tile, (dude_cur_rot + 2) % 6, 4), elev, -1);
test_fid_obj := create_object_sid(TEST_ITEM_PID, tile_num_in_direction(dude_tile, (dude_cur_rot + 3) % 6, 4), elev, -1);
test_fid_source_obj := create_object_sid(TEST_FID_SOURCE_PID, tile_num_in_direction(dude_tile, (dude_cur_rot + 4) % 6, 4), elev, -1);
test_hide_obj := create_object_sid(TEST_ITEM_PID, tile_num_in_direction(dude_tile, (dude_cur_rot + 5) % 6, 4), elev, -1);
test_destroy_obj := create_object_sid(TEST_ITEM_PID, tile_num_in_direction(dude_tile, dude_cur_rot, 5), elev, -1);
initial_rotation := obj_get_rot(test_move_critter_obj);
initial_tile := tile_num(test_move_critter_obj);
expected_turn_tile := tile_num_in_direction(initial_tile, (initial_rotation + 3) % 6, 1);
expected_move_tile := tile_num_in_direction(initial_tile, initial_rotation, 1);
initial_fid := get_object_data(test_fid_obj, OBJ_DATA_FID);
expected_fid := get_object_data(test_fid_source_obj, OBJ_DATA_FID);
initial_hide_visibility := obj_is_visible_flag(test_hide_obj);
initial_destroy_visibility := obj_is_visible_flag(test_destroy_obj);
pending_frames := 0;
test_objects_ready := true;
end
procedure ensure_test_objects begin
if (test_objects_ready) then return;
call setup_test_objects();
end
procedure queue_full_sequence begin
reg_anim_begin();
reg_anim_turn_towards(test_move_critter_obj, expected_turn_tile, -1);
reg_anim_take_out(test_takeout_critter_obj, TEST_TAKE_OUT_FRAME, -1);
reg_anim_light(test_light_obj, TEST_LIGHT_VALUE, -1);
reg_anim_change_fid(test_fid_obj, expected_fid, -1);
reg_anim_animate_and_hide(test_hide_obj, TEST_STAND_ANIM, -1);
reg_anim_destroy(test_destroy_obj);
reg_anim_animate_and_move(test_move_critter_obj, expected_move_tile, TEST_WALK_ANIM, -1);
reg_anim_end();
end
procedure start_blocked_test begin
pending_test := 1;
pending_timeout_frames := 30;
call ensure_test_objects();
call queue_full_sequence();
display_msg("Started blocked-case test.");
end
procedure start_enabled_test begin
pending_test := 2;
pending_timeout_frames := 120;
call ensure_test_objects();
reg_anim_combat_check(0);
call queue_full_sequence();
display_msg("Started enabled-case test.");
end
procedure start_noncombat_test begin
pending_test := 3;
pending_timeout_frames := 120;
call ensure_test_objects();
call queue_full_sequence();
display_msg("Started non-combat opcode test.");
end
procedure keypress_handler begin
variable
pressed := get_sfall_arg_at(0),
key := get_sfall_arg_at(1);
if (not pressed) then return;
if (pending_test != 0) then begin
if (key == DIK_X or key == DIK_V or key == DIK_N) then
display_msg("A reg_anim_combat_check test is already running.");
return;
end
if (combat_data and key == DIK_X) then begin
display_msg("X: combat should block all reg_anim calls in this sequence.");
call start_blocked_test();
end else if (combat_data and key == DIK_V) then begin
display_msg("V: reg_anim_combat_check(0) should allow the registered animations in combat.");
call start_enabled_test();
end else if ((not combat_data) and key == DIK_N) then begin
if (noncombat_test_completed) then begin
display_msg("N already ran once. Reload the map or game before running it again.");
return;
end
display_msg("N: outside combat, the full reg_anim opcode sequence should run.");
call start_noncombat_test();
end else if (key == DIK_X or key == DIK_V) then begin
display_msg("Enter combat before running X or V.");
end else if (key == DIK_N) then begin
display_msg("Leave combat before running N.");
end
end
procedure report_blocked_results begin
variable errors_before := test_suite_errors;
call assertEquals("turn_towards blocked", obj_get_rot(test_move_critter_obj), initial_rotation);
call assertEquals("animate_and_move blocked", tile_num(test_move_critter_obj), initial_tile);
call assertEquals("change_fid blocked", get_object_data(test_fid_obj, OBJ_DATA_FID), initial_fid);
call assertEquals("animate_and_hide blocked", obj_is_visible_flag(test_hide_obj), initial_hide_visibility);
call assertEquals("destroy blocked", obj_is_visible_flag(test_destroy_obj), initial_destroy_visibility);
if (test_suite_errors == errors_before) then
display_msg("PASS blocked automatic checks");
else
display_msg("FAIL blocked automatic checks");
call report_test_results("reg_anim_combat_check_blocked");
end
procedure report_enabled_results begin
variable errors_before := test_suite_errors;
call assertNotEquals("turn_towards runs in combat when enabled", obj_get_rot(test_move_critter_obj), initial_rotation);
call assertEquals("animate_and_move runs in combat when enabled", tile_num(test_move_critter_obj), expected_move_tile);
call assertEquals("change_fid runs in combat when enabled", get_object_data(test_fid_obj, OBJ_DATA_FID), expected_fid);
if (test_suite_errors == errors_before) then
display_msg("PASS enabled automatic checks");
else
display_msg("FAIL enabled automatic checks");
call report_test_results("reg_anim_combat_check_enabled");
end
procedure report_noncombat_results begin
variable errors_before := test_suite_errors;
noncombat_test_completed := true;
call assertNotEquals("turn_towards outside combat", obj_get_rot(test_move_critter_obj), initial_rotation);
call assertEquals("animate_and_move outside combat", tile_num(test_move_critter_obj), expected_move_tile);
call assertEquals("change_fid outside combat", get_object_data(test_fid_obj, OBJ_DATA_FID), expected_fid);
call assertEquals("animate_and_hide outside combat", obj_is_visible_flag(test_hide_obj), 0);
if (test_suite_errors == errors_before) then
display_msg("PASS non-combat automatic checks");
else
display_msg("FAIL non-combat automatic checks");
call report_test_results("reg_anim_full_noncombat");
display_msg("Manual check: outside combat, the other clone should play take out, light should visibly apply if supported by the object type, and destroy_obj should be gone.");
end
procedure poll_pending_test begin
if (pending_test == 0) then return;
pending_frames += 1;
if (pending_frames < pending_timeout_frames) then return;
variable current_test := pending_test;
pending_test := 0;
pending_frames := 0;
pending_timeout_frames := 0;
if (current_test == 1) then begin
call report_blocked_results();
end else if (current_test == 2) then begin
call report_enabled_results();
end else if (current_test == 3) then begin
call report_noncombat_results();
end
end
procedure start begin
set_global_script_repeat(1);
if (not hook_registered and game_loaded) then begin
hook_registered := true;
call ensure_test_objects();
register_hook_proc(HOOK_KEYPRESS, keypress_handler);
display_msg("reg_anim_combat_check test loaded. Enter combat for X/V, or stay out of combat for N (one-shot).");
end
call poll_pending_test();
end
+78
View File
@@ -0,0 +1,78 @@
#include "sfall.h"
#include "test_utils.h"
variable blocker_obj := 0;
procedure cleanup begin
if (blocker_obj) then begin
destroy_object(blocker_obj);
blocker_obj := 0;
end
end
procedure start begin
variable elev := elevation(dude_obj);
variable dude_tile := tile_num(dude_obj);
variable dir;
variable step_tile := -1;
variable step_dir := -1;
variable block_tile := -1;
variable path;
variable objs;
variable ground_fid;
variable roof_fid;
display_msg("Testing tile/path functions...");
call cleanup();
for (dir := 0; dir < 6; dir += 1) begin
variable candidate := tile_num_in_direction(dude_tile, dir, 1);
if (candidate != dude_tile and obj_blocking_tile(candidate, elev, BLOCKING_TYPE_BLOCK) == 0) then begin
if (step_tile == -1) then begin
step_tile := candidate;
step_dir := dir;
end
if (block_tile == -1 and candidate != step_tile) then begin
block_tile := candidate;
end
end
end
call assertNotEquals("found reachable neighboring tile", step_tile, -1);
if (step_tile != -1) then begin
path := path_find_to(dude_obj, step_tile, BLOCKING_TYPE_BLOCK);
call assertEquals("one-step path length", len_array(path), 1);
if (len_array(path) == 1) then
call assertEquals("one-step path direction", get_array(path, 0), step_dir);
end
if (block_tile != -1) then begin
blocker_obj := create_object_sid(obj_pid(dude_obj), block_tile, elev, -1);
call assertNotEquals("spawned blocking critter", blocker_obj, 0);
if (blocker_obj) then begin
path := path_find_to(dude_obj, tile_num(blocker_obj), BLOCKING_TYPE_BLOCK);
call assertEquals("occupied destination returns empty path", len_array(path), 0);
end
end else begin
display_msg("Skipping occupied-destination path check: no free adjacent tile for blocker.");
end
objs := tile_get_objs(dude_tile, elev);
call assertTrue("tile_get_objs contains dude", scan_array(objs, dude_obj) != -1);
ground_fid := get_tile_ground_fid(dude_tile, elev);
roof_fid := get_tile_roof_fid(dude_tile, elev);
call assertTrue("ground fid is non-negative", ground_fid >= 0);
call assertTrue("roof fid is non-negative", roof_fid >= 0);
call assertEquals("ground helper matches ext", ground_fid, get_tile_fid_ext(dude_tile, elev, 0));
call assertEquals("roof helper matches ext", roof_fid, get_tile_fid_ext(dude_tile, elev, 1));
call assertTrue("tile_light returns non-negative intensity", tile_light(elev, dude_tile) >= 0);
call assertEquals("tile_by_position metarule exists", metarule_exist("tile_by_position"), 1);
call assertEquals("tile_by_position matches tile_under_cursor", tile_by_position(get_mouse_x, get_mouse_y), tile_under_cursor);
tile_refresh_display;
call cleanup();
call report_test_results("tile_path");
end
+166
View File
@@ -0,0 +1,166 @@
#include "../sfall/sfall.h"
#include "../sfall/dik.h"
#include "../sfall/define_lite.h"
#include "../test_utils.h"
#include "../sfall/lib.arrays.h"
variable last_game_mode;
variable gamemodechange_assertions_enabled := false;
variable gamemodechange_reported := false;
procedure test_with_args(variable arg1) begin
display_msg("test_w_arg" + arg1);
end
procedure test_wo_args begin
display_msg("test_wo_args");
end
procedure tohit_handler begin
variable
attacker := get_sfall_arg_at(1),
target := get_sfall_arg_at(2),
chanceRaw := get_sfall_arg_at(7);
display_msg(string_format("tohit %s -> %s (%d)", obj_name(attacker), obj_name(target), chanceRaw));
if (attacker == dude_obj) then
set_sfall_return(100);
else if (target == dude_obj) then
set_sfall_return(0);
end
procedure useobj_handler begin
variable
user := get_sfall_arg,
item := get_sfall_arg;
display_msg(string_format("useobj %s (%s)", obj_name(user), obj_name(item)));
set_sfall_return(2);
end
procedure useobjon_handler begin
variable args := get_sfall_args,
target := args[0],
user := args[1],
item := args[2];
display_msg(string_format("useobjon %s (%s) -> %s [len: %d]", obj_name(user), obj_name(item), obj_name(target), len_array(args)));
set_sfall_return(0);
end
procedure keypress_handler begin
variable
pressed := get_sfall_arg_at(0),
key := get_sfall_arg_at(1);
display_msg(string_format("keypress %d dx: %d", pressed, key));
if (not pressed) then return;
if (key == DIK_F2) then begin
display_msg(string_format("tile %d, elev %d, map %d", tile_under_cursor, elevation(dude_obj), cur_map_index));
end if (key == DIK_F3) then begin
if (key_pressed(DIK_LCONTROL)) then begin
variable cr := obj_under_cursor(true, false);
if (cr) then
critter_dmg(cr, 1000, DMG_BYPASS_ARMOR);
end else
move_to(dude_obj, tile_under_cursor, elevation(dude_obj));
end
end
procedure gamemodechange_handler begin
variable
event_type := get_sfall_arg_at(0),
previous_mode := get_sfall_arg_at(1),
current_mode := get_game_mode;
display_msg(string_format("gamemodechange event=%d prev=%d cur=%d", event_type, previous_mode, current_mode));
if (not gamemodechange_assertions_enabled) then begin
last_game_mode := current_mode;
return;
end
call assertTrue("gamemodechange event_type is 0 or 1", event_type == 0 or event_type == 1);
call assertEquals("gamemodechange previous mode", previous_mode, last_game_mode);
if (event_type == 0) then
call assertNotEquals("gamemodechange mode changed", current_mode, previous_mode);
else if (not gamemodechange_reported) then begin
gamemodechange_reported := true;
call report_test_results("hook_gamemodechange");
end
last_game_mode := current_mode;
end
procedure combatdamage_handler begin
variable args := get_sfall_args;
display_msg(sprintf("combatdamage %s", debug_array_str(args)));
set_sfall_return(10); // target dmg
set_sfall_return(5);
set_sfall_return(0);
set_sfall_return(DAM_HIT); // attacker flag
set_sfall_return(10); // knockback
end
procedure barterprice_handler begin
variable
args := get_sfall_args,
value := args[2],
caps := args[4],
offerValue := args[7],
offerTable := args[6],
offerCaps := item_caps_total(offerTable);
display_msg(sprintf("barterprice %s", debug_array_str(args)));
set_sfall_return((value - caps) * 2 + caps);
set_sfall_return((offerValue - offerCaps) / 2 + offerCaps);
end
#define ANIM_fall_back (20)
#define ANIM_fall_front (21)
#define ANIM_fire_dance (33)
procedure deathanim_handler begin
variable
args := get_sfall_args;
display_msg(sprintf("deathanim %s", debug_array_str(args)));
set_sfall_return(ANIM_fire_dance);
end
procedure start begin
//call test_with_args(777);
display_msg("start hook test");
if (not game_loaded) then return;
add_obj_to_inven(dude_obj, create_object_sid(40, 0, 0, -1));
add_obj_to_inven(dude_obj, create_object_sid(76, 0, 0, -1));
add_obj_to_inven(dude_obj, create_object_sid(85, 0, 0, -1));
last_game_mode := get_game_mode;
gamemodechange_assertions_enabled := true;
register_hook_proc(HOOK_KEYPRESS, keypress_handler);
register_hook_proc(HOOK_TOHIT, tohit_handler);
register_hook_proc(HOOK_USEOBJ, useobj_handler);
register_hook_proc(HOOK_USEOBJON, useobjon_handler);
register_hook_proc(HOOK_GAMEMODECHANGE, gamemodechange_handler);
register_hook_proc(HOOK_COMBATDAMAGE, combatdamage_handler);
register_hook_proc(HOOK_BARTERPRICE, barterprice_handler);
register_hook_proc(HOOK_DEATHANIM2, deathanim_handler);
end
procedure map_enter_p_proc begin
display_msg("Map Enter hook test");
end
+57
View File
@@ -0,0 +1,57 @@
#include "sfall.h"
#include "dik.h"
#include "lib.arrays.h"
variable inventorymove_blocking := false;
procedure inventorymove_handler begin
variable
args := get_sfall_args,
action := args[0],
item := args[1],
arg2 := args[2],
action_name := "unknown";
if (action == 0) then action_name := "main_backpack";
else if (action == 1) then action_name := "left_hand";
else if (action == 2) then action_name := "right_hand";
else if (action == 3) then action_name := "armor_slot";
else if (action == 4) then action_name := "weapon_reload";
else if (action == 5) then action_name := "container";
else if (action == 6) then action_name := "ground";
else if (action == 7) then action_name := "pickup";
else if (action == 8) then action_name := "character_portrait";
display_msg(string_format2("inventorymove %s args=%s", action_name, debug_array_str(args)));
if (item) then
display_msg(string_format2("inventorymove item=%s pid=%d", obj_name(item), obj_pid(item)));
if (arg2) then
display_msg(string_format2("inventorymove arg2=%s pid=%d", obj_name(arg2), obj_pid(arg2)));
display_msg(string_format1("inventorymove blocking=%d", inventorymove_blocking));
if (inventorymove_blocking) then begin
display_msg("inventorymove blocked");
set_sfall_return(0);
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;
inventorymove_blocking := not inventorymove_blocking;
display_msg(string_format1("inventorymove blocking %d", inventorymove_blocking));
end
procedure start begin
if (not game_loaded) then return;
display_msg("inventorymove manual test ready: press X to toggle blocking");
register_hook_proc(HOOK_KEYPRESS, keypress_handler);
register_hook_proc(HOOK_INVENTORYMOVE, inventorymove_handler);
end
+1
View File
@@ -1,6 +1,7 @@
[ValidSection]
Key1=Value1
Key2=2
EmptyValue=
[SectionWithSpaces]
LeadingSpaceKey = LeadingSpaceValue
+1
View File
@@ -0,0 +1 @@
{100}{}{auto extra msg}

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