Compare commits

...
Author SHA1 Message Date
Mike Klaas 9e3e94ffd9 PR findings 2026-06-21 22:05:31 -07:00
github-actions[bot] 25e006cffd chore: auto-format with clang-format 2026-06-22 04:56:15 +00:00
Mike Klaas 3edd00d85f clarify which are transient 2026-06-21 21:53:16 -07:00
Mike Klaas 2bfa10411c better cleaup 2026-06-21 21:43:39 -07:00
Mike Klaas a9eddc1705 Expose transient message lists.
Some message lists are only loaded while a given screen is active.  Sfall still lets scripts access them, though scripts need to be careful to only do so when the windows are active.
2026-06-21 21:22:35 -07:00
Mike Klaas 26d7b632fc HOOK_MOVECOST (#496) 2026-06-17 06:11:08 +00:00
c6818f0849 Fix ddraw.ini -> game.cfg migration on unix-y systems (#497)
* Fix ddraw.ini -> game.cfg migration on unix-y systems

We were using `compat_splitpath` on a windows path.  We want to work on windows paths, so that's good.  This fixes compat_splitpath so that it works with both folder separators.  Also tweak `compat_makepath` to recognize both, for consistency.

This means that filenames with `\` will be broken, but I think we can (and should) live with that)

* Apply suggestions from code review

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

* chore: auto-format with clang-format

* fix

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-06-16 22:48:46 -07:00
Mike Klaas 84cac43da1 HOOK_ITEMDAMAGE (#494)
* HOOK_ITEMDAMAGE
2026-06-16 22:22:53 -07:00
Mike Klaas 261b54c603 Implement unwield_slot (#488)
* Implement `unwield_slot`

This one was surprisingly subtle.  Supports many game modes and handles things differently depending on game mode.
2026-06-08 22:25:46 -07:00
Vlad K 46b89abbd8 Merge pull request #490 from fallout2-ce/feature/mapper-edg
Mapper EDG support
2026-06-08 23:17:25 +02:00
phobos2077 1ee64988bc Guard against saving EDG when saving SAV maps in gameplay 2026-06-07 20:53:35 +02:00
Vlad KandCopilot Autofix powered by AI c78ba885d7 Guard against using dev_path out of mapper
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-06-07 20:44:34 +02:00
phobos2077 6835d10590 mapper: fix TMP map files not being deleted, fix edg not loading when changing maps in play mode 2026-06-07 02:05:57 +02:00
phobos2077 20ad8d8778 mapper: fix SAV files being created in the dev_path 2026-06-07 01:17:01 +02:00
Vlad K 8c98a15ef6 sfall: get_ini_config + config cache (#489)
* sfall: get_ini_config + config cache

* Code review fixes and comment standardization
2026-06-06 22:34:37 +00:00
github-actions[bot] 6782a21185 chore: auto-format with clang-format 2026-06-06 21:33:20 +00:00
phobos2077 d12dbca951 mapper: EDG angled edges (squareRect) editing 2026-06-06 23:27:51 +02:00
phobos2077 823858f275 mapper: fix selected object being deleted when opening map 2026-06-06 23:27:50 +02:00
phobos2077 b93591f5d7 EDG: fix incorrect image corruption when scroll limiting kicks in right at the edge border 2026-06-06 23:27:50 +02:00
phobos2077 a91e40e3cc mapper: EDG support (edges only) 2026-06-06 23:27:50 +02:00
phobos2077 7da7d75f30 mapper: move common utility functions to mp_utils.cc 2026-06-06 23:27:50 +02:00
phobos2077 72b4680ff5 mapper: implement dev_path, a path override where saved files will go 2026-06-06 23:27:50 +02:00
Mike Klaas 99a762b649 frm2png can convert to/from indexed pngs (#487)
* frm2png can convert to/from indexed pngs
2026-06-05 20:39:56 -07:00
Mike KlaasandCopilot Autofix powered by AI 1afe925cbf Better fix for directory detection (#486)
* Better fix for directory detection

A couple places we used compat_file_exists to distinguish between files and folders/dirs, but this breaks on POSIX.  This attempts to fix it more holistically

* Make compat_file_exists to be consistent

All platforms now return false for folders/dirs

* Apply suggestions from code review

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

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-06-05 20:24:59 -07:00
Mike Klaas 95c07dba9f Fix compile warnings (#482)
Most are of this type

```
warning: 'size' overrides a member function but is not marked 'override' [-Winconsistent-missing-override]

sfall_arrays.cc:313:26: note: overridden virtual function is here
    313 |     virtual ProgramValue GetArrayKey(int index, Program* program) = 0;
        |
```
2026-06-04 08:27:48 -07:00
Mike Klaas 0fc042ef84 Fix save games being missing on Mac OS (#484) 2026-06-03 22:46:57 -07:00
Mike Klaas 29df63dd99 [HRP] Scale movies (#478)
SDL Surface for performance and nice scaling
2026-06-03 21:16:16 -07:00
Mike Klaas 6f2693edf7 Change use_walk_distance to match sfall setting (#481)
* Change `use_walk_distance` to match sfall setting

It's pointless to set this to 0,1 because you don't move if the target is adjacent or yourself, so sfall changes this to "number of spaces" between player and target.  We were reading the old ddraw.ini setting before, incorrectly.

Also add a small optimization to not compute path for adjacent targets

* add comment

* return true when distance is self/adjacent
2026-06-03 15:34:55 -07:00
Mike Klaas e51004a3d1 Ability to use indexed pngs as interface art (#454)
* Ability to load indexed pngs in game

* Switch 2-col inventory .frms to .png
2026-06-02 23:48:08 -07:00
Vlad K 9aa2ae4bbc ZIP DB archive support (#480)
* ZIP: inital support (directly in DBase)

* ZIP: log and bail on any unsupported feature

* dfile.cc compile error fix

* ZIP: minor refactoring

* ZIP: code review fixes and refactoring

* ZIP: simpler and more robust code

* Formatting

* ZIP: remove unnecessary dataOffset normalization
2026-06-02 18:30:20 +02:00
Vlad K 968534314d gameDbInit: change load order to keep master_patches at the top and to match sfall (#479) 2026-06-01 16:57:31 +02:00
Mike Klaas a38f3436f8 A few combat opcodes (#477)
* set_critter_burst_disable

* get/set/reset_critical_table

* `attack_is_aimed`
2026-05-31 16:28:00 -07:00
Mike Klaas 15c22ccdfc HRP/Sfall main menu scaling (#457)
Similar to HRP/Sfall, except:

    Does not support non-aspect-ratio-preserving scale mode
    Uses vanilla art if it fits without scaling (e.g. 640x480 or non-scaling res like 800x480). The scaled-down HR assets looks considerably worse.
2026-05-28 19:49:29 -07:00
Mike Klaas f263075abf Bundle Sfall's gl_highlighting.ssl (#461)
* Bundle Sfall's gl_highlight.ssl

Most mods/distributions assume this is exists since it is bundled with Sfall.  It's also a highly requested feature

I decided to merge this as a first-class game config instead of continuing to read mods/sfall-mods.ini.  I think most people to this to be a default capability that can be toggled in-engine.  Open to other thoughts on the matter.  Lost of people will already have sfall-mods.ini configured, which is a reason to keep it the way it was.

* PR feedback
2026-05-27 23:15:09 -07:00
Mike Klaas c95f91c2ff Fix returnval of critter_mod_skill (#476)
Fixes #474
2026-05-27 22:14:16 -07:00
Vlad K db86e53af2 Fix crash in opTokenize when prev is not null but not present in the source string (#473) 2026-05-27 18:40:54 +02:00
Vlad K 349af8fbb8 Fix aiPacketRead inducing crash when loading saves in certain order (#472)
* Fix aiPacketRead overwriting packet_num from savegames, leading to corrupted state on subsequent loads and crash

* Mark variable as unused and add comment
2026-05-27 10:21:45 +02: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
127 changed files with 7385 additions and 1738 deletions
+63 -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/*")
@@ -579,6 +614,8 @@ if((NOT ANDROID) AND (NOT IOS) AND (NOT CMAKE_SYSTEM_NAME MATCHES "Emscripten"))
target_sources(mapper-ce PUBLIC
${FALLOUT_ENGINE_SOURCES}
${FALLOUT_PLATFORM_SOURCES}
"src/mapper/map_edge_setup.cc"
"src/mapper/map_edge_setup.h"
"src/mapper/map_func.cc"
"src/mapper/map_func.h"
"src/mapper/mapper.cc"
@@ -591,6 +628,8 @@ if((NOT ANDROID) AND (NOT IOS) AND (NOT CMAKE_SYSTEM_NAME MATCHES "Emscripten"))
"src/mapper/mp_scrpt.h"
"src/mapper/mp_targt.cc"
"src/mapper/mp_targt.h"
"src/mapper/mp_utils.cc"
"src/mapper/mp_utils.h"
"src/mapper/mp_text.cc"
"src/mapper/mp_text.h"
)
@@ -605,11 +644,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 +691,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"
}
]
}
+13 -5
View File
@@ -76,7 +76,7 @@ For example:
## `ce-frm2png` CLI Tool
There is also a small FRM conversion tool in this repo for exporting Fallout `.frm` art to `.png`.
There is also a small FRM conversion tool in this repo for converting Fallout art between `.frm` and `.png`.
From the repo root, build it with your normal CMake workflow, targeting `ce-frm2png`:
@@ -89,18 +89,26 @@ Basic usage:
1. `./<BUILD_DIR>/ce-frm2png <input.frm> [output.png]`
2. `./<BUILD_DIR>/ce-frm2png <input.frm> [output.png] --palette <path-to-color.pal>`
3. `./<BUILD_DIR>/ce-frm2png <input.frm> [output.png] --frame <index> --direction <index>`
4. `./<BUILD_DIR>/ce-frm2png - [output.png|-] --palette <path-to-color.pal>`
4. `./<BUILD_DIR>/ce-frm2png <input.png> [output.frm] --palette <path-to-color.pal>`
5. `./<BUILD_DIR>/ce-frm2png <input.png> [output.frm] --fps 10 --action-frame 0 --x-offset 0 --y-offset 0`
6. `./<BUILD_DIR>/ce-frm2png <input.png> <output.png> --palette <path-to-color.pal>`
7. `./<BUILD_DIR>/ce-frm2png - [output.png|-] --from-frm --palette <path-to-color.pal>`
8. `./<BUILD_DIR>/ce-frm2png - [output.frm|-] --from-png --palette <path-to-color.pal>`
`-` can be used to take input from stdin (e.g. from `ce-dat-tool cat` or write to stdout).
`-` can be used to take input from stdin (e.g. from `ce-dat-tool cat`) or write to stdout. When reading from stdin, pass `--from-frm` or `--from-png`. Output defaults to PNG for FRM input and FRM for PNG input when output is omitted or `-`; use `--to-png` for PNG output to stdout from PNG input.
If `--palette` isn't specified, it will pick up `color.pal` in the .frm's directory or cwd.
Output file extensions are validated against the selected conversion. For example, PNG-to-FRM output must use `.frm`, and PNG output must use `.png`.
If `--palette` isn't specified, it will pick up `color.pal` in the input file's directory or cwd.
Notes:
- The tool expects a raw `color.pal` file. It does not read palettes directly from `master.dat`, so extract `color.pal` first if needed.
- A typical extraction flow is: `ce-dat-tool master.dat extract --lower . 'color.pal'`
- By default palette index `0` becomes transparent in the output PNG. Pass `--opaque` to keep it opaque.
- The current implementation writes RGBA PNGs. It does not preserve the original Fallout palette indices as a palette-indexed PNG.
- PNG output is always palette-indexed and preserves Fallout palette indices when possible.
- For `png -> frm`, transparent pixels (alpha below 128) map to palette index `0` by default. Pass `--opaque` to quantize every pixel instead.
- The current `png -> frm` implementation writes a single-frame FRM and points all six direction slots at the same frame payload.
## Updating SDL
+12
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,9 @@ 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
* Item/Corpse/Container/Critter highlighting (configure using [mods/sfall-mods.ini](https://github.com/sfall-team/sfall/blob/master/artifacts/config_files/sfall-mods.ini))
## Contributing
@@ -159,6 +163,14 @@ Integrating Sfall goodies is the top priority. Quality of life updates are OK to
For current sfall compatibility status and the remaining work needed to close gaps, see [SFALL_COMPATIBILITY.md](SFALL_COMPATIBILITY.md).
## For modders
* Robust (though not 100%) Sfall opcode and hook support
* .ogg/.wav support for map music
* .png support for static assets (must be 8bit indexed)
* .zip support for .dat archives
* Working BIS mapper
## License
The source code is this repository is available under the [Sustainable Use License](LICENSE.md).
+24 -10
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,13 +67,13 @@ 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 | âś… | - |
| Perks and traits / NPC perks | set_fake_perk_npc<br>set_fake_trait_npc<br>set_selectable_perk_npc<br>has_fake_perk_npc<br>has_fake_trait_npc | not implemented | - |
| Global scripts / Global script functions | set_global_script_repeat<br>set_global_script_type<br>available_global_script_types | âś… except available_global_script_types | - |
| Combat | attack_is_aimed<br>block_combat<br>force_aimed_shots<br>disable_aimed_shots<br>get_attack_type<br>get/set_bodypart_hit_modifier<br>combat_data<br>get/set/reset_critical_table<br>get_last_target<br>get_last_attacker<br>set_critter_burst_disable<br>get/set_critter_current_ap<br>set_spray_settings<br>get/set_combat_free_move | implemented: get_attack_type, get/set_bodypart_hit_modifier, combat_data, get/set_critter_current_ap, get/set_combat_free_move | - |
| Combat | attack_is_aimed<br>block_combat<br>force_aimed_shots<br>disable_aimed_shots<br>get_attack_type<br>get/set_bodypart_hit_modifier<br>combat_data<br>get/set/reset_critical_table<br>get_last_target<br>get_last_attacker<br>set_critter_burst_disable<br>get/set_critter_current_ap<br>set_spray_settings<br>get/set_combat_free_move | âś… except block_combat, force_aimed_shots, disable_aimed_shots, get_last_target, get_last_attacker, set_spray_settings | - |
| Car | set_car_current_town<br>car_gas_amount<br>set_car_intface_art | implemented: all except set_car_intface_art | - |
| Interface / Windows and images | art_frame_data<br>interface_art_draw<br>interface_print<br>draw_image<br>draw_image_scaled<br>get_window_under_mouse<br>create_win<br>get_window_attribute<br>message_box<br>set_window_flag<br>win_fill_color<br>interface_overlay<br>dialog_message<br>get_text_width<br>hide_window<br>show_window<br>create_message_window | implemented: only message_box, get_text_width, show_window, create_message_window | - |
| Interface / Outline | outlined_object<br>get_outline<br>set_outline | âś… | - |
@@ -69,12 +81,14 @@ See [`https://sfall-team.github.io/sfall/`](https://sfall-team.github.io/sfall/)
| Interface / Inventory | display_stats<br>inventory_redraw<br>critter_inven_obj2<br>get_current_inven_size<br>item_weight | implemented: critter_inven_obj2, item_weight | - |
| Interface / 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 | - |
| 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 | âś… | `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, 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 | - |
| 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. |
| Hero Appearange | set_dm/df_model<br>hero_select_win | not implemented | - |
| Events | add_g_timer_event<br>remove_timer_event<br>create_spatial<br>spatial_radius | not implemented | - |
| Other | get_year<br>active_hand<br>toggle_active_hand<br>get/set_viewport_x/y<br>get_light_level<br>message_str_game<br>sneak_success<br>unwield_slot<br>add_extra_msg_file<br>get_metarule_table<br>metarule_exist<br> | âś… except get/set_viewport_x/y, sneak_success | `input_funcs_available`, `nb_create_char` are deprecated in sfall and intentionally absent in CE. `add_extra_msg_file` does not support the explicit `fileNumber` form in CE. |
## Hooks
@@ -92,12 +106,12 @@ See [`https://sfall-team.github.io/sfall/`](https://sfall-team.github.io/sfall/)
| UseObj | `HOOK_USEOBJ` | âś… | CE notes an sfall-matching inconsistency around return code `2` behavior between interface contexts. |
| RemoveInvenObj | `HOOK_REMOVEINVENOBJ` | đźš« | - |
| BarterPrice | `HOOK_BARTERPRICE` | âś… | - |
| MoveCost | `HOOK_MOVECOST` | đźš« | - |
| ItemDamage | `HOOK_ITEMDAMAGE` | đźš« | - |
| ItemDamage | `HOOK_ITEMDAMAGE` | âś… | - |
| MoveCost | `HOOK_MOVECOST` | âś… | - |
| 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 +127,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.
Binary file not shown.
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.
+16 -2
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
@@ -117,14 +119,26 @@ numbers_in_dialogue=0
;Set to 2 to also skip the splash screen
skip_opening_movies=0
splash_screen_size=1
;Set to 1 to scale movies to fit the screen while preserving aspect ratio.
;Set to 0 to keep movies at their native size, centered on screen.
movie_aspect_fit=1
;Maximum number of columns shown in the main inventory and loot/steal windows (valid range: 1..2)
;2-column Loot/steal window requires minimum screen width of 673px
inventory_columns=2
;Set to 0 to keep the main menu art and controls at native size.
;Set to 1 to scale only the main menu background image to fit the screen while maintaining aspect ratio.
;Set to 2 to also scale the main menu button panel, red buttons, and button labels with the menu art.
main_menu_scale_mode=1
[qol]
; Automatically open doors that are unlocked and has no script attached.
auto_open_doors=0
; Overrides distance at which walk animation is used when using an object.
use_walk_distance=5
; Overrides distance at which walk animation is used when using an object. 0 disables walking
; This is the number of empty tiles between the player and the target
use_walk_distance=3
; 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
@@ -60,12 +60,14 @@ procedure start begin
display_msg("Testing combat/inventory/object helpers...");
call assertEquals("metarule attack_is_aimed", metarule_exist("attack_is_aimed"), 1);
call assertEquals("metarule get_combat_free_move", metarule_exist("get_combat_free_move"), 1);
call assertEquals("metarule set_combat_free_move", metarule_exist("set_combat_free_move"), 1);
call assertEquals("metarule item_weight", metarule_exist("item_weight"), 1);
call assertEquals("metarule obj_is_openable", metarule_exist("obj_is_openable"), 1);
call assertEquals("opcode get_critter_current_ap", opcode_exists(OP_GET_CRITTER_CURRENT_AP), 1);
call assertEquals("opcode set_critter_current_ap", opcode_exists(OP_SET_CRITTER_CURRENT_AP), 1);
call assertEquals("attack_is_aimed defaults to false", attack_is_aimed, 0);
testItem := create_object_sid(TEST_ITEM_PID, 0, 0, -1);
call assertEquals("item_weight matches proto weight for test item", item_weight(testItem), obj_weight(testItem));
+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
+6
View File
@@ -65,6 +65,12 @@ procedure ini_test_suite begin
call assertEquals("TC7 get_ini_sections size", len_array(result_array), 3);
call assertTrue("TC7 get_ini_sections", scan_array(result_array, "TestMisc") >= 0);
// get_ini_config
result_array := get_ini_config_db("config\\config_from_db.cfg");
call assertEquals("TC8 get_ini_config_db size", len_array(result_array), 2);
call assertTrue("TC8 get_ini_config ValidSection", result_array.ValidSection != 0);
call assertEquals("TC8 get_ini_config_db ValidValue", result_array.ValidSection.ValidKey, "ValidValue");
call report_test_results("ini");
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
+43
View File
@@ -0,0 +1,43 @@
#include "sfall.h"
#include "dik.h"
#include "define_lite.h"
#include "define_extra.h"
#include "test_utils.h"
procedure run_unwield_slot_test(variable slot, variable slotName) begin
variable equippedItem := critter_inven_obj2(dude_obj, slot);
if (equippedItem == 0) then begin
display_msg("unwield_slot " + slotName + ": slot is empty");
return;
end
unwield_slot(dude_obj, slot);
call assertTrue("unwield_slot clears " + slotName + " slot", critter_inven_obj2(dude_obj, slot) == 0);
call report_test_results("unwield_slot");
end
procedure keypress_handler begin
variable pressed := get_sfall_arg_at(0);
variable key := get_sfall_arg_at(1);
if (not pressed) then return;
if (key == DIK_J) then begin
call run_unwield_slot_test(INVEN_TYPE_WORN, "armor");
end else if (key == DIK_K) then begin
call run_unwield_slot_test(INVEN_TYPE_RIGHT_HAND, "right");
end else if (key == DIK_L) then begin
call run_unwield_slot_test(INVEN_TYPE_LEFT_HAND, "left");
end
end
procedure start begin
if (not game_loaded) then return;
call assertEquals("unwield_slot metarule exists", metarule_exist("unwield_slot"), 1);
call assertEquals("critter_inven_obj2 metarule exists", metarule_exist("critter_inven_obj2"), 1);
display_msg("unwield_slot manual test ready: open inventory or barter, then press J armor, K right, L left");
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

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