Compare commits

...
Author SHA1 Message Date
Mike Klaas d2de6715dd more 2026-05-25 14:58:55 -07:00
Mike Klaas df4b787e86 prototype 1 2026-05-24 17:35:26 -07:00
Mike Klaas 6b442546cc better validation of transparency 2026-05-13 14:20:37 -07:00
Mike Klaas 2c08cc0b3a Switch 2-col inventory .frms to .png 2026-05-12 23:53:17 -07:00
Mike Klaas 09a1971cd8 Ability to load indexed pngs in game 2026-05-12 23:02:21 -07:00
Mike Klaas 7ae0452e1a Add original symbol beside all function and symbol offsets (#332)
* Add original symbol beside all function offsets

e.g.

```
// 0x414E98 register_object_call
int animationRegisterCallback(

// 0x414E20 register_object_must_erase
int animationRegisterHideObjectForced(
```

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

* mapper: save map corrections + some stubs

* mapper: fix playmode and cleanup discrepancy

* mapper: edit_mapper inacuracies and tidying up some code

* mapper: corrected Delete Spatial, minor fixes

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

* chore: auto-format with clang-format

* mapper: fix inaccuracies after AI review, round 1

* mapper: fix inaccuracies after AI review, round 2

* chore: auto-format with clang-format

---------

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

Description

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

* Emty commit for start clang formatter

* Used clang format

---------

Co-authored-by: ZoschukEN <ZoschukEN@RTK-CD.RU>
2026-05-11 10:10:23 -07:00
Mike Klaas 924e12b94c [HRP] Enable HR dialog screen (#441)
* [HRP] Enable HR dialog screen

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

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

* get/set_inven_ap_cost
2026-05-10 21:48:22 -07:00
Mike Klaas 1175efd379 Implement HOOK_STEAL (#442)
* Implement HOOK_STEAL
2026-05-10 10:15:50 -07:00
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
173 changed files with 17569 additions and 4400 deletions
+115 -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"
@@ -200,6 +205,8 @@ target_sources(${EXECUTABLE_NAME} PUBLIC
"src/proto_types.h"
"src/proto.cc"
"src/proto.h"
"src/prototype_options.cc"
"src/prototype_options.h"
"src/queue.cc"
"src/queue.h"
"src/random.cc"
@@ -263,7 +270,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 +304,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 +318,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 +571,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")
+7 -7
View File
@@ -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 | - |
@@ -72,7 +72,7 @@ See [`https://sfall-team.github.io/sfall/`](https://sfall-team.github.io/sfall/)
| INI settings | get_ini_setting<br>get_ini_string<br>get_ini_section<br>get_ini_sections<br>get_ini_config<br>get_ini_config_db<br>set_ini_setting | âś… except get_ini_config, get_ini_config_db | `modified_ini` is intentionally omitted as deprecated. |
| Objects and scripts | set_self<br>set_dude_obj<br>real_dude_obj<br>remove_script<br>get/set_script<br>obj_is_carrying_obj<br>loot_obj<br>dialog_obj<br>obj_under_cursor<br>get/set_object_data<br>get/set_flags<br>set_unique_id<br>set_scr_name<br>obj_is_openable<br>get/set_proto_data<br>get_object_ai_data | implemented: set_self, get/set/remove_script, obj_is_carrying_obj, loot_obj, dialog_obj, obj_under_cursor, get_object_data, get_flags, set_flags, obj_is_openable, get_proto_data, set_proto_data | - |
| Other / Game management | set_movie_path<br>stop/resume_game<br>mark_movie_played<br>game_loaded<br>get_game_mode<br>get_uptime<br>signal_close_game | implemented: game_loaded, get_game_mode, get_uptime, signal_close_game | - |
| Gameplay tweaks | set_pickpocket_max<br>set_hit_chance_max<br>set_xp_mod<br>set_critter_hit_chance_mod<br>set_base_hit_chance_mod<br>set_hp_per_level_mod<br>gdialog_get_barter_mod<br>get/set_unspent_ap_bonus<br>get/<br>set_base_pickpocket_mod<br>set_critter_pickpocket_mod<br>get/set_inven_ap_cost<br>set_drugs_data<br>get_kill_counter<br>mod_kill_counter<br>set_pipboy_available | implemented: gdialog_get_barter_mod | - |
| Gameplay tweaks | set_pickpocket_max<br>set_hit_chance_max<br>set_xp_mod<br>set_critter_hit_chance_mod<br>set_base_hit_chance_mod<br>set_hp_per_level_mod<br>gdialog_get_barter_mod<br>get/set_unspent_ap_bonus<br>get/set_unspent_ap_perk_bonus<br>set_base_pickpocket_mod<br>set_critter_pickpocket_mod<br>get/set_inven_ap_cost<br>set_drugs_data<br>get_kill_counter<br>mod_kill_counter<br>set_pipboy_available | implemented: gdialog_get_barter_mod, get/set_unspent_ap{_perk}_bonus, get/set_inven_ap_cost | - |
| NPCs | inc_npc_level<br>get_npc_level<br>npc_engine_level_up | not implemented | - |
| Other | get_year<br>set_dm/df_model<br>active_hand<br>toggle_active_hand<br>get/set_viewport_x/y<br>hero_select_win<br>get_light_level<br>message_str_game<br>sneak_success<br>create_spatial<br>unwield_slot<br>add_g_timer_event<br>add_extra_msg_file<br>get_metarule_table<br>metarule_exist<br>remove_timer_event<br>spatial_radius | implemented: get_year, active_hand, toggle_active_hand, get_light_level, message_str_game, add_extra_msg_file, metarule_exist | `input_funcs_available`, `nb_create_char` are deprecated in sfall and intentionally absent in CE. `add_extra_msg_file` does not support the explicit `fileNumber` form in CE. |
@@ -98,11 +98,11 @@ See [`https://sfall-team.github.io/sfall/`](https://sfall-team.github.io/sfall/)
| KeyPress | `HOOK_KEYPRESS` | âś… | Third hook arg is currently `0`; CE doesn't use VK codes. |
| MouseClick | `HOOK_MOUSECLICK` | âś… | - |
| UseSkill | `HOOK_USESKILL` | đźš« | - |
| Steal | `HOOK_STEAL` | đźš« | Et tu |
| Steal | `HOOK_STEAL` | âś… | - |
| 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` | đźš« | - |
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

+8
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
@@ -94,6 +98,8 @@ auto_quick_save=3
display_bonus_damage=1
;Set to 1 to get notification of karma changes in the notification window
display_karma_changes=0
;Set to 1 to use the hi-res dialog border/background at resolutions above 640x480. Requires art\intrface\HR_ALLTLK.FRM (for example from f2_res.dat)
dialog_border=1
;Set to one to hide areas outside map bounds when using higher than 640x480 resolution, and to zero to disable
enable_high_resolution_stencil=1
;Set to 1 to extend the action points bar to show up to 16 AP instead of 10 (requires iface_apbar_e.frm)
@@ -120,3 +126,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
+83
View File
@@ -0,0 +1,83 @@
#include "sfall.h"
#include "dik.h"
#include "lib.arrays.h"
variable steal_mode := 0;
procedure steal_mode_name(variable mode) begin
if (mode == 1) then return "force_success";
if (mode == 2) then return "caught_fail";
if (mode == 3) then return "silent_fail";
if (mode == 4) then return "xp_override";
return "vanilla";
end
procedure steal_handler begin
variable
args := get_sfall_args,
thief := args[0],
target := args[1],
item := args[2],
is_planting := args[3],
quantity := args[4],
thief_name := "<null>",
target_name := "<null>",
item_name := "<null>";
if (thief) then thief_name := obj_name(thief);
if (target) then target_name := obj_name(target);
if (item) then item_name := obj_name(item);
display_msg(string_format6("steal mode=%s thief=%s target=%s item=%s planting=%d qty=%d",
steal_mode_name(steal_mode),
thief_name,
target_name,
item_name,
is_planting,
quantity));
display_msg(string_format1("steal args=%s", debug_array_str(args)));
if (thief != dude_obj) then return;
if (steal_mode == 1) then begin
display_msg("steal forcing success");
display_msg(sprintf(mstr_skill(571 + is_planting * 2), item_name));
set_sfall_return(1);
end else if (steal_mode == 2) then begin
display_msg("steal forcing caught failure");
display_msg(sprintf(mstr_skill(570 + is_planting * 2), item_name));
set_sfall_return(0);
end else if (steal_mode == 3) then begin
display_msg("steal forcing silent failure");
set_sfall_return(2);
end else if (steal_mode == 4) then begin
display_msg("steal forcing success with xp override 77");
display_msg(sprintf(mstr_skill(571 + is_planting * 2), item_name));
set_sfall_return(1);
set_sfall_return(77);
end
end
procedure keypress_handler begin
variable
pressed := get_sfall_arg_at(0),
key := get_sfall_arg_at(1);
if (not pressed) then return;
if (key != DIK_X) then return;
steal_mode += 1;
if (steal_mode > 4) then steal_mode := 0;
display_msg(string_format1("steal mode -> %s", steal_mode_name(steal_mode)));
end
procedure start begin
if (not game_loaded) then return;
display_msg("steal manual test ready: press X to cycle vanilla / force_success / caught_fail / silent_fail / xp_override");
display_msg("open a steal screen and move an item in either direction; mode 4 should award 77 XP for a successful action");
register_hook_proc(HOOK_KEYPRESS, keypress_handler);
register_hook_proc(HOOK_STEAL, steal_handler);
end
+5 -5
View File
@@ -46,13 +46,13 @@ typedef enum ScienceRepairTargetType {
SCIENCE_REPAIR_TARGET_TYPE_ANYONE,
} ScienceRepairTargetType;
// 0x5106D0
// 0x5106D0 action_in_explode
static bool _action_in_explode = false;
// 0x5106D4
// 0x5106D4 rotation
int rotation;
// 0x5106E0
// 0x5106E0 death_2
static const int gNormalDeathAnimations[DAMAGE_TYPE_COUNT] = {
ANIM_DANCING_AUTOFIRE,
ANIM_SLICED_IN_HALF,
@@ -63,7 +63,7 @@ static const int gNormalDeathAnimations[DAMAGE_TYPE_COUNT] = {
ANIM_BIG_HOLE,
};
// 0x5106FC
// 0x5106FC death_3
static const int gMaximumBloodDeathAnimations[DAMAGE_TYPE_COUNT] = {
ANIM_CHUNKS_OF_FLESH,
ANIM_SLICED_IN_HALF,
@@ -519,7 +519,7 @@ int _show_death(Object* obj, int anim)
// Animates damage to extras in a given attack (secondary targets).
//
// 0x410FEC _show_damage_extras
// 0x410FEC show_damage_extras
int showDamageToExtras(Attack* attack)
{
for (int index = 0; index < attack->extrasLength; index++) {
+11 -11
View File
@@ -309,39 +309,39 @@ static unsigned int animationComputeTicksPerFrame(Object* object, int fid);
static void reportOverloaded(Object* critter);
// 0x510718
// 0x510718 curr_sad
static int gAnimationCurrentSad = 0;
// 0x51071C
// 0x51071C curr_anim_set
static int gAnimationSequenceCurrentIndex = -1;
// 0x510720
// 0x510720 anim_in_init
static bool gAnimationInInit = false;
// 0x510724
// 0x510724 anim_in_anim_stop
static bool gAnimationInStop = false;
// 0x510728
// 0x510728 anim_in_bk
static bool _anim_in_bk = false;
// 0x530014
// 0x530014 sad
static AnimationSad gAnimationSads[ANIMATION_SAD_LIST_CAPACITY];
#define PATH_NODE_CAPACITY 10000
// 0x542FD4
// 0x542FD4 dad
static PathNode gClosedPathNodeList[PATH_NODE_CAPACITY];
// 0x54CC14
// 0x54CC14 anim_set
static AnimationSequence gAnimationSequences[32];
// 0x561814
// 0x561814 seen_tile
static unsigned char gPathfinderProcessedTiles[5000];
// 0x562B9C
// 0x562B9C child_path
static PathNode gOpenPathNodeList[PATH_NODE_CAPACITY];
// 0x56C7DC
// 0x56C7DC curr_anim_counter
static int gAnimationDescriptionCurrentIndex;
// anim_init
+220 -26
View File
@@ -1,6 +1,9 @@
#include "art.h"
#include <lodepng.h>
#include <assert.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -44,19 +47,19 @@ static int artReadHeader(Art* art, File* stream);
static int artGetDataSize(const Art* art);
static int paddingForSize(int size);
// 0x5002D8
// 0x5002D8 str2
static char gDefaultJumpsuitMaleFileName[] = "hmjmps";
// 0x05002E0
// 0x05002E0 aHfjmps
static char gDefaultJumpsuitFemaleFileName[] = "hfjmps";
// 0x5002E8
// 0x5002E8 aHmwarr
static char gDefaultTribalMaleFileName[] = "hmwarr";
// 0x5002F0
// 0x5002F0 aHfprim
static char gDefaultTribalFemaleFileName[] = "hfprim";
// 0x510738
// 0x510738 art
static ArtListDescription gArtListDescriptions[OBJ_TYPE_COUNT] = {
{ 0, "items", nullptr, nullptr, 0 },
{ 0, "critters", nullptr, nullptr, 0 },
@@ -74,18 +77,18 @@ static ArtListDescription gArtListDescriptions[OBJ_TYPE_COUNT] = {
// This flag denotes that localized arts should be looked up first. Used
// together with [gArtLanguage].
//
// 0x510898
// 0x510898 darn_foreigners
static bool gArtLanguageInitialized = false;
// 0x51089C
// 0x51089C head1
static const char* _head1 = "gggnnnbbbgnb";
// 0x5108A0
// 0x5108A0 head2
static const char* _head2 = "vfngfbnfvppp";
// Current native look base fid.
//
// 0x5108A4
// 0x5108A4 art_vault_guy_num
int _art_vault_guy_num = 0;
// Base fids for unarmored dude.
@@ -99,41 +102,42 @@ int _art_vault_guy_num = 0;
// been accessed differently in 0x49F984, which clearly uses look type as an
// index, not gender.
//
// 0x5108A8
// 0x5108A8 art_vault_person_nums
int _art_vault_person_nums[DUDE_NATIVE_LOOK_COUNT][GENDER_COUNT];
// Index of "grid001.frm" in tiles.lst.
//
// 0x5108B8
// 0x5108B8 art_mapper_blank_tile
static int _art_mapper_blank_tile = 1;
// Non-english language name.
//
// This value is used as a directory name to display localized arts.
//
// 0x56C970
// 0x56C970 darn_foreign_sub_path
static char gArtLanguage[32];
// 0x56C990
// 0x56C990 art_cache
Cache gArtCache;
// 0x56C9E4
// 0x56C9E4 art_name
static char _art_name[COMPAT_MAX_PATH];
// head_info
// 0x56CAE8
// 0x56CAE8 head_info
static HeadDescription* gHeadDescriptions;
// anon_alias
// 0x56CAEC
// 0x56CAEC anon_alias
static int* _anon_alias;
// artCritterFidShouldRunData
// 0x56CAF0
// 0x56CAF0 artCritterFidShouldRunData
static int* gArtCritterFidShoudRunData;
static std::unordered_map<std::string, std::shared_ptr<NamedCacheEntry>> gNamedArtCache;
constexpr int kNamedCacheMaxBytes = 32 * 1024 * 1024; // 32MB soft limit
constexpr size_t kMaxNamedPngPixels = 16 * 1024 * 1024;
static unsigned int gNamedArtCacheMruCounter = 0;
static int gNamedArtCacheCurrentBytes = 0;
@@ -355,6 +359,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)
{
@@ -444,6 +456,40 @@ int art_list_str(int fid, char* name)
return -1;
}
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;
}
// 0x419160
Art* artLock(int fid, CacheEntry** handlePtr)
{
@@ -1074,7 +1120,7 @@ static int artReadFrameData(unsigned char* data, File* stream, int count, int* p
// 0x419E1C
static int artReadHeader(Art* art, File* stream)
{
if (fileReadInt32(stream, &(art->field_0)) == -1) return -1;
if (fileReadInt32(stream, &(art->version)) == -1) return -1;
if (fileReadInt16(stream, &(art->framesPerSecond)) == -1) return -1;
if (fileReadInt16(stream, &(art->actionFrame)) == -1) return -1;
if (fileReadInt16(stream, &(art->frameCount)) == -1) return -1;
@@ -1091,13 +1137,137 @@ static int artReadHeader(Art* art, File* stream)
return 0;
}
// NOTE: Original function was slightly different, but never used. Basically
// it's a memory allocating variant of `artRead` (which reads data into given
// buffer). This function is useful to load custom `frm` files since `Art` now
// needs more memory then it's on-disk size (due to memory padding).
//
// 0x419EC0
Art* artLoad(const char* path)
static bool artPathHasExtension(const char* path, const char* extension)
{
size_t pathLength = strlen(path);
size_t extensionLength = strlen(extension);
if (pathLength < extensionLength) {
return false;
}
return compat_stricmp(path + pathLength - extensionLength, extension) == 0;
}
static bool artReadFile(const char* path, std::vector<unsigned char>& data)
{
int size = 0;
if (dbGetFileSize(path, &size) != 0 || size <= 0) {
return false;
}
data.resize(size);
return dbGetFileContents(path, data.data()) == 0;
}
static bool artUnpackIndexedPngPixels(const std::vector<unsigned char>& indexedData, unsigned width, unsigned height, unsigned bitdepth, unsigned char* output)
{
size_t pixelCount = static_cast<size_t>(width) * static_cast<size_t>(height);
if (bitdepth == 8) {
if (indexedData.size() < pixelCount) {
return false;
}
memcpy(output, indexedData.data(), pixelCount);
return true;
}
unsigned char mask = static_cast<unsigned char>((1 << bitdepth) - 1);
size_t neededBytes = (pixelCount * bitdepth + 7) / 8;
if (indexedData.size() < neededBytes) {
return false;
}
for (size_t index = 0; index < pixelCount; index++) {
size_t bitOffset = index * bitdepth;
unsigned char byte = indexedData[bitOffset / 8];
unsigned shift = 8 - bitdepth - static_cast<unsigned>(bitOffset % 8);
output[index] = (byte >> shift) & mask;
}
return true;
}
static Art* artLoadIndexedPng(const char* path)
{
std::vector<unsigned char> encoded;
if (!artReadFile(path, encoded)) {
return nullptr;
}
lodepng::State state;
state.decoder.color_convert = 0;
std::vector<unsigned char> indexedData;
unsigned width = 0;
unsigned height = 0;
unsigned error = lodepng::decode(indexedData, width, height, state, encoded);
if (error != 0) {
debugPrint("ART: failed to decode indexed PNG %s: %s\n", path, lodepng_error_text(error));
return nullptr;
}
if (state.info_png.color.colortype != LCT_PALETTE) {
debugPrint("ART: PNG is not palette-indexed: %s\n", path);
return nullptr;
}
if (lodepng_has_palette_alpha(&state.info_png.color)) {
debugPrint("ART: indexed PNG transparency is unsupported, reserve palette index 0 instead: %s\n", path);
return nullptr;
}
size_t pixelCount = static_cast<size_t>(width) * static_cast<size_t>(height);
if (width == 0 || height == 0 || width > SHRT_MAX || height > SHRT_MAX) {
debugPrint("ART: invalid indexed PNG dimensions for %s: %ux%u\n", path, width, height);
return nullptr;
}
if (pixelCount > kMaxNamedPngPixels || pixelCount > INT_MAX) {
debugPrint("ART: indexed PNG is too large: %s\n", path);
return nullptr;
}
Art header = {};
header.version = 4;
header.framesPerSecond = 10;
header.actionFrame = 0;
header.frameCount = 1;
header.dataSize = sizeof(ArtFrame) + static_cast<int>(pixelCount);
int currentPadding = paddingForSize(sizeof(Art));
for (int rotation = 0; rotation < ROTATION_COUNT; rotation++) {
header.dataOffsets[rotation] = 0;
header.padding[rotation] = currentPadding;
}
int dataSize = artGetDataSize(&header);
unsigned char* data = reinterpret_cast<unsigned char*>(internal_malloc(dataSize));
if (data == nullptr) {
return nullptr;
}
memset(data, 0, dataSize);
Art* art = reinterpret_cast<Art*>(data);
*art = header;
ArtFrame* frame = reinterpret_cast<ArtFrame*>(data + sizeof(Art) + art->padding[0]);
frame->width = static_cast<short>(width);
frame->height = static_cast<short>(height);
frame->size = static_cast<int>(pixelCount);
frame->x = 0;
frame->y = 0;
if (!artUnpackIndexedPngPixels(indexedData, width, height, state.info_png.color.bitdepth, reinterpret_cast<unsigned char*>(frame) + sizeof(ArtFrame))) {
debugPrint("ART: failed to read indexed PNG pixels: %s\n", path);
internal_free(data);
return nullptr;
}
return art;
}
static Art* artLoadFrm(const char* path)
{
File* stream = fileOpen(path, "rb");
if (stream == nullptr) {
@@ -1125,6 +1295,30 @@ Art* artLoad(const char* path)
return reinterpret_cast<Art*>(data);
}
// NOTE: Original function was slightly different, but never used. Basically
// it's a memory allocating variant of `artRead` (which reads data into given
// buffer). This function is useful to load custom `frm` files since `Art` now
// needs more memory then it's on-disk size (due to memory padding).
//
// 0x419EC0
Art* artLoad(const char* path)
{
if (path == nullptr) {
return nullptr;
}
if (artPathHasExtension(path, ".png")) {
return artLoadIndexedPng(path);
}
Art* art = artLoadFrm(path);
if (art != nullptr) {
return art;
}
return nullptr;
}
static Art* artLoadLocalized(const char* path)
{
const char* localizedPath;
@@ -1197,7 +1391,7 @@ int artWriteFrameData(unsigned char* data, File* stream, int count)
// 0x41A138
int artWriteHeader(Art* art, File* stream)
{
if (fileWriteInt32(stream, art->field_0) == -1) return -1;
if (fileWriteInt32(stream, art->version) == -1) return -1;
if (fileWriteInt16(stream, art->framesPerSecond) == -1) return -1;
if (fileWriteInt16(stream, art->actionFrame) == -1) return -1;
if (fileWriteInt16(stream, art->frameCount) == -1) return -1;
+3 -1
View File
@@ -69,7 +69,7 @@ typedef enum Background {
} Background;
typedef struct Art {
int field_0;
int version;
short framesPerSecond;
short actionFrame;
short frameCount;
@@ -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);
+214 -14
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,28 +19,41 @@ 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
// 0x5108BC queryCompressedFunc
static AudioQueryCompressedFunc* queryCompressedFunc = defaultCompressionFunc;
// 0x56CB00
// 0x56CB00 numAudio
static int gAudioListLength;
// 0x56CB04
// 0x56CB04 audio
static Audio* gAudioList;
// 0x41A2B0
@@ -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);
+17 -6
View File
@@ -30,13 +30,13 @@ typedef struct AudioFile {
static bool defaultCompressionFunc(char* filePath);
static int audioFileSoundDecoderReadHandler(void* data, void* buffer, unsigned int size);
// 0x5108C0
// 0x5108C0 queryCompressedFunc_2
static AudioFileQueryCompressedFunc* queryCompressedFunc = defaultCompressionFunc;
// 0x56CB10
// 0x56CB10 audiof
static AudioFile* gAudioFileList;
// 0x56CB14
// 0x56CB14 numAudiof
static int gAudioFileListLength;
// 0x41A850
@@ -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);
+23 -23
View File
@@ -62,14 +62,14 @@ typedef struct AutomapEntry {
unsigned char* data;
} AutomapEntry;
// 0x41ADE0
// 0x41ADE0 defam
static const int _defam[AUTOMAP_MAP_COUNT][ELEVATION_COUNT] = {
{ -1, -1, -1 },
{ -1, -1, -1 },
{ -1, -1, -1 },
};
// 0x41B560
// 0x41B560 displayMapList
static int _displayMapList[AUTOMAP_MAP_COUNT] = {
-1,
-1,
@@ -242,17 +242,17 @@ static const int gAutomapFrmIds[AUTOMAP_FRM_COUNT] = {
173, // autodwn.frm - switch down
};
// 0x5108C4
// 0x5108C4 autoflags
static int gAutomapFlags = 0;
// 0x56CB18
// 0x56CB18 amdbhead
static AutomapHeader gAutomapHeader;
// 0x56D2A0
// 0x56D2A0 amdbsubhead
static AutomapEntry gAutomapEntry;
// automap_init
// 0x41B7F4
// 0x41B7F4 automap_init
int automapInit()
{
gAutomapFlags = 0;
@@ -260,7 +260,7 @@ int automapInit()
return 0;
}
// 0x41B808
// 0x41B808 automap_reset
int automapReset()
{
gAutomapFlags = 0;
@@ -268,7 +268,7 @@ int automapReset()
return 0;
}
// 0x41B81C
// 0x41B81C automap_exit
void automapExit()
{
char path[COMPAT_MAX_PATH];
@@ -276,25 +276,25 @@ void automapExit()
compat_remove(path);
}
// 0x41B87C
// 0x41B87C automap_load
int automapLoad(File* stream)
{
return fileReadInt32(stream, &gAutomapFlags);
}
// 0x41B898
// 0x41B898 automap_save
int automapSave(File* stream)
{
return fileWriteInt32(stream, gAutomapFlags);
}
// 0x41B8B4
// 0x41B8B4 automapDisplayMap
int _automapDisplayMap(int map)
{
return _displayMapList[map];
}
// 0x41B8BC
// 0x41B8BC automap
void automapShow(bool isInGame, bool isUsingScanner)
{
ScopedGameMode gm(GameMode::kAutomap);
@@ -499,7 +499,7 @@ void automapShow(bool isInGame, bool isUsingScanner)
// Renders automap in Map window.
//
// 0x41BD1C
// 0x41BD1C draw_top_down_map
static void automapRenderInMapWindow(int window, int elevation, unsigned char* backgroundData, int flags)
{
int color;
@@ -620,7 +620,7 @@ static void automapRenderInMapWindow(int window, int elevation, unsigned char* b
// Renders automap in Pipboy window.
//
// 0x41C004
// 0x41C004 draw_top_down_map_pipboy
int automapRenderInPipboyWindow(int window, int map, int elevation)
{
unsigned char* windowBuffer = windowGetBuffer(window) + 640 * AUTOMAP_PIPBOY_VIEW_Y + AUTOMAP_PIPBOY_VIEW_X;
@@ -682,7 +682,7 @@ int automapRenderInPipboyWindow(int window, int map, int elevation)
}
// automap_pip_save
// 0x41C0F0
// 0x41C0F0 automap_pip_save
int automapSaveCurrent()
{
int map = mapGetCurrentMap();
@@ -896,7 +896,7 @@ int automapSaveCurrent()
// Saves automap entry into stream.
//
// 0x41C844
// 0x41C844 WriteAM_Entry
static int automapSaveEntry(File* stream)
{
unsigned char* buffer;
@@ -928,7 +928,7 @@ err:
return -1;
}
// 0x41C8CC
// 0x41C8CC AM_ReadEntry
static int automapLoadEntry(int map, int elevation)
{
gAutomapEntry.compressedData = nullptr;
@@ -1015,7 +1015,7 @@ out:
// Saves automap.db header.
//
// 0x41CAD8
// 0x41CAD8 WriteAM_Header
static int automapSaveHeader(File* stream)
{
fileRewind(stream);
@@ -1045,7 +1045,7 @@ err:
// Loads automap.db header.
//
// 0x41CB50
// 0x41CB50 AM_ReadMainHeader
static int automapLoadHeader(File* stream)
{
@@ -1068,7 +1068,7 @@ static int automapLoadHeader(File* stream)
return 0;
}
// 0x41CBA4
// 0x41CBA4 decode_map_data
static void _decode_map_data(int elevation)
{
memset(gAutomapEntry.data, 0, SQUARE_GRID_SIZE);
@@ -1101,7 +1101,7 @@ static void _decode_map_data(int elevation)
}
}
// 0x41CC98
// 0x41CC98 am_pip_init
static int automapCreate()
{
gAutomapHeader.version = 1;
@@ -1128,7 +1128,7 @@ static int automapCreate()
// Copy data from stream1 to stream2.
//
// 0x41CD6C
// 0x41CD6C copy_file_data
static int _copy_file_data(File* stream1, File* stream2, int length)
{
void* buffer = internal_malloc(0xFFFF);
@@ -1160,7 +1160,7 @@ static int _copy_file_data(File* stream1, File* stream2, int length)
return 0;
}
// 0x41CE74
// 0x41CE74 ReadAMList
int automapGetHeader(AutomapHeader** automapHeaderPtr)
{
char path[COMPAT_MAX_PATH];
+3 -3
View File
@@ -5,13 +5,13 @@
#endif
#ifdef _WIN32
// 0x530010
// 0x530010 autorun_mutex
static HANDLE gInterplayGenericAutorunMutex;
#endif
namespace fallout {
// 0x4139C0
// 0x4139C0 autorun_mutex_create
bool autorunMutexCreate()
{
#ifdef _WIN32
@@ -25,7 +25,7 @@ bool autorunMutexCreate()
return true;
}
// 0x413A00
// 0x413A00 autorun_mutex_destroy
void autorunMutexClose()
{
#ifdef _WIN32

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