Compare commits

..
Author SHA1 Message Date
Mike Klaas 1bbbc98065 Harden input reset on activation changes 2026-04-24 23:12:21 -07:00
Mike Klaas b0b08ed14e Avoid recursive sfall tap_key hooks 2026-04-24 23:05:40 -07:00
Mike Klaas 5b8281922d Fix worldmap 100% cpu lockup
On Mac, when on worldmap, cmd-tab out of a windowed game then back locks the game 100% of the time for me.  This PR does a more thorough reset of the input to prevent it from getting out of sync
2026-04-23 22:32:28 -07:00
Mike Klaas cd123b1ad3 Augment fallout2-dat to create dat archives (#405)
* Augment `fallout2-dat` to create dat archives

Verified by round-tripping master.dat, critter.dat, f2_res.dat with the tool
2026-04-23 22:00:05 -07:00
Mike Klaas 49affe4e80 --dev-load-game commandline arg (#404)
* `--dev-load-game` commandline arg

This allows you to use --dev-load-game=SLOTXX` to immediately load a saved game after the game is launched.  This is mostly useful so that LLMs can launch the game and see the result of an .ssl test script without a human needing to be in the loop
2026-04-23 08:19:39 -07:00
Mike Klaas 07d1649b63 Attempt to fix windows CI (#409)
* Attempt to fix windows CI

* PR feedback
2026-04-23 07:55:52 -07:00
Vlad KandCopilot efd8a4a525 Fix last remaining warnings in MSVC (#406)
* Fix last remaining warnings in MSVC

* Update CMakeLists.txt

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

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-22 21:04:21 +00:00
Vlad Kandgithub-actions[bot] 4c09ffe9e6 Extended AP bar from sfall (#400)
* Extended AP bar from sfall

* Rewrote using new Buffer2D and blitBuffer2D + code review fixes

* chore: auto-format with clang-format

* Add ConstBuffer2D + other fixes

* chore: auto-format with clang-format

* Linux build fix attempt

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-04-22 22:09:12 +02:00
Mike Klaas 1026c79ac5 Implement objects_in_radius (#399)
* Implement `objects_in_radius`

Used by Et tu and a few other mods
2026-04-21 22:20:59 -07:00
Vlad K 3849b4ea17 Add AI agent related files to git ignore (#395)
* Add AI agent related files to git ignore

* Add gemini.md
2026-04-21 09:46:59 +02:00
Mike Klaas 6cbe68aef9 Fix setup (play) instructions, and add basic compile instructions to CONTRIBUTING.md (#390)
* Fix setup (play) instructions, and add basic compile instructions to CONTRIBUTING.md
2026-04-21 06:09:42 +00:00
Mike Klaas 066595f683 Add Fallout 1 .dat file support to fallout2-dat (#393) 2026-04-20 20:21:19 -07:00
44 changed files with 2402 additions and 350 deletions
+5 -5
View File
@@ -320,9 +320,7 @@ jobs:
matrix:
include:
- arch: x86
generator-platform: Win32
- arch: x64
generator-platform: x64
steps:
- name: Clone
@@ -337,18 +335,20 @@ jobs:
uses: actions/cache@v4
with:
path: out
key: windows-${{ matrix.arch }}-cmake-v102
key: windows-${{ matrix.arch }}-cmake-v103
- name: Configure
shell: cmd
run: |
call "C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\Common7\Tools\VsDevCmd.bat" ^
for /f "usebackq tokens=*" %%i in (`"%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe" -latest -products Microsoft.VisualStudio.Product.BuildTools -requires Microsoft.VisualStudio.Component.VC.v141.x86.x64 Microsoft.VisualStudio.Component.VC.v141.xp -property installationPath`) do set "VS_PATH=%%i"
call "%VS_PATH%\Common7\Tools\VsDevCmd.bat" ^
&& cmake --preset windows-${{ matrix.arch }}
- name: Build
shell: cmd
run: |
call "C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\Common7\Tools\VsDevCmd.bat" ^
for /f "usebackq tokens=*" %%i in (`"%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe" -latest -products Microsoft.VisualStudio.Product.BuildTools -requires Microsoft.VisualStudio.Component.VC.v141.x86.x64 Microsoft.VisualStudio.Component.VC.v141.xp -property installationPath`) do set "VS_PATH=%%i"
call "%VS_PATH%\Common7\Tools\VsDevCmd.bat" ^
&& cmake --build --preset windows-${{ matrix.arch }}-release
- name: Change executable file name
+11 -11
View File
@@ -250,40 +250,40 @@ jobs:
matrix:
include:
- arch: x86
generator-platform: Win32
- arch: x64
generator-platform: x64
steps:
- 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: build
key: windows-${{ matrix.arch }}-cmake-v1
path: out
key: windows-${{ matrix.arch }}-cmake-v2
- name: Configure
shell: cmd
run: |
call "C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\Common7\Tools\VsDevCmd.bat" ^
&& cmake -B build -G "Visual Studio 16 2019" -A ${{ matrix.generator-platform }}
for /f "usebackq tokens=*" %%i in (`"%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe" -latest -products Microsoft.VisualStudio.Product.BuildTools -requires Microsoft.VisualStudio.Component.VC.v141.x86.x64 Microsoft.VisualStudio.Component.VC.v141.xp -property installationPath`) do set "VS_PATH=%%i"
call "%VS_PATH%\Common7\Tools\VsDevCmd.bat" ^
&& cmake --preset windows-${{ matrix.arch }}
- name: Build
shell: cmd
run: |
call "C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\Common7\Tools\VsDevCmd.bat" ^
&& cmake --build build --config RelWithDebInfo
for /f "usebackq tokens=*" %%i in (`"%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe" -latest -products Microsoft.VisualStudio.Product.BuildTools -requires Microsoft.VisualStudio.Component.VC.v141.x86.x64 Microsoft.VisualStudio.Component.VC.v141.xp -property installationPath`) do set "VS_PATH=%%i"
call "%VS_PATH%\Common7\Tools\VsDevCmd.bat" ^
&& cmake --build --preset windows-${{ matrix.arch }}-release
- name: Upload
run: |
cd build/RelWithDebInfo
cd out/build/windows-${{ matrix.arch }}/RelWithDebInfo
7z a fallout2-ce-windows-${{ matrix.arch }}.zip fallout2-ce.exe
gh release upload ${{ github.event.release.tag_name }} fallout2-ce-windows-${{ matrix.arch }}.zip
rm fallout2-ce-windows-${{ matrix.arch }}.zip
+12
View File
@@ -400,3 +400,15 @@ FodyWeavers.xsd
# versioning header
src/platform/git_version.h
CMakeUserPresets.json
# Local configs for AI agents
.opencode
.agents
.aider*
.gemini
.claude
.mcp.json
AGENTS.md
CLAUDE.md
GEMINI.md
opencode.json
+30 -19
View File
@@ -20,6 +20,10 @@ set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED YES)
set(CMAKE_CXX_EXTENSIONS NO)
if(MSVC)
add_compile_options($<$<COMPILE_LANGUAGE:CXX>:/EHsc>)
endif()
option(FALLOUT_VENDORED "Use vendored third-party libraries" ON)
if(ANDROID)
@@ -448,27 +452,34 @@ target_include_directories(${EXECUTABLE_NAME} PRIVATE ${ZLIB_INCLUDE_DIRS})
target_link_libraries(${EXECUTABLE_NAME} ${SDL2_LIBRARIES})
target_include_directories(${EXECUTABLE_NAME} PRIVATE ${SDL2_INCLUDE_DIRS})
add_executable(fallout2-dat
"tools/dat_tool.cc"
"src/dfile.cc"
"src/platform_compat.cc"
)
if(APPLE)
set_target_properties(fallout2-dat PROPERTIES
XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED "NO"
XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED "NO"
if((NOT ANDROID) AND (NOT IOS) AND (NOT CMAKE_SYSTEM_NAME MATCHES "Emscripten"))
add_executable(fallout2-dat
"tools/dat_tool.cc"
"tools/dat_archive.cc"
"tools/fo1_dat_lzss.cc"
"src/dfile.cc"
"src/platform_compat.cc"
)
endif()
target_link_libraries(fallout2-dat
fpattern_windows::fpattern_windows
${ZLIB_LIBRARIES}
${SDL2_LIBRARIES}
)
target_include_directories(fallout2-dat PRIVATE "src")
target_include_directories(fallout2-dat PRIVATE ${ZLIB_INCLUDE_DIRS})
target_include_directories(fallout2-dat PRIVATE ${SDL2_INCLUDE_DIRS})
if(APPLE)
set_target_properties(fallout2-dat PROPERTIES
XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED "NO"
XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED "NO"
)
endif()
target_link_libraries(fallout2-dat
fpattern_windows::fpattern_windows
${ZLIB_LIBRARIES}
${SDL2_LIBRARIES}
)
if(WIN32)
target_link_libraries(fallout2-dat winmm)
endif()
target_include_directories(fallout2-dat PRIVATE "src")
target_include_directories(fallout2-dat PRIVATE ${ZLIB_INCLUDE_DIRS})
target_include_directories(fallout2-dat PRIVATE ${SDL2_INCLUDE_DIRS})
endif()
if(APPLE)
if(IOS)
+56 -5
View File
@@ -2,13 +2,64 @@
This file is currently a shell to be filled in over time. For now, it only captures project-specific maintenance notes that are easy to miss.
For current sfall compatibility status and the remaining work needed to close gaps, see [SFALL_COMPATIBILITY.md](SFALL_COMPATIBILITY.md).
## Building
The project uses CMake, so building mostly comes down to picking the right preset.
## Commandline arguments
- `--scan-unimplemented` - do an analysis of opcodes and hooks used by loaded mods
- `--dev-load-game=1` - load the given save game automatically (useful for LLM automation)
### macOS
- Install the Command Line Tools if needed: `xcode-select --install`
- Install the common build dependencies: `brew install cmake ninja`
- Configure and build:
```sh
cmake -S . -B out/build/macos -G Ninja -DCMAKE_BUILD_TYPE=Debug
cmake --build out/build/macos --target fallout2-ce
```
### Linux
Install SDL and a toolchain:
- Debian / Ubuntu:
```sh
sudo apt install build-essential cmake ninja-build libsdl2-dev zlib1g-dev
```
- Fedora:
```sh
sudo dnf install gcc-c++ cmake ninja-build SDL2-devel zlib-devel
```
- Arch Linux:
```sh
sudo pacman -S base-devel cmake ninja sdl2 zlib
```
Then build:
```
cmake -S . -B out/build/linux-x64-debug -G Ninja -DCMAKE_BUILD_TYPE=Debug
cmake --build out/build/linux-x64-debug --target fallout2-ce
```
### Windows
TODO: add Visual Studio build instructions. This will likely need a Visual Studio generator or preset plus the MSVC toolchain and Windows SDK.
## `.dat` CLI Tool
There is a small command-line archive tool in this repo for inspecting Fallout `.dat` files using the same reader implementation as the game.
There is a small command-line archive tool in this repo for inspecting Fallout `.dat` files. It supports both Fallout 2 and Fallout 1 archives.
Build it with `make TARGET=fallout2-dat`.
From the repo root, build it with your normal CMake workflow, targeting `fallout2-dat`:
The executable is written to your selected `BUILD_DIR`, so the default path is `out/build/local-debug-arm64/fallout2-dat` on this macOS setup, but other platforms or custom build directories will differ.
`cmake --build out/build/<preset-name> --target fallout2-dat`
The executable is written to your selected build directory, so the exact path will vary by preset and configuration.
The current tool is read-only. Available commands:
@@ -18,9 +69,9 @@ The current tool is read-only. Available commands:
4. `./<BUILD_DIR>/fallout2-dat <archive.dat> cat <entry>`
Use `--lower` with `extract` when you want every extracted file and directory name forced to lowercase.
For example, from `/Applications/Fallout2Codex` you can run:
For example:
`/Users/klaas/game/fallout2-ce/out/build/local-debug-arm64/fallout2-dat master.dat extract --lower /tmp/fallout2-dat-lower data\\*`
`fallout2-dat master.dat extract --lower /tmp/fallout2-dat-lower data\\*`
## Updating SDL
+12 -19
View File
@@ -2,13 +2,15 @@
Fallout 2 Community Edition is a fully working re-implementation of Fallout 2, with the same original gameplay, engine bugfixes, and some quality of life improvements, that works (mostly) hassle-free on multiple platforms. This is a fork of the original project, which isn't getting regular updates.
Popular Fallout 2 total conversion mods are partially supported. Original versions of Nevada and Sonora (that do not rely on extended features provided by Sfall) work. [Fallout 2 Restoration Project](https://github.com/BGforgeNet/Fallout2_Restoration_Project), [Fallout Et Tu](https://github.com/rotators/Fo1in2) and [Olympus 2207](https://olympus2207.com) are not yet supported. Other mods (particularly Resurrection and Yesterday) are not tested.
Popular Fallout 2 total conversion mods are partially supported. Original versions of Nevada and Sonora (that do not rely on extended features provided by Sfall) work. [Fallout 2 Restoration Project](https://github.com/BGforgeNet/Fallout2_Restoration_Project) is supported (in Beta). [Fallout Et Tu](https://github.com/rotators/Fo1in2) and [Olympus 2207](https://olympus2207.com) are not yet supported. Other mods (particularly Resurrection and Yesterday) are not tested.
There is also [Fallout Community Edition](https://github.com/alexbatalov/fallout1-ce) (not affiliated with this fork).
For build and contributor notes, see [CONTRIBUTING.md](CONTRIBUTING.md).
## Installation
You must own the game to play. Purchase your copy on [GOG](https://www.gog.com/game/fallout_2), [Epic Games](https://store.epicgames.com/p/fallout-2) or [Steam](https://store.steampowered.com/app/38410). Download latest [release](https://github.com/fallout2-ce/fallout2-ce/releases) or build from source.
You must own the game to play. Purchase your copy on [GOG](https://www.gog.com/game/fallout_2), [Epic Games](https://store.epicgames.com/p/fallout-2) or [Steam](https://store.steampowered.com/app/38410). Download latest [release](https://github.com/fallout2-ce/fallout2-ce/releases) or build from source.
### Windows
@@ -22,17 +24,10 @@ Download and copy `fallout2-ce.exe` to your `Fallout2` folder. It serves as a dr
```console
$ sudo apt install innoextract
$ innoextract ~/Downloads/setup_fallout2_2.1.0.18.exe -I app
$ mv app Fallout2
$ innoextract ~/Downloads/setup_fallout_2_1.02_gog_v1_\(77792\).exe -d Fallout2
```
- Download and copy `fallout2-ce` to this folder.
- Install [SDL2](https://libsdl.org/download-2.0.php):
```console
$ sudo apt install libsdl2-2.0-0
```
- Download the Linux release archive, extract `fallout2-ce`, and copy it into this folder.
- Run `./fallout2-ce`.
@@ -48,13 +43,13 @@ $ sudo apt install libsdl2-2.0-0
```console
$ brew install innoextract
$ innoextract ~/Downloads/setup_fallout2_2.1.0.18.exe -I app
$ mv app /Applications/Fallout2
$ innoextract ~/Downloads/setup_fallout_2_1.02_gog_v1_\(77792\).exe -d fallout2
$ mv fallout2 /Applications/Fallout2
```
- Download and copy `fallout2-ce.app` to this folder.
- [Download](https://github.com/fallout2-ce/fallout2-ce/releases) and copy `Fallout II Community Edition.app` to this folder.
- Run `fallout2-ce.app`.
- Run `Fallout II Community Edition.app`.
### Android
@@ -141,11 +136,9 @@ For a sample ddraw.ini configuration file, containing all currently working sett
## Contributing
Integrating Sfall goodies is the top priority. Quality of life updates are OK too. Please no large scale refactorings at this time as we need to reconcile changes from Reference Edition, which will make this process slow and error-prone. In any case open up an issue with your suggestion or to notify other people that something is being worked on.
Integrating Sfall goodies is the top priority. Quality of life updates are OK too. In any case open up an issue with your suggestion or to notify other people that something is being worked on.
### Integrating Sfall
There are literally hundreds if not thousands of fixes and features in sfall. I guess not all of them are needed in Community Edition, but for the sake of compatibility with big mods out there, let's integrate them all.
For current sfall compatibility status and the remaining work needed to close gaps, see [SFALL_COMPATIBILITY.md](SFALL_COMPATIBILITY.md).
## License
+3 -3
View File
@@ -52,7 +52,7 @@ See [`https://sfall-team.github.io/sfall/`](https://sfall-team.github.io/sfall/)
| Explosions | 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 | - |
| 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. |
@@ -61,7 +61,7 @@ See [`https://sfall-team.github.io/sfall/`](https://sfall-team.github.io/sfall/)
| 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 | âś… 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: get_attack_type, get_bodypart_hit_modifier, combat_data, set_bodypart_hit_modifier, get_critter_current_ap, set_critter_current_ap, get_combat_free_move, set_combat_free_move | - |
| 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: get_attack_type, get/set_bodypart_hit_modifier, combat_data, get/set_critter_current_ap, get/set_combat_free_move | - |
| 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<br>create_message_window | implemented: only message_box, get_text_width, show_window, create_message_window | - |
| Interface / Outline | outlined_object<br>get_outline<br>set_outline | âś… | - |
@@ -72,7 +72,7 @@ See [`https://sfall-team.github.io/sfall/`](https://sfall-team.github.io/sfall/)
| 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 | - |
| 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>get_unspent_ap_bonus<br>gdialog_get_barter_mod<br>set_unspent_ap_bonus<br>get/set_unspent_ap_perk_bonus<br>set_inven_ap_cost<br>set_base_pickpocket_mod<br>set_critter_pickpocket_mod<br>get_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/<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 | - |
| 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. |
Binary file not shown.
+2
View File
@@ -96,6 +96,8 @@ display_bonus_damage=1
display_karma_changes=0
;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)
extend_ap_bar=0
iface_bar_mode=0
iface_bar_side_art=2
iface_bar_sides_ori=0
+28
View File
@@ -1,13 +1,21 @@
#include "sfall.h"
#include "define_extra.h"
#include "test_utils.h"
#define TEST_ITEM_PID 40
variable blocker_obj := 0;
variable radius_obj := 0;
procedure cleanup begin
if (blocker_obj) then begin
destroy_object(blocker_obj);
blocker_obj := 0;
end
if (radius_obj) then begin
destroy_object(radius_obj);
radius_obj := 0;
end
end
procedure start begin
@@ -45,6 +53,26 @@ procedure start begin
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);
radius_obj := create_object_sid(TEST_ITEM_PID, step_tile, elev, -1);
call assertNotEquals("spawned radius item", radius_obj, 0);
if (radius_obj) then begin
objs := sfall_func3("objects_in_radius", dude_tile, 1, elev);
call assertTrue("objects_in_radius all contains dude", scan_array(objs, dude_obj) != -1);
call assertTrue("objects_in_radius all contains item", scan_array(objs, radius_obj) != -1);
objs := sfall_func3("objects_in_radius", dude_tile, 0, elev);
call assertTrue("objects_in_radius radius 0 contains dude", scan_array(objs, dude_obj) != -1);
call assertEquals("objects_in_radius radius 0 excludes adjacent item", scan_array(objs, radius_obj), -1);
objs := objects_in_radius(dude_tile, 1, elev, OBJ_TYPE_ITEM);
call assertTrue("objects_in_radius item filter contains item", scan_array(objs, radius_obj) != -1);
call assertEquals("objects_in_radius item filter excludes dude", scan_array(objs, dude_obj), -1);
objs := objects_in_radius(dude_tile, 1, elev, OBJ_TYPE_CRITTER);
call assertTrue("objects_in_radius critter filter contains dude", scan_array(objs, dude_obj) != -1);
call assertEquals("objects_in_radius critter filter excludes item", scan_array(objs, radius_obj), -1);
end
end
if (block_tile != -1) then begin
+11 -2
View File
@@ -11,7 +11,7 @@
#include "draw.h"
#include "game.h"
#include "memory.h"
#include "object.h"
#include "obj_types.h"
#include "proto.h"
#include "settings.h"
@@ -821,7 +821,7 @@ unsigned char* artGetFrameData(Art* art, int frame, int direction)
}
// 0x419880
ArtFrame* artGetFrame(Art* art, int frame, int rotation)
ArtFrame* artGetFrame(const Art* art, int frame, int rotation)
{
if (rotation < 0 || rotation >= 6) {
return nullptr;
@@ -842,6 +842,15 @@ ArtFrame* artGetFrame(Art* art, int frame, int rotation)
return frm;
}
ConstBuffer2D artGetFrameBuffer(const Art* art, int frame, int direction)
{
ArtFrame* frm = artGetFrame(art, frame, direction);
if (frm == nullptr) {
return { nullptr, 0, 0 };
}
return { reinterpret_cast<unsigned char*>(frm) + sizeof(*frm), frm->width, frm->height };
}
// 0x4198C8
bool artExists(int fid)
{
+8 -5
View File
@@ -2,10 +2,8 @@
#define ART_H
#include "cache.h"
#include "db.h"
#include "heap.h"
#include "obj_types.h"
#include "platform_compat.h"
#include "draw.h"
#include "memory.h"
#include "proto_types.h"
namespace fallout {
@@ -141,7 +139,8 @@ int artGetSize(Art* art, int frame, int direction, int* out_width, int* out_heig
int artGetFrameOffsets(Art* art, int frame, int direction, int* xPtr, int* yPtr);
int artGetRotationOffsets(Art* art, int rotation, int* out_offset_x, int* out_offset_y);
unsigned char* artGetFrameData(Art* art, int frame, int direction);
ArtFrame* artGetFrame(Art* art, int frame, int direction);
ArtFrame* artGetFrame(const Art* art, int frame, int direction);
ConstBuffer2D artGetFrameBuffer(const Art* art, int frame, int direction);
bool artExists(int fid);
bool _art_fid_valid(int fid);
int _art_alias_num(int index);
@@ -165,6 +164,8 @@ public:
int getHeight() const { return _height; }
unsigned char* getData() const { return _data; }
ConstBuffer2D getBuffer() const { return { _data, _width, _height }; };
private:
CacheEntry* _key;
unsigned char* _data;
@@ -172,6 +173,8 @@ private:
int _height;
};
using ArtPtr = InternalPtr<Art>;
} // namespace fallout
#endif
-20
View File
@@ -331,26 +331,6 @@ bool configRead(Config* config, const char* filePath, bool isDb)
configParseLine(config, string);
}
fileClose(stream);
// Build patch file path by inserting "#patch" before the extension.
char patchPath[COMPAT_MAX_PATH];
const char* dot = strrchr(filePath, '.');
if (dot != nullptr) {
snprintf(patchPath, sizeof(patchPath), "%.*s#patch%s", (int)(dot - filePath), filePath, dot);
} else {
snprintf(patchPath, sizeof(patchPath), "%s#patch", filePath);
}
struct PatchContext {
Config* config;
char string[CONFIG_FILE_MAX_LINE_LENGTH];
} patchCtx = { config };
xfileOpenEachReverse(patchPath, "rb", [](XFile* file, void* ctx) {
auto* pc = static_cast<PatchContext*>(ctx);
while (fileReadString(pc->string, sizeof(pc->string), file) != nullptr) {
configParseLine(pc->config, pc->string);
} }, &patchCtx);
} else {
FILE* stream = compat_fopen(filePath, "rt");
+3
View File
@@ -23,6 +23,9 @@ void contentConfigInit()
}
configRead(&gContentConfig, kConfigPath, true);
// Patch config allows to override only certain fields, without replacing the whole file.
// TODO: remove this after config patching by mods is implemented inside configRead
configRead(&gContentConfig, kConfigPatchPath, true);
}
void contentConfigExit()
+55 -1
View File
@@ -216,6 +216,60 @@ void blitBufferToBufferTrans(unsigned char* src, int width, int height, int srcP
transSrcCopy(dest, destPitch, src, srcPitch, width, height);
}
void blitBuffer2D(ConstBuffer2D src, Buffer2D dst, int dstX, int dstY)
{
blitBuffer2D(src, 0, 0, src.width, src.height, dst, dstX, dstY);
}
void blitBuffer2D(ConstBuffer2D src, int srcX, int srcY, int width, int height,
Buffer2D dst, int dstX, int dstY)
{
// Clip source region to src bounds.
if (srcX < 0) {
width += srcX;
dstX -= srcX;
srcX = 0;
}
if (srcY < 0) {
height += srcY;
dstY -= srcY;
srcY = 0;
}
if (srcX + width > src.width) {
width = src.width - srcX;
}
if (srcY + height > src.height) {
height = src.height - srcY;
}
// Clip destination region to dst bounds.
if (dstX < 0) {
srcX -= dstX;
width += dstX;
dstX = 0;
}
if (dstY < 0) {
srcY -= dstY;
height += dstY;
dstY = 0;
}
if (dstX + width > dst.width) {
width = dst.width - dstX;
}
if (dstY + height > dst.height) {
height = dst.height - dstY;
}
if (width <= 0 || height <= 0) {
return;
}
srcCopy(
dst.data + dstY * dst.width + dstX, dst.width,
src.data + srcY * src.width + srcX, src.width,
width, height);
}
// 0x4D387C
void bufferFill(unsigned char* buf, int width, int height, int pitch, int value)
{
@@ -321,7 +375,7 @@ void bufferOutline(unsigned char* buf, int width, int height, int pitch, int col
}
// 0x4E0DB0
void srcCopy(unsigned char* dest, int destPitch, unsigned char* src, int srcPitch, int width, int height)
void srcCopy(unsigned char* dest, int destPitch, const unsigned char* src, int srcPitch, int width, int height)
{
for (int y = 0; y < height; y++) {
memcpy(dest, src, width);
+40 -1
View File
@@ -1,8 +1,47 @@
#ifndef DRAW_H
#define DRAW_H
#include <type_traits>
namespace fallout {
template <typename T>
struct Buffer2DBase {
T* data = nullptr;
int width = 0;
int height = 0;
Buffer2DBase() = default;
Buffer2DBase(T* data, int width, int height)
: data(data)
, width(width)
, height(height)
{
}
// Implicit conversion Buffer2D -> ConstBuffer2D.
template <typename U, typename = std::enable_if_t<std::is_same_v<const U, T>>>
Buffer2DBase(const Buffer2DBase<U>& other)
: data(other.data)
, width(other.width)
, height(other.height)
{
}
explicit operator bool() const { return data != nullptr; }
};
using Buffer2D = Buffer2DBase<unsigned char>;
using ConstBuffer2D = Buffer2DBase<const unsigned char>;
// Copies src into dst at (dstX, dstY). Clips to dst bounds; no-op if fully outside.
void blitBuffer2D(ConstBuffer2D src, Buffer2D dst, int dstX = 0, int dstY = 0);
// Copies a region of src (starting at srcX, srcY with given width/height) into
// dst at (dstX, dstY). Clips to both src and dst bounds.
void blitBuffer2D(ConstBuffer2D src, int srcX, int srcY, int width, int height,
Buffer2D dst, int dstX = 0, int dstY = 0);
void bufferDrawLine(unsigned char* buf, int pitch, int left, int top, int right, int bottom, int color);
void bufferDrawRect(unsigned char* buf, int pitch, int left, int top, int right, int bottom, int color);
void bufferDrawRectShadowed(unsigned char* buf, int pitch, int left, int top, int right, int bottom, int ltColor, int rbColor);
@@ -16,7 +55,7 @@ void _buf_texture(unsigned char* buf, int width, int height, int pitch, void* te
void _lighten_buf(unsigned char* buf, int width, int height, int pitch);
void _swap_color_buf(unsigned char* buf, int width, int height, int pitch, int color1, int color2);
void bufferOutline(unsigned char* buf, int width, int height, int pitch, int color);
void srcCopy(unsigned char* dest, int destPitch, unsigned char* src, int srcPitch, int width, int height);
void srcCopy(unsigned char* dest, int destPitch, const unsigned char* src, int srcPitch, int width, int height);
void transSrcCopy(unsigned char* dest, int destPitch, unsigned char* src, int srcPitch, int width, int height);
} // namespace fallout
+66 -22
View File
@@ -14,6 +14,8 @@
namespace fallout {
static void fileCopy(const char* existingFilePath, const char* newFilePath);
static bool copyGzToFile(gzFile inStream, FILE* outStream);
static bool copyFileToGz(FILE* inStream, gzFile outStream);
// 0x452740
int fileCopyDecompressed(const char* existingFilePath, const char* newFilePath)
@@ -33,13 +35,10 @@ int fileCopyDecompressed(const char* existingFilePath, const char* newFilePath)
FILE* outStream = compat_fopen(newFilePath, "wb");
if (inStream != nullptr && outStream != nullptr) {
for (;;) {
int ch = gzgetc(inStream);
if (ch == -1) {
break;
}
fputc(ch, outStream);
if (!copyGzToFile(inStream, outStream)) {
gzclose(inStream);
fclose(outStream);
return -1;
}
gzclose(inStream);
@@ -87,14 +86,10 @@ int fileCopyCompressed(const char* existingFilePath, const char* newFilePath)
return -1;
}
// Copy byte-by-byte.
for (;;) {
int ch = fgetc(inStream);
if (ch == -1) {
break;
}
gzputc(outStream, ch);
if (!copyFileToGz(inStream, outStream)) {
fclose(inStream);
gzclose(outStream);
return -1;
}
fclose(inStream);
@@ -130,13 +125,10 @@ int _gzdecompress_file(const char* existingFilePath, const char* newFilePath)
return -1;
}
while (1) {
int ch = gzgetc(gzstream);
if (ch == -1) {
break;
}
fputc(ch, stream);
if (!copyGzToFile(gzstream, stream)) {
gzclose(gzstream);
fclose(stream);
return -1;
}
gzclose(gzstream);
@@ -180,4 +172,56 @@ static void fileCopy(const char* existingFilePath, const char* newFilePath)
}
}
static bool copyGzToFile(gzFile inStream, FILE* outStream)
{
std::vector<unsigned char> buffer(0xFFFF);
for (;;) {
int bytesRead = gzread(inStream, buffer.data(), buffer.size());
if (bytesRead < 0) {
return false;
}
if (bytesRead == 0) {
return true;
}
size_t offset = 0;
size_t remaining = bytesRead;
while (remaining > 0) {
size_t bytesWritten = fwrite(buffer.data() + offset, sizeof(*buffer.data()), remaining, outStream);
if (bytesWritten == 0) {
return false;
}
offset += bytesWritten;
remaining -= bytesWritten;
}
}
}
static bool copyFileToGz(FILE* inStream, gzFile outStream)
{
std::vector<unsigned char> buffer(0xFFFF);
for (;;) {
size_t bytesRead = fread(buffer.data(), sizeof(*buffer.data()), buffer.size(), inStream);
if (bytesRead == 0) {
return feof(inStream) != 0;
}
size_t offset = 0;
size_t remaining = bytesRead;
while (remaining > 0) {
int bytesWritten = gzwrite(outStream, buffer.data() + offset, remaining);
if (bytesWritten <= 0) {
return false;
}
offset += bytesWritten;
remaining -= bytesWritten;
}
}
}
} // namespace fallout
+46 -8
View File
@@ -48,6 +48,8 @@ static void buildNormalizedQwertyKeys();
static void _GNW95_process_key(KeyboardData* data);
static int inputGetHookMouseButton(int sdlButton);
static void inputHandleMouseClickHook(int sdlButton, bool pressed);
static void inputResetInternalEventQueue();
static void inputHandleProgramActivationChange(bool isActive);
// 0x51E23C
static int gKeyboardKeyRepeatRate = 80;
@@ -135,6 +137,39 @@ static void inputHandleMouseClickHook(int sdlButton, bool pressed)
ScriptHookCall(HOOK_MOUSECLICK, 0, { pressed ? 1 : 0, hookButton }).call();
}
static void inputHandleProgramActivationChange(bool isActive)
{
if (gProgramIsActive == isActive) {
return;
}
gProgramIsActive = isActive;
// Cmd-tab on macOS can leave modifier/key repeat state and mouse mode out
// of sync with SDL's actual app activation state. Drop queued input on
// both transitions and reapply the desired mouse mode on return.
keyboardReset();
mouseReset();
windowResetButtonState();
inputResetInternalEventQueue();
if (isActive) {
mouseDeviceInitMode();
windowRefreshAll(&_scr_size);
audioEngineResume();
} else {
audioEnginePause();
}
}
static void inputResetInternalEventQueue()
{
gInputEventQueueReadIndex = -1;
gInputEventQueueWriteIndex = 0;
_input_mx = -1;
_input_my = -1;
}
// 0x4C8A70
int inputInit()
{
@@ -300,8 +335,7 @@ static int dequeueInputEvent()
// 0x4C8D04
void inputEventQueueReset()
{
gInputEventQueueReadIndex = -1;
gInputEventQueueWriteIndex = 0;
inputResetInternalEventQueue();
SDL_Event e;
while (SDL_PollEvent(&e)) { } // Clear all input events
}
@@ -1016,8 +1050,9 @@ void _GNW95_process_message()
if (!keyboardIsDisabled()) {
keyboardData.key = e.key.keysym.scancode;
keyboardData.down = (e.key.state & SDL_PRESSED) != 0;
bool syntheticSfallKey = sfall_kb_consume_synthetic_key_event(keyboardData.key, keyboardData.down);
if (!e.key.repeat) {
if (!e.key.repeat && !syntheticSfallKey) {
int keyOverride = sfall_kb_handle_key_pressed(keyboardData.key, keyboardData.down);
if (keyOverride != SDL_SCANCODE_UNKNOWN) {
keyboardData.key = keyOverride;
@@ -1034,14 +1069,17 @@ void _GNW95_process_message()
case SDL_WINDOWEVENT_SIZE_CHANGED:
handleWindowSizeChanged();
break;
case SDL_WINDOWEVENT_FOCUS_GAINED:
gProgramIsActive = true;
case SDL_WINDOWEVENT_SHOWN:
case SDL_WINDOWEVENT_RESTORED:
windowRefreshAll(&_scr_size);
audioEngineResume();
break;
case SDL_WINDOWEVENT_FOCUS_GAINED:
inputHandleProgramActivationChange(true);
break;
case SDL_WINDOWEVENT_HIDDEN:
case SDL_WINDOWEVENT_MINIMIZED:
case SDL_WINDOWEVENT_FOCUS_LOST:
gProgramIsActive = false;
audioEnginePause();
inputHandleProgramActivationChange(false);
break;
}
break;
+97 -26
View File
@@ -128,6 +128,8 @@ static bool indicatorBarAdd(int indicator);
static void customInterfaceBarInit();
static void customInterfaceBarExit();
static void extendedApBarInitToWindow();
static void sidePanelsInit();
static void sidePanelsExit();
static void sidePanelsHide();
@@ -190,9 +192,6 @@ static int gEndTurnButton = -1;
// 0x518FBC
static int gEndCombatButton = -1;
// 0x518FD4
static Rect gInterfaceBarActionPointsBarRect;
// 0x518FE8
static IndicatorDescription gIndicatorDescriptions[INDICATOR_COUNT] = {
{ 102, true, nullptr }, // ADDICT
@@ -249,14 +248,34 @@ static unsigned char _itemButtonUp[INTERFACE_ITEM_ACTION_BUTTON_WIDTH * INTERFAC
// 0x59D3F4
static unsigned char* gInterfaceWindowBuffer;
// A slice of main interface background containing 10 shadowed action point
// Rectangle within Interface Bar window covering the Action Points bar.
// 0x518FD4
static Rect apBarRect;
// Width and height of AP bulbs.
constexpr int kApBarBulbSize = 5;
// Horizontal margin between AP bulbs.
constexpr int kApBarBulbMargin = 4;
constexpr int kApBarMaxBulbs = 16;
constexpr int kApBarMaxWidth = (kApBarBulbSize + kApBarBulbMargin) * kApBarMaxBulbs;
// A slice of main interface background containing up to 16 shadowed action point
// dots. In combat mode individual colored dots are rendered on top of this
// background.
//
// This buffer is initialized once and does not change throughout the game.
//
// 0x59D40C
static unsigned char gInterfaceActionPointsBarBackground[90 * 5];
static unsigned char apBarBackgroundData[kApBarMaxWidth * kApBarBulbSize];
static int apBarMaxAP;
static int apBarWidth;
// AP bar offset relative to the content area of the Interface Bar window.
static int apBarXOffset;
constexpr int kApBarYOffset = 14;
static FrmImage _inventoryButtonNormalFrmImage;
static FrmImage _inventoryButtonPressedFrmImage;
@@ -287,15 +306,26 @@ static FrmImage _greenLightFrmImage;
static FrmImage _yellowLightFrmImage;
static FrmImage _redLightFrmImage;
// X offset of interface bar content (used when widescreen version of iface bar is used).
// TODO: this seems like a bad solution, maybe better to separate message box and other content into two separate windows and avoid this offset?
int gInterfaceBarContentOffset = 0;
int gInterfaceBarWidth = 800; // will fall back to 640 if screen width is too narrow or asset is absent
bool gInterfaceBarIsCustom = false;
static Art* gCustomInterfaceBarBackground = nullptr;
bool gInterfaceSidePanelsExtendFromScreenEdge = false;
static int gInterfaceSidePanelsLeadingWindow = -1;
static int gInterfaceSidePanelsTrailingWindow = -1;
static Buffer2D interfaceWindowBuf2D()
{
return { gInterfaceWindowBuffer, gInterfaceBarWidth, INTERFACE_BAR_HEIGHT };
}
static Buffer2D apBarBackgroundBuf2D()
{
return { apBarBackgroundData, apBarWidth, kApBarBulbSize };
}
// intface_init
// 0x45D880
int interfaceInit()
@@ -308,7 +338,6 @@ int interfaceInit()
customInterfaceBarInit();
gInterfaceBarActionPointsBarRect = { 316 + gInterfaceBarContentOffset, 14, 406 + gInterfaceBarContentOffset, 19 };
gInterfaceBarEndButtonsRect = { 580 + gInterfaceBarContentOffset, 38, 637 + gInterfaceBarContentOffset, 96 };
gInterfaceBarMainActionRect = { 267 + gInterfaceBarContentOffset, 26, 455 + gInterfaceBarContentOffset, 93 };
@@ -329,6 +358,7 @@ int interfaceInit()
return intface_fatal_error(-1);
}
// Blit interface bar FRM into static window buffer.
if (gInterfaceBarIsCustom) {
blitBufferToBuffer(customInterfaceBarGetBackgroundImageData(), gInterfaceBarWidth, INTERFACE_BAR_HEIGHT - 1, gInterfaceBarWidth, gInterfaceWindowBuffer, gInterfaceBarWidth);
} else {
@@ -342,6 +372,8 @@ int interfaceInit()
backgroundFrmImage.unlock();
}
extendedApBarInitToWindow();
fid = buildFid(OBJ_TYPE_INTERFACE, 47, 0, 0, 0);
if (!_inventoryButtonNormalFrmImage.lock(fid)) {
// NOTE: Uninline.
@@ -560,8 +592,6 @@ int interfaceInit()
return intface_fatal_error(-1);
}
blitBufferToBuffer(gInterfaceWindowBuffer + gInterfaceBarWidth * 14 + 316 + gInterfaceBarContentOffset, 90, 5, gInterfaceBarWidth, gInterfaceActionPointsBarBackground, 90);
if (indicatorBarInit() == -1) {
// NOTE: Uninline.
return intface_fatal_error(-1);
@@ -970,49 +1000,49 @@ void interfaceRenderArmorClass(bool animate)
// 0x45EE0C
void interfaceRenderActionPoints(int actionPointsLeft, int bonusActionPoints)
{
unsigned char* frmData;
ConstBuffer2D bulbFrmBuf {};
if (gInterfaceBarWindow == -1) {
return;
}
blitBufferToBuffer(gInterfaceActionPointsBarBackground, 90, 5, 90, gInterfaceWindowBuffer + 14 * gInterfaceBarWidth + gInterfaceBarContentOffset + 316, gInterfaceBarWidth);
blitBuffer2D(apBarBackgroundBuf2D(), interfaceWindowBuf2D(), gInterfaceBarContentOffset + apBarXOffset, kApBarYOffset);
if (actionPointsLeft == -1) {
frmData = _redLightFrmImage.getData();
actionPointsLeft = 10;
bulbFrmBuf = _redLightFrmImage.getBuffer();
actionPointsLeft = apBarMaxAP;
bonusActionPoints = 0;
} else {
frmData = _greenLightFrmImage.getData();
bulbFrmBuf = _greenLightFrmImage.getBuffer();
if (actionPointsLeft < 0) {
actionPointsLeft = 0;
}
if (actionPointsLeft > 10) {
actionPointsLeft = 10;
if (actionPointsLeft > apBarMaxAP) {
actionPointsLeft = apBarMaxAP;
}
if (bonusActionPoints >= 0) {
if (actionPointsLeft + bonusActionPoints > 10) {
bonusActionPoints = 10 - actionPointsLeft;
if (actionPointsLeft + bonusActionPoints > apBarMaxAP) {
bonusActionPoints = apBarMaxAP - actionPointsLeft;
}
} else {
bonusActionPoints = 0;
}
}
int index;
for (index = 0; index < actionPointsLeft; index++) {
blitBufferToBuffer(frmData, 5, 5, 5, gInterfaceWindowBuffer + 14 * gInterfaceBarWidth + 316 + index * 9 + gInterfaceBarContentOffset, gInterfaceBarWidth);
}
for (; index < (actionPointsLeft + bonusActionPoints); index++) {
blitBufferToBuffer(_yellowLightFrmImage.getData(), 5, 5, 5, gInterfaceWindowBuffer + 14 * gInterfaceBarWidth + 316 + gInterfaceBarContentOffset + index * 9, gInterfaceBarWidth);
int numBulbs = actionPointsLeft + bonusActionPoints;
for (int index = 0; index < numBulbs; index++) {
constexpr int bulbXOffset = kApBarBulbSize + kApBarBulbMargin;
auto frmBuf = index < actionPointsLeft
? bulbFrmBuf
: _yellowLightFrmImage.getBuffer();
blitBuffer2D(frmBuf, 0, 0, kApBarBulbSize, kApBarBulbSize, interfaceWindowBuf2D(), gInterfaceBarContentOffset + apBarXOffset + index * bulbXOffset, kApBarYOffset);
}
if (!gInterfaceBarInitialized) {
windowRefreshRect(gInterfaceBarWindow, &gInterfaceBarActionPointsBarRect);
windowRefreshRect(gInterfaceBarWindow, &apBarRect);
}
}
@@ -2496,6 +2526,47 @@ static void customInterfaceBarExit()
}
}
// Inits AP bar offsets based on custom AP bar setting and blits the correct AP graphic into the window buffer. Must be called after main panel FRM art is blitted.
static void extendedApBarInitToWindow()
{
constexpr int apBarXOffsetOriginal = 316;
constexpr int apBarWidthOriginal = 90;
constexpr int apBarMaxAPOriginal = 10;
static_assert(apBarWidthOriginal <= kApBarMaxWidth);
static_assert(apBarMaxAPOriginal <= kApBarMaxBulbs);
if (settings.ui.extend_ap_bar) {
apBarMaxAP = kApBarMaxBulbs;
apBarWidth = kApBarMaxWidth;
// Shift X offset left according to the extra AP bar width.
apBarXOffset = apBarXOffsetOriginal - (kApBarMaxWidth - apBarWidthOriginal) / 2;
} else {
apBarMaxAP = apBarMaxAPOriginal;
apBarWidth = apBarWidthOriginal;
apBarXOffset = apBarXOffsetOriginal;
}
apBarRect = { gInterfaceBarContentOffset + apBarXOffset, kApBarYOffset, gInterfaceBarContentOffset + apBarXOffset + apBarWidth - 1, kApBarYOffset + kApBarBulbSize - 1 };
Buffer2D ifaceBarBuf = interfaceWindowBuf2D();
// Blit extended AP bar art into static window buffer, overwriting pixels from the interface bar FRM.
if (settings.ui.extend_ap_bar) {
if (ArtPtr apBarArt { artLoad("art\\intrface\\iface_apbar_e.frm") }) {
if (auto apBarFrmBuf = artGetFrameBuffer(apBarArt.get(), 0, 0)) {
int apBarBgXOffset = apBarXOffset - 23;
constexpr int apBarBgYOffset = kApBarYOffset - 4;
blitBuffer2D(apBarFrmBuf, ifaceBarBuf, gInterfaceBarContentOffset + apBarBgXOffset, apBarBgYOffset);
}
}
}
// Blit the thin bar of pixels covering the bulbs (that change color) into static apBarBackgroundData buffer.
Buffer2D abBarBgBuf = apBarBackgroundBuf2D();
blitBuffer2D(ifaceBarBuf, gInterfaceBarContentOffset + apBarXOffset, kApBarYOffset, abBarBgBuf.width, abBarBgBuf.height, abBarBgBuf);
}
unsigned char* customInterfaceBarGetBackgroundImageData()
{
if (!gInterfaceBarIsCustom) {
-1
View File
@@ -34,7 +34,6 @@ extern bool gInterfaceBarMode;
extern int gInterfaceBarWidth;
extern bool gInterfaceBarIsCustom;
extern int gInterfaceBarContentOffset;
extern bool gInterfaceSidePanelsExtendFromScreenEdge;
int interfaceInit();
void interfaceReset();

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