Compare commits

...
Author SHA1 Message Date
Mike Klaas 80bdd7fdb0 remove isIniatlized() 2026-05-29 18:51:53 -07:00
Mike Klaas 4eb9e869df outdent one more place 2026-05-27 08:34:15 -07:00
Mike Klaas 49a24f6925 Use ScopeConfig for function-scoped Config uses
Eliminates the free, which sometimes required gotos.

Left configRead in a couple places that return different values if the file is missing (0) vs hard error (-1). With more careful analysis it's possible we could fold the read into the ScopeConfig constructor in these cases too.

Also, doing this makes it feel like a ScopeConfig::get(section, value, &out) function would feel nice.
2026-05-27 08:19:52 -07:00
Mike Klaas 9423a2c289 Sfall: save/restore last savegame page/slot (#464)
* Sfall: save/restore last savegame page/slot

* ScopedConfig for RAII convenience
2026-05-26 23:38:02 -07:00
Mike Klaas dc4753cde9 Improve music/sound system to allow .ogg/.wav map music + 44.1 kHz output (#465)
* Change output sound buffer to 44.1 kHz

This allow CE to play 44.1 kHz input files without loss of precision.  In particular, it plays HQ music that ships with RPU.

There is a very similar fix in Sfall

* use generic sound loading path to allow .ogg/.wav music files

* rm a few dangling references

* PR feedback

* rm references to audio file

* rm audio_file.cc/h

* restore default audio io

* PR fixes
2026-05-26 23:12:42 -07:00
Mike Klaas cd3adf1621 Implement set_unique_id (#466)
* Implement `set_unique_id`

* save/load watermark, including compat with previous saves (would be `0`)
* metarule to set and reset unique_id
* inventory stacking rules
2026-05-26 22:38:40 -07:00
Mike Klaas d42c8456dd Custom iface tags + metarules (#463)
* custom iface tag

* add_iface_tag, set_iface_tag_text

Used by FO2Tweaks and other mods
2026-05-26 22:30:42 -07:00
Vlad K c574812f1d EDG: fix getIsoWindowSize returning (1, 1) in release builds (#471) 2026-05-26 18:46:08 +02:00
Mike Klaas 8afe4b3061 Use in-game gamma when generating screenshots (#469)
This fixes the slight darkening effect that currently exists
2026-05-26 09:23:14 -07:00
Vlad Kandgithub-actions[bot] c423d475c5 EDG support (#450)
* HRP's edg file support (WIP)

* Update docs and fix bug

* chore: auto-format with clang-format

* EDG: proper scroll blocking based on pre-calculated rects, exactly as in HRP

* chore: auto-format with clang-format

* EDG: fix scrolling past pixel-space bounds with sub-hex map edge mod

* EDG: review fixes

* EDG: proper edge clipping matching HRP logic

* chore: auto-format with clang-format

* EDG: code review fixes and refactoring

* chore: auto-format with clang-format

* EDG: fix clamping

* EDG: show arrow cursor and block events when over clipped area

* chore: auto-format with clang-format

* EDG: option to disable

* EDG: deobfuscate code

* chore: auto-format with clang-format

* EDG: get rid of rect2 and simplify algebra

* EDG: update docs, clarify that squareRect is not implemented

* EDG: square mask support

* chore: auto-format with clang-format

* Code review fixes

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-05-26 07:25:40 +00:00
Mike Klaas 06d7f09728 Implement HOOK_USESKILL, HOOK_USESKILLON (#456)
* Implement HOOK_USESKILL, HOOK_USESKILLON

* make comment more accurate
2026-05-19 22:40:33 -07:00
Mike Klaas 81164465cf Load f2_res.dat after master.dat (#462)
This bring CE much closer to sfall's load order, and fixes RPU install.

 master_patches > critter_patches > mods_order.txt > PatchFileXX > patchXXX.dat > sfall.dat > critter_dat > f2_res_patches > f2_res_dat > master_dat

Also fixed typo in `showMessageBox`, and added a debugPrint in that function, so errors are visible in the log (LLMs otherwise find it hard to figure out what happened)
2026-05-19 22:20:13 -07:00
IronKing24 0a7834f81b Merge pull request #459 from fallout2-ce/mingw
Add Mingw build system
2026-05-17 01:15:07 +03:00
IronKing24 f76e310f10 Fix mapper compilation 2026-05-16 19:45:00 +03:00
IronKing24 303e7573ca Merge branch 'main' into mingw 2026-05-16 18:30:39 +03:00
Mike Klaas 16ae69fa5a HOOK_ENCOUNTER (#453)
* HOOK_ENCOUNTER

Pretty much 1:1 with Sfall except adding extra validation that the mapId returns belongs to an area
2026-05-13 15:09:43 -07:00
Mike Klaas 6d2d9d6ef3 Add "trade/loot" context menu for party members. (#449)
* Add "trade/loot" context menu for party members.

This was an idea I saw listed on NMA. Veterans of classic Fallout know you can trade with your companions much more easily using the steal skill, But this would be difficult for most new players to discover. This replaces the dialogue option in the long press (which is redundant since it is the default action) with the hand, which is normally used to loot from containers and corpses.

This does introduce a slight inconsistency in the default action, not being present when you long press. how almost everyone will be single-clicking to talk to NPCs.

Also, "you plant..." and "you steal..." messages when trading with companions this way. Put this behind the same flag in case someone wants the original experience.

* Full context menu

* container -> target

* PR feedback

* rebase on main

* rename function
2026-05-12 23:03:43 -07:00
IronKing24 471ebbf417 Merge branch 'main' into mingw 2026-05-11 10:37:26 +03:00
IronKing24 bc7ab1ecb2 Merge branch 'main' into mingw 2026-05-03 11:04:19 +03:00
IronKing24 a0a6091bd0 add package dependency fix 2026-05-02 20:56:29 +03:00
IronKing24 f2b2127709 fix processor name typo 2026-05-02 20:53:19 +03:00
IronKing24 ec6647968b Merge branch 'main' into mingw 2026-05-02 19:15:08 +03:00
IronKing24 f97ceff645 add x86 build and move presets to the public 2026-05-02 19:07:27 +03:00
IronKing24 4247d7ac3f - Set correct checks for msvc or win
- change SDL2 cmake for mingw to link correctly
2026-05-02 15:56:41 +03:00
IronKing24 bd3358709f add toolchain file 2026-05-01 20:28:30 +03:00
65 changed files with 2927 additions and 1100 deletions
+59 -4
View File
@@ -47,8 +47,6 @@ set(FALLOUT_ENGINE_SOURCES
"src/animation.h"
"src/art.cc"
"src/art.h"
"src/audio_file.cc"
"src/audio_file.h"
"src/audio.cc"
"src/audio.h"
"src/automap.cc"
@@ -161,6 +159,8 @@ set(FALLOUT_ENGINE_SOURCES
"src/map_defs.h"
"src/map.cc"
"src/map.h"
"src/map_edge.cc"
"src/map_edge.h"
"src/memory_defs.h"
"src/memory_manager.cc"
"src/memory_manager.h"
@@ -372,14 +372,33 @@ if(WIN32)
)
endif()
if(MINGW)
target_compile_definitions(${EXECUTABLE_NAME} PUBLIC
_USE_MATH_DEFINES
)
endif()
if(WIN32)
target_link_libraries(${EXECUTABLE_NAME}
winmm
)
endif()
if(MSVC)
target_link_libraries(${EXECUTABLE_NAME}
debug libcpmtd
optimized libcpmt
)
endif()
if(MINGW)
target_link_options(${EXECUTABLE_NAME} PRIVATE
-static
-static-libgcc
-static-libstdc++
)
endif()
if(WIN32)
target_sources(${EXECUTABLE_NAME} PUBLIC
"os/windows/fallout2-ce.ico"
@@ -496,6 +515,7 @@ target_link_libraries(${EXECUTABLE_NAME} ${ZLIB_LIBRARIES})
target_include_directories(${EXECUTABLE_NAME} PRIVATE ${ZLIB_INCLUDE_DIRS})
target_link_libraries(${EXECUTABLE_NAME} ${SDL2_LIBRARIES})
target_link_libraries(${EXECUTABLE_NAME} ${SDL2_MAIN_LIBRARIES})
target_include_directories(${EXECUTABLE_NAME} PRIVATE ${SDL2_INCLUDE_DIRS})
if((NOT ANDROID) AND (NOT IOS) AND (NOT CMAKE_SYSTEM_NAME MATCHES "Emscripten"))
@@ -522,6 +542,13 @@ if((NOT ANDROID) AND (NOT IOS) AND (NOT CMAKE_SYSTEM_NAME MATCHES "Emscripten"))
if(WIN32)
target_link_libraries(ce-dat-tool winmm)
endif()
if(MINGW)
target_link_options(ce-dat-tool PRIVATE
-static
-static-libgcc
-static-libstdc++
)
endif()
target_include_directories(ce-dat-tool PRIVATE "src")
target_include_directories(ce-dat-tool PRIVATE ${ZLIB_INCLUDE_DIRS})
target_include_directories(ce-dat-tool PRIVATE ${SDL2_INCLUDE_DIRS})
@@ -545,6 +572,14 @@ if((NOT ANDROID) AND (NOT IOS) AND (NOT CMAKE_SYSTEM_NAME MATCHES "Emscripten"))
target_include_directories(ce-frm2png PRIVATE ${ZLIB_INCLUDE_DIRS})
target_link_libraries(ce-frm2png ${SDL2_LIBRARIES})
target_link_libraries(ce-frm2png ${ZLIB_LIBRARIES})
if(MINGW)
target_link_options(ce-frm2png PRIVATE
-static
-static-libgcc
-static-libstdc++
)
endif()
if(APPLE)
file(GLOB_RECURSE CE_DAT_INPUTS CONFIGURE_DEPENDS "${CMAKE_SOURCE_DIR}/files/ce.dat/*")
@@ -605,11 +640,30 @@ if((NOT ANDROID) AND (NOT IOS) AND (NOT CMAKE_SYSTEM_NAME MATCHES "Emscripten"))
WIN32_LEAN_AND_MEAN
_STATIC_CPPLIB
)
if(MINGW)
target_compile_definitions(mapper-ce PUBLIC
_USE_MATH_DEFINES
)
target_link_options(mapper-ce PRIVATE
-static
-static-libgcc
-static-libstdc++
)
endif()
target_link_libraries(mapper-ce
winmm
debug libcpmtd
optimized libcpmt
)
if(MSVC)
target_link_libraries(mapper-ce
debug libcpmtd
optimized libcpmt
)
endif()
target_sources(mapper-ce PUBLIC
"os/windows/fallout2-ce.rc"
)
@@ -633,6 +687,7 @@ if((NOT ANDROID) AND (NOT IOS) AND (NOT CMAKE_SYSTEM_NAME MATCHES "Emscripten"))
fpattern_windows::fpattern_windows
${ZLIB_LIBRARIES}
${SDL2_LIBRARIES}
${SDL2_MAIN_LIBRARIES}
)
endif()
+91
View File
@@ -139,6 +139,77 @@
"cacheVariables": {
"CMAKE_SYSTEM_NAME": "iOS"
}
},
{
"name": "Mingw-base",
"hidden": true,
"description": "Base preset for all MinGW builds.",
"inherits":[
"base"
],
"generator": "Ninja",
"toolchainFile": "${sourceDir}/cmake/toolchain/mingw.cmake",
"cacheVariables": {
"CMAKE_SYSTEM_NAME": "Windows"
}
},
{
"name": "Mingw-x86-base",
"hidden": true,
"description": "Base preset for all MinGW x86 builds.",
"inherits": [
"Mingw-base"
],
"cacheVariables": {
"CMAKE_C_FLAGS": "-m32",
"CMAKE_CXX_FLAGS": "-m32",
"CMAKE_SYSTEM_PROCESSOR": "i686"
}
},
{
"name": "Mingw-x86-debug",
"displayName": "MinGW x86 Debug",
"description": "Cross-compile a Debug Windows x86 build with MinGW-w64 and Ninja.",
"inherits": [
"Mingw-x86-base"
],
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug"
}
},
{
"name": "Mingw-x86-release",
"displayName": "MinGW x86 Release",
"description": "Cross-compile a RelWithDebInfo Windows x86 build with MinGW-w64 and Ninja.",
"inherits": [
"Mingw-x86-debug"
],
"cacheVariables": {
"CMAKE_BUILD_TYPE": "RelWithDebInfo"
}
},
{
"name": "Mingw-x64-debug",
"displayName": "MinGW x64 Debug",
"description": "Cross-compile a Debug Windows x64 build with MinGW-w64 and Ninja.",
"inherits": [
"Mingw-base"
],
"cacheVariables": {
"CMAKE_SYSTEM_PROCESSOR": "x86_64",
"CMAKE_BUILD_TYPE": "Debug"
}
},
{
"name": "Mingw-x64-release",
"displayName": "MinGW x64 Release",
"description": "Cross-compile a RelWithDebInfo Windows x64 build with MinGW-w64 and Ninja.",
"inherits": [
"Mingw-x64-debug"
],
"cacheVariables": {
"CMAKE_BUILD_TYPE": "RelWithDebInfo"
}
}
],
"buildPresets": [
@@ -197,6 +268,26 @@
"name": "ios-release",
"configurePreset": "ios",
"configuration": "RelWithDebInfo"
},
{
"name": "Mingw-x86-Debug",
"displayName": "MinGW x86 Debug",
"configurePreset": "Mingw-x86-debug"
},
{
"name": "Mingw-x86-Release",
"displayName": "MinGW x86 Release",
"configurePreset": "Mingw-x86-release"
},
{
"name": "Mingw-x64-Debug",
"displayName": "MinGW x64 Debug",
"configurePreset": "Mingw-x64-debug"
},
{
"name": "Mingw-x64-Release",
"displayName": "MinGW x64 Release",
"configurePreset": "Mingw-x64-release"
}
]
}
+3
View File
@@ -143,6 +143,7 @@ In time this stuff will receive in-game interface, right now you have to do it m
* Expanded 2-column inventory
* Expanded 4-row barter screen
* Expanded AP bar
* Party members can loot and barter in place of PC
* Increased pathfinding nodes 5x for more accurate pathfinding
* Ctrl-click to quickly move items when bartering, looting, or stealing
* _a_ to select "all" when selecting item quantity
@@ -150,6 +151,8 @@ In time this stuff will receive in-game interface, right now you have to do it m
* When bartering, caps default to the right amount to balance the trade (if possible)
* Music continues playing between maps
* Auto open doors
* 44.1 kHz stereo sound/music supported, in .ogg and .wav format as well as legacy .acm
* Last used save slot is remembered
## Contributing
+17 -5
View File
@@ -4,6 +4,18 @@ This document tracks Fallout 2 CE compatibility with sfall. This is for modders
For now, this covers opcodes/metarules, and hooks. In the future, it will include other ways of modifying the engine (like ini files), and other Sfall-specific behaviour.
## HRP EDG Scroll-Blocker Support
CE supports the `.edg` file format from the HRP (High Resolution Patch), which defines per-map scroll boundaries and square-level render clipping.
**How it works in CE:**
- On map load, `maps/<mapname>.edg` is read if present. Missing file = silent fallback to the scroll-blocker object system.
- The `.edg` file defines per-elevation rectangle boundary zones. Multiple chained zones per elevation are supported.
- When loaded, these zones are used for both scroll blocking and visible area clipping (black bars), replacing both vanilla scroll blocking and CE hi-res stencil system.
- v2 EDG files also contain a `SquareRect` that defines "Angled edges", or square-grid stencil. This is also supported.
## Settings (ddraw.ini → fallout2.cfg / game.cfg)
Settings previously read from `ddraw.ini` have been moved into standard CE config files.
@@ -55,7 +67,7 @@ See [`https://sfall-team.github.io/sfall/`](https://sfall-team.github.io/sfall/)
| Tiles and paths | get_tile_fid<br>tile_under_cursor<br>tile_light<br>tile_get_objs<br>tile_refresh_display<br>obj_blocking_tile<br>tile_by_position<br>get_tile_ground_fid<br>get_tile_roof_fid<br>obj_blocking_line<br>path_find_to<br>objects_in_radius | âś… | - |
| Utility | sprintf<br>typeof<br>atoi<br>atof | âś… | - |
| Utility / Strings | string_split<br>substr<br>strlen<br>charcode<br>get_string_pointer<br>string_find<br>string_find_from<br>string_format<br>string_format_array<br>string_replace<br>string_to_case<br>string_compare | âś… | `get_string_pointer` is deprecated and intentionally omitted. |
| Interface / Tags | show_iface_tag<br>hide_iface_tag<br>is_iface_tag_active<br>set_iface_tag_text<br>add_iface_tag | âś… except set_iface_tag_text, add_iface_tag | CE only handles built-in interface tags here; custom tag creation/text is not supported yet. |
| Interface / Tags | show_iface_tag<br>hide_iface_tag<br>is_iface_tag_active<br>set_iface_tag_text<br>add_iface_tag | âś… | Legacy `BoxBarCount`, `BoxBarColors` ddraw.ini settings not supported. |
| 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 | âś… | - |
@@ -70,7 +82,7 @@ See [`https://sfall-team.github.io/sfall/`](https://sfall-team.github.io/sfall/)
| Interface / Cursor | get/set_cursor_mode | âś… | - |
| Locks | lock_is_jammed<br>unjam_lock<br>set_unjam_locks_time | not implemented | - |
| INI settings | get_ini_setting<br>get_ini_string<br>get_ini_section<br>get_ini_sections<br>get_ini_config<br>get_ini_config_db<br>set_ini_setting | âś… except get_ini_config, get_ini_config_db | `modified_ini` is intentionally omitted as deprecated. |
| Objects and scripts | set_self<br>set_dude_obj<br>real_dude_obj<br>remove_script<br>get/set_script<br>obj_is_carrying_obj<br>loot_obj<br>dialog_obj<br>obj_under_cursor<br>get/set_object_data<br>get/set_flags<br>set_unique_id<br>set_scr_name<br>obj_is_openable<br>get/set_proto_data<br>get_object_ai_data | implemented: set_self, get/set/remove_script, obj_is_carrying_obj, loot_obj, dialog_obj, obj_under_cursor, get_object_data, get_flags, set_flags, obj_is_openable, get_proto_data, set_proto_data | - |
| 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, set_unique_id, 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/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 | - |
@@ -97,7 +109,7 @@ See [`https://sfall-team.github.io/sfall/`](https://sfall-team.github.io/sfall/)
| AmmoCost | `HOOK_AMMOCOST` | âś… | Requires `check_weapon_ammo_cost=1` if you want pre-attack ammo validation to respect per-shot/per-round overrides. |
| KeyPress | `HOOK_KEYPRESS` | âś… | Third hook arg is currently `0`; CE doesn't use VK codes. |
| MouseClick | `HOOK_MOUSECLICK` | âś… | - |
| UseSkill | `HOOK_USESKILL` | đźš« | - |
| UseSkill | `HOOK_USESKILL` | âś… | - |
| Steal | `HOOK_STEAL` | âś… | - |
| WithinPerception | `HOOK_WITHINPERCEPTION` | âś… | - |
| InventoryMove | `HOOK_INVENTORYMOVE` | âś… | - |
@@ -113,13 +125,13 @@ See [`https://sfall-team.github.io/sfall/`](https://sfall-team.github.io/sfall/)
| UseAnimObj | `HOOK_USEANIMOBJ` | đźš« | Et tu; (maybe) |
| ExplosiveTimer | `HOOK_EXPLOSIVETIMER` | âś… | - |
| DescriptionObj | `HOOK_DESCRIPTIONOBJ` | đźš« | Et tu |
| UseSkillOn | `HOOK_USESKILLON` | đźš« | Et tu |
| UseSkillOn | `HOOK_USESKILLON` | âś… | - |
| OnExplosion | `HOOK_ONEXPLOSION` | đźš« | (maybe) |
| SubCombatDamage | `HOOK_SUBCOMBATDAMAGE` | đźš« | (maybe) |
| SetLighting | `HOOK_SETLIGHTING` | đźš« | Et tu; (maybe) |
| Sneak | `HOOK_SNEAK` | đźš« | - |
| TargetObject | `HOOK_TARGETOBJECT` | đźš« | (maybe) |
| Encounter | `HOOK_ENCOUNTER` | đźš« | Et tu |
| Encounter | `HOOK_ENCOUNTER` | âś… | - |
| AdjustPoison | `HOOK_ADJUSTPOISON` | đźš« | (maybe) |
| AdjustRads | `HOOK_ADJUSTRADS` | đźš« | (maybe) |
| RollCheck | `HOOK_ROLLCHECK` | đźš« | - |
+18
View File
@@ -0,0 +1,18 @@
# specify the cross compiler
set(CMAKE_C_COMPILER ${CMAKE_SYSTEM_PROCESSOR}-w64-mingw32-gcc)
set(CMAKE_CXX_COMPILER ${CMAKE_SYSTEM_PROCESSOR}-w64-mingw32-g++)
set(CMAKE_RC_COMPILER ${CMAKE_SYSTEM_PROCESSOR}-w64-mingw32-windres)
# where is the target environment
set(CMAKE_FIND_ROOT_PATH /usr/${CMAKE_SYSTEM_PROCESSOR}-w64-mingw32)
# search for programs in the build host directories
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
# for libraries and headers in the target directories
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
# CMake determines how to examine dependencies based on the *host* system, leading to
# a `file unknown error` unless the target platform is explicitly specified.
set(CMAKE_GET_RUNTIME_DEPENDENCIES_PLATFORM "windows+pe")
Binary file not shown.
+4
View File
@@ -110,6 +110,8 @@ iface_bar_mode=0
iface_bar_side_art=2
iface_bar_sides_ori=0
iface_bar_width=800
;Whether to load EDG files (HRP format) when loading maps. If loaded, they override default edge clipping and scroll blocking behavior.
edg_support=1
ignore_map_edges=0
;Set to 1 to display numbered dialogue options
numbers_in_dialogue=0
@@ -126,5 +128,7 @@ inventory_columns=2
auto_open_doors=0
; Overrides distance at which walk animation is used when using an object.
use_walk_distance=5
; Allow opening party member inventory from the long-press world menu.
party_trade_from_menu=1
; Allow switching to companions' inventories in loot screens (and barter, in the future)
party_loot_and_barter=1
+100
View File
@@ -1,5 +1,63 @@
#include "test_utils.h"
#include "sfall.h"
#include "dik.h"
variable manual_extra_tag := 0;
variable manual_tag5_color := 2;
procedure print_custom_tag_state(variable tag, variable label) begin
display_msg(label + ": " + is_iface_tag_active(tag));
end
procedure toggle_custom_tag(variable tag, variable label) begin
if (is_iface_tag_active(tag)) then begin
hide_iface_tag(tag);
display_msg(label + " hidden");
end else begin
show_iface_tag(tag);
display_msg(label + " shown");
end
call print_custom_tag_state(tag, label);
end
procedure apply_tag5_color begin
set_iface_tag_text(5, "TAG FIVE", manual_tag5_color);
display_msg(string_format1("custom tag 5 color -> %d", manual_tag5_color));
end
procedure keypress_handler begin
variable pressed := get_sfall_arg_at(0);
variable key := get_sfall_arg_at(1);
if (not pressed) then return;
if (key == DIK_B) then begin
call toggle_custom_tag(5, "custom tag 5");
end else if (key == DIK_C) then begin
manual_tag5_color := manual_tag5_color + 1;
if (manual_tag5_color > 7) then begin
manual_tag5_color := 0;
end
call apply_tag5_color;
end else if (key == DIK_N) then begin
call toggle_custom_tag(6, "custom tag 6");
end else if (key == DIK_M) then begin
if (manual_extra_tag == 0) then begin
display_msg("manual extra tag is not initialized");
return;
end
call toggle_custom_tag(manual_extra_tag, "manual extra tag");
end else if (key == DIK_V) then begin
call print_custom_tag_state(5, "custom tag 5");
call print_custom_tag_state(6, "custom tag 6");
if (manual_extra_tag != 0) then begin
call print_custom_tag_state(manual_extra_tag, "manual extra tag");
end
end
end
procedure start begin
display_msg("Testing iface_tag functions...");
@@ -29,5 +87,47 @@ procedure start begin
// RADIATED
call assertFalse("hide_iface_tag(RADIATED)", is_iface_tag_active(2));
// Default custom box from BoxBarCount.
call apply_tag5_color;
call assertFalse("custom tag 5 starts inactive", is_iface_tag_active(5));
show_iface_tag(5);
call assertTrue("show_iface_tag(custom 5)", is_iface_tag_active(5));
show_iface_tag(5);
call assertTrue("show_iface_tag(custom 5 again)", is_iface_tag_active(5));
hide_iface_tag(5);
call assertFalse("hide_iface_tag(custom 5)", is_iface_tag_active(5));
hide_iface_tag(5);
call assertFalse("hide_iface_tag(custom 5 again)", is_iface_tag_active(5));
set_iface_tag_text(6, "TAG SIX", 6);
call assertFalse("custom tag 6 starts inactive", is_iface_tag_active(6));
show_iface_tag(6);
call assertTrue("show_iface_tag(custom 6)", is_iface_tag_active(6));
show_iface_tag(5);
call assertTrue("custom tag 5 still active", is_iface_tag_active(5));
call assertTrue("custom tag 6 still active", is_iface_tag_active(6));
hide_iface_tag(5);
call assertFalse("hide_iface_tag(custom 5 with 6 active)", is_iface_tag_active(5));
call assertTrue("custom tag 6 remains active", is_iface_tag_active(6));
hide_iface_tag(6);
call assertFalse("hide_iface_tag(custom 6)", is_iface_tag_active(6));
// Dynamically added custom box.
variable extra_tag := add_iface_tag;
call assertTrue("add_iface_tag lower bound", extra_tag >= 10);
call assertTrue("add_iface_tag upper bound", extra_tag <= 126);
manual_extra_tag := extra_tag;
set_iface_tag_text(extra_tag, "EXTRA TAG", 3);
call assertFalse("added custom tag starts inactive", is_iface_tag_active(extra_tag));
show_iface_tag(extra_tag);
call assertTrue("show_iface_tag(added custom)", is_iface_tag_active(extra_tag));
hide_iface_tag(extra_tag);
call assertFalse("hide_iface_tag(added custom)", is_iface_tag_active(extra_tag));
variable next_tag := add_iface_tag;
call assertEquals("add_iface_tag increments", next_tag, extra_tag + 1);
call report_test_results("iface_tag");
display_msg("iface_tag manual test ready: B toggle tag 5, C cycle tag 5 color, N toggle tag 6, M toggle extra tag, V print states");
register_hook_proc(HOOK_KEYPRESS, keypress_handler);
end
+136
View File
@@ -0,0 +1,136 @@
#include "define_lite.h"
#include "sfall.h"
#include "dik.h"
#include "define_extra.h"
#include "lib.inven.h"
#include "test_utils.h"
#define TEST_ITEM_PID 40
#define PARTY_UNIQUE_ID_LOWER_BOUND 18000
#define UNIQUE_ID_LOWER_BOUND 0x10000000
procedure find_random_inventory_item_for_unique_id begin
variable items;
variable length;
variable start;
variable scan;
variable index;
variable item;
variable item_id;
items := inven_as_array(dude_obj);
length := len_array(items);
if (length <= 0) then return 0;
start := random(0, length - 1);
scan := 0;
while (scan < length) do begin
index := start + scan;
if (index >= length) then index -= length;
item := items[index];
item_id := get_object_data(item, OBJ_DATA_ID);
if (item_id < PARTY_UNIQUE_ID_LOWER_BOUND) then begin
return item;
end
scan += 1;
end
return items[start];
end
procedure assign_unique_id_to_random_inventory_item begin
variable item;
variable old_id;
variable new_id;
item := find_random_inventory_item_for_unique_id;
if (item == 0) then begin
display_msg("unique_id manual test: inventory is empty");
return;
end
old_id := get_object_data(item, OBJ_DATA_ID);
new_id := set_unique_id(item);
if (new_id == old_id) then begin
display_msg(string_format3("unique_id manual U: %s pid=%d id unchanged=%d", obj_name(item), obj_pid(item), new_id));
end else begin
display_msg(string_format4("unique_id manual U: %s pid=%d %d->%d", obj_name(item), obj_pid(item), old_id, new_id));
end
end
procedure keypress_handler begin
variable pressed := get_sfall_arg_at(0);
variable key := get_sfall_arg_at(1);
if (not pressed) then return;
if (key == DIK_U) then begin
call assign_unique_id_to_random_inventory_item();
end
end
procedure start begin
variable item_a;
variable item_b;
variable item_c;
variable baseline_total;
variable unique_id_a;
variable unique_id_a_again;
variable unique_id_c;
variable engine_id_a;
variable regular_count;
variable total;
if (not game_loaded) then return;
display_msg("Testing set_unique_id...");
call assertEquals("metarule exists", metarule_exist("set_unique_id"), 1);
baseline_total := obj_is_carrying_obj_pid(dude_obj, TEST_ITEM_PID);
item_a := create_object_sid(TEST_ITEM_PID, 0, 0, -1);
item_b := create_object_sid(TEST_ITEM_PID, 0, 0, -1);
item_c := create_object_sid(TEST_ITEM_PID, 0, 0, -1);
unique_id_a := set_unique_id(item_a);
call assertTrue("unique id lower bound", unique_id_a >= UNIQUE_ID_LOWER_BOUND);
call assertEquals("unique id stored on object", get_object_data(item_a, OBJ_DATA_ID), unique_id_a);
unique_id_a_again := set_unique_id(item_a);
call assertEquals("existing unique id reused", unique_id_a_again, unique_id_a);
add_obj_to_inven(dude_obj, item_a);
add_obj_to_inven(dude_obj, item_b);
call assertEquals("unique item stays separate", obj_is_carrying_obj(dude_obj, item_a), 1);
regular_count := obj_is_carrying_obj(dude_obj, item_b);
call assertTrue("regular item still exists after unique item add", regular_count >= 1);
total := obj_is_carrying_obj_pid(dude_obj, TEST_ITEM_PID);
call assertEquals("unique and regular items add two total items", total, baseline_total + 2);
unique_id_c := set_unique_id(item_c);
call assertNotEquals("second unique item gets new id", unique_id_c, unique_id_a);
add_obj_to_inven(dude_obj, item_c);
call assertEquals("second unique item stays separate", obj_is_carrying_obj(dude_obj, item_c), 1);
total := obj_is_carrying_obj_pid(dude_obj, TEST_ITEM_PID);
call assertEquals("pid total counts all three added items", total, baseline_total + 3);
engine_id_a := unset_unique_id(item_a);
call assertTrue("unset returns engine id", engine_id_a < UNIQUE_ID_LOWER_BOUND);
call assertNotEquals("unset replaces unique id", engine_id_a, unique_id_a);
call assertEquals("engine id stored on object", get_object_data(item_a, OBJ_DATA_ID), engine_id_a);
call assertEquals("remove unique item a", rm_mult_objs_from_inven(dude_obj, item_a, 1), 1);
call assertEquals("remove regular item b", rm_mult_objs_from_inven(dude_obj, item_b, 1), 1);
call assertEquals("remove unique item c", rm_mult_objs_from_inven(dude_obj, item_c, 1), 1);
destroy_object(item_a);
destroy_object(item_b);
destroy_object(item_c);
call report_test_results("unique_id");
display_msg("unique_id manual test ready: press U to assign/print a unique id for a random inventory item");
register_hook_proc(HOOK_KEYPRESS, keypress_handler);
end
+93
View File
@@ -0,0 +1,93 @@
#include "sfall.h"
#include "dik.h"
#include "../test_utils.h"
#define AREA_ARROYO (0)
#define AREA_RND_CITY (28)
#define MAP_RND_CITY1 (68)
variable mode := 0;
variable pending_event := -1;
variable expected_map := -1;
variable arroyo_known_before := 0;
variable car_town_before := 0;
procedure mode_name(variable value) begin
if (value == 0) then return "observe";
if (value == 1) then return "reroute_worldmap_enter";
if (value == 2) then return "cancel_encounters";
return "unknown";
end
procedure log_mode begin
display_msg(string_format2("encounter mode=%d (%s)", mode, mode_name(mode)));
end
procedure begin_pending_assertions(variable event_type) begin
pending_event := event_type;
expected_map := MAP_RND_CITY1;
arroyo_known_before := town_known(AREA_ARROYO);
car_town_before := car_current_town;
end
procedure encounter_handler begin
variable
event_type := get_sfall_arg_at(0),
map_id := get_sfall_arg_at(1),
is_special := get_sfall_arg_at(2),
table_num := get_sfall_arg_at(3),
entry_num := get_sfall_arg_at(4);
display_msg(string_format5("encounter event=%d map=%d special=%d table=%d entry=%d", event_type, map_id, is_special, table_num, entry_num));
if (mode == 1 and event_type == 1) then begin
call begin_pending_assertions(event_type);
display_msg(string_format1("encounter rerouting worldmap entry to map %d", expected_map));
set_sfall_return(expected_map);
end else if (mode == 2 and event_type == 0) then begin
display_msg("canceling encounter");
set_sfall_return(-1);
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;
mode := mode + 1;
if (mode > 2) then mode := 0;
call log_mode;
end
procedure start begin
if (not game_loaded) then return;
display_msg("encounter manual test ready:");
display_msg("press X to cycle mode: observe, reroute worldmap enter, cancel encounters");
display_msg("mode 1: enter any location from the world map");
display_msg("mode 2: cancel encounters");
call log_mode;
register_hook_proc(HOOK_KEYPRESS, keypress_handler);
register_hook_proc(HOOK_ENCOUNTER, encounter_handler);
end
procedure map_enter_p_proc begin
if (expected_map == -1) then return;
call assertEquals("encounter reroute loaded expected map", cur_map_index, expected_map);
call assertEquals("encounter reroute load area matches rerouted map", map_get_load_area, AREA_RND_CITY);
call assertEquals("encounter reroute does not mutate Arroyo known state", town_known(AREA_ARROYO), arroyo_known_before);
call assertEquals("encounter reroute preserves car town", car_current_town, car_town_before);
if (pending_event == 1) then
call report_test_results("hook_encounter_worldmap_enter");
pending_event := -1;
expected_map := -1;
end
+153
View File
@@ -0,0 +1,153 @@
#include "sfall.h"
#include "dik.h"
#include "define_lite.h"
#include "../test_utils.h"
variable seen_adjustfid := 0;
variable seen_invenwield := 0;
variable seen_invenwield_remove := 0;
variable seen_canuseweapon_player := 0;
variable seen_canuseweapon_ai := 0;
variable temp_container := 0;
variable invenwield_override_enabled := false;
variable canuseweapon_override := -1;
procedure invenwield_mode_name begin
if (invenwield_override_enabled) then return "suppress";
return "engine";
end
procedure canuseweapon_mode_name(variable value) begin
if (value == -1) then return "engine";
if (value == 1) then return "on";
if (value == 0) then return "off";
return "unknown";
end
procedure cycle_canuseweapon_override begin
if (canuseweapon_override == -1) then canuseweapon_override := 1;
else if (canuseweapon_override == 1) then canuseweapon_override := 0;
else canuseweapon_override := -1;
display_msg(string_format1("hook_item_compat canuseweapon mode=%s", canuseweapon_mode_name(canuseweapon_override)));
end
procedure adjustfid_handler begin
variable args := get_sfall_args;
call assertEquals("adjustfid arg count", len_array(args), 2);
call assertEquals("adjustfid arg0 type", typeof(args[0]), VALTYPE_INT);
call assertEquals("adjustfid arg1 type", typeof(args[1]), VALTYPE_INT);
seen_adjustfid := seen_adjustfid + 1;
display_msg(string_format2("adjustfid vanilla=%d modified=%d", args[0], args[1]));
end
procedure invenwield_handler begin
variable
args := get_sfall_args,
item := args[1],
slot := args[2],
is_wield := args[3],
is_remove := args[4];
call assertEquals("invenwield arg count", len_array(args), 5);
call assertTrue("invenwield slot range", slot >= 0 and slot <= 2);
call assertTrue("invenwield is_wield flag", is_wield == 0 or is_wield == 1);
call assertTrue("invenwield is_remove flag", is_remove == 0 or is_remove == 1);
seen_invenwield := seen_invenwield + 1;
if (is_remove) then begin
seen_invenwield_remove := seen_invenwield_remove + 1;
end
display_msg(string_format4("invenwield item=%s slot=%d wield=%d remove=%d", obj_name(item), slot, is_wield, is_remove));
if (invenwield_override_enabled) then begin
set_sfall_return(0);
end
end
procedure canuseweapon_handler begin
variable
args := get_sfall_args,
hit_mode := args[2],
result := args[3],
weapon := args[1];
call assertEquals("canuseweapon arg count", len_array(args), 4);
call assertTrue("canuseweapon result flag", result == 0 or result == 1);
if (hit_mode == -1) then begin
seen_canuseweapon_player := seen_canuseweapon_player + 1;
end else begin
seen_canuseweapon_ai := seen_canuseweapon_ai + 1;
end
display_msg(string_format3("canuseweapon item=%s hitmode=%d result=%d", obj_name(weapon), hit_mode, result));
if (canuseweapon_override != -1) then begin
set_sfall_return(canuseweapon_override);
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_Z) then begin
display_msg("hook_item_compat: unwield armor");
unwield_slot(dude_obj, INVEN_TYPE_WORN);
end else if (key == DIK_X) then begin
display_msg("hook_item_compat: unwield right hand");
unwield_slot(dude_obj, INVEN_TYPE_RIGHT_HAND);
end else if (key == DIK_C) then begin
display_msg("hook_item_compat: unwield left hand");
unwield_slot(dude_obj, INVEN_TYPE_LEFT_HAND);
end else if (key == DIK_V) then begin
display_msg("hook_item_compat: toggle active hand");
toggle_active_hand;
end else if (key == DIK_N) then begin
display_msg("hook_item_compat: move inventory to temp container");
if (temp_container == 0) then begin
temp_container := create_object_sid(467, 0, 0, -1);
end
move_obj_inven_to_obj(dude_obj, temp_container);
end else if (key == DIK_M) then begin
invenwield_override_enabled := not invenwield_override_enabled;
display_msg(string_format1("hook_item_compat invenwield mode=%s", invenwield_mode_name));
end else if (key == DIK_G) then begin
call cycle_canuseweapon_override;
end else if (key == DIK_B) then begin
display_msg(string_format7("hook_item_compat summary inven_mode=%s canuse_mode=%s adjust=%d inven=%d remove=%d player=%d ai=%d",
invenwield_mode_name,
canuseweapon_mode_name(canuseweapon_override),
seen_adjustfid,
seen_invenwield,
seen_invenwield_remove,
seen_canuseweapon_player,
seen_canuseweapon_ai));
call assertTrue("adjustfid observed", seen_adjustfid > 0);
call assertTrue("invenwield observed", seen_invenwield > 0);
call assertTrue("player canuseweapon observed", seen_canuseweapon_player > 0);
call report_test_results("hook_item_compat");
end
end
procedure start begin
if (not game_loaded) then return;
display_msg("hook_item_compat ready:");
display_msg("open inventory/barter to trigger adjustfid");
display_msg("press Z armor, X right, C left, V swap hand");
display_msg("press N disposable-save inventory move, M invenwield mode");
display_msg("press G canuseweapon mode (engine/on/off), B report");
register_hook_proc(HOOK_KEYPRESS, keypress_handler);
register_hook_proc(HOOK_INVENWIELD, invenwield_handler);
register_hook_proc(HOOK_ADJUSTFID, adjustfid_handler);
register_hook_proc(HOOK_CANUSEWEAPON, canuseweapon_handler);
end
+145
View File
@@ -0,0 +1,145 @@
#include "sfall.h"
#include "dik.h"
#include "lib.arrays.h"
#include "../test_utils.h"
variable useskill_mode := 0;
variable useskillon_calls := 0;
variable useskill_calls := 0;
variable last_useskillon_skill := -1;
variable last_useskill_skill := -1;
procedure skill_name(variable skill) begin
if (skill == SKILL_FIRST_AID) then return "first_aid";
if (skill == SKILL_DOCTOR) then return "doctor";
if (skill == SKILL_LOCKPICK) then return "lockpick";
if (skill == SKILL_STEAL) then return "steal";
if (skill == SKILL_TRAPS) then return "traps";
if (skill == SKILL_SCIENCE) then return "science";
if (skill == SKILL_REPAIR) then return "repair";
if (skill == SKILL_SNEAK) then return "sneak";
return string_format1("skill_%d", skill);
end
procedure obj_name_safe(variable obj) begin
if (obj == 0) then return "<null>";
return obj_name(obj);
end
procedure useskill_mode_name(variable mode) begin
if (mode == 1) then return "useskillon_cancel";
if (mode == 2) then return "useskillon_user_override";
if (mode == 3) then return "useskillon_allow_combat";
if (mode == 4) then return "useskill_override";
if (mode == 5) then return "useskill_cancel_steal";
return "vanilla";
end
procedure print_mode begin
display_msg(string_format1("useskill mode -> %s", useskill_mode_name(useskill_mode)));
end
procedure useskillon_handler begin
variable
args := get_sfall_args,
user := args[0],
target := args[1],
skill := args[2];
useskillon_calls++;
last_useskillon_skill := skill;
display_msg(string_format("useskillon mode=%s user=%s target=%s skill=%s",
useskill_mode_name(useskill_mode),
obj_name_safe(user),
obj_name_safe(target),
skill_name(skill)));
display_msg(string_format1("useskillon args=%s", debug_array_str(args)));
call assertEquals("useskillon arg count", len_array(args), 3);
call assertTrue("useskillon user is set", user != 0);
call assertTrue("useskillon target is set", target != 0);
if (useskill_mode == 1) then begin
display_msg("useskillon cancelling skill action");
set_sfall_return(-1);
end else if (useskill_mode == 2) then begin
if (skill == SKILL_STEAL) then begin
display_msg("useskillon returning dude_obj for Steal; engine should ignore this return value");
end else begin
display_msg("useskillon returning dude_obj as user override");
end
set_sfall_return(dude_obj);
end else if (useskill_mode == 3) then begin
display_msg("useskillon allowing combat use");
set_sfall_return(0);
set_sfall_return(1);
end
end
procedure useskill_handler begin
variable
args := get_sfall_args,
user := args[0],
target := args[1],
skill := args[2],
skill_bonus := args[3];
useskill_calls++;
last_useskill_skill := skill;
display_msg(string_format("useskill mode=%s user=%s target=%s skill=%s bonus=%d",
useskill_mode_name(useskill_mode),
obj_name_safe(user),
obj_name_safe(target),
skill_name(skill),
skill_bonus));
display_msg(string_format1("useskill args=%s", debug_array_str(args)));
call assertEquals("useskill arg count", len_array(args), 4);
call assertTrue("useskill user is set", user != 0);
call assertTrue("useskill target is set", target != 0);
if (useskill_mode == 4) then begin
display_msg("useskill overriding hard-coded handler with result 1");
set_sfall_return(1);
end else if (useskill_mode == 5) then begin
if (skill == SKILL_STEAL) then begin
display_msg("useskill canceling steal by overriding hard-coded handler with result 0");
set_sfall_return(0);
end
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_U) then begin
useskill_mode += 1;
if (useskill_mode > 5) then useskill_mode := 0;
call print_mode;
end else if (key == DIK_Y) then begin
display_msg(string_format("useskill stats: on=%d skill=%s, handler=%d skill=%s",
useskillon_calls,
skill_name(last_useskillon_skill),
useskill_calls,
skill_name(last_useskill_skill)));
call report_test_results("hook_useskill");
end
end
procedure start begin
if (not game_loaded) then return;
display_msg("useskill manual test ready");
display_msg("press U to cycle modes: vanilla / useskillon_cancel / useskillon_user_override / useskillon_allow_combat / useskill_override / useskill_cancel_steal");
display_msg("use a Skilldex skill or a medical kit on a valid target; press Y to report assertions");
register_hook_proc(HOOK_KEYPRESS, keypress_handler);
register_hook_proc(HOOK_USESKILLON, useskillon_handler);
register_hook_proc(HOOK_USESKILL, useskill_handler);
end
+25 -22
View File
@@ -1262,20 +1262,18 @@ int actionPickUp(Object* critter, Object* item)
return reg_anim_end();
}
// TODO: Looks like the name is a little misleading, container can only be a
// critter, which is enforced by this function as well as at the call sites.
// Used to loot corpses, so probably should be something like actionLootCorpse.
// Check if it can be called with a living critter.
// This is primarily used to loot critters, namely corpses. I don't believe it can
// be called with a living creature, but am not 100% certain.
//
// 0x4123E8
int _action_loot_container(Object* critter, Object* container)
// 0x4123E8 was _action_loot_container
int actionLootCritter(Object* critter, Object* target)
{
if (FID_TYPE(container->fid) != OBJ_TYPE_CRITTER) {
if (FID_TYPE(target->fid) != OBJ_TYPE_CRITTER) {
return -1;
}
// SFALL: Fix for trying to loot corpses with the "NoSteal" flag.
if (critterFlagCheck(container->pid, CRITTER_NO_STEAL)) {
if (critterFlagCheck(target->pid, CRITTER_NO_STEAL)) {
return -1;
}
@@ -1288,20 +1286,20 @@ int _action_loot_container(Object* critter, Object* container)
if (isInCombat()) {
reg_anim_begin(ANIMATION_REQUEST_RESERVED);
animationRegisterMoveToObject(critter, container, critter->data.critter.combat.ap, 0);
animationRegisterMoveToObject(critter, target, critter->data.critter.combat.ap, 0);
} else {
reg_anim_begin(critter == gDude ? ANIMATION_REQUEST_RESERVED : ANIMATION_REQUEST_UNRESERVED);
if (objectWithinWalkDistance(critter, container)) {
animationRegisterMoveToObject(critter, container, -1, 0);
if (objectWithinWalkDistance(critter, target)) {
animationRegisterMoveToObject(critter, target, -1, 0);
} else {
animationRegisterRunToObject(critter, container, -1, 0);
animationRegisterRunToObject(critter, target, -1, 0);
}
}
animationRegisterCallbackForced(critter, container, (AnimationCallback*)_is_next_to, -1);
animationRegisterCallback(critter, container, (AnimationCallback*)checkSceneryUseActionPointCost, -1);
animationRegisterCallback(critter, container, (AnimationCallback*)scriptsRequestLooting, -1);
animationRegisterCallbackForced(critter, target, (AnimationCallback*)_is_next_to, -1);
animationRegisterCallback(critter, target, (AnimationCallback*)checkSceneryUseActionPointCost, -1);
animationRegisterCallback(critter, target, (AnimationCallback*)scriptsRequestLooting, -1);
return reg_anim_end();
}
@@ -1338,10 +1336,15 @@ static int _action_use_skill_in_combat_error(Object* critter)
// 0x41255C
int actionUseSkill(Object* user, Object* target, int skill)
{
UseSkillOnHookResult hookResult = scriptHooks_UseSkillOn(&user, target, skill);
if (!hookResult.shouldContinue) {
return -1;
}
switch (skill) {
case SKILL_FIRST_AID:
case SKILL_DOCTOR:
if (isInCombat()) {
if (isInCombat() && !hookResult.allowInCombat) {
// NOTE: Uninline.
return _action_use_skill_in_combat_error(user);
}
@@ -1351,7 +1354,7 @@ int actionUseSkill(Object* user, Object* target, int skill)
}
break;
case SKILL_LOCKPICK:
if (isInCombat()) {
if (isInCombat() && !hookResult.allowInCombat) {
// NOTE: Uninline.
return _action_use_skill_in_combat_error(user);
}
@@ -1362,7 +1365,7 @@ int actionUseSkill(Object* user, Object* target, int skill)
break;
case SKILL_STEAL:
if (isInCombat()) {
if (isInCombat() && !hookResult.allowInCombat) {
// NOTE: Uninline.
return _action_use_skill_in_combat_error(user);
}
@@ -1377,7 +1380,7 @@ int actionUseSkill(Object* user, Object* target, int skill)
break;
case SKILL_TRAPS:
if (isInCombat()) {
if (isInCombat() && !hookResult.allowInCombat) {
// NOTE: Uninline.
return _action_use_skill_in_combat_error(user);
}
@@ -1389,7 +1392,7 @@ int actionUseSkill(Object* user, Object* target, int skill)
break;
case SKILL_SCIENCE:
case SKILL_REPAIR:
if (isInCombat()) {
if (isInCombat() && !hookResult.allowInCombat) {
// NOTE: Uninline.
return _action_use_skill_in_combat_error(user);
}
@@ -1430,9 +1433,9 @@ int actionUseSkill(Object* user, Object* target, int skill)
// Performer is either dude, or party member who's best at the specified
// skill in entire party, and this skill is his/her own best.
Object* performer = gDude;
Object* performer = hookResult.userOverridden ? user : gDude;
if (user == gDude) {
if (user == gDude && !hookResult.userOverridden) {
Object* partyMember = partyMemberGetBestInSkill(skill);
if (partyMember == gDude) {
+1 -1
View File
@@ -12,7 +12,7 @@ int _action_attack(Attack* attack);
int _action_use_an_item_on_object(Object* user, Object* targetObj, Object* item);
int _action_use_an_object(Object* user, Object* targetObj);
int actionPickUp(Object* critter, Object* item);
int _action_loot_container(Object* critter, Object* container);
int actionLootCritter(Object* critter, Object* target);
int _action_skill_use(int skill);
int actionUseSkill(Object* user, Object* target, int skill);
bool _is_hit_from_front(const Object* attacker, const Object* defender);
+3
View File
@@ -263,6 +263,9 @@ int audioOpen(const char* fname, AudioFileInfo* info, bool* isMemoryBackedPtr)
audioFile->bitsPerSample = 16;
if (info != nullptr) {
// Do not propagate decoder-reported channels by default. Legacy
// speech/lips paths rely on the caller's existing mono/stereo
// choice unless they explicitly opt into stereo before soundLoad.
info->sampleRate = audioFile->sampleRate;
info->bitsPerSample = audioFile->bitsPerSample;
}
+7 -2
View File
@@ -10,6 +10,8 @@ namespace fallout {
#define AUDIO_ENGINE_SOUND_BUFFERS 8
static constexpr int kAudioEngineTargetSampleRate = 44100;
struct AudioEngineSoundBuffer {
bool active;
unsigned int size;
@@ -96,14 +98,17 @@ static void audioEngineMixin(void* userData, Uint8* stream, int length)
bool audioEngineInit()
{
SDL_AudioSpec desiredSpec;
desiredSpec.freq = 22050;
// Request 44.1 kHz output so 44.1 kHz ACM music can play without being
// downsampled by the mixer on the common path.
desiredSpec.freq = kAudioEngineTargetSampleRate;
desiredSpec.format = AUDIO_S16;
desiredSpec.channels = 2;
desiredSpec.samples = 1024;
desiredSpec.callback = audioEngineMixin;
gAudioEngineDeviceId = SDL_OpenAudioDevice(nullptr, 0, &desiredSpec, &gAudioEngineSpec, SDL_AUDIO_ALLOW_ANY_CHANGE);
if (gAudioEngineDeviceId == -1) {
if (gAudioEngineDeviceId == 0) {
gAudioEngineDeviceId = -1;
return false;
}
-262
View File
@@ -1,262 +0,0 @@
#include "audio_file.h"
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include "debug.h"
#include "memory_manager.h"
#include "platform_compat.h"
#include "sound.h"
#include "sound_decoder.h"
namespace fallout {
typedef enum AudioFileFlags {
AUDIO_FILE_IN_USE = 0x01,
AUDIO_FILE_COMPRESSED = 0x02,
} AudioFileFlags;
typedef struct AudioFile {
int flags;
FILE* stream;
SoundDecoder* soundDecoder;
int fileSize;
int sampleRate;
int channels;
int position;
} AudioFile;
static bool defaultCompressionFunc(char* filePath);
static int audioFileSoundDecoderReadHandler(void* data, void* buffer, unsigned int size);
// 0x5108C0 queryCompressedFunc_2
static AudioFileQueryCompressedFunc* queryCompressedFunc = defaultCompressionFunc;
// 0x56CB10 audiof
static AudioFile* gAudioFileList;
// 0x56CB14 numAudiof
static int gAudioFileListLength;
// 0x41A850
static bool defaultCompressionFunc(char* filePath)
{
char* pch = strrchr(filePath, '.');
if (pch != nullptr) {
strcpy(pch + 1, "raw");
}
return false;
}
// 0x41A870
static int audioFileSoundDecoderReadHandler(void* data, void* buffer, unsigned int size)
{
return fread(buffer, 1, size, reinterpret_cast<FILE*>(data));
}
// 0x41A88C
int audioFileOpen(const char* fname, AudioFileInfo* info, bool* isMemoryBackedPtr)
{
char path[COMPAT_MAX_PATH];
strcpy(path, fname);
int compression;
if (queryCompressedFunc(path)) {
compression = 2;
} else {
compression = 0;
}
FILE* stream = compat_fopen(path, "rb");
if (stream == nullptr) {
return -1;
}
int index;
for (index = 0; index < gAudioFileListLength; index++) {
if ((gAudioFileList[index].flags & AUDIO_FILE_IN_USE) == 0) {
break;
}
}
if (index == gAudioFileListLength) {
if (gAudioFileList != nullptr) {
gAudioFileList = (AudioFile*)internal_realloc_safe(gAudioFileList, sizeof(*gAudioFileList) * (gAudioFileListLength + 1), __FILE__, __LINE__); // "..\int\audiof.c", 207
} else {
gAudioFileList = (AudioFile*)internal_malloc_safe(sizeof(*gAudioFileList), __FILE__, __LINE__); // "..\int\audiof.c", 209
}
gAudioFileListLength++;
}
AudioFile* audioFile = &(gAudioFileList[index]);
audioFile->flags = AUDIO_FILE_IN_USE;
audioFile->stream = stream;
if (compression == 2) {
audioFile->flags |= AUDIO_FILE_COMPRESSED;
audioFile->soundDecoder = soundDecoderInit(audioFileSoundDecoderReadHandler, audioFile->stream, &(audioFile->channels), &(audioFile->sampleRate), &(audioFile->fileSize));
audioFile->fileSize *= 2;
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;
return index + 1;
}
// 0x41AAA0
int audioFileClose(int handle)
{
AudioFile* audioFile = &(gAudioFileList[handle - 1]);
fclose(audioFile->stream);
if ((audioFile->flags & AUDIO_FILE_COMPRESSED) != 0) {
soundDecoderFree(audioFile->soundDecoder);
}
// Reset audio file (which also resets it's use flag).
memset(audioFile, 0, sizeof(*audioFile));
return 0;
}
// 0x41AB08
int audioFileRead(int handle, void* buffer, unsigned int size)
{
AudioFile* ptr = &(gAudioFileList[handle - 1]);
int bytesRead;
if ((ptr->flags & AUDIO_FILE_COMPRESSED) != 0) {
bytesRead = soundDecoderDecode(ptr->soundDecoder, buffer, size);
} else {
bytesRead = fread(buffer, 1, size, ptr->stream);
}
ptr->position += bytesRead;
return bytesRead;
}
// 0x41AB74
long audioFileSeek(int handle, long offset, int origin)
{
void* buf;
int remaining;
int targetPosition;
AudioFile* audioFile = &(gAudioFileList[handle - 1]);
switch (origin) {
case SEEK_SET:
targetPosition = offset;
break;
case SEEK_CUR:
targetPosition = audioFile->fileSize + offset;
break;
case SEEK_END:
targetPosition = audioFile->position + offset;
break;
default:
assert(false && "Should be unreachable");
}
if ((audioFile->flags & AUDIO_FILE_COMPRESSED) != 0) {
if (targetPosition <= audioFile->position) {
soundDecoderFree(audioFile->soundDecoder);
fseek(audioFile->stream, 0, 0);
audioFile->soundDecoder = soundDecoderInit(audioFileSoundDecoderReadHandler, audioFile->stream, &(audioFile->channels), &(audioFile->sampleRate), &(audioFile->fileSize));
audioFile->fileSize *= 2;
audioFile->position = 0;
if (targetPosition != 0) {
buf = internal_malloc_safe(4096, __FILE__, __LINE__); // "..\int\audiof.c", 364
while (targetPosition > 4096) {
audioFileRead(handle, buf, 4096);
targetPosition -= 4096;
}
if (targetPosition != 0) {
audioFileRead(handle, buf, targetPosition);
}
internal_free_safe(buf, __FILE__, __LINE__); // "..\int\audiof.c", 370
}
} else {
buf = internal_malloc_safe(0x400, __FILE__, __LINE__); // "..\int\audiof.c", 316
remaining = audioFile->position - targetPosition;
while (remaining > 1024) {
audioFileRead(handle, buf, 1024);
remaining -= 1024;
}
if (remaining != 0) {
audioFileRead(handle, buf, remaining);
}
// TODO: Obiously leaks memory.
}
return audioFile->position;
}
return fseek(audioFile->stream, offset, origin);
}
// 0x41AD20
long audioFileGetSize(int handle)
{
AudioFile* audioFile = &(gAudioFileList[handle - 1]);
return audioFile->fileSize;
}
// 0x41AD3C
long audioFileTell(int handle)
{
AudioFile* audioFile = &(gAudioFileList[handle - 1]);
return audioFile->position;
}
// AudiofWrite
// 0x41AD58
int audioFileWrite(int handle, const void* buffer, unsigned int size)
{
debugPrint("AudiofWrite shouldn't be ever called\n");
return 0;
}
// 0x41AD68
int audioFileInit(AudioFileQueryCompressedFunc* func)
{
queryCompressedFunc = func;
gAudioFileList = nullptr;
gAudioFileListLength = 0;
return soundSetDefaultFileIO(audioFileOpen, audioFileClose, audioFileRead, audioFileWrite, audioFileSeek, audioFileTell, audioFileGetSize);
}
// 0x41ADAC
void audioFileExit()
{
if (gAudioFileList != nullptr) {
internal_free_safe(gAudioFileList, __FILE__, __LINE__); // "..\int\audiof.c", 405
}
gAudioFileListLength = 0;
gAudioFileList = nullptr;
}
} // namespace fallout
-22
View File
@@ -1,22 +0,0 @@
#ifndef AUDIO_FILE_H
#define AUDIO_FILE_H
#include "sound.h"
namespace fallout {
typedef bool(AudioFileQueryCompressedFunc)(char* filePath);
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);
long audioFileGetSize(int handle);
long audioFileTell(int handle);
int audioFileWrite(int handle, const void* buf, unsigned int size);
int audioFileInit(AudioFileQueryCompressedFunc* func);
void audioFileExit();
} // namespace fallout
#endif /* AUDIO_FILE_H */
+78 -88
View File
@@ -6254,79 +6254,75 @@ static void criticalsInit()
}
if (mode == 1 || mode == 3) {
Config criticalsConfig;
if (configInit(&criticalsConfig)) {
char* criticalsConfigFilePath;
configGetString(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_OVERRIDE_CRITICALS_FILE_KEY, &criticalsConfigFilePath);
if (criticalsConfigFilePath != nullptr && *criticalsConfigFilePath == '\0') {
criticalsConfigFilePath = nullptr;
}
char* criticalsConfigFilePath;
configGetString(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_OVERRIDE_CRITICALS_FILE_KEY, &criticalsConfigFilePath);
if (criticalsConfigFilePath != nullptr && *criticalsConfigFilePath == '\0') {
criticalsConfigFilePath = nullptr;
}
if (configRead(&criticalsConfig, criticalsConfigFilePath, false)) {
if (mode == 1) {
char sectionKey[16];
ScopedConfig criticalsConfig(criticalsConfigFilePath, false);
if (criticalsConfig) {
if (mode == 1) {
char sectionKey[16];
// Read original kill types (19) plus one for the player.
for (int killType = 0; killType < KILL_TYPE_COUNT + 1; killType++) {
for (int hitLocation = 0; hitLocation < HIT_LOCATION_COUNT; hitLocation++) {
for (int effect = 0; effect < CRTICIAL_EFFECT_COUNT; effect++) {
snprintf(sectionKey, sizeof(sectionKey), "c_%02d_%d_%d", killType, hitLocation, effect);
// Read original kill types (19) plus one for the player.
for (int killType = 0; killType < KILL_TYPE_COUNT + 1; killType++) {
for (int hitLocation = 0; hitLocation < HIT_LOCATION_COUNT; hitLocation++) {
for (int effect = 0; effect < CRTICIAL_EFFECT_COUNT; effect++) {
snprintf(sectionKey, sizeof(sectionKey), "c_%02d_%d_%d", killType, hitLocation, effect);
// Update player kill type if needed.
int newKillType = killType == KILL_TYPE_COUNT ? SFALL_KILL_TYPE_COUNT : killType;
for (int dataMember = 0; dataMember < CRIT_DATA_MEMBER_COUNT; dataMember++) {
int value = criticalsGetValue(newKillType, hitLocation, effect, dataMember);
if (configGetInt(&criticalsConfig, sectionKey, gCritDataMemberKeys[dataMember], &value)) {
criticalsSetValue(newKillType, hitLocation, effect, dataMember, value);
}
// Update player kill type if needed.
int newKillType = killType == KILL_TYPE_COUNT ? SFALL_KILL_TYPE_COUNT : killType;
for (int dataMember = 0; dataMember < CRIT_DATA_MEMBER_COUNT; dataMember++) {
int value = criticalsGetValue(newKillType, hitLocation, effect, dataMember);
if (configGetInt(criticalsConfig.get(), sectionKey, gCritDataMemberKeys[dataMember], &value)) {
criticalsSetValue(newKillType, hitLocation, effect, dataMember, value);
}
}
}
}
} else if (mode == 3) {
char ktSectionKey[32];
char hitLocationSectionKey[32];
char key[32];
}
} else if (mode == 3) {
char ktSectionKey[32];
char hitLocationSectionKey[32];
char key[32];
// Read Sfall kill types (38) plus one for the player.
for (int killType = 0; killType < SFALL_KILL_TYPE_COUNT + 1; killType++) {
snprintf(ktSectionKey, sizeof(ktSectionKey), "c_%02d", killType);
// Read Sfall kill types (38) plus one for the player.
for (int killType = 0; killType < SFALL_KILL_TYPE_COUNT + 1; killType++) {
snprintf(ktSectionKey, sizeof(ktSectionKey), "c_%02d", killType);
int enabled = 0;
configGetInt(&criticalsConfig, ktSectionKey, "Enabled", &enabled);
if (enabled == 0) {
continue;
int enabled = 0;
configGetInt(criticalsConfig.get(), ktSectionKey, "Enabled", &enabled);
if (enabled == 0) {
continue;
}
for (int hitLocation = 0; hitLocation < HIT_LOCATION_COUNT; hitLocation++) {
if (enabled < 2) {
bool hitLocationChanged = false;
snprintf(key, sizeof(key), "Part_%d", hitLocation);
configGetBool(criticalsConfig.get(), ktSectionKey, key, &hitLocationChanged);
if (!hitLocationChanged) {
continue;
}
}
for (int hitLocation = 0; hitLocation < HIT_LOCATION_COUNT; hitLocation++) {
if (enabled < 2) {
bool hitLocationChanged = false;
snprintf(hitLocationSectionKey, sizeof(hitLocationSectionKey), "c_%02d_%d", killType, hitLocation);
snprintf(key, sizeof(key), "Part_%d", hitLocation);
configGetBool(&criticalsConfig, ktSectionKey, key, &hitLocationChanged);
if (!hitLocationChanged) {
continue;
}
}
snprintf(hitLocationSectionKey, sizeof(hitLocationSectionKey), "c_%02d_%d", killType, hitLocation);
for (int effect = 0; effect < CRTICIAL_EFFECT_COUNT; effect++) {
for (int dataMember = 0; dataMember < CRIT_DATA_MEMBER_COUNT; dataMember++) {
int value = criticalsGetValue(killType, hitLocation, effect, dataMember);
snprintf(key, sizeof(key), "e%d_%s", effect, gCritDataMemberKeys[dataMember]);
if (configGetInt(&criticalsConfig, hitLocationSectionKey, key, &value)) {
criticalsSetValue(killType, hitLocation, effect, dataMember, value);
}
for (int effect = 0; effect < CRTICIAL_EFFECT_COUNT; effect++) {
for (int dataMember = 0; dataMember < CRIT_DATA_MEMBER_COUNT; dataMember++) {
int value = criticalsGetValue(killType, hitLocation, effect, dataMember);
snprintf(key, sizeof(key), "e%d_%s", effect, gCritDataMemberKeys[dataMember]);
if (configGetInt(criticalsConfig.get(), hitLocationSectionKey, key, &value)) {
criticalsSetValue(killType, hitLocation, effect, dataMember, value);
}
}
}
}
}
}
configFree(&criticalsConfig);
}
}
@@ -6600,46 +6596,40 @@ static void unarmedInitCustom()
{
char* unarmedFileName = nullptr;
configGetString(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_UNARMED_FILE_KEY, &unarmedFileName);
if (unarmedFileName != nullptr && *unarmedFileName == '\0') {
unarmedFileName = nullptr;
}
if (unarmedFileName == nullptr) {
if (unarmedFileName == nullptr || *unarmedFileName == '\0') {
return;
}
Config unarmedConfig;
if (configInit(&unarmedConfig)) {
if (configRead(&unarmedConfig, unarmedFileName, false)) {
char section[4];
char statKey[6];
ScopedConfig unarmedConfig(unarmedFileName, false);
if (!unarmedConfig) {
return;
}
for (int hitMode = 0; hitMode < HIT_MODE_COUNT; hitMode++) {
if (!isUnarmedHitMode(hitMode)) {
continue;
}
char section[4];
char statKey[6];
UnarmedHitDescription* hitDescription = &(gUnarmedHitDescriptions[hitMode]);
snprintf(section, sizeof(section), "%d", hitMode);
configGetInt(&unarmedConfig, section, "ReqLevel", &(hitDescription->requiredLevel));
configGetInt(&unarmedConfig, section, "SkillLevel", &(hitDescription->requiredSkill));
configGetInt(&unarmedConfig, section, "MinDamage", &(hitDescription->minDamage));
configGetInt(&unarmedConfig, section, "MaxDamage", &(hitDescription->maxDamage));
configGetInt(&unarmedConfig, section, "BonusDamage", &(hitDescription->bonusDamage));
configGetInt(&unarmedConfig, section, "BonusCrit", &(hitDescription->bonusCriticalChance));
configGetInt(&unarmedConfig, section, "APCost", &(hitDescription->actionPointCost));
configGetBool(&unarmedConfig, section, "BonusDamage", &(hitDescription->isPenetrate));
configGetBool(&unarmedConfig, section, "Secondary", &(hitDescription->isSecondary));
for (int stat = 0; stat < PRIMARY_STAT_COUNT; stat++) {
snprintf(statKey, sizeof(statKey), "Stat%d", stat);
configGetInt(&unarmedConfig, section, statKey, &(hitDescription->requiredStats[stat]));
}
}
for (int hitMode = 0; hitMode < HIT_MODE_COUNT; hitMode++) {
if (!isUnarmedHitMode(hitMode)) {
continue;
}
configFree(&unarmedConfig);
UnarmedHitDescription* hitDescription = &(gUnarmedHitDescriptions[hitMode]);
snprintf(section, sizeof(section), "%d", hitMode);
configGetInt(unarmedConfig.get(), section, "ReqLevel", &(hitDescription->requiredLevel));
configGetInt(unarmedConfig.get(), section, "SkillLevel", &(hitDescription->requiredSkill));
configGetInt(unarmedConfig.get(), section, "MinDamage", &(hitDescription->minDamage));
configGetInt(unarmedConfig.get(), section, "MaxDamage", &(hitDescription->maxDamage));
configGetInt(unarmedConfig.get(), section, "BonusDamage", &(hitDescription->bonusDamage));
configGetInt(unarmedConfig.get(), section, "BonusCrit", &(hitDescription->bonusCriticalChance));
configGetInt(unarmedConfig.get(), section, "APCost", &(hitDescription->actionPointCost));
configGetBool(unarmedConfig.get(), section, "BonusDamage", &(hitDescription->isPenetrate));
configGetBool(unarmedConfig.get(), section, "Secondary", &(hitDescription->isSecondary));
for (int stat = 0; stat < PRIMARY_STAT_COUNT; stat++) {
snprintf(statKey, sizeof(statKey), "Stat%d", stat);
configGetInt(unarmedConfig.get(), section, statKey, &(hitDescription->requiredStats[stat]));
}
}
}
+55 -63
View File
@@ -378,81 +378,77 @@ int aiInit()
gAiPacketsLength = 0;
Config config;
if (!configInit(&config)) {
ScopedConfig config("data\\ai.txt", true);
if (!config) {
return -1;
}
if (!configRead(&config, "data\\ai.txt", true)) {
return -1;
}
gAiPackets = (AiPacket*)internal_malloc(sizeof(*gAiPackets) * config.entriesLength);
gAiPackets = (AiPacket*)internal_malloc(sizeof(*gAiPackets) * config.get()->entriesLength);
if (gAiPackets == nullptr) {
goto err;
}
for (index = 0; index < config.entriesLength; index++) {
for (index = 0; index < config.get()->entriesLength; index++) {
aiPacketInit(&(gAiPackets[index]));
}
for (index = 0; index < config.entriesLength; index++) {
DictionaryEntry* sectionEntry = &(config.entries[index]);
for (index = 0; index < config.get()->entriesLength; index++) {
DictionaryEntry* sectionEntry = &(config.get()->entries[index]);
AiPacket* ai = &(gAiPackets[index]);
char* stringValue;
ai->name = internal_strdup(sectionEntry->key);
if (!configGetInt(&config, sectionEntry->key, "packet_num", &(ai->packet_num))) goto err;
if (!configGetInt(&config, sectionEntry->key, "max_dist", &(ai->max_dist))) goto err;
if (!configGetInt(&config, sectionEntry->key, "min_to_hit", &(ai->min_to_hit))) goto err;
if (!configGetInt(&config, sectionEntry->key, "min_hp", &(ai->min_hp))) goto err;
if (!configGetInt(&config, sectionEntry->key, "aggression", &(ai->aggression))) goto err;
if (!configGetInt(config.get(), sectionEntry->key, "packet_num", &(ai->packet_num))) goto err;
if (!configGetInt(config.get(), sectionEntry->key, "max_dist", &(ai->max_dist))) goto err;
if (!configGetInt(config.get(), sectionEntry->key, "min_to_hit", &(ai->min_to_hit))) goto err;
if (!configGetInt(config.get(), sectionEntry->key, "min_hp", &(ai->min_hp))) goto err;
if (!configGetInt(config.get(), sectionEntry->key, "aggression", &(ai->aggression))) goto err;
if (configGetString(&config, sectionEntry->key, "hurt_too_much", &stringValue)) {
if (configGetString(config.get(), sectionEntry->key, "hurt_too_much", &stringValue)) {
_parse_hurt_str(stringValue, &(ai->hurt_too_much));
}
if (!configGetInt(&config, sectionEntry->key, "secondary_freq", &(ai->secondary_freq))) goto err;
if (!configGetInt(&config, sectionEntry->key, "called_freq", &(ai->called_freq))) goto err;
if (!configGetInt(&config, sectionEntry->key, "font", &(ai->font))) goto err;
if (!configGetInt(&config, sectionEntry->key, "color", &(ai->color))) goto err;
if (!configGetInt(&config, sectionEntry->key, "outline_color", &(ai->outline_color))) goto err;
if (!configGetInt(&config, sectionEntry->key, "chance", &(ai->chance))) goto err;
if (!configGetInt(&config, sectionEntry->key, "run_start", &(ai->run.start))) goto err;
if (!configGetInt(&config, sectionEntry->key, "run_end", &(ai->run.end))) goto err;
if (!configGetInt(&config, sectionEntry->key, "move_start", &(ai->move.start))) goto err;
if (!configGetInt(&config, sectionEntry->key, "move_end", &(ai->move.end))) goto err;
if (!configGetInt(&config, sectionEntry->key, "attack_start", &(ai->attack.start))) goto err;
if (!configGetInt(&config, sectionEntry->key, "attack_end", &(ai->attack.end))) goto err;
if (!configGetInt(&config, sectionEntry->key, "miss_start", &(ai->miss.start))) goto err;
if (!configGetInt(&config, sectionEntry->key, "miss_end", &(ai->miss.end))) goto err;
if (!configGetInt(&config, sectionEntry->key, "hit_head_start", &(ai->hit[HIT_LOCATION_HEAD].start))) goto err;
if (!configGetInt(&config, sectionEntry->key, "hit_head_end", &(ai->hit[HIT_LOCATION_HEAD].end))) goto err;
if (!configGetInt(&config, sectionEntry->key, "hit_left_arm_start", &(ai->hit[HIT_LOCATION_LEFT_ARM].start))) goto err;
if (!configGetInt(&config, sectionEntry->key, "hit_left_arm_end", &(ai->hit[HIT_LOCATION_LEFT_ARM].end))) goto err;
if (!configGetInt(&config, sectionEntry->key, "hit_right_arm_start", &(ai->hit[HIT_LOCATION_RIGHT_ARM].start))) goto err;
if (!configGetInt(&config, sectionEntry->key, "hit_right_arm_end", &(ai->hit[HIT_LOCATION_RIGHT_ARM].end))) goto err;
if (!configGetInt(&config, sectionEntry->key, "hit_torso_start", &(ai->hit[HIT_LOCATION_TORSO].start))) goto err;
if (!configGetInt(&config, sectionEntry->key, "hit_torso_end", &(ai->hit[HIT_LOCATION_TORSO].end))) goto err;
if (!configGetInt(&config, sectionEntry->key, "hit_right_leg_start", &(ai->hit[HIT_LOCATION_RIGHT_LEG].start))) goto err;
if (!configGetInt(&config, sectionEntry->key, "hit_right_leg_end", &(ai->hit[HIT_LOCATION_RIGHT_LEG].end))) goto err;
if (!configGetInt(&config, sectionEntry->key, "hit_left_leg_start", &(ai->hit[HIT_LOCATION_LEFT_LEG].start))) goto err;
if (!configGetInt(&config, sectionEntry->key, "hit_left_leg_end", &(ai->hit[HIT_LOCATION_LEFT_LEG].end))) goto err;
if (!configGetInt(&config, sectionEntry->key, "hit_eyes_start", &(ai->hit[HIT_LOCATION_EYES].start))) goto err;
if (!configGetInt(&config, sectionEntry->key, "hit_eyes_end", &(ai->hit[HIT_LOCATION_EYES].end))) goto err;
if (!configGetInt(&config, sectionEntry->key, "hit_groin_start", &(ai->hit[HIT_LOCATION_GROIN].start))) goto err;
if (!configGetInt(&config, sectionEntry->key, "hit_groin_end", &(ai->hit[HIT_LOCATION_GROIN].end))) goto err;
if (!configGetInt(config.get(), sectionEntry->key, "secondary_freq", &(ai->secondary_freq))) goto err;
if (!configGetInt(config.get(), sectionEntry->key, "called_freq", &(ai->called_freq))) goto err;
if (!configGetInt(config.get(), sectionEntry->key, "font", &(ai->font))) goto err;
if (!configGetInt(config.get(), sectionEntry->key, "color", &(ai->color))) goto err;
if (!configGetInt(config.get(), sectionEntry->key, "outline_color", &(ai->outline_color))) goto err;
if (!configGetInt(config.get(), sectionEntry->key, "chance", &(ai->chance))) goto err;
if (!configGetInt(config.get(), sectionEntry->key, "run_start", &(ai->run.start))) goto err;
if (!configGetInt(config.get(), sectionEntry->key, "run_end", &(ai->run.end))) goto err;
if (!configGetInt(config.get(), sectionEntry->key, "move_start", &(ai->move.start))) goto err;
if (!configGetInt(config.get(), sectionEntry->key, "move_end", &(ai->move.end))) goto err;
if (!configGetInt(config.get(), sectionEntry->key, "attack_start", &(ai->attack.start))) goto err;
if (!configGetInt(config.get(), sectionEntry->key, "attack_end", &(ai->attack.end))) goto err;
if (!configGetInt(config.get(), sectionEntry->key, "miss_start", &(ai->miss.start))) goto err;
if (!configGetInt(config.get(), sectionEntry->key, "miss_end", &(ai->miss.end))) goto err;
if (!configGetInt(config.get(), sectionEntry->key, "hit_head_start", &(ai->hit[HIT_LOCATION_HEAD].start))) goto err;
if (!configGetInt(config.get(), sectionEntry->key, "hit_head_end", &(ai->hit[HIT_LOCATION_HEAD].end))) goto err;
if (!configGetInt(config.get(), sectionEntry->key, "hit_left_arm_start", &(ai->hit[HIT_LOCATION_LEFT_ARM].start))) goto err;
if (!configGetInt(config.get(), sectionEntry->key, "hit_left_arm_end", &(ai->hit[HIT_LOCATION_LEFT_ARM].end))) goto err;
if (!configGetInt(config.get(), sectionEntry->key, "hit_right_arm_start", &(ai->hit[HIT_LOCATION_RIGHT_ARM].start))) goto err;
if (!configGetInt(config.get(), sectionEntry->key, "hit_right_arm_end", &(ai->hit[HIT_LOCATION_RIGHT_ARM].end))) goto err;
if (!configGetInt(config.get(), sectionEntry->key, "hit_torso_start", &(ai->hit[HIT_LOCATION_TORSO].start))) goto err;
if (!configGetInt(config.get(), sectionEntry->key, "hit_torso_end", &(ai->hit[HIT_LOCATION_TORSO].end))) goto err;
if (!configGetInt(config.get(), sectionEntry->key, "hit_right_leg_start", &(ai->hit[HIT_LOCATION_RIGHT_LEG].start))) goto err;
if (!configGetInt(config.get(), sectionEntry->key, "hit_right_leg_end", &(ai->hit[HIT_LOCATION_RIGHT_LEG].end))) goto err;
if (!configGetInt(config.get(), sectionEntry->key, "hit_left_leg_start", &(ai->hit[HIT_LOCATION_LEFT_LEG].start))) goto err;
if (!configGetInt(config.get(), sectionEntry->key, "hit_left_leg_end", &(ai->hit[HIT_LOCATION_LEFT_LEG].end))) goto err;
if (!configGetInt(config.get(), sectionEntry->key, "hit_eyes_start", &(ai->hit[HIT_LOCATION_EYES].start))) goto err;
if (!configGetInt(config.get(), sectionEntry->key, "hit_eyes_end", &(ai->hit[HIT_LOCATION_EYES].end))) goto err;
if (!configGetInt(config.get(), sectionEntry->key, "hit_groin_start", &(ai->hit[HIT_LOCATION_GROIN].start))) goto err;
if (!configGetInt(config.get(), sectionEntry->key, "hit_groin_end", &(ai->hit[HIT_LOCATION_GROIN].end))) goto err;
ai->hit[HIT_LOCATION_GROIN].end++;
if (configGetString(&config, sectionEntry->key, "area_attack_mode", &stringValue)) {
if (configGetString(config.get(), sectionEntry->key, "area_attack_mode", &stringValue)) {
_cai_match_str_to_list(stringValue, gAreaAttackModeKeys, AREA_ATTACK_MODE_COUNT, &(ai->area_attack_mode));
} else {
ai->area_attack_mode = -1;
}
if (configGetString(&config, sectionEntry->key, "run_away_mode", &stringValue)) {
if (configGetString(config.get(), sectionEntry->key, "run_away_mode", &stringValue)) {
_cai_match_str_to_list(stringValue, gRunAwayModeKeys, RUN_AWAY_MODE_COUNT, &(ai->run_away_mode));
if (ai->run_away_mode >= 0) {
@@ -460,49 +456,47 @@ int aiInit()
}
}
if (configGetString(&config, sectionEntry->key, "best_weapon", &stringValue)) {
if (configGetString(config.get(), sectionEntry->key, "best_weapon", &stringValue)) {
_cai_match_str_to_list(stringValue, gBestWeaponKeys, BEST_WEAPON_COUNT, &(ai->best_weapon));
}
if (configGetString(&config, sectionEntry->key, "distance", &stringValue)) {
if (configGetString(config.get(), sectionEntry->key, "distance", &stringValue)) {
_cai_match_str_to_list(stringValue, gDistanceModeKeys, DISTANCE_COUNT, &(ai->distance));
}
if (configGetString(&config, sectionEntry->key, "attack_who", &stringValue)) {
if (configGetString(config.get(), sectionEntry->key, "attack_who", &stringValue)) {
_cai_match_str_to_list(stringValue, gAttackWhoKeys, ATTACK_WHO_COUNT, &(ai->attack_who));
}
if (configGetString(&config, sectionEntry->key, "chem_use", &stringValue)) {
if (configGetString(config.get(), sectionEntry->key, "chem_use", &stringValue)) {
_cai_match_str_to_list(stringValue, gChemUseKeys, CHEM_USE_COUNT, &(ai->chem_use));
}
configGetIntList(&config, sectionEntry->key, "chem_primary_desire", ai->chem_primary_desire, AI_PACKET_CHEM_PRIMARY_DESIRE_COUNT);
configGetIntList(config.get(), sectionEntry->key, "chem_primary_desire", ai->chem_primary_desire, AI_PACKET_CHEM_PRIMARY_DESIRE_COUNT);
if (configGetString(&config, sectionEntry->key, "disposition", &stringValue)) {
if (configGetString(config.get(), sectionEntry->key, "disposition", &stringValue)) {
_cai_match_str_to_list(stringValue, gDispositionKeys, DISPOSITION_COUNT, &(ai->disposition));
ai->disposition--;
}
if (configGetString(&config, sectionEntry->key, "body_type", &stringValue)) {
if (configGetString(config.get(), sectionEntry->key, "body_type", &stringValue)) {
ai->body_type = internal_strdup(stringValue);
} else {
ai->body_type = nullptr;
}
if (configGetString(&config, sectionEntry->key, "general_type", &stringValue)) {
if (configGetString(config.get(), sectionEntry->key, "general_type", &stringValue)) {
ai->general_type = internal_strdup(stringValue);
} else {
ai->general_type = nullptr;
}
}
if (index < config.entriesLength) {
if (index < config.get()->entriesLength) {
goto err;
}
gAiPacketsLength = config.entriesLength;
configFree(&config);
gAiPacketsLength = config.get()->entriesLength;
gAiInitialized = true;
@@ -511,7 +505,7 @@ int aiInit()
err:
if (gAiPackets != nullptr) {
for (index = 0; index < config.entriesLength; index++) {
for (index = 0; index < config.get()->entriesLength; index++) {
AiPacket* ai = &(gAiPackets[index]);
if (ai->name != nullptr) {
internal_free(ai->name);
@@ -525,8 +519,6 @@ err:
debugPrint("Error processing ai.txt");
configFree(&config);
return -1;
}

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