Compare commits

..
Author SHA1 Message Date
Mike Klaas f9a5aeff9a PR feedback 2026-04-10 23:48:45 -07:00
Mike Klaas bf55a6fbb1 cleanup 2026-04-10 23:48:45 -07:00
github-actions[bot] 9764e2911d chore: auto-format with clang-format 2026-04-11 06:30:12 +00:00
Mike Klaas 5d5bb78087 Implement perk carryover, plus get/set_perk_owed
As in Sfall, the 0/1 "perk owned" boolean becomes an unsigned char, allowing perks to accumulate if they are not spent by the time the next perk is granted.

Unlike Sfall, we add a mode where perks must be spent "in order".  For instance, if the player has not spent their level 3 perk when they hit level 6, they still have 2 perks to spend, but the first perk must be their level 3 perk (with only level 3 perks available), and then they can spend their level 6 perk.  This behaviour is configurable to be: vanilla, CE, or Sfall.

This value is the same char saved in vanilla and Sfall, so is backward compatible with old saves.

To implement `get/set_perk_owed`, perk incrementing has to happen at level-up time instead of the first time the character sheet is opened after level up.  This matches Sfall's implementation.

It would be pretty easy to implement set_perk_freq / seq_perk_freq / set_perk_level_mod / get_perk_available.  For now leaving this out since the PR is big and I'm not sure how often these are used in mods.
2026-04-10 23:21:41 -07:00
104 changed files with 1401 additions and 4178 deletions
-13
View File
@@ -10,7 +10,6 @@
*.userosscache
*.sln.docstates
.DS_Store
.local-tools/
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
@@ -400,15 +399,3 @@ 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
-35
View File
@@ -20,10 +20,6 @@ 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)
@@ -66,8 +62,6 @@ target_sources(${EXECUTABLE_NAME} PUBLIC
"src/combat.h"
"src/config.cc"
"src/config.h"
"src/content_config.cc"
"src/content_config.h"
"src/credits.cc"
"src/credits.h"
"src/critter.cc"
@@ -452,35 +446,6 @@ 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})
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"
)
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)
install(TARGETS ${EXECUTABLE_NAME} DESTINATION "Payload")
+3
View File
@@ -119,6 +119,9 @@
"base"
],
"generator": "Xcode",
"cacheVariables": {
"CMAKE_XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY": ""
},
"condition": {
"type": "equals",
"lhs": "${hostSystemName}",
-66
View File
@@ -2,72 +2,6 @@
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.
### 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. It supports both Fallout 2 and Fallout 1 archives.
From the repo root, build it with your normal CMake workflow, targeting `fallout2-dat`:
`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:
1. `./<BUILD_DIR>/fallout2-dat <archive.dat> list [pattern]`
2. `./<BUILD_DIR>/fallout2-dat <archive.dat> info [pattern]`
3. `./<BUILD_DIR>/fallout2-dat <archive.dat> extract [--lower] <output-dir> [pattern]`
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:
`fallout2-dat master.dat extract --lower /tmp/fallout2-dat-lower data\\*`
## Updating SDL
SDL is pinned for native builds in `third_party/sdl2/CMakeLists.txt`. Right now, Android also relies on checked-in Java bindings in `os/android/app/src/main/java/org/libsdl/app`, and those bindings must match the SDL version fetched by CMake.
+63 -32
View File
@@ -2,15 +2,13 @@
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) 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.
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.
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
@@ -24,10 +22,17 @@ Download and copy `fallout2-ce.exe` to your `Fallout2` folder. It serves as a dr
```console
$ sudo apt install innoextract
$ innoextract ~/Downloads/setup_fallout_2_1.02_gog_v1_\(77792\).exe -d Fallout2
$ innoextract ~/Downloads/setup_fallout2_2.1.0.18.exe -I app
$ mv app Fallout2
```
- Download the Linux release archive, extract `fallout2-ce`, and copy it into this folder.
- 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
```
- Run `./fallout2-ce`.
@@ -43,13 +48,13 @@ $ innoextract ~/Downloads/setup_fallout_2_1.02_gog_v1_\(77792\).exe -d Fallout2
```console
$ brew install innoextract
$ innoextract ~/Downloads/setup_fallout_2_1.02_gog_v1_\(77792\).exe -d fallout2
$ mv fallout2 /Applications/Fallout2
$ innoextract ~/Downloads/setup_fallout2_2.1.0.18.exe -I app
$ mv app /Applications/Fallout2
```
- [Download](https://github.com/fallout2-ce/fallout2-ce/releases) and copy `Fallout II Community Edition.app` to this folder.
- Download and copy `fallout2-ce.app` to this folder.
- Run `Fallout II Community Edition.app`.
- Run `fallout2-ce.app`.
### Android
@@ -79,7 +84,7 @@ $ mv fallout2 /Applications/Fallout2
### Browser
> **NOTE**: WebAssembly build with emscripten
> **NOTE**: WebAssebly build with emscripten
```
docker run --rm -v $(pwd):/src emscripten/emsdk:3.1.74 sh -c 'git config --global --add safe.directory "*" && mkdir -p build && cd build && emcmake cmake ../ -DCMAKE_TOOLCHAIN_FILE=../cmake/toolchain/Emscripten.cmake && emmake make'
```
@@ -91,35 +96,59 @@ The main configuration file is `fallout2.cfg`. There are several important setti
The `sound` folder (with `music` folder inside) might be located either in `data` folder, or be in the Fallout folder. Update `music_path1` setting to match your hierarchy, usually it's `data/sound/music/` or `sound/music/`. Make sure it matches your path exactly (so it might be `SOUND/MUSIC/` if you've installed Fallout from CD). Music files themselves (with `ACM` extension) should be all uppercased, regardless of `sound` and `music` folders.
Additional settings for screen resolution, UI customization, and map options are now integrated into the main `fallout2.cfg` file (previously part of `f2_res.ini` from Mash's HRP). When Fallout 2 CE starts, if it detects an existing `f2_res.ini` file, it automatically migrates these settings into `fallout2.cfg`. After migration, `fallout2.cfg` becomes the single source of truth for this configuration.
The following settings can be configured in `fallout2.cfg` under the `[screen]` and `[ui]` sections:
The second configuration file is `f2_res.ini`. Use it to change game window size, enable/disable fullscreen mode and configure IFACE settings (the control bar at the bottom of the game screen).
```ini
[screen]
resolution_x=1600 ; actual game window size (screen resolution), in pixels
resolution_y=1080
windowed=1 ; 0 = fullscreen
scale=2 ; 1 = original scale, 2 = 2x scale, etc. (e.g. at scale 2 and screen resolution 1920x1080, in-game resolution will be 960x540, thus every pixel is twice as wide and tall)
[MAIN]
SCR_WIDTH=1280
SCR_HEIGHT=720
WINDOWED=1 ; 0 = fullscreen
SCALE_2X=1 ; 0 = original scale, 1 = 2x - requires increasing the minimum resolution from 640x480 to 1280x960.
[ui]
iface_bar_mode=0 ; 0 = interface bar below game window, 1 = interface bar overlaps game window
iface_bar_width=800 ; Width of interface bar (640 = original, 800 = extended)
iface_bar_side_art=2 ; Interface bar side art style (0=black, 1=metal grey, 2=leather, 3-8=alternative styles)
iface_bar_sides_ori=0 ; Side graphics orientation (0=extend from bar to edges, 1=extend from edges to bar)
splash_screen_size=1 ; Splash screen scaling (0=original size, 1=fit preserving aspect, 2=stretch to fill)
ignore_map_edges=0 ; Hi-res map scroll edges (0=enabled, 1=ignored)
[IFACE]
; if IFACE_BAR_MODE=0 - the bottom of the map view window sits at the top of the IFACE Bar.
; if IFACE_BAR_MODE=1 - the bottom of the map view window extends to the base of the screen and is overlapped by the IFACE Bar.
IFACE_BAR_MODE=0
;if IFACE_BAR_SIDE_ART=0 - Black, No Iface-bar side art used.
;if IFACE_BAR_SIDE_ART=1 - Metal (grey) look Iface-bar side art used.
;if IFACE_BAR_SIDE_ART=2 - Leather look Iface-bar side art used.
;if IFACE_BAR_SIDE_ART=3-6 - Alternative Metal look Iface-bar side art used.
;if IFACE_BAR_SIDE_ART=7 - Alternative Leather look Iface-bar side art used.
;if IFACE_BAR_SIDE_ART=8 - Metal (brown) look Iface-bar side art used.
IFACE_BAR_SIDE_ART=2
;if IFACE_BAR_SIDES_ORI=0 - Iface-bar side graphics extend from the Iface-Bar to the Screen edges.
;if IFACE_BAR_SIDES_ORI=1 - Iface-bar side graphics extend from the Screen edges to the Iface-Bar.
IFACE_BAR_SIDES_ORI=0
;This will increase the width of the interface bar expanding the area used to display text.
;if IFACE_BAR_WIDTH=640 - Interface bar will remain at it's original width.
;if IFACE_BAR_WIDTH=800 - Interface bar will use 800pix wide asset from f2_res.dat.
;IFACE_BAR_WIDTH=640
[STATIC_SCREENS]
;if SPLASH_SCRN_SIZE=0 - Splash screen shows at original size if it fits, otherwise scales down while preserving aspect ratio.
;if SPLASH_SCRN_SIZE=1 - Splash screen scales to fit the screen while preserving aspect ratio.
;if SPLASH_SCRN_SIZE=2 - Splash screen stretches to fill the entire screen.
SPLASH_SCRN_SIZE=0
[MAPS]
;if IGNORE_MAP_EDGES=0 - Hi-Res map scroll edges are enabled.
;if IGNORE_MAP_EDGES=1 - Hi-Res map scroll edges are ignored.
IGNORE_MAP_EDGES=0
```
**Recommendations:**
Recommendations:
- **Desktops**: Use any size you see fit.
- **Tablets**: Set these values to logical resolution of your device, for example iPad Pro 11 is 1668x2388 (pixels), but it's logical resolution is 834x1194 (points).
- **Mobile phones**: Set height to 480, calculate width according to your device screen (aspect) ratio, for example Samsung S21 is 20:9 device, so the width should be 480 * 20 / 9 = 1067.
In time this stuff will receive in-game interface, right now you have to do it manually. To see all currently working fallout2.cfg settings, just run the game once and quit. It will be automatically updated with defaults for every supported setting.
*Note*: Use of the IFACE_BAR settings requires the `f2_res.dat` file, which contains graphical assets. Various versions are available, but one compatible with the above settings can be found here: [f2_res.dat](https://github.com/fallout2-ce/fallout2-ce/raw/refs/heads/main/files/f2_res.dat)
In time this stuff will receive in-game interface, right now you have to do it manually. For a sample f2_res.ini configuration file, containing all currently working settings use this link: [f2_res.ini](https://raw.githubusercontent.com/fallout2-ce/fallout2-ce/refs/heads/mainmenu/files/f2_res.ini)
The second configuration file is `ddraw.ini` (part of Sfall). There are dozens of options that adjust or override engine behaviour and gameplay mechanics. This file is intended for modders and advanced users.
*Note*: use of the IFACE_BAR settings requires the f2_res.dat file, which contains graphical assets. Various versions are available, but one compatible with the above f2_res.ini be found here: [f2_res.dat](https://github.com/fallout2-ce/fallout2-ce/raw/refs/heads/mainmenu/files/f2_res.dat)
The third configuration file is `ddraw.ini` (part of Sfall). There are dozens of options that adjust or override engine behaviour and gameplay mechanics. This file is intended for modders and advanced users.
For a sample ddraw.ini configuration file, containing all currently working settings use this link: [ddraw.ini](https://raw.githubusercontent.com/fallout2-ce/fallout2-ce/refs/heads/main/files/ddraw.ini)
@@ -136,9 +165,11 @@ 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. 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. 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.
For current sfall compatibility status and the remaining work needed to close gaps, see [SFALL_COMPATIBILITY.md](SFALL_COMPATIBILITY.md).
### 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.
## License
+15 -37
View File
@@ -4,28 +4,6 @@ This document tracks Fallout 2 CE compatibility with sfall. This is for modders
For now, this covers opcodes/metarules, and hooks. In the future, it will include other ways of modifying the engine (like ini files), and other Sfall-specific behaviour.
## Settings (ddraw.ini → fallout2.cfg / game.cfg)
Settings previously read from `ddraw.ini` have been moved into standard CE config files.
Most settings that control game behavior (premade characters, extra message files, combat tweaks, worldmap, etc.) have been moved into [`<DAT>/config/game.cfg`](files/ce.dat/config/game.cfg), which is a content-mod config file intended to be overridden by mods. See that file for the full list with descriptions.
The following settings were moved into [`fallout2.cfg`](files/fallout2.cfg) instead:
| ddraw.ini section | ddraw.ini key | fallout2.cfg section | fallout2.cfg key |
| --- | --- | --- | --- |
| `Misc` | `SkipOpeningMovies` | `ui` | `skip_opening_movies` |
| `Misc` | `DisplayKarmaChanges` | `ui` | `display_karma_changes` |
| `Misc` | `DisplayBonusDamage` | `ui` | `display_bonus_damage` |
| `Misc` | `NumbersInDialogue` | `ui` | `numbers_in_dialogue` |
| `Misc` | `AutoQuickSave` | `ui` | `auto_quick_save` |
| `Main` | `EnableHighResolutionStencil` | `ui` | `enable_high_resolution_stencil` |
| `Misc` | `ConsoleOutputPath` | `debug` | `console_output_path` |
| `Misc` | `GaplessMusic` | `sound` | `gapless_music` |
| `Misc` | `ScreenshotsFormat` | `system` | `screenshots_format` |
| `Misc` | `UseWalkDistance` | `qol` | `use_walk_distance` |
| `Misc` | `AutoOpenDoors` | `qol` | `auto_open_doors` |
## Opcodes / Metarules
See [`https://sfall-team.github.io/sfall/`](https://sfall-team.github.io/sfall/) for documentation on specific functions.
@@ -38,7 +16,7 @@ See [`https://sfall-team.github.io/sfall/`](https://sfall-team.github.io/sfall/)
| 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>has/set_fake_perk<br>has/set_fake_trait<br>set_selectable_perk<br>set_perkbox_title<br>show/hide_real_perks<br>perk_add_mode<br>clear_selectable_perks<br>add/remove_trait<br>seq_perk_freq | not implemented | - |
| Perks and traits | get_perk_owed<br>set_perk_owed<br>set_perk_image<br>set_perk_*<br>set_pyromaniac_mod<br>apply_heaveho_fix<br>set_swiftlearner_mod<br>has/set_fake_perk<br>has/set_fake_trait<br>set_selectable_perk<br>set_perkbox_title<br>show/hide_real_perks<br>perk_add_mode<br>clear_selectable_perks<br>add/remove_trait<br>seq_perk_freq | implemented: only get_perk_owed, set_perk_owed | - |
| 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 | - |
@@ -52,7 +30,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 | âś… | - |
| 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 | âś… | `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,20 +39,20 @@ 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/set_bodypart_hit_modifier, combat_data, get/set_critter_current_ap, get/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: 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<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 | âś… | - |
| Interface / Outline | outlined_object<br>get_outline<br>set_outline | âś… 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: critter_inven_obj2, item_weight | - |
| 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 | 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>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 | - |
| 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, obj_is_carrying_obj, loot_obj, dialog_obj, obj_under_cursor, get_object_data, get_flags, set_flags, get_proto_data, set_proto_data | - |
| Other / Game management | set_movie_path<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, 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 | not implemented | - |
| 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. |
| 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, 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
@@ -82,11 +60,11 @@ See [`https://sfall-team.github.io/sfall/`](https://sfall-team.github.io/sfall/)
| --- | --- | --- | --- |
| ToHit | `HOOK_TOHIT` | âś… | - |
| AfterHitRoll | `HOOK_AFTERHITROLL` | đźš« | Et tu |
| CalcAPCost | `HOOK_CALCAPCOST` | âś… | - |
| CalcAPCost | `HOOK_CALCAPCOST` | đźš« | - |
| DeathAnim1 | `HOOK_DEATHANIM1` | đźš« | Use DEATHANIM2 instead |
| DeathAnim2 | `HOOK_DEATHANIM2` | âś… | - |
| CombatDamage | `HOOK_COMBATDAMAGE` | âś… | - |
| OnDeath | `HOOK_ONDEATH` | âś… | - |
| 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. |
@@ -96,16 +74,16 @@ See [`https://sfall-team.github.io/sfall/`](https://sfall-team.github.io/sfall/)
| 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` | âś… | - |
| MouseClick | `HOOK_MOUSECLICK` | đźš« | - |
| UseSkill | `HOOK_USESKILL` | đźš« | - |
| Steal | `HOOK_STEAL` | đźš« | Et tu |
| WithinPerception | `HOOK_WITHINPERCEPTION` | âś… | - |
| WithinPerception | `HOOK_WITHINPERCEPTION` | đźš« | Et tu |
| InventoryMove | `HOOK_INVENTORYMOVE` | âś… | - |
| InvenWield | `HOOK_INVENWIELD` | đźš« | - |
| AdjustFID | `HOOK_ADJUSTFID` | đźš« | - |
| CombatTurn | `HOOK_COMBATTURN` | âś… | - |
| StdProcedure | `HOOK_STDPROCEDURE` | âś… | - |
| StdProcedureEnd | `HOOK_STDPROCEDURE_END` | âś… | - |
| StdProcedure | `HOOK_STDPROCEDURE` | đźš« | Et tu |
| StdProcedureEnd | `HOOK_STDPROCEDURE_END` | đźš« | - |
| CarTravel | `HOOK_CARTRAVEL` | đźš« | - |
| SetGlobalVar | `HOOK_SETGLOBALVAR` | đźš« | - |
| RestTimer | `HOOK_RESTTIMER` | đźš« | Et tu |
Binary file not shown.
-128
View File
@@ -1,128 +0,0 @@
;
; Game general config
;
; Fallout 2
;
[start]
; Starting year, month, day. Month and day are 0-indexed (0 = January, 0 = first day of month).
year=2241
month=6
day=24
; To start a new game somewhere other than artemple.map, set this to the map file name.
map=
; Starting (tribal) and default (jumpsuit) player models. Can also be changed in-game via script.
model_male=hmwarr
model_male_default=hmjmps
model_female=hfprim
model_female_default=hfjmps
; Set to 1 if you want the pipboy to be available at the start of the game.
; Set to 2 to make the pipboy available by only skipping the vault suit movie check.
pipboy=0
[karma]
; FRM numbers for karma icons on the character screen. Number of points entries should be 1 less than number of frms entries.
; Example: frms=47,48,49 points=-100,100
frms=
points=
[items]
; Proto IDs for healing items.
stimpak=40
super_stimpak=144
healing_powder=273
[dialog]
; Set to 1 to prevent using 0 to escape from dialogue at any time.
no_exit_hotkey=1
; Set to 1 to allow using '^' in dialog msg files to specify gender-based alternative text.
; Text must be enclosed in angle brackets, e.g. <MaleText^FemaleText>.
gender_words=0
[main_menu]
; Custom version string shown on the main menu. Supports up to two %d placeholders for the game version numbers.
; Example: FALLOUT II v1.02d
version_string=
; Font color for the version string and copyright text. Last byte picks the color (default 0x3C).
; First byte flags: 1 - version string only, 2 - underline, 4 - monospace font.
; Example: 0x000055
font_color=0
; Font color for the main menu button text.
big_font_color=0
; Pixel offset for main menu buttons.
offset_x=0
offset_y=0
; Pixel offset for the copyright text line.
credits_offset_x=0
credits_offset_y=0
[movies]
; Number of days after game start when each Hakunin dream sequence becomes available.
artimer1=90
artimer2=180
artimer3=270
artimer4=360
[combat]
; Damage formula used to calculate combat damage.
; 0 - Fallout default, 1 - Glovz's Damage Fix, 2 - Glovz's Damage Fix with Damage Multiplier tweak, 5 - Haenlomal's Yet Another Ammo Mod
; Don't change this unless another mod explicitly requires it.
damage_formula=0
; Set to 1 to fix the bug that caused bonus HtH damage to not be applied correctly.
bonus_hth_damage_fix=1
; Set to 1 to remove the time limits that prevent critical hits/misses in the first few days of the game.
remove_critical_time_limits=0
; Set to 1 to allow science and repair to be used on the player, or 2 for all critters (rather than only brahmin/robots).
science_on_critters=0
; Set to 1 to enable modified burst bullet distribution (spray mod).
burst_enabled=0
; Ratio of bullets going to the center group vs. left/right in burst attacks. Multiplier is capped at divisor.
burst_center_mult=1
burst_center_div=3
; Ratio of center-group bullets that target the primary target directly. Multiplier is capped at divisor.
burst_target_mult=1
burst_target_div=2
[explosions]
; Set to 1 to make explosions and projectiles emit light.
emit_light=0
; Dynamite damage range. Max is capped at 9999; min is capped at max.
dynamite_max=50
dynamite_min=30
; Plastic explosive damage range. Max is capped at 9999; min is capped at max.
plastic_explosive_max=80
plastic_explosive_min=40
[skilldex]
; FRM numbers for Skilldex skill cursors. Default is 293 for all skills.
lockpick=293
steal=293
traps=293
first_aid=293
doctor=293
science=293
repair=293
[worldmap]
; Set to 1 to prevent using number keys to enter unvisited areas on a town map.
town_map_hotkeys_fix=1
; Set to 1 to show travel markers on the world map, or 2 to show only when sneaking.
trail_markers=0
; Set to 1 to skip the encounter with Frank Horrigan.
disable_horrigan=0
; City reputation overrides. Syntax: city_id:global_id pairs separated by commas.
; Example: 0:5001,2:5003,3:5004
city_reputation_list=
[characters]
; Custom premade characters for the character selection screen.
; premade_paths is a comma-separated list of file names (max 11 chars each) in the premade\ folder, without extension.
; premade_fids is a comma-separated list of FRM numbers for the character portrait, matching the order of premade_paths.
; Example: premade_paths=combat,diplomat,stealth premade_fids=201,203,202
premade_paths=
premade_fids=
[text]
; Comma-separated list of extra game message file names (without .msg extension) to load from the game\ folder.
; Use 'filename:number' syntax to assign explicit IDs. Access via message_str_game script function.
extra_msg_file_list=
+153
View File
@@ -3,9 +3,26 @@
;XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
[Main]
;Set to one to hide areas outside map bounds when using higher than 640x480 resolution, and to zero to disable
;EnableHighResolutionStencil=1
;XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
[Misc]
;Uncomment these lines to change the timers of how many days after the game starts Hakunin dream sequences will occur
;MovieTimer_artimer1=5000
;MovieTimer_artimer2=5000
;MovieTimer_artimer3=5000
;MovieTimer_artimer4=5000
;To start a new game somewhere other than artemple.map, uncomment the next line and set it to the map you want to load
;StartingMap=V13ent.map
;To change the 'FALLOUT II v1.02d' version string on the main menu, uncomment the next line
;You can use up to 2 %d's in this if you want to include Fallout's version number somewhere
;VersionString=FALLOUT II v1.02d
;To use a config file other than fallout2.cfg, uncomment the next line and add the name of your new file
;ConfigFile=
@@ -13,6 +30,40 @@
;If you want to load multiple patch files (up to 1000) at once, you can include a %d in the file name (sprintf syntax)
;PatchFile=patch%03d.dat
;Set to 1 to allow using the caret character '^' in dialog msg files to specify alternative text in dialogue based on the player's gender
;The text must be enclosed in angle brackets (example: <MaleText^FemaleText>)
;DialogGenderWords=0
;To change the default and starting player models, uncomment the next four lines.
;The default models can also be changed in-game via script
;MaleStartModel=hmjmps
;MaleDefaultModel=hmjmps
;FemaleStartModel=hfjmps
;FemaleDefaultModel=hfjmps
;To change the starting year, month or day, uncomment the next 3 lines
;Both StartMonth and StartDay are 0-indexed (i.e. 0 is January or the first day of a month)
;StartYear=2241
;StartMonth=01
;StartDay=01
;Set to 1 if you want the pipboy to be available at the start of the game
;Set to 2 to make the pipboy available by only skipping the vault suit movie check
;PipBoyAvailableAtGameStart=1
;Choose the damage formula used to calculate combat damage.
;Don't set this to anything other than 0 unless another mod you're using explicitly tells you to!
;0 - Fallout default
;1 - Glovz's Damage Fix
;2 - Glovz's Damage Fix with Damage Multiplier tweak
;5 - Haenlomal's Yet Another Ammo Mod
;DamageFormula=0
;Prevents you from using 0 to escape from dialogue at any time.
;DialogueFix=1
;Prevents you from using number keys to enter unvisited areas on a town map
TownMapHotkeysFix=1
;Set to 1 to use a CriticalOverrides.ini file to override the default critical table
;Set to 2 to use the default critical with bug fixes (doesn't require an ini)
@@ -20,9 +71,108 @@
;If the ExtraKillTypes option is enabled, this should be set to 3, with containing entries for any new types
;Must be non-zero to use the edit/get/reset_critical script functions
;OverrideCriticalTable=2
;Set to 1 to get notification of karma changes in the notification window
DisplayKarmaChanges=0
;Set to 1 to skip the 3 opening movies
;Set to 2 to also skip the splash screen
SkipOpeningMovies=0
;Change the Skilldex cursor FRM numbers
;Default is 293 for all skills
Lockpick=293
Steal=293
Traps=293
FirstAid=293
Doctor=293
Science=293
Repair=293
;Uncomment these lines to control the premade characters offered when starting a new game
;Multiple options should be separated by commas, and there must be the same number of entries in both lines
;Each name in PremadePaths is limited to 11 characters
;PremadePaths=combat,diplomat,stealth
;PremadeFIDs=201,203,202
;Use this line to modify the list of cities and their associated global variables used for city reputations
;Syntax is 'city id:global id', with each city/global pair separated by a comma.
;CityRepsList=0:5001,2:5003,3:5004,4:5005,5:5006,6:5007,7:5008,8:5009,10:5011,11:5012,60:1640
;Set this to a valid path to save a copy of the console contents
;ConsoleOutputPath=console.txt
;Set to 1 to add additional pages of save slots !!! can be implemented !!!
;ExtraSaveSlots=1
;To use more than one save slot for quick saving (F6 key) without picking a slot beforehand, set the next two lines
;Quick save will cyclically overwrite saves from the first slot on the specified page to the last slot on the n-th page
;AutoQuickSave sets how many pages you want to use for quick saving (valid range: 1..10)
;Set to 0 to disable
AutoQuickSave=0
;These lines allow you to control the karma FRMs displayed on the character screen
;Number of KarmaPoints should be 1 less than number of KarmaFRMs
;KarmaFRMs=47,48,49
;KarmaPoints=-100,100
;Set to 1 to allow science and repair to be used on the player, or 2 for all critters. (Rather than only brahmin/robots)
ScienceOnCritters=0
;Set to 1 to fix the bug that caused bonus HtH damage to not be applied correctly.
BonusHtHDamageFix=1
;Set to 1 to display additional points of damage from Bonus HtH/Ranged Damage perks in the inventory
;DisplayBonusDamage=1
;Set to 1 to remove the limits that stop the game from rolling critical successes/failures in the first few days of game time
RemoveCriticalTimelimits=0
;Change the colour of the font used on the main menu for the Fallout/sfall version string and copyright text
;It's the last byte ('3C' by default) that picks the colour used. The first byte supplies additional flags for this option
;1 - change the colour for the version string only
;2 - underline text for the version string
;4 - use monospace font for the version string
;MainMenuFontColour=0x000055
;Change the colour of the font used on the main menu for the button text
;MainMenuBigFontColour=0x3C
;Uncomment the next four lines to move the main menu buttons and credit text (the 'Copyright(c)' line on the main menu)
;MainMenuOffsetX=392
;MainMenuOffsetY=26
;MainMenuCreditsOffsetX=0
;MainMenuCreditsOffsetY=0
;These options modify the bullet distribution of burst attacks
;All the bullets are divided into three groups: center, left, and right
;These groups will then travel along three parallel tracks, trying to hit targets on the way
;CenterMult/Div set the ratio of how many bullets go to the center group, and the remaining are divided equally to the left and right sides
;TargetMult/Div set the ratio of how many bullets in the center group will attack the primary target directly
;Multiplier values are capped at divisor values
;ComputeSpray_CenterMult=1
;ComputeSpray_CenterDiv=3
;ComputeSpray_TargetMult=1
;ComputeSpray_TargetDiv=2
;Set to 1 to make explosions and projectiles emit light
;ExplosionsEmitLight=1
;Uncomment these lines to change explosives damage. DmgMax can be set to 9999 at max, and DmgMin is capped at DmgMax
;Dynamite_DmgMax=50
;Dynamite_DmgMin=30
;PlasticExplosive_DmgMax=80
;PlasticExplosive_DmgMin=40
;To add additional game msg files, uncomment the next line and set a comma delimited list of filenames without .msg extension
;By default, the files will have consecutive numbers assigned beginning with 0
;You can use the syntax 'filename:number' to manually assign numbers to specific msg files, with each pair separated by a comma
;If a file after the specified pair does not have a number assigned, it will have the next consecutive number from the last pair
;You need to use the message_str_game script function to get messages from the files
;ExtraGameMsgFileList=
;Set to 1 to display numbered dialogue options
NumbersInDialogue=0
;XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
; Configuration ini files
;XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
@@ -41,6 +191,9 @@
;See the Unarmed.ini in the modders pack for an example file
;UnarmedFile=sfall\Unarmed.ini
;To change some engine parameters for the game mechanics, uncomment the next line
;TweaksFile=sfall\Tweaks.ini
;XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
[Scripts]
-117
View File
@@ -1,117 +0,0 @@
[debug]
;Set this to a valid path to save a copy of the console contents
console_output_path=
mode=log
output_map_data_info=1
show_load_info=1
show_script_messages=1
show_tile_num=1
window_height=192
window_width=300
[mapper]
default_f8_as_game=1
fix_map_inventory=0
fix_map_objects=0
ignore_rebuild_errors=0
librarian=0
override_librarian=0
rebuild_protos=0
run_mapper_as_game=0
save_text_maps=0
show_pid_numbers=0
sort_script_list=0
use_art_not_protos=0
[preferences]
brightness=1.000000
combat_difficulty=1
combat_looks=1
combat_messages=1
combat_speed=50
combat_taunts=1
game_difficulty=1
item_highlight=1
language_filter=0
mouse_sensitivity=1.000000
player_speedup=0
running=1
running_burning_guy=1
subtitles=1
target_highlight=1
text_base_delay=6.000000
text_line_delay=2.000000
violence_level=3
[screen]
resolution_x=640
resolution_y=480
scale=1
windowed=1
[sound]
cache_size=448
debug=1
debug_sfxc=1
gapless_music=1
initialize=1
master_volume=22281
music=1
music_path1=data\sound\music\
music_path2=sound\music\
music_volume=22281
sndfx_volume=22281
sounds=1
speech=1
speech_volume=22281
[system]
art_cache_size=8
color_cycling=1
critter_dat=critter.dat
critter_patches=data
cycle_speed_factor=1
executable=game
free_space=20480
hashing=1
interrupt_walk=1
language=english
master_dat=master.dat
master_patches=data
screenshots_format=png
scroll_lock=0
splash=5
[ui]
;Speed up or slow down all UI animations such as screen fade, dialog window show/hide, AC numbers roll, etc.
anim_speed=2.000000
;To use more than one save slot for quick saving (F6 key) without picking a slot beforehand
;Quick save will cyclically overwrite saves from the first slot on the specified page to the last slot on the n-th page
;auto_quick_save sets how many pages you want to use for quick saving (valid range: 1..10)
;Set to 0 to disable
auto_quick_save=3
;Set to 1 to display additional points of damage from Bonus HtH/Ranged Damage perks in the inventory
display_bonus_damage=1
;Set to 1 to get notification of karma changes in the notification window
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
iface_bar_width=800
ignore_map_edges=0
;Set to 1 to display numbered dialogue options
numbers_in_dialogue=0
;Set to 1 to skip the 3 opening movies
;Set to 2 to also skip the splash screen
skip_opening_movies=0
splash_screen_size=1
[qol]
; Automatically open doors that are unlocked and has no script attached.
auto_open_doors=0
; Overrides distance at which walk animation is used when using an object.
use_walk_distance=5
@@ -1,80 +0,0 @@
#include "sfall.h"
#include "dik.h"
#include "define_lite.h"
#include "command_lite.h"
#include "test_utils.h"
#define TEST_ITEM_PID 40
#define OP_GET_CRITTER_CURRENT_AP 0x8191
#define OP_SET_CRITTER_CURRENT_AP 0x8192
#define opcode_exists(opcode) sfall_func1("opcode_exists", opcode)
procedure run_combat_turn_roundtrip begin
variable originalAp := get_critter_current_ap(dude_obj);
variable originalFreeMove := get_combat_free_move;
variable expectedAp := originalAp + 1;
variable expectedFreeMove := originalFreeMove + 2;
set_critter_current_ap(dude_obj, expectedAp);
set_combat_free_move(expectedFreeMove);
call assertEquals("set/get_critter_current_ap roundtrip", get_critter_current_ap(dude_obj), expectedAp);
call assertEquals("set/get_combat_free_move roundtrip", get_combat_free_move, expectedFreeMove);
set_critter_current_ap(dude_obj, originalAp);
set_combat_free_move(originalFreeMove);
call assertEquals("get_critter_current_ap restored", get_critter_current_ap(dude_obj), originalAp);
call assertEquals("get_combat_free_move restored", get_combat_free_move, originalFreeMove);
call report_test_results("combat_inventory_misc_combat");
end
procedure report_obj_under_cursor begin
variable target := obj_under_cursor(0, 1);
if (target == 0) then begin
display_msg("No object under cursor.");
return;
end
display_msg("Cursor object: " + obj_name(target) + ", openable=" + obj_is_openable(target));
end
procedure keypress_handler begin
variable pressed := get_sfall_arg_at(0);
variable key := get_sfall_arg_at(1);
if (not pressed) then return;
if (key == DIK_J) then begin
call run_combat_turn_roundtrip();
end else if (key == DIK_K) then begin
call report_obj_under_cursor();
end
end
procedure start begin
variable testItem;
if (not game_loaded) then return;
display_msg("Testing combat/inventory/object helpers...");
call assertEquals("metarule get_combat_free_move", metarule_exist("get_combat_free_move"), 1);
call assertEquals("metarule set_combat_free_move", metarule_exist("set_combat_free_move"), 1);
call assertEquals("metarule item_weight", metarule_exist("item_weight"), 1);
call assertEquals("metarule obj_is_openable", metarule_exist("obj_is_openable"), 1);
call assertEquals("opcode get_critter_current_ap", opcode_exists(OP_GET_CRITTER_CURRENT_AP), 1);
call assertEquals("opcode set_critter_current_ap", opcode_exists(OP_SET_CRITTER_CURRENT_AP), 1);
testItem := create_object_sid(TEST_ITEM_PID, 0, 0, -1);
call assertEquals("item_weight matches proto weight for test item", item_weight(testItem), obj_weight(testItem));
call assertEquals("non-container item is not openable", obj_is_openable(testItem), 0);
destroy_object(testItem);
call report_test_results("combat_inventory_misc");
display_msg("Manual test: enter combat, wait for your turn, then press J to roundtrip AP/free-move.");
display_msg("Manual test: point at a door or container and press K to print obj_is_openable().");
register_hook_proc(HOOK_KEYPRESS, keypress_handler);
end
@@ -1,59 +0,0 @@
#include "sfall.h"
#include "test_utils.h"
#define TEST_SCRIPT_INDEX (2)
// Depends on Test0.int, which provides a controllable object script helper.
// Put the compiled helper at mods/test_script_override/scripts/Test0.int,
// and add "test_script_override" to mod_order.txt. The helper must live there
// (and not scripts/) because patch000.dat already contains a stock Test0.int and
// scripts/ load before it.
// Without Test0, these 2 tests will fail:
// - "set_script normal hp"
// - "set_script no map_enter hp"
variable testScriptObj := 0;
procedure create_test_object begin
if (testScriptObj != 0) then return;
testScriptObj := create_object_sid(obj_pid(dude_obj), tile_num_in_direction(tile_num(dude_obj), has_trait(TRAIT_OBJECT, dude_obj, OBJECT_CUR_ROT), 3), elevation(dude_obj), -1);
end
procedure stdprocedure_hook begin
if (get_sfall_arg_at(0) != 15) then return;
if (get_sfall_arg_at(3)) then begin
set_sfall_global("T0HAFTR1", 1);
end else begin
set_sfall_global("T0HBEFO1", 1);
end
end
procedure exercise_set_script(variable scriptId, variable expectedHp, variable expectedBeforeHook, variable expectedAfterHook, variable desc) begin
set_sfall_global("T0MAPEN1", 0);
set_sfall_global("T0HBEFO1", 0);
set_sfall_global("T0HAFTR1", 0);
set_script(testScriptObj, scriptId);
call assertEquals(desc + " get_script", get_script(testScriptObj), TEST_SCRIPT_INDEX);
call assertEquals(desc + " map_enter global", get_sfall_global_int("T0MAPEN1"), expectedHp);
call assertEquals(desc + " stdprocedure hook before", get_sfall_global_int("T0HBEFO1"), expectedBeforeHook);
call assertEquals(desc + " stdprocedure hook after", get_sfall_global_int("T0HAFTR1"), expectedAfterHook);
remove_script(testScriptObj);
call assertEquals(desc + " remove_script clears get_script", get_script(testScriptObj), 0);
end
procedure start begin
if (not game_loaded) then return;
display_msg("Testing set_script/remove_script/get_script...");
register_hook_proc(HOOK_STDPROCEDURE, stdprocedure_hook);
register_hook_proc(HOOK_STDPROCEDURE_END, stdprocedure_hook);
call create_test_object;
call exercise_set_script(TEST_SCRIPT_INDEX, 22, 1, 1, "set_script normal");
call exercise_set_script(TEST_SCRIPT_INDEX bwor 0x80000000, 11, 0, 0, "set_script no map_enter");
destroy_object(testScriptObj);
testScriptObj := 0;
call report_test_results("script_manipulation");
end
-28
View File
@@ -1,21 +1,13 @@
#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
@@ -53,26 +45,6 @@ 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
-64
View File
@@ -1,64 +0,0 @@
#include "sfall.h"
#include "dik.h"
#include "lib.arrays.h"
#include "lib.obj.h"
variable calcapcost_override_enabled := true;
procedure calcapcost_mode_name begin
if (calcapcost_override_enabled) then return "override";
return "engine";
end
procedure calcapcost_item_name(variable item) begin
if (item == 0) then return "<null>";
return obj_name(item);
end
procedure calcapcost_handler begin
variable
args := get_sfall_args,
critter := args[0],
attackType := args[1],
isAimed := args[2],
defaultCost := args[3],
weapon := args[4],
weaponPid := 0;
if (critter != dude_obj) then return;
if (weapon) then weaponPid := obj_pid(weapon);
display_msg(string_format("calcapcost mode=%s attack=%d aimed=%d default=%d weapon=%s pid=%d",
calcapcost_mode_name,
attackType,
isAimed,
defaultCost,
calcapcost_item_name(weapon),
weaponPid));
if (calcapcost_override_enabled) then
set_sfall_return(defaultCost + 3);
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_H) then return;
calcapcost_override_enabled := not calcapcost_override_enabled;
display_msg(string_format1("calcapcost mode -> %s", calcapcost_mode_name));
intface_redraw;
end
procedure start begin
if (not game_loaded) then return;
display_msg("calcapcost manual test ready: press H to toggle override (+3 AP)");
display_msg("check weapon, unarmed, and non-weapon hand items; hook logs args for dude only");
register_hook_proc(HOOK_KEYPRESS, keypress_handler);
register_hook_proc(HOOK_CALCAPCOST, calcapcost_handler);
end
-52
View File
@@ -1,52 +0,0 @@
#include "sfall.h"
#include "dik.h"
#include "define_lite.h"
#define TEST_KEY (DIK_J)
procedure ondeath_handler begin
variable
dead_critter := get_sfall_arg_at(0),
critter_name := "<null>",
critter_pid := -1;
if (dead_critter) then begin
critter_name := obj_name(dead_critter);
critter_pid := obj_pid(dead_critter);
end
display_msg(string_format2("ondeath critter=%s pid=%d", critter_name, critter_pid));
end
procedure keypress_handler begin
variable
pressed := get_sfall_arg_at(0),
key := get_sfall_arg_at(1),
target;
if (not pressed) then return;
if (key != TEST_KEY) then return;
target := obj_under_cursor(true, false);
if (target == 0) then begin
display_msg("ondeath test: no critter under cursor");
return;
end
if (target == dude_obj) then begin
display_msg("ondeath test: refusing to kill dude");
return;
end
display_msg(string_format1("ondeath test: killing %s", obj_name(target)));
critter_dmg(target, 9999, DMG_BYPASS_ARMOR);
end
procedure start begin
if (not game_loaded) then return;
display_msg("ondeath manual test ready: hover a critter and press J");
register_hook_proc(HOOK_KEYPRESS, keypress_handler);
register_hook_proc(HOOK_ONDEATH, ondeath_handler);
end
-105
View File
@@ -1,105 +0,0 @@
#include "sfall.h"
#include "dik.h"
#include "lib.arrays.h"
#include "lib.obj.h"
variable withinperception_override := -1;
procedure withinperception_override_name(variable value) begin
if (value == -1) then return "engine";
if (value == 0) then return "block";
if (value == 1) then return "allow";
if (value == 2) then return "force_detect";
return "unknown";
end
procedure withinperception_type_name(variable hook_type) begin
if (hook_type == 0) then return "engine";
if (hook_type == 1) then return "see";
if (hook_type == 2) then return "hear";
if (hook_type == 3) then return "ai_target";
return "unknown";
end
procedure cycle_override begin
if (withinperception_override == -1) then withinperception_override := 0;
else if (withinperception_override == 0) then withinperception_override := 1;
else if (withinperception_override == 1) then withinperception_override := 2;
else withinperception_override := -1;
display_msg(string_format1("withinperception override=%s", withinperception_override_name(withinperception_override)));
end
procedure print_target_checks begin
variable
target := obj_under_cursor(true, false),
canSee,
canHear,
target_name := "(null)";
target_name := obj_name_safe(target);
display_msg(string_format2("withinperception target=%s pid=%d", target_name, target and obj_pid(target)));
if (target == 0) then begin
display_msg("withinperception no target under cursor");
return;
end
canSee := obj_can_see_obj(dude_obj, target);
canHear := obj_can_hear_obj(dude_obj, target);
display_msg(string_format2("withinperception see=%d hear=%d", canSee, canHear));
end
procedure withinperception_handler begin
variable
args := get_sfall_args,
watcher := args[0],
target := args[1],
original_result := args[2],
hook_type := args[3],
watcher_name := "(null)",
target_name := "(null)",
override_result := withinperception_override;
watcher_name := obj_name_safe(watcher);
target_name := obj_name_safe(target);
if (watcher == dude_obj) then begin
display_msg(string_format("withinperception %s %s -> %s [res=%d]",
withinperception_type_name(hook_type),
watcher_name,
target_name, original_result));
end
if (override_result == -1) then return;
if (hook_type != 1 and override_result == 2) then override_result := 1;
set_sfall_return(override_result);
end
procedure keypress_handler begin
variable
pressed := get_sfall_arg_at(0),
key := get_sfall_arg_at(1);
if (not pressed) then return;
if (key == DIK_J) then begin
call print_target_checks;
end else if (key == DIK_K) then begin
call cycle_override;
end
end
procedure start begin
if (not game_loaded) then return;
display_msg("withinperception manual test ready: J checks target under cursor, K cycles override");
display_msg("withinperception override order: engine -> block -> allow -> force_detect");
register_hook_proc(HOOK_KEYPRESS, keypress_handler);
register_hook_proc(HOOK_WITHINPERCEPTION, withinperception_handler);
end
+2 -2
View File
@@ -9,7 +9,6 @@
#include "combat.h"
#include "combat_ai.h"
#include "config.h"
#include "content_config.h"
#include "critter.h"
#include "debug.h"
#include "display_monitor.h"
@@ -29,6 +28,7 @@
#include "random.h"
#include "scripts.h"
#include "settings.h"
#include "sfall_config.h"
#include "sfall_script_hooks.h"
#include "skill.h"
#include "stat.h"
@@ -1410,7 +1410,7 @@ int actionUseSkill(Object* user, Object* target, int skill)
// SFALL: Science on critters patch.
if (1) {
int targetType = SCIENCE_REPAIR_TARGET_TYPE_DEFAULT;
configGetInt(&gContentConfig, CONTENT_CONFIG_COMBAT_SECTION, "science_on_critters", &targetType, SCIENCE_REPAIR_TARGET_TYPE_DEFAULT);
configGetInt(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_SCIENCE_REPAIR_TARGET_TYPE_KEY, &targetType);
if (targetType == SCIENCE_REPAIR_TARGET_TYPE_DUDE) {
if (target == gDude) {
break;
+19 -16
View File
@@ -6,14 +6,14 @@
#include <string.h>
#include "animation.h"
#include "content_config.h"
#include "debug.h"
#include "draw.h"
#include "game.h"
#include "memory.h"
#include "obj_types.h"
#include "object.h"
#include "proto.h"
#include "settings.h"
#include "sfall_config.h"
namespace fallout {
@@ -190,16 +190,28 @@ int artInit()
// SFALL: Modify player model settings.
char* jumpsuitMaleFileName = nullptr;
configGetString(&gContentConfig, CONTENT_CONFIG_START_SECTION, "model_male_default", &jumpsuitMaleFileName, gDefaultJumpsuitMaleFileName);
configGetString(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_DUDE_NATIVE_LOOK_JUMPSUIT_MALE_KEY, &jumpsuitMaleFileName);
if (jumpsuitMaleFileName == nullptr || jumpsuitMaleFileName[0] == '\0') {
jumpsuitMaleFileName = gDefaultJumpsuitMaleFileName;
}
char* jumpsuitFemaleFileName = nullptr;
configGetString(&gContentConfig, CONTENT_CONFIG_START_SECTION, "model_female_default", &jumpsuitFemaleFileName, gDefaultJumpsuitFemaleFileName);
configGetString(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_DUDE_NATIVE_LOOK_JUMPSUIT_FEMALE_KEY, &jumpsuitFemaleFileName);
if (jumpsuitFemaleFileName == nullptr || jumpsuitFemaleFileName[0] == '\0') {
jumpsuitFemaleFileName = gDefaultJumpsuitFemaleFileName;
}
char* tribalMaleFileName = nullptr;
configGetString(&gContentConfig, CONTENT_CONFIG_START_SECTION, "model_male", &tribalMaleFileName, gDefaultTribalMaleFileName);
configGetString(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_DUDE_NATIVE_LOOK_TRIBAL_MALE_KEY, &tribalMaleFileName);
if (tribalMaleFileName == nullptr || tribalMaleFileName[0] == '\0') {
tribalMaleFileName = gDefaultTribalMaleFileName;
}
char* tribalFemaleFileName = nullptr;
configGetString(&gContentConfig, CONTENT_CONFIG_START_SECTION, "model_female", &tribalFemaleFileName, gDefaultTribalFemaleFileName);
configGetString(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_DUDE_NATIVE_LOOK_TRIBAL_FEMALE_KEY, &tribalFemaleFileName);
if (tribalFemaleFileName == nullptr || tribalFemaleFileName[0] == '\0') {
tribalFemaleFileName = gDefaultTribalFemaleFileName;
}
char* critterFileNames = gArtListDescriptions[OBJ_TYPE_CRITTER].fileNames;
for (int critterIndex = 0; critterIndex < gArtListDescriptions[OBJ_TYPE_CRITTER].fileNamesLength; critterIndex++) {
@@ -821,7 +833,7 @@ unsigned char* artGetFrameData(Art* art, int frame, int direction)
}
// 0x419880
ArtFrame* artGetFrame(const Art* art, int frame, int rotation)
ArtFrame* artGetFrame(Art* art, int frame, int rotation)
{
if (rotation < 0 || rotation >= 6) {
return nullptr;
@@ -842,15 +854,6 @@ ArtFrame* artGetFrame(const 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)
{
+5 -8
View File
@@ -2,8 +2,10 @@
#define ART_H
#include "cache.h"
#include "draw.h"
#include "memory.h"
#include "db.h"
#include "heap.h"
#include "obj_types.h"
#include "platform_compat.h"
#include "proto_types.h"
namespace fallout {
@@ -139,8 +141,7 @@ 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(const Art* art, int frame, int direction);
ConstBuffer2D artGetFrameBuffer(const Art* art, int frame, int direction);
ArtFrame* artGetFrame(Art* art, int frame, int direction);
bool artExists(int fid);
bool _art_fid_valid(int fid);
int _art_alias_num(int index);
@@ -164,8 +165,6 @@ 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;
@@ -173,8 +172,6 @@ private:
int _height;
};
using ArtPtr = InternalPtr<Art>;
} // namespace fallout
#endif
+169 -38
View File
@@ -11,7 +11,6 @@
#include "art.h"
#include "color.h"
#include "combat.h"
#include "content_config.h"
#include "critter.h"
#include "cycle.h"
#include "db.h"
@@ -38,6 +37,8 @@
#include "platform_compat.h"
#include "proto.h"
#include "scripts.h"
#include "settings.h"
#include "sfall_config.h"
#include "skill.h"
#include "stat.h"
#include "svga.h"
@@ -144,6 +145,11 @@ enum {
EDITOR_BUTTONS_COUNT = EDITOR_FIRST_TRAIT + TRAIT_COUNT,
};
static constexpr int kMaxPerkOwed = 250;
// nb. this is number of perks, not total perk ranks. This only prevents level ups from granting more perks.
static constexpr int kMaxSelectablePerks = 37;
static constexpr int kPrimaryStatDescriptionWidth = 100;
enum {
EDITOR_GRAPHIC_BIG_NUMBERS,
EDITOR_GRAPHIC_AGE_MASK,
@@ -282,6 +288,8 @@ static int perkDialogDrawTraits(int a1);
static int perkDialogOptionCompare(const void* a1, const void* a2);
static int perkDialogDrawCard(int frmId, const char* name, const char* rank, char* description);
static void _pop_perks();
static int characterEditorGetLevelsPerPerk();
static void characterEditorGrantPerkAtLevel(int level);
static int _is_supper_bonus();
static int characterEditorFolderViewInit();
static void characterEditorFolderViewScroll(int direction);
@@ -769,7 +777,7 @@ static int gCharacterEditorTaggedSkillCount;
static int gCharacterEditorTempTaggedSkills[NUM_TAGGED_SKILLS];
// 0x570A28
static char gCharacterEditorHasFreePerkBackup;
static unsigned char gCharacterEditorHasFreePerkBackup;
// 0x570A29
static unsigned char gCharacterEditorHasFreePerk;
@@ -2474,7 +2482,7 @@ static void characterEditorDrawPrimaryStat(int stat, bool animate, int previousV
characterEditorDrawBigNumber(58, gCharacterEditorPrimaryStatY[stat], flags, value, previousValue, gCharacterEditorWindow);
blitBufferToBuffer(_editorBackgroundFrmImage.getData() + off, 40, fontGetLineHeight(), 640, gCharacterEditorWindowBuffer + off, 640);
blitBufferToBuffer(_editorBackgroundFrmImage.getData() + off, kPrimaryStatDescriptionWidth, fontGetLineHeight(), 640, gCharacterEditorWindowBuffer + off, 640);
messageListItemId = critterGetStat(gDude, stat) + 199;
if (messageListItemId > 210) {
@@ -2486,7 +2494,7 @@ static void characterEditorDrawPrimaryStat(int stat, bool animate, int previousV
} else {
value = critterGetStat(gDude, stat);
characterEditorDrawBigNumber(58, gCharacterEditorPrimaryStatY[stat], 0, value, 0, gCharacterEditorWindow);
blitBufferToBuffer(_editorBackgroundFrmImage.getData() + off, 40, fontGetLineHeight(), 640, gCharacterEditorWindowBuffer + off, 640);
blitBufferToBuffer(_editorBackgroundFrmImage.getData() + off, kPrimaryStatDescriptionWidth, fontGetLineHeight(), 640, gCharacterEditorWindowBuffer + off, 640);
value = critterGetStat(gDude, stat);
if (value > 10) {
@@ -5668,11 +5676,117 @@ int characterEditorLoad(File* stream)
return 0;
}
int characterEditorGetPerkOwed()
{
return gCharacterEditorHasFreePerk;
}
void characterEditorSetPerkOwed(int value)
{
unsigned int maskedValue = static_cast<unsigned int>(value) & 0xFF;
if (maskedValue <= kMaxPerkOwed) {
gCharacterEditorHasFreePerk = maskedValue;
}
}
// External interface: called when level up occurs
void characterEditorHandleLevelUp(int level)
{
characterEditorGrantPerkAtLevel(level);
}
// compute the character level that the next perk selection should use
int characterEditorGetPerkSelectionLevel()
{
int currentLevel = pcGetStat(PC_STAT_LEVEL);
if (settings.gameplay.perk_carryover != PERK_CARRY_OVER_MODE_ON) {
return currentLevel;
}
int owed = characterEditorGetPerkOwed();
if (owed <= 1) {
// If a single perk is owed, use the current level. This prevents odd effects in
// saves where perks have been skipped and is a general safe fallback.
return currentLevel;
}
int levelsPerPerk = characterEditorGetLevelsPerPerk();
// This therefore uses the number of perk thresholds strictly before the
// current level, then compares that against the total perk ranks already
// spent. In the common non-scripted case:
// - level 6 with 0 spent perks yields one deferred threshold (level 3)
// - after spending one perk, there is no older backlog left, so the next
// owed perk uses level 6 requirements
// - level 9 with 2 spent perks has no older backlog, so owed perks are
// immediately spendable at level 9
int previousThresholdsReached = (currentLevel - 1) / levelsPerPerk;
int spentPerks = 0;
for (int perk = 0; perk < PERK_COUNT; perk++) {
spentPerks += perkGetRank(gDude, perk);
}
// This is a heuristic for the 99.9% case where perk ranks come from normal
// perk picks. If enough perk ranks have already been spent to cover all
// earlier thresholds, there is no deferred backlog to force in order.
if (spentPerks >= previousThresholdsReached) {
return currentLevel;
}
// The next deferred pick corresponds to the earliest previously missed
// threshold after the already-spent perk ranks.
int thresholdIndex = spentPerks + 1;
int selectionLevel = std::max(3, thresholdIndex * levelsPerPerk);
if (selectionLevel <= 0 || selectionLevel > currentLevel) {
return currentLevel;
}
return selectionLevel;
}
// 0x43C20C
void characterEditorReset()
{
gCharacterEditorRemainingCharacterPoints = 5;
gCharacterEditorLastLevel = 1;
characterEditorSetPerkOwed(0);
}
static int characterEditorGetLevelsPerPerk()
{
int progression = 3;
if (traitIsSelected(TRAIT_SKILLED)) {
progression += 1;
}
return progression;
}
static void characterEditorGrantPerkAtLevel(int level)
{
if (level <= 0 || level % characterEditorGetLevelsPerPerk() != 0) {
return;
}
int selectedPerksCount = 0;
for (int perk = 0; perk < PERK_COUNT; perk++) {
if (perkGetRank(gDude, perk) != 0) {
selectedPerksCount += 1;
if (selectedPerksCount >= kMaxSelectablePerks) {
return;
}
}
}
switch (settings.gameplay.perk_carryover) {
case PERK_CARRY_OVER_MODE_OFF:
characterEditorSetPerkOwed(1);
break;
case PERK_CARRY_OVER_MODE_ON:
case PERK_CARRY_OVER_MODE_SFALL:
characterEditorSetPerkOwed(characterEditorGetPerkOwed() + 1);
break;
}
}
// level up if needed
@@ -5682,6 +5796,13 @@ static int characterEditorUpdateLevel()
{
int level = pcGetStat(PC_STAT_LEVEL);
if (level != gCharacterEditorLastLevel && level <= PC_LEVEL_MAX) {
// Runtime level-up now grants perks immediately. However, older saves
// may still have last_level behind the real level with no owed perks
// recorded, because perk grants used to be deferred until the next
// character screen visit. Replay perk grants only for that legacy
// catch-up case.
bool shouldReplayPerkGrants = characterEditorGetPerkOwed() == 0;
for (int nextLevel = gCharacterEditorLastLevel + 1; nextLevel <= level; nextLevel++) {
int sp = pcGetStat(PC_STAT_UNSPENT_SKILL_POINTS);
sp += 5;
@@ -5700,30 +5821,13 @@ static int characterEditorUpdateLevel()
pcSetStat(PC_STAT_UNSPENT_SKILL_POINTS, sp);
int selectedPerksCount = 0;
for (int perk = 0; perk < PERK_COUNT; perk++) {
if (perkGetRank(gDude, perk) != 0) {
selectedPerksCount += 1;
if (selectedPerksCount >= 37) {
break;
}
}
}
if (selectedPerksCount < 37) {
int progression = 3;
if (traitIsSelected(TRAIT_SKILLED)) {
progression += 1;
}
if (nextLevel % progression == 0) {
gCharacterEditorHasFreePerk = 1;
}
if (shouldReplayPerkGrants) {
characterEditorGrantPerkAtLevel(nextLevel);
}
}
}
if (gCharacterEditorHasFreePerk != 0) {
while (characterEditorGetPerkOwed() != 0) {
characterEditorWindowSelectedFolder = 0;
characterEditorDrawFolders();
windowRefresh(gCharacterEditorWindow);
@@ -5732,11 +5836,15 @@ static int characterEditorUpdateLevel()
if (rc == -1) {
debugPrint("\n *** Error running perks dialog! ***\n");
return -1;
} else if (rc == 0) {
}
if (rc == 0) {
characterEditorDrawFolders();
} else if (rc == 1) {
break;
}
if (rc == 1) {
characterEditorDrawFolders();
gCharacterEditorHasFreePerk = 0;
}
}
@@ -5785,6 +5893,10 @@ static int perkDialogShow()
gPerkDialogCardFrmId = -1;
gPerkDialogCardTitle[0] = '\0';
gPerkDialogCardDrawn = false;
int previousPerkRanks[PERK_COUNT];
for (int perk = 0; perk < PERK_COUNT; perk++) {
previousPerkRanks[perk] = perkGetRank(gDude, perk);
}
int backgroundFid = buildFid(OBJ_TYPE_INTERFACE, 86, 0, 0, 0);
if (!_perkDialogBackgroundFrmImage.lock(backgroundFid)) {
@@ -5910,6 +6022,12 @@ static int perkDialogShow()
fontDrawText(gPerkDialogWindowBuffer + PERK_WINDOW_WIDTH * 186 + 171, msg, PERK_WINDOW_WIDTH, PERK_WINDOW_WIDTH, _colorTable[18979]);
int count = perkDialogDrawPerks();
if (count == 0) {
debugPrint("*** No perks available for current owed slot! ***");
_perkDialogBackgroundFrmImage.unlock();
windowDestroy(gPerkDialogWindow);
return -1;
}
// NOTE: Original code is slightly different, but does the same thing.
int perk = gPerkDialogOptionList[gPerkDialogTopLine + gPerkDialogCurrentLine].value;
@@ -5931,7 +6049,8 @@ static int perkDialogShow()
int rc = perkDialogHandleInput(count, perkDialogRefreshPerks);
if (rc == 1) {
if (perkAdd(gDude, gPerkDialogOptionList[gPerkDialogTopLine + gPerkDialogCurrentLine].value) == -1) {
int selectionLevel = characterEditorGetPerkSelectionLevel();
if (perkAddAtLevel(gDude, gPerkDialogOptionList[gPerkDialogTopLine + gPerkDialogCurrentLine].value, selectionLevel) == -1) {
debugPrint("\n*** Unable to add perk! ***\n");
rc = 2;
}
@@ -5940,19 +6059,20 @@ static int perkDialogShow()
rc &= 1;
if (rc != 0) {
if (perkGetRank(gDude, PERK_TAG) != 0 && gCharacterEditorPerksBackup[PERK_TAG] == 0) {
characterEditorSetPerkOwed(characterEditorGetPerkOwed() - 1);
if (perkGetRank(gDude, PERK_TAG) != 0 && previousPerkRanks[PERK_TAG] == 0) {
if (!perkDialogHandleTagPerk()) {
perkRemove(gDude, PERK_TAG);
}
} else if (perkGetRank(gDude, PERK_MUTATE) != 0 && gCharacterEditorPerksBackup[PERK_MUTATE] == 0) {
} else if (perkGetRank(gDude, PERK_MUTATE) != 0 && previousPerkRanks[PERK_MUTATE] == 0) {
if (!perkDialogHandleMutatePerk()) {
perkRemove(gDude, PERK_MUTATE);
}
} else if (perkGetRank(gDude, PERK_LIFEGIVER) != gCharacterEditorPerksBackup[PERK_LIFEGIVER]) {
} else if (perkGetRank(gDude, PERK_LIFEGIVER) != previousPerkRanks[PERK_LIFEGIVER]) {
int maxHp = critterGetBonusStat(gDude, STAT_MAXIMUM_HIT_POINTS);
critterSetBonusStat(gDude, STAT_MAXIMUM_HIT_POINTS, maxHp + 4);
critterAdjustHitPoints(gDude, 4);
} else if (perkGetRank(gDude, PERK_EDUCATED) != gCharacterEditorPerksBackup[PERK_EDUCATED]) {
} else if (perkGetRank(gDude, PERK_EDUCATED) != previousPerkRanks[PERK_EDUCATED]) {
int sp = pcGetStat(PC_STAT_UNSPENT_SKILL_POINTS);
pcSetStat(PC_STAT_UNSPENT_SKILL_POINTS, sp + 2);
}
@@ -6266,7 +6386,7 @@ static int perkDialogDrawPerks()
fontSetCurrent(101);
int perks[PERK_COUNT];
int count = perkGetAvailablePerks(gDude, perks);
int count = perkGetAvailablePerks(gDude, characterEditorGetPerkSelectionLevel(), perks);
if (count == 0) {
return 0;
}
@@ -7167,14 +7287,22 @@ static int genericReputationCompare(const void* a1, const void* a2)
static void customKarmaFolderInit()
{
char* karmaFrms = nullptr;
configGetString(&gContentConfig, CONTENT_CONFIG_KARMA_SECTION, "frms", &karmaFrms, "");
if (!*karmaFrms) {
configGetString(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_KARMA_FRMS_KEY, &karmaFrms);
if (karmaFrms != nullptr && karmaFrms[0] == '\0') {
karmaFrms = nullptr;
}
if (karmaFrms == nullptr) {
return;
}
char* karmaPoints = nullptr;
configGetString(&gContentConfig, CONTENT_CONFIG_KARMA_SECTION, "points", &karmaPoints, "");
if (!*karmaPoints) {
configGetString(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_KARMA_POINTS_KEY, &karmaPoints);
if (karmaPoints != nullptr && karmaPoints[0] == '\0') {
karmaPoints = nullptr;
}
if (karmaPoints == nullptr) {
return;
}
@@ -7248,7 +7376,10 @@ static int customKarmaFolderGetFrmId()
static void customTownReputationInit()
{
char* reputationList = nullptr;
configGetString(&gContentConfig, CONTENT_CONFIG_WORLDMAP_SECTION, "city_reputation_list", &reputationList, nullptr);
configGetString(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_CITY_REPUTATION_LIST_KEY, &reputationList);
if (reputationList != nullptr && *reputationList == '\0') {
reputationList = nullptr;
}
char* curr = reputationList;
while (curr != nullptr) {

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