Compare commits

..
Author SHA1 Message Date
phobos2077 aa69e343dc mapper: obj_load_text corrections 2026-05-11 13:44:00 +02:00
phobos2077 69cc853475 mapper: obj_load_text (WIP) 2026-05-11 00:13:33 +02:00
phobos2077 331bf79ec0 mapper: save map to TXT 2026-05-10 22:56:09 +02:00
phobos2077 456f44b2a3 mapper: map_save_text initial impl 2026-05-10 17:24:02 +02:00
github-actions[bot] 8d8b19e979 chore: auto-format with clang-format 2026-05-10 01:40:28 +00:00
phobos2077 80bd1b2c8b mapper: implement debug.show_tile_num, Mark Exit Grid, toolbar name drawing fix and refactoring 2026-05-10 03:38:30 +02:00
phobos2077 0804ccac87 mapper: corrected Delete Spatial, minor fixes 2026-05-10 02:50:13 +02:00
phobos2077 4ec9f88986 mapper: edit_mapper inacuracies and tidying up some code 2026-05-10 01:55:39 +02:00
phobos2077 491384522f mapper: fix playmode and cleanup discrepancy 2026-05-09 23:55:05 +02:00
phobos2077 49f4376e89 mapper: save map corrections + some stubs 2026-05-09 23:05:47 +02:00
phobos2077 f4688409fb Mapper: fixed playmode, added some mapper features 2026-05-09 18:32:45 +02:00
Mike Klaas ab2a09610d Party members can barter (#436)
Very simple party barter support. Builds upon the loot screen so is very little code:

    Switch party members with left/right arrow keys
    When leaving barter with items on the table (canceling), all items go to the PC's inventory
    No special handling of money (yet)

The mod pins the player's money stack on top of the inventory list for all party members. That's nice, but also feels a little odd, but likely just because I'm used to the money stack being stuck at bottom. We can iterate on that.

Adding an interface will require art. I considered enabling mouse use triggered by clicking on the avatar which would be quite simple.
2026-05-07 06:02:37 +00:00
Mike Klaas cef24823f4 Speed up Fallout1 .dat reader (#439)
* Speed up Fallout1 .dat reader

It was reallocating a buffer on a loop, which caused large files to take minutes.  Fixing this reduced it to a reasonable time, but still ~120s for the entire Fallout1 master.dat.

Also, bump the F2 gzip buffer to 4kB. It was only 400 bytes
2026-05-06 13:36:11 -07:00
Mike Klaas 63d79e4bfd Fix high pitch voiced dialog (#440)
In 8b2ead8a2b, soundLoad started honoring AudioFileInfo.channels, but the ACM decoder path historically only propagated sample rate.  This changes playback in a way that caused the bug.

Verified by talking to good ol' Sulik
2026-05-05 22:55:53 -07:00
Vlad Kandgithub-actions[bot] e42d8021c1 WIP Mapper implementation (#438)
* Add mapper CMakeTarget, tool for mapping function names to originals, load/save toolbar & update_art implemented

* edit_mapper function + stubs

* Rename exe to mapper-ce

* load_lbm_to_buf

* Add comments for read/write functions in db.h

* load_dialog, save_dialog, save_as, info_dialog and some other functions

* Fix LBM loading

* Fix mouse input not working on initial empty map, changed error in partyMemberRecoverLoadInstance to print to log, matching vanilla

* mapper.cc: basic hi-res support, NULL->nullptr

* load_lbm_to_buf rewrite, print_toolbar_name background fix

* Stubs for enter/exit playmode, art slot indexes fix, map_scr_toggle_hexes

* Fix memory corruption on screen_width > 640, fix various UI offset bugs

* mapper.cc: UI code style, toggle button fixes, rotation keys, edit button placeholders, PAGEUP key fix

* Elevation display fix, object type switching

* Spatial script placement and display, basic object selection

* Fixed dragging objects, block object showing, add all missing cases in edit_mapper with stubs, move all keys codes to constants

* chore: auto-format with clang-format

* Fix non-win builds

* Add stub calls from edit_mapper, fix objects being incorrectly deleted when unselected, fix tile number display

* Fix compile on Linux

* Attempt to fix iOS signing error

* Placing of objects and tiles, F12 to erase map, bug fixes

* Fixed block object toggling logic and add missing switch cases to edit_mapper

* Object editing added, 'p' to scroll palette fixed

* Add new files to CMakeLists

* Attempt to fix some colors + alignment in critter edit window

* chore: auto-format with clang-format

* Linux build fix attempt

* Critter inventory editing

* Vanilla grid-based inventory item picker

* Review fixes

* More review fixes and const correctness

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-05-05 18:47:16 +00:00
8254c758fe HOOK_INVENWIELD, HOOK_CANUSEWEAPON, HOOK_ADJUSTFID (#437)
* Add support for new talking heads

Add [Heads] config section in ddraw.ini that maps critter PIDs to talking
head indices in art\heads\heads.lst. When a dialog script starts with
headId=-1, the engine now checks the critter proto's headFid first, then
falls back to the [Heads] mapping. This lets mods add talking heads
without patching dialog scripts.

Includes Cassidy mapping (PID 16777305 → head index 13) for use with
cassidy_head.dat mod.

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

* Implement HOOK_INVENWIELD, HOOK_CANUSEWEAPON and fix HOOK_ADJUSTFID

Wire the sfall hooks that npc_armor.mod relies on so party-member sprites
actually change when armor is equipped:

- HOOK_INVENWIELD fires from inventoryEquipFunc/inventoryUnequipFunc with
  (critter, item, slot, isWield). Script may veto by returning 0. Slot
  values mirror interpreter_extra.cc: WORN=0, RIGHT_HAND=1, LEFT_HAND=2.
- HOOK_CANUSEWEAPON fires from _ai_search_inven_weap so the mod's
  weapon-anim restriction can veto AI weapon picks.
- HOOK_ADJUSTFID arg list corrected to match sfall (single currFid arg,
  critter resolved via dude_obj).
- ProgramValue::isEmpty no longer treats string values as empty — sfall
  scripts like npc_armor's `while (sect.PID)` loop depend on a non-empty
  string being truthy.

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

* enum class

* chore: auto-format with clang-format

* bad merge

* PR feedback

* restore isEmpty() to vanilla while fixing bug

* fixes

* minor fixes

* extra

* Potential fix for pull request finding

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

---------

Co-authored-by: tectiv3 <tectiv3@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-05-04 21:36:50 -07:00
Mike Klaas 8b2ead8a2b Sfall-like sound system (#429)
* Sfall-like sound system

(I would like a better name for this system, but the opcodes themselves are called `play|stop_sfall_sound` and I can't think of a better name)

This is a sound management system very similar to Sfall's.  With a few differences:
* No support for new sound formats yet.  Support for at least ogg+wav will come next
* Does support reading from .dat files, hence playing any sound effect in the base game
* Support mode=3 for speech volume.  This exists in Sfall but is ignored when called from the opcode.

Also, change GaplessMusic to use this system to play the "wind2" loading sound.  This restores the "vanilla" feel map transitions, so I think it's reasonable to make GaplessMusic=1 the default.  I can't imagine someone wanting to change it to 0 unless they are ultra purists
2026-05-04 07:21:37 -07:00
Mike Klaas 4f89bb7d77 Companions can be the "looter" in loot screen (#435) 2026-05-03 09:57:25 -07:00
Vlad K 0d8741e082 Debug logging improvements (#434)
* debug log buffering to catch early logs, always duplicate log to stdout, const char* for debug fns

* AI nitpicking fixes
2026-05-03 09:15:13 +00:00
Vlad Kandgithub-actions[bot] 6ce6bea309 Sfall Saved Arrays (#431)
* Sfall saved arrays support

* Add "...all_arrays..." special case for a list of saved arrays

* Adjust error message on load fail

* Fix crash in GetArrayKey when index == size

* chore: auto-format with clang-format

* Update sfall compatibility md

* Add keyIndex for Assoc Arrays for O(1) key lookups

* Review feedback, more error logging

* Fix build, increase ARRAY_MAX_STRING to 1024 to match sfall, fix gl_test_arrays triggering dynamic string block merge failure, prevent potential string buffer corruption on very long strings

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-05-03 01:24:30 +02:00
Mike Klaas 6027c7cf2e Vertically center inventory above interface, isntead of related to screen (#428) 2026-05-01 12:53:02 -07:00
91 changed files with 13705 additions and 1668 deletions
+113 -4
View File
@@ -25,6 +25,9 @@ if(MSVC)
endif()
option(FALLOUT_VENDORED "Use vendored third-party libraries" ON)
option(FALLOUT_AUDIO_OGG "Enable OGG decoding with stb_vorbis when available" ON)
set(FALLOUT_STB_VORBIS_DIR "" CACHE PATH "Directory containing stb_vorbis.c")
if(ANDROID)
add_library(${EXECUTABLE_NAME} SHARED)
@@ -35,7 +38,9 @@ endif()
# Git Info
include("cmake/gitver.cmake")
target_sources(${EXECUTABLE_NAME} PUBLIC
# Shared engine sources — used by both game and mapper targets.
# Excludes main.cc/main.h (game-only entry logic).
set(FALLOUT_ENGINE_SOURCES
"src/actions.cc"
"src/actions.h"
"src/animation.cc"
@@ -151,8 +156,6 @@ target_sources(${EXECUTABLE_NAME} PUBLIC
"src/lips.h"
"src/loadsave.cc"
"src/loadsave.h"
"src/main.cc"
"src/main.h"
"src/mainmenu.cc"
"src/mainmenu.h"
"src/map_defs.h"
@@ -180,6 +183,8 @@ target_sources(${EXECUTABLE_NAME} PUBLIC
"src/obj_types.h"
"src/object.cc"
"src/object.h"
"src/ogg_decoder.cc"
"src/ogg_decoder.h"
"src/opcode_context.cc"
"src/opcode_context.h"
"src/options.cc"
@@ -263,7 +268,7 @@ target_sources(${EXECUTABLE_NAME} PUBLIC
"src/xfile.h"
)
target_sources(${EXECUTABLE_NAME} PUBLIC
set(FALLOUT_PLATFORM_SOURCES
"src/audio_engine.cc"
"src/audio_engine.h"
"src/delay.cc"
@@ -297,6 +302,8 @@ target_sources(${EXECUTABLE_NAME} PUBLIC
"src/sfall_opcodes.cc"
"src/sfall_opcodes.h"
"src/sfall_script_hooks.cc"
"src/script_sound.cc"
"src/script_sound.h"
"src/sfall_arrays.cc"
"src/sfall_arrays.h"
"src/sfall_animation.cc"
@@ -309,8 +316,43 @@ target_sources(${EXECUTABLE_NAME} PUBLIC
"third_party/lodepng/lodepng.cpp"
)
target_sources(${EXECUTABLE_NAME} PUBLIC
${FALLOUT_ENGINE_SOURCES}
"src/main.cc"
"src/main.h"
)
target_sources(${EXECUTABLE_NAME} PUBLIC ${FALLOUT_PLATFORM_SOURCES})
target_include_directories(${EXECUTABLE_NAME} PUBLIC "third_party/lodepng")
set(FALLOUT_HAVE_STB_VORBIS OFF)
if(FALLOUT_AUDIO_OGG)
if(EXISTS "${CMAKE_SOURCE_DIR}/third_party/stb_vorbis/stb_vorbis.c")
set(_fallout_stb_vorbis_dir "${CMAKE_SOURCE_DIR}/third_party/stb_vorbis")
else()
set(_fallout_stb_vorbis_dir "${FALLOUT_STB_VORBIS_DIR}")
if(NOT _fallout_stb_vorbis_dir)
find_path(_fallout_stb_vorbis_dir
NAMES "stb_vorbis.c"
DOC "Directory containing stb_vorbis.c")
endif()
endif()
if(_fallout_stb_vorbis_dir AND EXISTS "${_fallout_stb_vorbis_dir}/stb_vorbis.c")
set(FALLOUT_HAVE_STB_VORBIS ON)
target_include_directories(${EXECUTABLE_NAME} PRIVATE "${_fallout_stb_vorbis_dir}")
else()
message(STATUS "OGG support disabled: stb_vorbis.c not found")
endif()
endif()
if(FALLOUT_HAVE_STB_VORBIS)
target_compile_definitions(${EXECUTABLE_NAME} PRIVATE HAVE_STB_VORBIS=1)
else()
target_compile_definitions(${EXECUTABLE_NAME} PRIVATE HAVE_STB_VORBIS=0)
endif()
if(IOS)
target_sources(${EXECUTABLE_NAME} PUBLIC
"src/platform/ios/paths.h"
@@ -527,6 +569,73 @@ if((NOT ANDROID) AND (NOT IOS) AND (NOT CMAKE_SYSTEM_NAME MATCHES "Emscripten"))
endif()
endif()
if((NOT ANDROID) AND (NOT IOS) AND (NOT CMAKE_SYSTEM_NAME MATCHES "Emscripten"))
if(WIN32)
add_executable(mapper-ce WIN32)
else()
add_executable(mapper-ce)
endif()
target_sources(mapper-ce PUBLIC
${FALLOUT_ENGINE_SOURCES}
${FALLOUT_PLATFORM_SOURCES}
"src/mapper/map_func.cc"
"src/mapper/map_func.h"
"src/mapper/mapper.cc"
"src/mapper/mapper.h"
"src/mapper/mp_instance.cc"
"src/mapper/mp_instance.h"
"src/mapper/mp_proto.cc"
"src/mapper/mp_proto.h"
"src/mapper/mp_scrpt.cc"
"src/mapper/mp_scrpt.h"
"src/mapper/mp_targt.cc"
"src/mapper/mp_targt.h"
"src/mapper/mp_text.cc"
"src/mapper/mp_text.h"
)
target_compile_definitions(mapper-ce PUBLIC FALLOUT_MAPPER)
if(WIN32)
target_compile_definitions(mapper-ce PUBLIC
_CRT_SECURE_NO_WARNINGS
_CRT_NONSTDC_NO_WARNINGS
NOMINMAX
WIN32_LEAN_AND_MEAN
_STATIC_CPPLIB
)
target_link_libraries(mapper-ce
winmm
debug libcpmtd
optimized libcpmt
)
target_sources(mapper-ce PUBLIC
"os/windows/fallout2-ce.rc"
)
endif()
if(APPLE)
target_link_libraries(mapper-ce "-framework CoreFoundation")
set_target_properties(mapper-ce PROPERTIES
XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED "NO"
XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED "NO"
)
endif()
target_include_directories(mapper-ce PUBLIC "third_party/lodepng")
target_include_directories(mapper-ce PRIVATE "src")
target_include_directories(mapper-ce PRIVATE ${ZLIB_INCLUDE_DIRS})
target_include_directories(mapper-ce PRIVATE ${SDL2_INCLUDE_DIRS})
target_link_libraries(mapper-ce
fpattern::fpattern
fpattern_windows::fpattern_windows
${ZLIB_LIBRARIES}
${SDL2_LIBRARIES}
)
endif()
if(APPLE)
if(IOS)
install(TARGETS ${EXECUTABLE_NAME} DESTINATION "Payload")
+3 -3
View File
@@ -105,13 +105,13 @@ docker run --rm -v $(pwd):/src emscripten/emsdk:3.1.74 sh -c 'git config --globa
## Configuration
The main configuration file is `fallout2-ce.cfg`. There are several important settings you might need to adjust for your installation. Depending on your Fallout distribution main game assets `master.dat`, `critter.dat`, `patch000.dat`, and `data` folder might be either all lowercased, or all uppercased. You can either update `master_dat`, `critter_dat`, `master_patches` and `critter_patches` settings to match your file names, or rename files to match entries in your `fallout2-ce.cfg`.
The main configuration file is `fallout2.cfg`. There are several important settings you might need to adjust for your installation. Depending on your Fallout distribution main game assets `master.dat`, `critter.dat`, `patch000.dat`, and `data` folder might be either all lowercased, or all uppercased. You can either update `master_dat`, `critter_dat`, `master_patches` and `critter_patches` settings to match your file names, or rename files to match entries in your `fallout2.cfg`.
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-ce.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 main config. After that, `fallout2-ce.cfg` becomes the single source of truth for this configuration.
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.
Here are some important settings in `fallout2-ce.cfg` under the `[screen]` and `[ui]` sections. See [the example config](https://github.com/fallout2-ce/fallout2-ce/tree/refs/heads/main/files/ce.cfg) for a full list of settings.
Here are some important settings in `fallout2.cfg` under the `[screen]` and `[ui]` sections. See [the example config](https://github.com/fallout2-ce/fallout2-ce/tree/refs/heads/main/files/fallout2.cfg) for a full list of settings.
```ini
[screen]
+6 -6
View File
@@ -10,7 +10,7 @@ Settings previously read from `ddraw.ini` have been moved into standard CE confi
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-ce.cfg`](files/ce.cfg) instead:
The following settings were moved into [`fallout2.cfg`](files/fallout2.cfg) instead:
| ddraw.ini section | ddraw.ini key | fallout2.cfg section | fallout2.cfg key |
| --- | --- | --- | --- |
@@ -43,7 +43,7 @@ See [`https://sfall-team.github.io/sfall/`](https://sfall-team.github.io/sfall/)
| Combat / Knockback | set_weapon_knockback<br>set_target_knockback<br>set_attacker_knockback<br>remove_weapon_knockback<br>remove_target_knockback<br>remove_attacker_knockback | not implemented | - |
| Maps and encounters | in_world_map<br>force_encounter<br>force_encounter_with_flags<br>set_map_time_multi<br>get/set_map_enter_position<br>exec_map_update_scripts<br>get/set_terrain_name<br>set_town_title<br>get/set_can_rest_on_map<br>set_rest_heal_time<br>set_rest_mode<br>set_worldmap_heal_time | implemented: in_world_map, force_encounter, force_encounter_with_flags, set_map_time_multi | - |
| Maps and encounters / Worldmap | get_world_map_x/y_pos<br>set_world_map_pos | âś… | - |
| Audio | eax_available<br>set_eax_environment<br>play_sfall_sound<br>stop_sfall_sound | not implemented | *eax* opcodes will not be implemented |
| Audio | play_sfall_sound<br>stop_sfall_sound | âś… | `play_sfall_sound` currently supports `.acm`, `.wav`, `.ogg` formats, and can load from `.dat` archives. `.mp3` is not yet supported. |
| Combat / Weapons and ammo | get/set_weapon_ammo_pid<br>get/set_weapon_ammo_count | âś… | - |
| Sfall / Version | sfall_ver_major<br>sfall_ver_minor<br>sfall_ver_build | âś… | CE currently reports `4.3.4` |
| Utility / Math | log, exponent, round, sqrt, abs, sin, cos, tan, arctan, ceil, ^, floor2, div | âś… | - |
@@ -58,7 +58,7 @@ See [`https://sfall-team.github.io/sfall/`](https://sfall-team.github.io/sfall/)
| 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. |
| Global variables | set_sfall_global<br>get_sfall_global_int<br>get_sfall_global_float | âś… except get_sfall_global_float | Current CE storage is int-backed; `set_sfall_global` stores integer values |
| Hooks / Hook functions | init_hook<br>get_sfall_arg<br>get_sfall_args<br>get_sfall_arg_at<br>set_sfall_return<br>set_sfall_arg<br>register_hook<br>register_hook_proc<br>register_hook_proc_spec | âś… | See below for implemented hooks. `init_hook` is deprecated and will not be implemented. register_hook_proc and register_hook_proc_spec both add hooks to the *end* of the hook list, instead of beginning and end, respectively. |
| Arrays / Array functions | create_array<br>temp_array<br>fix_array<br>get/set_array<br>resize_array<br>free_array<br>scan_array<br>len_array<br>save/load_array<br>array_key<br>arrayexpr | âś… except save_array, load_array | - |
| 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 | âś… | - |
| 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 | - |
@@ -101,8 +101,8 @@ See [`https://sfall-team.github.io/sfall/`](https://sfall-team.github.io/sfall/)
| Steal | `HOOK_STEAL` | đźš« | Et tu |
| WithinPerception | `HOOK_WITHINPERCEPTION` | âś… | - |
| InventoryMove | `HOOK_INVENTORYMOVE` | âś… | - |
| InvenWield | `HOOK_INVENWIELD` | đźš« | - |
| AdjustFID | `HOOK_ADJUSTFID` | đźš« | - |
| InvenWield | `HOOK_INVENWIELD` | âś… | - |
| AdjustFID | `HOOK_ADJUSTFID` | âś… | Second hook arg currently matches the first because CE has no internal FID modifiers like Hero Appearance. |
| CombatTurn | `HOOK_COMBATTURN` | âś… | - |
| StdProcedure | `HOOK_STDPROCEDURE` | âś… | - |
| StdProcedureEnd | `HOOK_STDPROCEDURE_END` | âś… | - |
@@ -124,5 +124,5 @@ See [`https://sfall-team.github.io/sfall/`](https://sfall-team.github.io/sfall/)
| AdjustRads | `HOOK_ADJUSTRADS` | đźš« | (maybe) |
| RollCheck | `HOOK_ROLLCHECK` | đźš« | - |
| BestWeapon | `HOOK_BESTWEAPON` | đźš« | - |
| CanUseWeapon | `HOOK_CANUSEWEAPON` | đźš« | - |
| CanUseWeapon | `HOOK_CANUSEWEAPON` | âś… | - |
| BuildSfxWeapon | `HOOK_BUILDSFXWEAPON` | đźš« | - |
+6
View File
@@ -22,6 +22,10 @@ save_text_maps=0
show_pid_numbers=0
sort_script_list=0
use_art_not_protos=0
; Allows to load a certain map right away after loading the mapper.
map=denbus2
; Allows to disable grid item picker and use a simpler list-based picker.
use_grid_item_picker=1
[preferences]
brightness=1.000000
@@ -120,3 +124,5 @@ inventory_columns=2
auto_open_doors=0
; Overrides distance at which walk animation is used when using an object.
use_walk_distance=5
; Allow switching to companions' inventories in loot screens (and barter, in the future)
party_loot_and_barter=1
+3 -6
View File
@@ -4,7 +4,7 @@
#define ARRAY_MAX_STRING (255)
#define ARRAY_MAX_STRING (1024)
#define ARRAY_MAX_SIZE (100000)
procedure array_test_suite begin
@@ -82,8 +82,8 @@ procedure array_test_suite begin
call assertEquals("string_split", get_array(string_split("this+is+good", "+"), 2), "good");
call assertEquals("string_split 2", len_array(string_split("advice", "")), 6);
s := "";
for (i := 0; i < ARRAY_MAX_STRING+30; i+=10) begin
s += "Verbosity.";
for (i := 0; i < ARRAY_MAX_STRING+1; i+=100) begin
s += "Verbosity.Verbosity.Verbosity.Verbosity.Verbosity.Verbosity.Verbosity.Verbosity.Verbosity.Verbosity.";
end
arr[0] := s;
call assertEquals("array max string", strlen(arr[0]), ARRAY_MAX_STRING-1);
@@ -141,8 +141,6 @@ procedure array_test_suite begin
end
call assertEquals("foreach 2", s, "ar2=name:John;hp:25;0:5.50000;");
/*
display_msg("Testing save/load...");
arr := [2,1];
arr2 := {1:2};
@@ -163,7 +161,6 @@ procedure array_test_suite begin
call assertEquals("unsave array 1", load_array(0.1), 0);
call assertEquals("unsave array 2", len_array(arr), 2);
call assertEquals("saved arrays 3", len_array(list_saved_arrays), len_array(arr2) - 1);
*/
display_msg("Testing nested expressions...");
arr := [["one", "two"]];
+78
View File
@@ -0,0 +1,78 @@
#include "sfall.h"
#include "dik.h"
variable loop_sound_id := 0;
variable quiet_loop_sound_id := 0;
variable music_sound_id := 0;
procedure stop_if_active(variable sound_id, variable label) begin
if (sound_id != 0) then begin
stop_sfall_sound(sound_id);
display_msg(label + " stopped");
end else begin
display_msg(label + " is not active");
end
end
procedure keypress_handler begin
variable pressed := get_sfall_arg_at(0);
variable key := get_sfall_arg_at(1);
variable ignored;
if (not pressed) then return;
if (key == DIK_K) then begin
display_msg("play_sfall_sound mode 0 one-shot: sound\\sfx\\pistol.ACM");
ignored := play_sfall_sound("sound\\sfx\\pistol.ACM", 0);
end else if (key == DIK_N) then begin
if (loop_sound_id != 0) then begin
display_msg(string_format1("loop already active id=%d", loop_sound_id));
return;
end
loop_sound_id := play_sfall_sound("sound\\music\\20CAR.ACM", 1);
display_msg(string_format1("mode 1 loop id=%d", loop_sound_id));
end else if (key == DIK_M) then begin
call stop_if_active(loop_sound_id, "mode 1 loop");
loop_sound_id := 0;
end else if (key == DIK_C) then begin
if (quiet_loop_sound_id != 0) then begin
display_msg(string_format1("quiet loop already active id=%d", quiet_loop_sound_id));
return;
end
quiet_loop_sound_id := play_sfall_sound("sound\\music\\20CAR.ACM", 0x30000001);
display_msg(string_format1("quiet mode 1 loop id=%d", quiet_loop_sound_id));
end else if (key == DIK_X) then begin
call stop_if_active(quiet_loop_sound_id, "quiet mode 1 loop");
quiet_loop_sound_id := 0;
end else if (key == DIK_G) then begin
if (music_sound_id != 0) then begin
display_msg(string_format1("mode 2 music replacement already active id=%d", music_sound_id));
return;
end
music_sound_id := play_sfall_sound("sound\\music\\20CAR.ACM", 2);
display_msg(string_format1("mode 2 music replacement id=%d", music_sound_id));
end else if (key == DIK_H) then begin
call stop_if_active(music_sound_id, "mode 2 music replacement");
music_sound_id := 0;
end else if (key == DIK_V) then begin
display_msg("play_sfall_sound mode 3 speech-volume one-shot: sound\\sfx\\pistol.ACM");
ignored := play_sfall_sound("sound\\sfx\\pistol.ACM", 3);
end else if (key == DIK_B) then begin
display_msg("play_sfall_sound mode 0 one-shot wav: smw_1-up.wav"); // needs a wav file in the game dir
ignored := play_sfall_sound("smw_1-up.wav", 0);
end
end
procedure start begin
if (not game_loaded) then return;
display_msg("sfall_sound manual test ready");
display_msg("One-shots use master.dat SFX and loose-file wav; loops/replacement use music");
display_msg("K one-shot ACM mode0, B one-shot WAV mode0, N start loop mode1, M stop loop");
display_msg("C start quiet loop mode1, X stop quiet loop");
display_msg("G start mode2 music replacement, H stop mode2 replacement, V mode3 one-shot");
register_hook_proc(HOOK_KEYPRESS, keypress_handler);
end
+89 -1
View File
@@ -355,6 +355,14 @@ int artIsObjectTypeHidden(int objectType)
return objectType >= OBJ_TYPE_ITEM && objectType < OBJ_TYPE_COUNT ? gArtListDescriptions[objectType].flags & 1 : 0;
}
// 0x409DF0
void artToggleObjectTypeHidden(int objectType)
{
if (objectType >= 0 && objectType < OBJ_TYPE_COUNT) {
gArtListDescriptions[objectType].flags ^= 1;
}
}
// 0x418F7C
int artGetFidgetCount(int headFid)
{
@@ -439,7 +447,87 @@ void artRender(int fid, unsigned char* dest, int width, int height, int pitch)
// mapper2.exe: 0x40A03C
int art_list_str(int fid, char* name)
{
// TODO: Incomplete.
if (fid == -1 || name == nullptr) {
return -1;
}
int objectType = (fid & 0xF000000) >> 24;
int index = fid & 0xFFF;
const char* typeName = artGetObjectTypeName(objectType);
if (typeName == nullptr) {
return -1;
}
char path[260];
snprintf(path, sizeof(path), "%s%s%s\\%s.lst", _cd_path_base, "art\\", typeName, typeName);
File* stream = fileOpen(path, "rt");
if (stream == nullptr) {
return -1;
}
char buffer[260];
int line = 0;
bool found = false;
while (fileReadString(buffer, sizeof(buffer), stream) != nullptr) {
if (line == index) {
char* p = buffer;
while (*p) {
if (*p == ' ' || *p == '\n') {
*p = '\0';
break;
}
p++;
}
char* dst = name;
const char* src = buffer;
do {
*dst++ = *src++;
*dst++ = *src++;
} while (dst[-2] != '\0' || dst[-1] != '\0');
found = true;
break;
}
line++;
}
fileClose(stream);
return found ? 0 : -1;
}
// art_list_index
int artListIndex(int objectType, const char* name)
{
if (objectType < 0 || objectType >= OBJ_TYPE_COUNT) return -1;
if (gArtListDescriptions[objectType].fileNames == nullptr) return -1;
char upperName[13] = { 0 };
strncpy(upperName, name, 12);
upperName[12] = '\0';
compat_strupr(upperName);
int length = gArtListDescriptions[objectType].fileNamesLength;
const char* fileNames = gArtListDescriptions[objectType].fileNames;
for (int index = 0; index < length; index++) {
const char* entry = fileNames + index * 13;
char upperEntry[13];
strncpy(upperEntry, entry, 12);
upperEntry[12] = '\0';
compat_strupr(upperEntry);
char* p = upperEntry;
while (*p && ((*p >= 'A' && *p <= 'Z') || (*p >= '0' && *p <= '9') || *p == '_'))
p++;
*p = '\0';
if (strcmp(upperEntry, upperName) == 0) {
return index;
}
}
return -1;
}
+2
View File
@@ -122,6 +122,7 @@ void artReset();
void artExit();
char* artGetObjectTypeName(int objectType);
int artIsObjectTypeHidden(int objectType);
void artToggleObjectTypeHidden(int objectType);
int artGetFidgetCount(int headFid);
void artRender(int fid, unsigned char* dest, int width, int height, int pitch);
int art_list_str(int fid, char* name);
@@ -150,6 +151,7 @@ int _art_alias_num(int index);
int artCritterFidShouldRun(int fid);
int artAliasFid(int fid);
int buildFid(int objectType, int frmId, int animType, int weaponCode, int rotation);
int artListIndex(int objectType, const char* name);
Art* artLoad(const char* path);
int artRead(const char* path, unsigned char* data);
int artWrite(const char* path, unsigned char* data);
+211 -11
View File
@@ -4,9 +4,13 @@
#include <stdio.h>
#include <string.h>
#include <SDL.h>
#include "db.h"
#include "debug.h"
#include "memory_manager.h"
#include "ogg_decoder.h"
#include "platform_compat.h"
#include "sound.h"
#include "sound_decoder.h"
@@ -15,20 +19,33 @@ namespace fallout {
typedef enum AudioFlags {
AUDIO_IN_USE = 0x01,
AUDIO_COMPRESSED = 0x02,
// CE: Decoded formats like WAV/OGG are fully loaded into memory up front.
AUDIO_MEMORY = 0x04,
} AudioFileFlags;
typedef struct Audio {
int flags;
File* stream;
SoundDecoder* soundDecoder;
unsigned char* data;
int fileSize;
int sampleRate;
int channels;
int bitsPerSample;
int position;
} Audio;
typedef enum AudioOpenMode {
AUDIO_OPEN_MODE_RAW = 0,
AUDIO_OPEN_MODE_COMPRESSED = 1, // ACM
AUDIO_OPEN_MODE_WAV = 2,
AUDIO_OPEN_MODE_OGG = 3,
} AudioOpenMode;
static bool defaultCompressionFunc(char* filePath);
static int audioSoundDecoderReadHandler(void* data, void* buf, unsigned int size);
static bool audioIsWavePath(const char* filePath);
static bool audioDecodeWave(File* stream, AudioFileInfo* info, unsigned char** dataPtr, int* sizePtr);
// 0x5108BC
static AudioQueryCompressedFunc* queryCompressedFunc = defaultCompressionFunc;
@@ -56,18 +73,128 @@ static int audioSoundDecoderReadHandler(void* data, void* buffer, unsigned int s
return fileRead(buffer, 1, size, reinterpret_cast<File*>(data));
}
static bool audioIsWavePath(const char* filePath)
{
const char* dot = strrchr(filePath, '.');
return dot != nullptr && compat_stricmp(dot + 1, "wav") == 0;
}
static bool audioDecodeWave(File* stream, AudioFileInfo* info, unsigned char** dataPtr, int* sizePtr)
{
bool success = false;
unsigned char* fileData = nullptr;
Uint8* loadedData = nullptr;
Uint8* convertedData = nullptr;
SDL_RWops* rw = nullptr;
SDL_AudioSpec spec = {};
Uint32 loadedLength = 0;
int channels = 0;
SDL_AudioCVT cvt;
memset(&cvt, 0, sizeof(cvt));
int convertedLength = 0;
bool convertedDataNeedsFree = false;
bool loadedDataNeedsFree = false;
int fileSize = fileGetSize(stream);
if (fileSize <= 0) {
return false;
}
fileData = reinterpret_cast<unsigned char*>(internal_malloc_safe(fileSize, __FILE__, __LINE__));
if (fileRead(fileData, 1, fileSize, stream) != fileSize) {
goto done;
}
rw = SDL_RWFromConstMem(fileData, fileSize);
if (rw == nullptr) {
goto done;
}
if (SDL_LoadWAV_RW(rw, 1, &spec, &loadedData, &loadedLength) == nullptr) {
rw = nullptr;
goto done;
}
rw = nullptr;
loadedDataNeedsFree = true;
channels = spec.channels == 1 ? 1 : 2;
if (SDL_BuildAudioCVT(&cvt, spec.format, spec.channels, spec.freq, AUDIO_S16SYS, channels, spec.freq) < 0) {
goto done;
}
convertedLength = static_cast<int>(loadedLength);
if (cvt.needed != 0) {
cvt.len = static_cast<int>(loadedLength);
cvt.buf = reinterpret_cast<Uint8*>(SDL_malloc(cvt.len * cvt.len_mult));
if (cvt.buf == nullptr) {
goto done;
}
memcpy(cvt.buf, loadedData, loadedLength);
if (SDL_ConvertAudio(&cvt) != 0) {
SDL_free(cvt.buf);
goto done;
}
convertedData = cvt.buf;
convertedLength = cvt.len_cvt;
convertedDataNeedsFree = true;
} else {
convertedData = loadedData;
}
if (info != nullptr) {
info->channels = channels;
info->sampleRate = spec.freq;
info->bitsPerSample = 16;
}
if (dataPtr != nullptr && sizePtr != nullptr) {
*dataPtr = reinterpret_cast<unsigned char*>(internal_malloc_safe(convertedLength, __FILE__, __LINE__));
memcpy(*dataPtr, convertedData, convertedLength);
*sizePtr = convertedLength;
}
success = true;
done:
if (rw != nullptr) {
SDL_RWclose(rw);
}
if (convertedData != nullptr) {
if (convertedDataNeedsFree) {
SDL_free(convertedData);
}
}
if (loadedDataNeedsFree && loadedData != nullptr) {
SDL_FreeWAV(loadedData);
}
if (fileData != nullptr) {
internal_free_safe(fileData, __FILE__, __LINE__);
}
return success;
}
// AudioOpen
// 0x41A2EC
int audioOpen(const char* fname, int* sampleRate)
int audioOpen(const char* fname, AudioFileInfo* info, bool* isMemoryBackedPtr)
{
char path[80];
char path[COMPAT_MAX_PATH];
snprintf(path, sizeof(path), "%s", fname);
int compression;
if (queryCompressedFunc(path)) {
compression = 2;
AudioOpenMode openMode;
if (audioIsWavePath(path)) {
openMode = AUDIO_OPEN_MODE_WAV;
} else if (oggDecoderIsFilePath(path)) {
openMode = AUDIO_OPEN_MODE_OGG;
} else if (queryCompressedFunc(path)) {
openMode = AUDIO_OPEN_MODE_COMPRESSED;
} else {
compression = 0;
openMode = AUDIO_OPEN_MODE_RAW;
}
File* stream = fileOpen(path, "rb");
@@ -93,17 +220,64 @@ int audioOpen(const char* fname, int* sampleRate)
}
Audio* audioFile = &(gAudioList[index]);
memset(audioFile, 0, sizeof(*audioFile));
audioFile->flags = AUDIO_IN_USE;
audioFile->stream = stream;
if (compression == 2) {
if (openMode == AUDIO_OPEN_MODE_WAV || openMode == AUDIO_OPEN_MODE_OGG) {
AudioFileInfo decodedInfo = {};
audioFile->flags |= AUDIO_MEMORY;
bool decoded = false;
if (openMode == AUDIO_OPEN_MODE_WAV) {
decoded = audioDecodeWave(stream, &decodedInfo, &(audioFile->data), &(audioFile->fileSize));
} else {
AudioFileInfo audioFileInfo = {};
decoded = oggDecoderDecode(stream, &audioFileInfo, &(audioFile->data), &(audioFile->fileSize));
decodedInfo.sampleRate = audioFileInfo.sampleRate;
decodedInfo.channels = audioFileInfo.channels;
decodedInfo.bitsPerSample = audioFileInfo.bitsPerSample;
}
if (!decoded) {
fileClose(stream);
memset(audioFile, 0, sizeof(*audioFile));
debugPrint("AudioOpen: Couldn't decode %s\n", path);
return -1;
}
fileClose(stream);
audioFile->stream = nullptr;
audioFile->sampleRate = decodedInfo.sampleRate;
audioFile->channels = decodedInfo.channels;
audioFile->bitsPerSample = decodedInfo.bitsPerSample;
if (info != nullptr) {
*info = decodedInfo;
}
if (isMemoryBackedPtr != nullptr) {
*isMemoryBackedPtr = true;
}
} else if (openMode == AUDIO_OPEN_MODE_COMPRESSED) {
audioFile->flags |= AUDIO_COMPRESSED;
audioFile->soundDecoder = soundDecoderInit(audioSoundDecoderReadHandler, audioFile->stream, &(audioFile->channels), &(audioFile->sampleRate), &(audioFile->fileSize));
audioFile->fileSize *= 2;
audioFile->bitsPerSample = 16;
*sampleRate = audioFile->sampleRate;
if (info != nullptr) {
info->sampleRate = audioFile->sampleRate;
info->bitsPerSample = audioFile->bitsPerSample;
}
if (isMemoryBackedPtr != nullptr) {
*isMemoryBackedPtr = false;
}
} else {
audioFile->fileSize = fileGetSize(stream);
audioFile->bitsPerSample = 8;
if (info != nullptr) {
info->bitsPerSample = audioFile->bitsPerSample;
}
if (isMemoryBackedPtr != nullptr) {
*isMemoryBackedPtr = false;
}
}
audioFile->position = 0;
@@ -115,7 +289,13 @@ int audioOpen(const char* fname, int* sampleRate)
int audioClose(int handle)
{
Audio* audioFile = &(gAudioList[handle - 1]);
fileClose(audioFile->stream);
if ((audioFile->flags & AUDIO_MEMORY) != 0) {
if (audioFile->data != nullptr) {
internal_free_safe(audioFile->data, __FILE__, __LINE__);
}
} else if (audioFile->stream != nullptr) {
fileClose(audioFile->stream);
}
if ((audioFile->flags & AUDIO_COMPRESSED) != 0) {
soundDecoderFree(audioFile->soundDecoder);
@@ -132,7 +312,16 @@ int audioRead(int handle, void* buffer, unsigned int size)
Audio* audioFile = &(gAudioList[handle - 1]);
int bytesRead;
if ((audioFile->flags & AUDIO_COMPRESSED) != 0) {
if ((audioFile->flags & AUDIO_MEMORY) != 0) {
bytesRead = audioFile->fileSize - audioFile->position;
if (bytesRead > static_cast<int>(size)) {
bytesRead = size;
}
if (bytesRead > 0) {
memcpy(buffer, audioFile->data + audioFile->position, bytesRead);
}
} else if ((audioFile->flags & AUDIO_COMPRESSED) != 0) {
bytesRead = soundDecoderDecode(audioFile->soundDecoder, buffer, size);
} else {
bytesRead = fileRead(buffer, 1, size, audioFile->stream);
@@ -166,7 +355,18 @@ long audioSeek(int handle, long offset, int origin)
assert(false && "Should be unreachable");
}
if ((audioFile->flags & AUDIO_COMPRESSED) != 0) {
if ((audioFile->flags & AUDIO_MEMORY) != 0) {
if (pos < 0) {
pos = 0;
}
if (pos > audioFile->fileSize) {
pos = audioFile->fileSize;
}
audioFile->position = pos;
return audioFile->position;
} else if ((audioFile->flags & AUDIO_COMPRESSED) != 0) {
if (pos < audioFile->position) {
soundDecoderFree(audioFile->soundDecoder);
fileSeek(audioFile->stream, 0, SEEK_SET);
+3 -1
View File
@@ -1,11 +1,13 @@
#ifndef AUDIO_H
#define AUDIO_H
#include "sound.h"
namespace fallout {
typedef bool(AudioQueryCompressedFunc)(char* filePath);
int audioOpen(const char* fname, int* sampleRate);
int audioOpen(const char* fname, AudioFileInfo* info, bool* isMemoryBackedPtr);
int audioClose(int handle);
int audioRead(int handle, void* buffer, unsigned int size);
long audioSeek(int handle, long offset, int origin);
+14 -3
View File
@@ -57,7 +57,7 @@ static int audioFileSoundDecoderReadHandler(void* data, void* buffer, unsigned i
}
// 0x41A88C
int audioFileOpen(const char* fname, int* sampleRate)
int audioFileOpen(const char* fname, AudioFileInfo* info, bool* isMemoryBackedPtr)
{
char path[COMPAT_MAX_PATH];
strcpy(path, fname);
@@ -98,10 +98,21 @@ int audioFileOpen(const char* fname, int* sampleRate)
audioFile->flags |= AUDIO_FILE_COMPRESSED;
audioFile->soundDecoder = soundDecoderInit(audioFileSoundDecoderReadHandler, audioFile->stream, &(audioFile->channels), &(audioFile->sampleRate), &(audioFile->fileSize));
audioFile->fileSize *= 2;
*sampleRate = audioFile->sampleRate;
if (info != nullptr) {
info->sampleRate = audioFile->sampleRate;
info->bitsPerSample = 16;
}
if (isMemoryBackedPtr != nullptr) {
*isMemoryBackedPtr = false;
}
} else {
audioFile->fileSize = getFileSize(stream);
if (info != nullptr) {
info->bitsPerSample = 8;
}
if (isMemoryBackedPtr != nullptr) {
*isMemoryBackedPtr = false;
}
}
audioFile->position = 0;
+3 -1
View File
@@ -1,11 +1,13 @@
#ifndef AUDIO_FILE_H
#define AUDIO_FILE_H
#include "sound.h"
namespace fallout {
typedef bool(AudioFileQueryCompressedFunc)(char* filePath);
int audioFileOpen(const char* fname, int* sampleRate);
int audioFileOpen(const char* fname, AudioFileInfo* info, bool* isMemoryBackedPtr);
int audioFileClose(int handle);
int audioFileRead(int handle, void* buf, unsigned int size);
long audioFileSeek(int handle, long offset, int origin);
+24 -14
View File
@@ -1972,31 +1972,41 @@ static Object* _ai_best_weapon(Object* attacker, Object* weapon1, Object* weapon
// 0x4298EC
static bool _ai_can_use_weapon(Object* critter, Object* weapon, int hitMode)
{
bool result = true;
int damageFlags = critter->data.critter.combat.results;
if ((damageFlags & DAM_CRIP_ARM_LEFT) != 0 && (damageFlags & DAM_CRIP_ARM_RIGHT) != 0) {
return false;
result = false;
}
if ((damageFlags & DAM_CRIP_ARM_ANY) != 0 && weaponIsTwoHanded(weapon)) {
return false;
if (result && (damageFlags & DAM_CRIP_ARM_ANY) != 0 && weaponIsTwoHanded(weapon)) {
result = false;
}
int rotation = critter->rotation + 1;
int animationCode = weaponGetAnimationCode(weapon);
int weaponAnimationCode = weaponGetAnimationForHitMode(weapon, hitMode);
int fid = buildFid(OBJ_TYPE_CRITTER, critter->fid & 0xFFF, weaponAnimationCode, animationCode, rotation);
if (!artExists(fid)) {
return false;
if (result) {
int rotation = critter->rotation + 1;
int animationCode = weaponGetAnimationCode(weapon);
int weaponAnimationCode = weaponGetAnimationForHitMode(weapon, hitMode);
int fid = buildFid(OBJ_TYPE_CRITTER, critter->fid & 0xFFF, weaponAnimationCode, animationCode, rotation);
if (!artExists(fid)) {
result = false;
}
}
int skill = weaponGetSkillForHitMode(weapon, hitMode);
AiPacket* ai = aiGetPacket(critter);
if (skillGetValue(critter, skill) < ai->min_to_hit) {
return false;
if (result) {
int skill = weaponGetSkillForHitMode(weapon, hitMode);
if (skillGetValue(critter, skill) < ai->min_to_hit) {
result = false;
}
}
int attackType = weaponGetAttackTypeForHitMode(weapon, HIT_MODE_RIGHT_WEAPON_PRIMARY);
return _caiHasWeapPrefType(ai, attackType) != 0;
if (result) {
int attackType = weaponGetAttackTypeForHitMode(weapon, HIT_MODE_RIGHT_WEAPON_PRIMARY);
result = _caiHasWeapPrefType(ai, attackType) != 0;
}
return scriptHooks_CanUseWeapon(result, critter, weapon, hitMode);
}
// 0x4299A0
+48
View File
@@ -29,34 +29,82 @@ long fileTell(File* stream);
void fileRewind(File* stream);
int fileEof(File* stream);
int fileReadUInt8(File* stream, unsigned char* valuePtr);
// Reads a 16-bit big-endian integer from stream and stores it in host byte order.
int fileReadInt16(File* stream, short* valuePtr);
// Reads a 16-bit big-endian unsigned integer from stream and stores it in host byte order.
int fileReadUInt16(File* stream, unsigned short* valuePtr);
// Reads a 32-bit big-endian integer from stream and stores it in host byte order.
int fileReadInt32(File* stream, int* valuePtr);
// Reads a 32-bit big-endian unsigned integer from stream and stores it in host byte order.
int fileReadUInt32(File* stream, unsigned int* valuePtr);
// Reads a 32-bit big-endian integer from stream and stores it in host byte order (alias).
int _db_freadInt(File* stream, int* valuePtr);
// Reads a 32-bit big-endian floating-point value from stream and stores it in host byte order.
int fileReadFloat(File* stream, float* valuePtr);
// Reads a 32-bit big-endian integer from stream and stores the boolean equivalent in host byte order.
int fileReadBool(File* stream, bool* valuePtr);
int fileWriteUInt8(File* stream, unsigned char value);
// Writes a 16-bit integer to stream in big-endian byte order.
int fileWriteInt16(File* stream, short value);
// Writes a 16-bit unsigned integer to stream in big-endian byte order.
int fileWriteUInt16(File* stream, unsigned short value);
// Writes a 32-bit integer to stream in big-endian byte order.
int fileWriteInt32(File* stream, int value);
// Writes a 32-bit integer to stream in big-endian byte order (alias).
int _db_fwriteLong(File* stream, int value);
// Writes a 32-bit unsigned integer to stream in big-endian byte order.
int fileWriteUInt32(File* stream, unsigned int value);
// Writes a 32-bit floating-point value to stream in big-endian byte order.
int fileWriteFloat(File* stream, float value);
// Writes a boolean value to stream as a 32-bit big-endian integer (1 or 0).
int fileWriteBool(File* stream, bool value);
int fileReadUInt8List(File* stream, unsigned char* arr, int count);
int fileReadFixedLengthString(File* stream, char* string, int length);
// Reads a list of 16-bit big-endian integers from stream into arr (in host byte order).
int fileReadInt16List(File* stream, short* arr, int count);
// Reads a list of 16-bit big-endian unsigned integers from stream into arr (in host byte order).
int fileReadUInt16List(File* stream, unsigned short* arr, int count);
// Reads a list of 32-bit big-endian integers from stream into arr (in host byte order).
int fileReadInt32List(File* stream, int* arr, int count);
// Reads a list of 32-bit big-endian integers from stream into arr (in host byte order, alias).
int _db_freadIntCount(File* stream, int* arr, int count);
// Reads a list of 32-bit big-endian unsigned integers from stream into arr (in host byte order).
int fileReadUInt32List(File* stream, unsigned int* arr, int count);
int fileWriteUInt8List(File* stream, unsigned char* arr, int count);
int fileWriteFixedLengthString(File* stream, char* string, int length);
// Writes a list of 16-bit integers to stream in big-endian byte order.
int fileWriteInt16List(File* stream, short* arr, int count);
// Writes a list of 16-bit unsigned integers to stream in big-endian byte order.
int fileWriteUInt16List(File* stream, unsigned short* arr, int count);
// Writes a list of 32-bit integers to stream in big-endian byte order.
int fileWriteInt32List(File* stream, int* arr, int count);
// Writes a list of 32-bit integers to stream in big-endian byte order (alias).
int _db_fwriteLongCount(File* stream, int* arr, int count);
// Writes a list of 32-bit unsigned integers to stream in big-endian byte order.
int fileWriteUInt32List(File* stream, unsigned int* arr, int count);
int fileNameListInit(const char* pattern, char*** fileNames);
void fileNameListFree(char*** fileNames, int unused);
+13 -8
View File
@@ -24,8 +24,9 @@ static void debugFlushBuffer();
// Messages logged before any debug proc is registered are held here and
// flushed when the first proc is registered.
static std::string gDebugBuffer;
static std::string debugBuffer;
static constexpr size_t kDebugBufferMaxSize = 64 * 1024;
static bool debugBufferDisabled = false;
// 0x51DEF8
static FILE* _fd = nullptr;
@@ -41,6 +42,8 @@ static DebugPrintProc* gDebugPrintProc = nullptr;
void debugModeInit(const char* debugMode)
{
debugBufferDisabled = true;
if (debugMode == nullptr) {
return;
}
@@ -59,7 +62,7 @@ void debugModeInit(const char* debugMode)
}
if (gDebugPrintProc == nullptr) {
gDebugBuffer.clear();
debugBuffer.clear();
}
}
@@ -166,15 +169,17 @@ int debugPrint(const char* format, ...)
char string[260];
int len = vsnprintf(string, sizeof(string), format, args);
if (len < 0) {
string[0] = '\0';
}
va_end(args);
int rc;
if (gDebugPrintProc != nullptr) {
rc = gDebugPrintProc(string);
} else {
if (len > 0 && gDebugBuffer.size() + len <= kDebugBufferMaxSize) {
gDebugBuffer += string;
if (!debugBufferDisabled && debugBuffer.size() + strlen(string) <= kDebugBufferMaxSize) {
debugBuffer += string;
}
rc = -1;
}
@@ -188,11 +193,11 @@ int debugPrint(const char* format, ...)
static void debugFlushBuffer()
{
if (gDebugBuffer.empty() || gDebugPrintProc == nullptr) {
if (debugBuffer.empty() || gDebugPrintProc == nullptr) {
return;
}
gDebugPrintProc(gDebugBuffer.c_str());
gDebugBuffer.clear();
gDebugPrintProc(debugBuffer.c_str());
debugBuffer.clear();
}
// 0x4C6F94
+1 -1
View File
@@ -14,7 +14,7 @@
namespace fallout {
// The size of decompression buffer for reading compressed [DFile]s.
#define DFILE_DECOMPRESSION_BUFFER_SIZE (0x400)
#define DFILE_DECOMPRESSION_BUFFER_SIZE (0x1000)
// Specifies that [DFile] has unget character.
//
+1 -2
View File
@@ -86,7 +86,6 @@ namespace fallout {
#define SPLASH_HEIGHT (480)
#define SPLASH_COUNT (10)
static int gameLoadGlobalVars();
static int gameTakeScreenshot(int width, int height, unsigned char* buffer, unsigned char* palette);
static void gameFreeGlobalVars();
static bool tryLoadBaseCEModAtPath(const char* path, bool* found, bool* openFailed);
@@ -1016,7 +1015,7 @@ int gameSetGlobalVar(int var, int value)
// game_load_info
// 0x443CC8
static int gameLoadGlobalVars()
int gameLoadGlobalVars()
{
if (globalVarsRead("data\\vault13.gam", "GAME_GLOBAL_VARS:", &gGameGlobalVarsLength, &gGameGlobalVars) != 0) {
return -1;
+1
View File
@@ -47,6 +47,7 @@ int gameRequestState(int newGameState);
void gameUpdateState();
int showQuitConfirmationDialog();
int gameLoadGlobalVars();
int gameShowDeathDialog(const char* message);
void gameHandleSkilldexResult(int rc);
void* gameGetGlobalPointer(int var);
+34 -31
View File
@@ -26,7 +26,7 @@ constexpr char kMapperConfigFileName[] = "mapper2.cfg";
// 0x5186D0
bool gGameConfigInitialized = false;
// ce.cfg
// fallout2.cfg
//
// 0x58E950
Config gGameConfig;
@@ -47,9 +47,9 @@ char gGameConfigFilePath[COMPAT_MAX_PATH];
// additional check for [argc], so it will crash if you pass NULL, or an empty
// array into [argv].
//
// The executable path from [argv] is used resolve path to `<executable>ce.cfg`,
// The executable path from [argv] is used resolve path to `fallout2.cfg`,
// which should be in the same folder. This function provide defaults if
// file is not present, or cannot be read for any reason.
// `fallout2.cfg` is not present, or cannot be read for any reason.
//
// Finally, this function merges key-value pairs from [argv] if any, see
// [configParseCommandLineArguments] for expected format.
@@ -86,45 +86,48 @@ bool gameConfigInit(bool isMapper, int argc, char** argv)
char* customConfigFileName = nullptr;
configGetString(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_CONFIG_FILE, &customConfigFileName);
// CE: Derive config file name from executable name.
char exeDrive[COMPAT_MAX_DRIVE];
char exeDir[COMPAT_MAX_DIR];
char exeFname[COMPAT_MAX_FNAME];
compat_splitpath(argv[0], exeDrive, exeDir, exeFname, nullptr);
const char* configFileName = customConfigFileName != nullptr && *customConfigFileName != '\0'
? customConfigFileName
: kDefaultGameConfigFileName;
char derivedConfigFileName[COMPAT_MAX_FNAME + 5];
snprintf(derivedConfigFileName, sizeof(derivedConfigFileName), "%s.cfg", exeFname);
const char* defaultConfigFileName = isMapper ? kMapperConfigFileName : kDefaultGameConfigFileName;
const bool hasCustomConfigFileName = customConfigFileName != nullptr && *customConfigFileName != '\0';
const char* configFileName = hasCustomConfigFileName ? customConfigFileName : derivedConfigFileName;
compat_makepath(gGameConfigFilePath, exeDrive, exeDir, configFileName, nullptr);
const bool usingDerivedConfig = !hasCustomConfigFileName
&& compat_stricmp(derivedConfigFileName, defaultConfigFileName) != 0;
// Make `fallout2.cfg` file path.
char* executable = argv[0];
char* ch = strrchr(executable, '\\');
if (ch != nullptr) {
*ch = '\0';
if (isMapper) {
snprintf(gGameConfigFilePath,
sizeof(gGameConfigFilePath),
"%s\\%s",
executable,
kMapperConfigFileName);
} else {
snprintf(gGameConfigFilePath,
sizeof(gGameConfigFilePath),
"%s\\%s",
executable,
configFileName);
}
*ch = '\\';
} else {
if (isMapper) {
strcpy(gGameConfigFilePath, kMapperConfigFileName);
} else {
strcpy(gGameConfigFilePath, configFileName);
}
}
configRead(&gGameConfig, gGameConfigFilePath, false);
debugPrint("Game config loaded from %s.\n", gGameConfigFilePath);
if (usingDerivedConfig) {
char defaultConfigFilePath[COMPAT_MAX_PATH];
compat_makepath(defaultConfigFilePath, exeDrive, exeDir, defaultConfigFileName, nullptr);
if (gameConfigMigrateFromDefaultConfig(defaultConfigFilePath, &gGameConfig)) {
debugPrint("Migrated settings from %s.\n", defaultConfigFileName);
configWriteEx(&gGameConfig, gGameConfigFilePath, CONFIG_RETAIN_ALL);
}
}
if (!isMapper && gameConfigMigrateFromF2Res(gGameConfigFilePath, &gGameConfig)) {
debugPrint("Migrated settings from f2_res.ini.\n");
configWriteEx(&gGameConfig, gGameConfigFilePath, CONFIG_RETAIN_ALL);
}
// Add key-values from command line, which overrides both defaults and
// whatever was loaded from cfg.
// whatever was loaded from `fallout2.cfg`.
configParseCommandLineArguments(&gGameConfig, argc, argv);
// Writes default values to config, skipping keys that were already loaded.
@@ -143,7 +146,7 @@ EM_ASYNC_JS(void, do_save_idbfs_gameconfig, (), {
// clang-format on
#endif
// Saves game config into cfg.
// Saves game config into `fallout2.cfg`.
//
// 0x444C14
bool gameConfigSave()
-1
View File
@@ -10,7 +10,6 @@ namespace fallout {
#define GAME_CONFIG_SCREEN_KEY "screen"
#define GAME_CONFIG_UI_KEY "ui"
#define GAME_CONFIG_SOUND_KEY "sound"
#define GAME_CONFIG_META_KEY "meta"
#define GAME_CONFIG_MASTER_DAT_KEY "master_dat"
#define GAME_CONFIG_MASTER_PATCHES_KEY "master_patches"

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