Compare commits

..
Author SHA1 Message Date
Mike Klaas 1194f0ec73 Generate sound list dynamically
Fixes crash when adding music without modifying the lst.

Contributed by @NovaRain
2026-07-20 20:39:47 -07:00
Mike Klaas 5dc9135689 Fix start_gdialog talking-head crash (#572)
Use the start_gdialog target object when deriving talking-head reaction, matching sfall's crash fix for calls outside talk_p_proc. Keep the separate StartGDialogFix behavior as a modder-controlled game.cfg option so mods can opt into honoring explicit mood arguments unless mood is -1.

sfall references:

- Crash fix/changelog: https://github.com/sfall-team/sfall/blob/d91d01f6256f083fd80a8b90d2202699f604055d/CHANGELOG.md#L171-L175

- Crash patch and StartGDialogFix hook: https://github.com/sfall-team/sfall/blob/d91d01f6256f083fd80a8b90d2202699f604055d/sfall/Modules/BugFixes.cpp#L4138-L4146

- Optional mood behavior hook: https://github.com/sfall-team/sfall/blob/d91d01f6256f083fd80a8b90d2202699f604055d/sfall/Modules/BugFixes.cpp#L2089-L2099

Verified with: make
2026-07-19 10:59:22 -07:00
jirik2077 dfe7eeb3a8 Merge pull request #565 from fallout2-ce/fix_use_after_free_in_use_item_on
Heap-use-after-free fix in objectUseItemOnInternal if  object or item is destroyed by script itself
2026-07-19 06:29:46 +02:00
jirik2077 436ce12022 Merge branch 'main' into fix_use_after_free_in_use_item_on 2026-07-19 06:18:38 +02:00
Mike Klaas 1bc0711327 Reorganize README and include image examples (#566)
* Reorganize README and include image examples
2026-07-18 21:15:54 -07:00
jirik2077 5a87638781 review comments 2026-07-19 06:04:34 +02:00
Mike KlaasandCopilot Autofix powered by AI 10f59c6b45 Bundle example config in distribution (#564)
* add fallout2.cfg docs

* flesh out fallout2.cfg

* add linux

* Potential fix for pull request finding

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-07-18 14:50:29 -07:00
github-actions[bot] 24562f57da chore: auto-format with clang-format 2026-07-18 19:22:04 +00:00
jirik2077 9711e61f89 fixes heap-use-after-free in objectUseItemOnInternal when object is destoryed by the script (fixing K9 and attaching pole in Mariposa cart) 2026-07-18 21:14:50 +02:00
jirik2077 d8d99fe94a Merge pull request #562 from fallout2-ce/deobfuscate_color_usage
Deobfuscate color usage
2026-07-18 08:38:19 +02:00
github-actions[bot] 1a9211d44d chore: auto-format with clang-format 2026-07-18 05:04:11 +00:00
jirik2077 d63f293291 fix typo 2026-07-18 07:01:41 +02:00
jirik2077 71eaabc957 unify the flag usage between text_font.h and window_manager.h.. deobfuscate more draw text flags usages 2026-07-18 06:59:35 +02:00
Mike Klaas aac42c162c Fix refresh after elevator transition (#561)
For some RPU elevator transitions, I was seeing the screen partially not update.  This was due to rejecting the setCenterTile due scroll blockers (I think).  This turns those off if EDG support is enabled.

This also seems to fix some of the jittery scrolling I was seeing in diagonal areas like SF
2026-07-17 20:35:38 -07:00
jirik2077 47156d2e45 deobfuscate the font transparency flag 2026-07-18 00:09:13 +02:00
github-actions[bot] 33744ecf2e chore: auto-format with clang-format 2026-07-17 21:19:31 +00:00
jirik2077 20e80d4bb3 fix the spaces and make rgb to rgb555 color conversion constexpre function 2026-07-17 23:02:48 +02:00
jirik2077 a756bc2700 convert to hexa 2026-07-17 21:51:54 +02:00
jirik2077 de73c90a89 create macros for the color usages 2026-07-17 21:51:26 +02:00
jirik2077 aa439efd04 Add guards for null references in skill.cc (#557)
* add guards for null reference in skill.cc

* removed skillGetBaseValue and add debugPrint
2026-07-17 09:17:37 +02:00
Mike Klaas 41f09fa343 Fix elevator cancelation in RPU (#546)
* Fix elevator cancelation in RPU

If you step into an elevator in RPU and cancel the floor selection, the door closes behind you (sometimes with glitchy graphics).  This is a latent bug in the elevators code that vanilla doesn't hit

  - elevator_select_ mutates the caller’s elevation to a UI/display level before the modal loop.
  - Escape/cancel returns 0 without writing a destination tile.
  - Caller then sees map == current map and elevation != current elevation, so it closes the nearby door.
  - tile is still -1.

* NovaRains approach

* dont default to first floor if not found
2026-07-16 20:06:48 -07:00
Mike Klaas 44067b1933 Avoid non-critter sneak check in obj_can_see_obj (#560)
* We call skillGetValue(gDude, SKILL_SNEAK) for every target
* For non-critters, that can result in OOB memory reads
* We only need it for gDude for sneaking

So let's only call it when dude is sneaking and is the target
2026-07-16 20:05:31 -07:00
alpacacommunist-cmdandCopilot Autofix powered by AI 68d7bd0668 Fix heap corruption in wmWorldMap_load when save file has more cities than current config (#533)
* Fix out-of-bounds write in wmWorldMap_load when numCities exceeds wmMaxAreaNum

* use ENTRANCE_LIST_CAPACITY to properly isolate city entrances

And read a rest to dummy struct

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

* adds debug print warning regarding extra data in .sav

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-07-15 22:11:42 -07:00
Mike Klaas 799184ab96 HOOK_REMOVEINVENOBJ (#540)
* HOOK_REMOVEINVENOBJ

* PR feedback
2026-07-15 21:14:11 -07:00
Mike Klaas 5774372924 Implement set_object_data, and add memory safety and correct offsets (#547)
* Correct `get_object_data` indexes

Currently they are raw offset pointers.  This does not work with CE since pointers are wide (64bit) on some (most) platforms.  So, we can treat these offsets like constants and return the appropriate fields instead.

There are already used for the most part as enum constants:
2026-07-15 21:12:33 -07:00
jirik2077 e90b68837d fix an opposite scroll direction in towns list (#550) 2026-07-15 07:50:06 +02:00
Mike Klaas ceecc3ebdb Two crash fixes (#552)
* Sfall fix for > 160 maps for automap code (RPU triggers this)
* Logic fix for action menu, where actionIndex was incremented unconditionally and could go out of bounds.  This was a regression from vanilla
2026-07-14 22:08:18 -07:00
jirik2077 83b6e081b7 Support for EPA gender change operation (#542)
* support for EPA gender change operation

* fix unintented return
2026-07-13 10:44:01 +02:00
Mike Klaas 083bd4a4cc Implement Sfall npc level cap (10) (#545)
Also, fix off-by-one error that can cause proto corruption
2026-07-12 21:10:23 -07:00
Mike Klaas 27ae59578a Implement real_dude_obj (#537)
This is used by scripts which are party-control aware.  Implementing this means they won't crash and should still work properly
2026-07-12 20:21:33 -07:00
jirik2077 fdee9914e3 Fix division by zero in objectExamineFunc (#541) 2026-07-12 18:25:06 +02:00
Mike Klaas 258de7c21e Increase animation cap (Sfall) (#539)
Fixes #538
2026-07-11 14:26:34 -07:00
jirik2077 feb0cd61ff fix healing revision op_tokenize (#536) 2026-07-11 05:49:44 +02:00
Mike Klaas cbbe2f00f1 Normalize ini paths that start with \ to game root (#501)
* Normalize ini paths that start with `\` to game root

Sfall does this, and we were missing it. https://github.com/sfall-team/sfall/blob/master/sfall/Modules/Scripting/Handlers/IniFiles.cpp#L61-L78

An example is InventoryFilter, which starts its ini path with `\`: https://github.com/rotators/InventoryFilter/blob/master/source/gl_InvenFilter.ssl#L19

* Normalize cache key
2026-07-10 15:10:05 -07:00
Mike Klaas bf1d35b2de Fix possible double-free in animation_free (#513)
* Fix possible double-free in animation_free

Port of roughly: https://github.com/cambragol/fission-ce/commit/7fafbb11c7daf5fe7f5ae05b92a3bffaaaa7b5a4

Not sure how to trigger it in CE, but seems plausibly possible
2026-07-10 15:09:33 -07:00
Mike Klaas 4a9e822e82 Fix "Uncontrolled format string" Code Scanning results (#512)
* Fix "Uncontrolled format string" Code Scanning results

https://github.com/fallout2-ce/fallout2-ce/security/code-scanning

* PR comments

* fix missing early return in programExecuteProcedureAsync path
2026-07-10 15:09:15 -07:00
Mike Klaas 843803e26a Party member equipment management (#528)
Add equipment slots to the companion loot screen.

Items can be equipped directly from player or companion inventory. This also allows switching between party members so you can manage all companion's inventories easily. Does not allow equipping items that would otherwise be disallowed.

Also:

    Calls HOOK_UNWIELD when adding/removing equipment, meaning mods like npc armor work
    Uses HOOK_CANUSE for weapon use checks

Ctrl-click to equip is not implemented since it already transfers items between the two inventories. I tried making it work only for equippable items (and otherwise transfer), but that felt awkward and random. Ctrl-click unequip is implemented.
2026-07-10 15:08:44 -07:00
Mike Klaas ef0085dcca Support Sfall extra animation codes (#535)
Fixes #534
2026-07-10 12:37:03 -07:00
Mike Klaas 9c19d212a6 Fix blue/green channel mixup in PCX reading (#500) 2026-07-09 17:43:28 -07:00
jirik2077andgithub-actions[bot] 41d908274f Handle unimplemented fs_copy/fs_create/fs_find more gracefully in scripts (#530)
* handle unimplemented fs_copy/fs_create/fs_find more gracefully from the script point of view

* chore: auto-format with clang-format

* moved to sfall_opcodes

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-07-09 20:05:06 +02:00
Mike Klaas dafb7b3831 Add offset tracking to --scan-unimplemented (#517)
* Add offset tracking to --scan-unimplemented

* Add a bunch of missing opcodes
* Hardcode a list of implemented hooks to avoid filtering a long list of hooks that are already done
* Add tracking of specific offsets accessed in unsafe scripting so we can plan replacements

Sample output

```
OFFSET write_int (0x81d1 - 0x1d1 - 465):
  0x5190f8 (5345528):
    - ./mods/InventoryFilter.dat/scripts/gl_InvenFilter_debug.int
    - ./mods/InventoryFilter.dat/scripts/release_gl_InvenFilter.int
  0x59e95c (5892444):
    - ./mods/InventoryFilter.dat/scripts/gl_InvenFilter_debug.int
    - ./mods/InventoryFilter.dat/scripts/release_gl_InvenFilter.int
```

Then we can corrlated it with source:

```
// 0x5190F8 curr_rot
static int gInventoryWindowDudeRotation = 0;
```

* PR feedback
2026-07-08 12:31:49 -07:00
77 changed files with 2170 additions and 1495 deletions
+6
View File
@@ -122,6 +122,7 @@ jobs:
mkdir -p out/dist/fallout2-ce-android-debug
cp os/android/app/build/outputs/apk/debug/app-debug.apk out/dist/fallout2-ce-android-debug/fallout2-ce.apk
cp out/dist/ce.dat out/dist/fallout2-ce-android-debug/ce.dat
cp files/fallout2.cfg out/dist/fallout2-ce-android-debug/EXAMPLE_fallout2.cfg
cd out/dist
zip -r fallout2-ce-android-debug.zip fallout2-ce-android-debug
@@ -178,6 +179,7 @@ jobs:
mkdir -p out/dist/fallout2-ce-ios
cp out/build/ios/fallout2-ce.ipa out/dist/fallout2-ce-ios/fallout2-ce-ios.ipa
cp out/dist/ce.dat out/dist/fallout2-ce-ios/ce.dat
cp files/fallout2.cfg out/dist/fallout2-ce-ios/EXAMPLE_fallout2.cfg
cd out/dist
zip -r fallout2-ce-ios.zip fallout2-ce-ios
@@ -243,6 +245,7 @@ jobs:
mkdir -p out/dist/fallout2-ce-linux-${{ matrix.arch }}
cp out/build/linux-${{ matrix.arch }}-debug/fallout2-ce out/dist/fallout2-ce-linux-${{ matrix.arch }}/fallout2-ce
cp out/dist/ce.dat out/dist/fallout2-ce-linux-${{ matrix.arch }}/ce.dat
cp files/fallout2.cfg out/dist/fallout2-ce-linux-${{ matrix.arch }}/EXAMPLE_fallout2.cfg
tar -czvf out/dist/fallout2-ce-linux-${{ matrix.arch }}.tar.gz -C out/dist fallout2-ce-linux-${{ matrix.arch }}
- name: Upload
@@ -293,6 +296,7 @@ jobs:
mkdir -p out/dist/fallout2-ce-linux-armhf
cp build/fallout2-ce out/dist/fallout2-ce-linux-armhf/fallout2-ce
cp out/dist/ce.dat out/dist/fallout2-ce-linux-armhf/ce.dat
cp files/fallout2.cfg out/dist/fallout2-ce-linux-armhf/EXAMPLE_fallout2.cfg
tar -czvf out/dist/fallout2-ce-linux-armhf.tar.gz -C out/dist fallout2-ce-linux-armhf
- name: Upload
@@ -334,6 +338,7 @@ jobs:
mkdir -p out/dist/fallout2-ce-linux-arm64
cp build/fallout2-ce out/dist/fallout2-ce-linux-arm64/fallout2-ce
cp out/dist/ce.dat out/dist/fallout2-ce-linux-arm64/ce.dat
cp files/fallout2.cfg out/dist/fallout2-ce-linux-arm64/EXAMPLE_fallout2.cfg
tar -czvf out/dist/fallout2-ce-linux-arm64.tar.gz -C out/dist fallout2-ce-linux-arm64
- name: Upload
@@ -431,6 +436,7 @@ jobs:
mkdir -p out/dist/fallout2-ce-windows-${{ matrix.arch }}
cp out/build/windows-${{ matrix.arch }}/RelWithDebInfo/fallout2-ce.exe out/dist/fallout2-ce-windows-${{ matrix.arch }}/fallout2-ce.exe
cp out/dist/ce.dat out/dist/fallout2-ce-windows-${{ matrix.arch }}/ce.dat
cp files/fallout2.cfg out/dist/fallout2-ce-windows-${{ matrix.arch }}/EXAMPLE_fallout2.cfg
cd out/dist
7z a fallout2-ce-windows-${{ matrix.arch }}.zip fallout2-ce-windows-${{ matrix.arch }}
+4
View File
@@ -70,6 +70,7 @@ jobs:
mkdir -p out/dist/fallout2-ce-android
cp os/android/app/build/outputs/apk/release/app-release.apk out/dist/fallout2-ce-android/fallout2-ce-android.apk
cp out/dist/ce.dat out/dist/fallout2-ce-android/ce.dat
cp files/fallout2.cfg out/dist/fallout2-ce-android/EXAMPLE_fallout2.cfg
cd out/dist
zip -r fallout2-ce-android.zip fallout2-ce-android
gh release upload ${{ github.event.release.tag_name }} fallout2-ce-android.zip
@@ -130,6 +131,7 @@ jobs:
mkdir -p out/dist/fallout2-ce-ios
cp build/fallout2-ce.ipa out/dist/fallout2-ce-ios/fallout2-ce-ios.ipa
cp out/dist/ce.dat out/dist/fallout2-ce-ios/ce.dat
cp files/fallout2.cfg out/dist/fallout2-ce-ios/EXAMPLE_fallout2.cfg
cd out/dist
zip -r fallout2-ce-ios.zip fallout2-ce-ios
gh release upload ${{ github.event.release.tag_name }} fallout2-ce-ios.zip
@@ -211,6 +213,7 @@ jobs:
mkdir -p out/dist/fallout2-ce-linux-${{ matrix.arch }}
cp build/fallout2-ce out/dist/fallout2-ce-linux-${{ matrix.arch }}/fallout2-ce
cp out/dist/ce.dat out/dist/fallout2-ce-linux-${{ matrix.arch }}/ce.dat
cp files/fallout2.cfg out/dist/fallout2-ce-linux-${{ matrix.arch }}/EXAMPLE_fallout2.cfg
tar -czvf fallout2-ce-linux-${{ matrix.arch }}.tar.gz -C out/dist fallout2-ce-linux-${{ matrix.arch }}
gh release upload ${{ github.event.release.tag_name }} fallout2-ce-linux-${{ matrix.arch }}.tar.gz
rm fallout2-ce-linux-${{ matrix.arch }}.tar.gz
@@ -341,6 +344,7 @@ jobs:
mkdir -p out/dist/fallout2-ce-windows-${{ matrix.arch }}
cp out/build/windows-${{ matrix.arch }}/RelWithDebInfo/fallout2-ce.exe out/dist/fallout2-ce-windows-${{ matrix.arch }}/fallout2-ce.exe
cp out/dist/ce.dat out/dist/fallout2-ce-windows-${{ matrix.arch }}/ce.dat
cp files/fallout2.cfg out/dist/fallout2-ce-windows-${{ matrix.arch }}/EXAMPLE_fallout2.cfg
cd out/dist
7z a fallout2-ce-windows-${{ matrix.arch }}.zip fallout2-ce-windows-${{ matrix.arch }}
gh release upload ${{ github.event.release.tag_name }} fallout2-ce-windows-${{ matrix.arch }}.zip
+90 -57
View File
@@ -1,18 +1,102 @@
# Fallout 2 Community Engine
Fallout 2 Community Engine is a fully working re-implementation of the Fallout 2 engine, optimized for a hassle-free experience on multiple platforms. It provides high resolution support, quality-of-life improvements, and dozens of bug fixes.
Fallout 2 Community Engine is a fully working re-implementation of the Fallout 2 engine, optimized for a hassle-free experience on multiple platforms, including Windows, Mac, iOS, Android, and Linux. It provides high resolution support, quality-of-life improvements, and dozens upon dozens of bug fixes.
This is a fork of the original Fallout2: CE project, which is no longer getting regular updates.
Popular Fallout 2 total conversion mods are partially supported. Original versions of Nevada and Sonora (that do not rely on extended features provided by Sfall) work. [Fallout 2 Restoration Project](https://github.com/BGforgeNet/Fallout2_Restoration_Project) is supported (in Beta). [Fallout Et Tu](https://github.com/rotators/Fo1in2) and [Olympus 2207](https://olympus2207.com) are not yet supported. Other mods (particularly Resurrection and Yesterday) are not tested.
Popular Fallout 2 total conversion mods are partially supported. Nevada and Sonora work. [Fallout 2 Restoration Project](https://github.com/BGforgeNet/Fallout2_Restoration_Project) is supported (in Beta). [Fallout Et Tu](https://github.com/rotators/Fo1in2) and [Olympus 2207](https://olympus2207.com) are not yet supported. Other mods (particularly Resurrection and Yesterday) are not tested.
Fallout2: CE has broad (though not total) compatibility with [Sfall](https://github.com/sfall-team/sfall) scripting extensions. Many traditional Fallout mods work out of the box.
Fallout2: CE has broad (though not total) compatibility with [Sfall](https://github.com/sfall-team/sfall) scripting extensions. Many traditional Fallout mods work out of the box, including NPC Armor, Party Orders, FO2Tweaks (we recommend v14.5+), Talking Heads, and many others.
There is also [Fallout 1 Community Edition](https://github.com/alexbatalov/fallout1-ce) (not affiliated with this fork).
## Installation — [Download Latest CE Release](https://github.com/fallout2-ce/fallout2-ce/releases)
## Installation
You *must* own the game to play. Purchase your copy on [GOG](https://www.gog.com/game/fallout_2), [Epic Games](https://store.epicgames.com/p/fallout-2) or [Steam](https://store.steampowered.com/app/38410).
You *must* own the game to play. Purchase your copy on [GOG](https://www.gog.com/game/fallout_2), [Epic Games](https://store.epicgames.com/p/fallout-2) or [Steam](https://store.steampowered.com/app/38410). Download latest [release](https://github.com/fallout2-ce/fallout2-ce/releases) or build from source.
Download CE from the link above, then extract the archive into your game folder. Launch `fallout2-ce.exe` on Windows (or the platform-specific executable) to play. CE has a lot of enhancements that require configuration. See [Configuration](#configuration) for examples.
For detailed instructions on how to play on Mac, Linux, iOS, or Android, see the [Platform-specific instructions](#platform-specific-installation).
## Quality of life benefits over vanilla Fallout
* High resolution support
* Party members can loot and barter in place of PC
* Directly equip party members instead of convincing them to use the right equipment.
* Expanded 2-column inventory, loot screens
* Expanded 4-row barter screen
* Expanded AP bar
* Ctrl-click to quickly move items when bartering, looting, or stealing, and auto-balance caps
* Music continues playing between maps
* Auto open doors
* Integrated "HELP" menu
* 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))
<table>
<tr>
<td align="center">
<img src="docs/images/two-column-inventory.jpg" alt="Expanded 2-column inventory" width="360"><br>
<sub>Expanded 2-column inventory</sub>
</td>
<td align="center">
<img src="docs/images/companion-equip.gif" alt="Companion equip screen" width="360"><br>
<sub>Companion equip screen</sub>
</td>
</tr>
<tr>
<td align="center">
<img src="docs/images/party-member-loot.gif" alt="Party member loot" width="360"><br>
<sub>Party member loot</sub>
</td>
<td align="center">
<img src="docs/images/help-menu.gif" alt="Integrated HELP menu" width="360"><br>
<sub>Integrated HELP menu</sub>
</td>
</tr>
</table>
There are also dozens of small things that just work a little better than they did in the original. Better pathfinding, fewer graphics glitches, less finicky weapon stacking, and much more.
## Configuration
The main configuration file is `fallout2.cfg`. This contains original game config as well as new CE settings.
Additional settings for screen resolution, UI customization, and map options are now integrated into the main `fallout2.cfg` file (previously part of `f2_res.ini` from Mash's HRP).
Here are some important settings in `fallout2.cfg` under the `[screen]` and `[ui]` sections. See [the example config](https://github.com/fallout2-ce/fallout2-ce/tree/refs/heads/main/files/fallout2.cfg) for a full list of settings.
```ini
[screen]
resolution_x=1600 ; actual game window size (screen resolution), in pixels
resolution_y=1080
windowed=1 ; 0 = fullscreen
scale=2 ; 1 = original scale, 2 = 2x scale, etc. (e.g. at scale 2 and screen resolution 1920x1080, in-game resolution will be 960x540, thus every pixel is twice as wide and tall)
[ui]
; Maximum number of columns shown in the main inventory and loot/steal windows (valid range: 1..2)
inventory_columns=2
; Set to 1 to expand the barter/trade window vertically, adding a 4th item slot per side (requires ce.dat)
expand_barter_window=1
[qol]
; Allow opening party member inventory from the long-press context menu.
party_trade_from_menu=1
; Allow switching to companions' inventories in loot screens (and barter, in the future)
party_loot_and_barter=1
```
**Recommendations:**
- **Desktops**: Use any size you see fit.
- **Tablets**: Set these values to logical resolution of your device, for example iPad Pro 11 is 1668x2388 (pixels), but its logical resolution is 834x1194 (points).
- **Mobile phones**: Set height to 480, calculate width according to your device screen (aspect) ratio, for example Samsung S21 is 20:9 device, so the width should be 480 * 20 / 9 = 1067.
In time this stuff will receive an in-game interface, right now you have to do it manually. To see all currently working fallout2.cfg settings, just run the game once and quit. It will be automatically updated with defaults for every supported setting.
*Note*: Use of the IFACE_BAR settings requires the `f2_res.dat` file, which contains graphical assets. Various versions are available, but one compatible with the above settings can be found here: [f2_res.dat](https://github.com/fallout2-ce/fallout2-ce/raw/refs/heads/main/files/f2_res.dat)
## Platform-specific installation
### Windows
@@ -103,57 +187,6 @@ docker run --rm -v $(pwd):/src emscripten/emsdk:3.1.74 sh -c 'git config --globa
```
- Demo available at https://github.com/ololoken/fallout2-ce-ems.git
## Configuration
The main configuration file is `fallout2.cfg`. There are several important settings you might need to adjust for your installation. Depending on your Fallout distribution main game assets `master.dat`, `critter.dat`, `patch000.dat`, and `data` folder might be either all lowercased, or all uppercased. You can either update `master_dat`, `critter_dat`, `master_patches` and `critter_patches` settings to match your file names, or rename files to match entries in your `fallout2.cfg`.
The `sound` folder (with `music` folder inside) might be located either in `data` folder, or be in the Fallout folder. Update `music_path1` setting to match your hierarchy, usually it's `data/sound/music/` or `sound/music/`. Make sure it matches your path exactly (so it might be `SOUND/MUSIC/` if you've installed Fallout from CD). Music files themselves (with `ACM` extension) should be all uppercased, regardless of `sound` and `music` folders.
Additional settings for screen resolution, UI customization, and map options are now integrated into the main `fallout2.cfg` file (previously part of `f2_res.ini` from Mash's HRP). When Fallout 2 CE starts, if it detects an existing `f2_res.ini` file, it automatically migrates these settings into `fallout2.cfg`. After migration, `fallout2.cfg` becomes the single source of truth for this configuration.
Here are some important settings in `fallout2.cfg` under the `[screen]` and `[ui]` sections. See [the example config](https://github.com/fallout2-ce/fallout2-ce/tree/refs/heads/main/files/fallout2.cfg) for a full list of settings.
```ini
[screen]
resolution_x=1600 ; actual game window size (screen resolution), in pixels
resolution_y=1080
windowed=1 ; 0 = fullscreen
scale=2 ; 1 = original scale, 2 = 2x scale, etc. (e.g. at scale 2 and screen resolution 1920x1080, in-game resolution will be 960x540, thus every pixel is twice as wide and tall)
[ui]
;Set to 1 to expand the barter/trade window vertically, adding a 4th item slot per side (requires ce.dat)
expand_barter_window=1
;Maximum number of columns shown in the main inventory and loot/steal windows (valid range: 1..2)
inventory_columns=2
splash_screen_size=1 ; Splash screen scaling (0=original size, 1=fit preserving aspect, 2=stretch to fill)
quick_toolbar_visible=0 ; Skills quick access toolbar visibility (iOS only) (0=hidden, 1=visible)
```
**Recommendations:**
- **Desktops**: Use any size you see fit.
- **Tablets**: Set these values to logical resolution of your device, for example iPad Pro 11 is 1668x2388 (pixels), but it's logical resolution is 834x1194 (points).
- **Mobile phones**: Set height to 480, calculate width according to your device screen (aspect) ratio, for example Samsung S21 is 20:9 device, so the width should be 480 * 20 / 9 = 1067.
In time this stuff will receive in-game interface, right now you have to do it manually. To see all currently working fallout2.cfg settings, just run the game once and quit. It will be automatically updated with defaults for every supported setting.
*Note*: Use of the IFACE_BAR settings requires the `f2_res.dat` file, which contains graphical assets. Various versions are available, but one compatible with the above settings can be found here: [f2_res.dat](https://github.com/fallout2-ce/fallout2-ce/raw/refs/heads/main/files/f2_res.dat)
## Quality of life benefits over vanilla Fallout
* High resolution support
* 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, and use accurate path length for walk vs run
* Ctrl-click to quickly move items when bartering, looting, or stealing
* _a_ to select "all" when selecting item quantity, and for `Take All` when looting
* When bartering, caps default to the right amount to balance the trade
* Music continues playing between maps
* Auto open doors
* Integrated "HELP" menu
* 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
+8 -2
View File
@@ -38,6 +38,12 @@ The following settings were moved into [`fallout2.cfg`](files/fallout2.cfg) inst
| `Misc` | `UseWalkDistance` | `qol` | `use_walk_distance` |
| `Misc` | `AutoOpenDoors` | `qol` | `auto_open_doors` |
The following settings were moved into [`<DAT>/config/game.cfg`](files/ce.dat/config/game.cfg):
| ddraw.ini section | ddraw.ini key | game.cfg section | game.cfg key |
| --- | --- | --- | --- |
| `Misc` | `StartGDialogFix` | `dialog` | `start_gdialog_fix` |
## Opcodes / Metarules
See [`https://sfall-team.github.io/sfall/`](https://sfall-team.github.io/sfall/) for documentation on specific functions.
@@ -82,7 +88,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 | âś… | `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, real_dude_obj, 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 | get_object_data assumes raw memory offsets which may return different values in CE |
| 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 | âś… except set_dude_obj, set_scr_name | `get/set_object_data` maps supported object and `C_ATTACK_*` offsets instead of exposing arbitrary raw memory offsets. |
| 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 | - |
@@ -104,7 +110,7 @@ See [`https://sfall-team.github.io/sfall/`](https://sfall-team.github.io/sfall/)
| FindTarget | `HOOK_FINDTARGET` | đźš« | (maybe) |
| UseObjOn | `HOOK_USEOBJON` | âś… | - |
| UseObj | `HOOK_USEOBJ` | âś… | CE notes an sfall-matching inconsistency around return code `2` behavior between interface contexts. |
| RemoveInvenObj | `HOOK_REMOVEINVENOBJ` | đźš« | - |
| RemoveInvenObj | `HOOK_REMOVEINVENOBJ` | âś… | - |
| BarterPrice | `HOOK_BARTERPRICE` | âś… | - |
| ItemDamage | `HOOK_ITEMDAMAGE` | âś… | - |
| MoveCost | `HOOK_MOVECOST` | âś… | - |
Binary file not shown.

After

Width:  |  Height:  |  Size: 301 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 303 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 381 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 KiB

+3
View File
@@ -38,6 +38,9 @@ no_exit_hotkey=1
; Set to 1 to allow using '^' in dialog msg files to specify gender-based alternative text.
; Text must be enclosed in angle brackets, e.g. <MaleText^FemaleText>.
gender_words=0
; Set to 1 to enable the sfall StartGDialogFix behavior: talking-head dialogs honor the start_gdialog mood argument unless it is -1.
; When set to 0, CE still applies sfall's crash fix for calling start_gdialog outside talk_p_proc.
start_gdialog_fix=0
[main_menu]
; Custom version string shown on the main menu. Supports up to two %d placeholders for the game version numbers.
+64 -29
View File
@@ -1,3 +1,6 @@
; This is an example configuration for Fallout CE. If you'd like to use the recommended settings without
; disturbing your game config, copy everything from [ui] and [qol] into your fallout2.cfg
[debug]
;Set this to a valid path to save a copy of the console contents
console_output_path=
@@ -48,15 +51,20 @@ text_line_delay=2.000000
violence_level=3
[screen]
resolution_x=640
; Physical screen resolution of the full screen or window.
resolution_x=800
resolution_y=480
; Scale of physical pixels to logical pixels. For instance, if x/y is 1280 x 960 and scale is 2,
; the game will be rendered at 640 x 480 and then scaled up to 1280 x 960. This is useful for windowed mode on high DPI displays.
scale=1
; Set to 1 to run the game in windowed mode. Set to 0 to run in fullscreen mode.
windowed=1
[sound]
cache_size=448
debug=1
debug_sfxc=1
; Set to 1 to avoid restarting music when changing maps when the music is the same track.
gapless_music=1
initialize=1
master_volume=22281
@@ -70,7 +78,7 @@ speech=1
speech_volume=22281
[system]
art_cache_size=8
art_cache_size=32
color_cycling=1
critter_dat=critter.dat
critter_patches=data
@@ -87,60 +95,87 @@ scroll_lock=0
splash=5
[ui]
;Speed up or slow down all UI animations such as screen fade, dialog window show/hide, AC numbers roll, etc.
; Speed up or slow down all UI animations such as screen fade, dialog window show/hide, AC numbers roll, etc.
anim_speed=2.000000
;To use more than one save slot for quick saving (F6 key) without picking a slot beforehand
;Quick save will cyclically overwrite saves from the first slot on the specified page to the last slot on the n-th page
;auto_quick_save sets how many pages you want to use for quick saving (valid range: 1..10)
;Set to 0 to disable
; To use more than one save slot for quick saving (F6 key) without picking a slot beforehand
; Quick save will cyclically overwrite saves from the first slot on the specified page to the last slot on the n-th page
; auto_quick_save sets how many pages you want to use for quick saving (valid range: 1..10)
; Set to 0 to disable
auto_quick_save=3
;Set to 1 to display additional points of damage from Bonus HtH/Ranged Damage perks in the inventory
; Set to 1 to display additional points of damage from Bonus HtH/Ranged Damage perks in the inventory
display_bonus_damage=1
;Set to 1 to get notification of karma changes in the notification window
; Set to 1 to get notification of karma changes in the notification window
display_karma_changes=0
;Set to 1 to use the hi-res dialog border/background at resolutions above 640x480. Requires art\intrface\HR_ALLTLK.FRM (for example from f2_res.dat)
; Set to 1 to use the hi-res dialog border/background at resolutions above 640x480. Requires art\intrface\HR_ALLTLK.FRM (for example from f2_res.dat)
dialog_border=1
;Set to one to hide areas outside map bounds when using higher than 640x480 resolution, and to zero to disable
; Set to one to hide areas outside map bounds when using higher than 640x480 resolution, and to zero to disable
enable_high_resolution_stencil=1
;Set to 1 to extend the action points bar to show up to 16 AP instead of 10 (requires iface_apbar_e.frm)
extend_ap_bar=0
;Set to 1 to expand the barter/trade window vertically, adding a 4th item slot per side (requires barter_e.frm, trade_e.frm, and a screen height of at least 528 pixels)
expand_barter_window=0
; Set to 1 to extend the action points bar to show up to 16 AP instead of 10 (requires iface_apbar_e.frm)
extend_ap_bar=1
; Set to 1 to expand the barter/trade window vertically, adding a 4th item slot per side (requires barter_e.frm, trade_e.frm, and a screen height of at least 528 pixels)
expand_barter_window=1
; if iface_bar_mode=0 - the bottom of the map view window sits at the top of the IFACE Bar.
; if iface_bar_mode=1 - the bottom of the map view window extends to the base of the screen and is overlapped by the IFACE Bar.
iface_bar_mode=0
; 0 = no side art, 1 = metal, 2 = leather, 3-6 = alternative metal, 7 = alternative leather, 8 = brown metal
iface_bar_side_art=2
; 0 = Iface-bar side graphics extend from the Iface-Bar to the Screen edges.
; 1 = Iface-bar side graphics extend from the Screen edges to the Iface-Bar
iface_bar_sides_ori=0
; Set to 640 to use the original interface bar width, or 800 to use the 800pix wide asset from f2_res.dat.
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.
; 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
; Set to 1 to display numbered dialogue options
numbers_in_dialogue=0
;Set to 1 to skip the 3 opening movies
;Set to 2 to also skip the splash screen
; Set to 1 to skip the 3 opening movies
; Set to 2 to also skip the splash screen
skip_opening_movies=0
; Set to 1 to scale the splash screen to fit the screen while preserving aspect ratio.
; Set to 2 to fill the screen
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.
; 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
; 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.
; This also enables npc equipment management on the loot screen
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.
; 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
;Set to 1 to show a Help option in the in-game options menu which opens the F1 help screen.
; Set to 1 to show a Help option in the in-game options menu which opens the F1 help screen.
in_game_menu_help=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. 0 disables walking
; This is the number of empty tiles between the player and the target
; This is the number of empty tiles between the player and the target. 3 is the default
use_walk_distance=3
; Allow opening party member inventory from the long-press world menu.
; Allow opening party member inventory from the long-press context menu.
party_trade_from_menu=1
; Allow switching to companions' inventories in loot screens (and barter, in the future)
party_loot_and_barter=1
@@ -0,0 +1,96 @@
#include "sfall.h"
#include "dik.h"
#include "define_lite.h"
#include "define_extra.h"
#include "../test_utils.h"
#define DAMAGE_TARGET_OVERRIDE 13
#define DAMAGE_SOURCE_OVERRIDE 7
#define FLAGS_SOURCE_OVERRIDE DAM_HIT
#define KNOCKBACK_OVERRIDE 2
#define TEST_LIGHT_DISTANCE 3
#define TEST_LIGHT_INTENSITY 0x10000
variable object_data_attack_hook_seen := false;
variable object_data_attack_reported := false;
procedure object_data_roundtrip_test begin
variable old_light_distance := get_object_data(dude_obj, OBJ_DATA_LIGHT_DISTANCE);
variable old_light_intensity := get_object_data(dude_obj, OBJ_DATA_LIGHT_INTENSITY);
set_object_data(dude_obj, OBJ_DATA_LIGHT_DISTANCE, TEST_LIGHT_DISTANCE);
set_object_data(dude_obj, OBJ_DATA_LIGHT_INTENSITY, TEST_LIGHT_INTENSITY);
call assertEquals("object light distance set", get_object_data(dude_obj, OBJ_DATA_LIGHT_DISTANCE), TEST_LIGHT_DISTANCE);
call assertEquals("object light intensity set", get_object_data(dude_obj, OBJ_DATA_LIGHT_INTENSITY), TEST_LIGHT_INTENSITY);
set_object_data(dude_obj, OBJ_DATA_LIGHT_DISTANCE, old_light_distance);
set_object_data(dude_obj, OBJ_DATA_LIGHT_INTENSITY, old_light_intensity);
call assertEquals("object light distance restored", get_object_data(dude_obj, OBJ_DATA_LIGHT_DISTANCE), old_light_distance);
call assertEquals("object light intensity restored", get_object_data(dude_obj, OBJ_DATA_LIGHT_INTENSITY), old_light_intensity);
end
procedure report_object_data_attack_results begin
if (object_data_attack_reported) then return;
object_data_attack_reported := true;
call report_test_results("object_data_attack");
end
procedure combatdamage_handler begin
variable target := get_sfall_arg_at(0);
variable attacker := get_sfall_arg_at(1);
variable attack_data := get_sfall_arg_at(12);
object_data_attack_hook_seen := true;
call assertTrue("attack data pointer exists", attack_data != 0);
call assertEquals("attack source matches hook attacker", get_object_data(attack_data, C_ATTACK_SOURCE), attacker);
call assertEquals("attack target matches hook target", get_object_data(attack_data, C_ATTACK_TARGET), target);
call assertEquals("combat_data returns same attack data", combat_data, attack_data);
set_object_data(attack_data, C_ATTACK_DAMAGE_TARGET, DAMAGE_TARGET_OVERRIDE);
set_object_data(attack_data, C_ATTACK_DAMAGE_SOURCE, DAMAGE_SOURCE_OVERRIDE);
set_object_data(attack_data, C_ATTACK_FLAGS_SOURCE, FLAGS_SOURCE_OVERRIDE);
set_object_data(attack_data, C_ATTACK_KNOCKBACK_VALUE, KNOCKBACK_OVERRIDE);
call assertEquals("attack target damage set", get_object_data(attack_data, C_ATTACK_DAMAGE_TARGET), DAMAGE_TARGET_OVERRIDE);
call assertEquals("attack source damage set", get_object_data(attack_data, C_ATTACK_DAMAGE_SOURCE), DAMAGE_SOURCE_OVERRIDE);
call assertEquals("attack source flags set", get_object_data(attack_data, C_ATTACK_FLAGS_SOURCE), FLAGS_SOURCE_OVERRIDE);
call assertEquals("attack knockback set", get_object_data(attack_data, C_ATTACK_KNOCKBACK_VALUE), KNOCKBACK_OVERRIDE);
set_sfall_return(DAMAGE_TARGET_OVERRIDE);
set_sfall_return(DAMAGE_SOURCE_OVERRIDE);
set_sfall_return(get_object_data(attack_data, C_ATTACK_FLAGS_TARGET));
set_sfall_return(FLAGS_SOURCE_OVERRIDE);
set_sfall_return(KNOCKBACK_OVERRIDE);
call report_object_data_attack_results();
end
procedure keypress_handler begin
variable
pressed := get_sfall_arg_at(0),
key := get_sfall_arg_at(1);
if (not pressed) then return;
if (key != DIK_J) then return;
if (not object_data_attack_hook_seen) then begin
display_msg("object_data_attack: enter combat and hit something to trigger HOOK_COMBATDAMAGE.");
end else begin
call report_object_data_attack_results();
end
end
procedure start begin
if (not game_loaded) then return;
display_msg("Testing get/set_object_data object fields...");
call object_data_roundtrip_test();
display_msg("object_data_attack ready: enter combat and hit something. Press J to repeat instructions.");
register_hook_proc(HOOK_KEYPRESS, keypress_handler);
register_hook_proc(HOOK_COMBATDAMAGE, combatdamage_handler);
end
+2 -2
View File
@@ -773,7 +773,7 @@ int _action_ranged(Attack* attack, int anim)
int rightItemAction;
interfaceGetItemActions(&leftItemAction, &rightItemAction);
itemRemove(attack->attacker, weapon, 1);
itemRemoveWithReason(attack->attacker, weapon, 1, RemoveInventoryObjectHookReason::Throw);
replacedWeapon = itemReplace(attack->attacker, weapon, weaponFlags & OBJECT_IN_ANY_HAND);
objectSetFid(projectile, projectileProto->fid, nullptr);
_cAIPrepWeaponItem(attack->attacker, weapon);
@@ -1477,7 +1477,7 @@ int actionUseSkill(Object* user, Object* target, int skill)
char* msg = skillsGetGenericResponse(partyMember, isDude);
Rect rect;
if (textObjectAdd(partyMember, msg, 101, _colorTable[32747], _colorTable[0], &rect) == 0) {
if (textObjectAdd(partyMember, msg, 101, COLOR_LIGHT_YELLOW, COLOR_BLACK, &rect) == 0) {
tileWindowRefreshRect(&rect, gElevation);
}
+1 -53
View File
@@ -1031,21 +1031,10 @@ static int artCacheGetFileSizeImpl(int fid, int* sizePtr)
Art art;
if (artReadHeader(&art, stream) == 0) {
*sizePtr = artGetDataSize(&art);
if (*sizePtr < 0) {
debugPrint("ART ERROR: fid %d path %s returned negative data size %d\n", fid, artFilePath, *sizePtr);
} else if (*sizePtr > 0x10000) {
debugPrint("ART INFO: fid %d path %s has large data size %d\n", fid, artFilePath, *sizePtr);
}
result = 0;
} else {
debugPrint("ART ERROR: artReadHeader failed for fid %d path %s\n", fid, artFilePath);
}
fileClose(stream);
} else {
debugPrint("ART ERROR: could not open file for fid %d path %s\n", fid, artFilePath);
}
} else {
debugPrint("ART ERROR: could not build path for fid %d\n", fid);
}
return result;
@@ -1063,32 +1052,19 @@ static int artCacheReadDataImpl(int fid, int* sizePtr, unsigned char* data)
if (artGetLocalizedPath(artFileName, &localizedPath)) {
if (artRead(localizedPath, data) == 0) {
loaded = true;
} else {
debugPrint("ART INFO: artRead localized failed for fid %d path %s\n", fid, localizedPath);
}
}
if (!loaded) {
if (artRead(artFileName, data) == 0) {
loaded = true;
} else {
debugPrint("ART INFO: artRead fallback failed for fid %d path %s\n", fid, artFileName);
}
}
if (loaded) {
*sizePtr = artGetDataSize((Art*)data);
if (*sizePtr < 0) {
debugPrint("ART ERROR: fid %d path %s read data returned negative size %d\n", fid, artFileName, *sizePtr);
} else if (*sizePtr > 0x10000) {
debugPrint("ART INFO: fid %d path %s read large data size %d\n", fid, artFileName, *sizePtr);
}
result = 0;
} else {
debugPrint("ART ERROR: failed to load ART data for fid %d path %s\n", fid, artFileName);
}
} else {
debugPrint("ART ERROR: could not build path for fid %d\n", fid);
}
return result;
@@ -1173,20 +1149,6 @@ static int artReadHeader(Art* art, File* stream)
art->dataSize = fileGetSize(stream);
}
if (art->frameCount < 0) {
debugPrint("ART WARNING: negative frameCount %d in header\n", art->frameCount);
}
if (art->dataSize < 0) {
debugPrint("ART WARNING: negative dataSize %d in header\n", art->dataSize);
}
for (int rotation = 0; rotation < ROTATION_COUNT; rotation++) {
if (art->dataOffsets[rotation] < 0) {
debugPrint("ART WARNING: negative dataOffset[%d] %d in header\n", rotation, art->dataOffsets[rotation]);
}
}
return 0;
}
@@ -1477,13 +1439,6 @@ int artRead(const char* path, unsigned char* data)
return -3;
}
int totalAllocSize = artGetDataSize(art);
if (totalAllocSize <= 0) {
debugPrint("ART ERROR: artRead computed invalid totalAllocSize %d for %s\n", totalAllocSize, path);
fileClose(stream);
return -5;
}
int currentPadding = paddingForSize(sizeof(Art));
int previousPadding = 0;
@@ -1493,14 +1448,7 @@ int artRead(const char* path, unsigned char* data)
if (index == 0 || art->dataOffsets[index - 1] != art->dataOffsets[index]) {
art->padding[index] += previousPadding;
currentPadding += previousPadding;
long long frameDataOffset = static_cast<long long>(sizeof(Art)) + art->dataOffsets[index] + art->padding[index];
if (frameDataOffset < 0 || frameDataOffset >= totalAllocSize) {
debugPrint("ART ERROR: invalid frame data destination for %s at rotation %d: offset=%lld base=%p size=%d\n", path, index, frameDataOffset, data, totalAllocSize);
fileClose(stream);
return -5;
}
unsigned char* frameDataPtr = data + frameDataOffset;
if (artReadFrameData(frameDataPtr, stream, art->frameCount, &previousPadding) != 0) {
if (artReadFrameData(data + sizeof(Art) + art->dataOffsets[index] + art->padding[index], stream, art->frameCount, &previousPadding) != 0) {
fileClose(stream);
return -5;
}
+25 -25
View File
@@ -323,10 +323,10 @@ void automapShow(bool isInGame, bool isUsingScanner)
int color;
if (isInGame) {
color = _colorTable[8456];
color = COLOR_DARK_GREY;
_obj_process_seen();
} else {
color = _colorTable[22025];
color = COLOR_SAND;
}
int oldFont = fontGetCurrent();
@@ -472,7 +472,7 @@ void automapShow(bool isInGame, bool isUsingScanner)
// 17 - The motion sensor is not installed.
// 18 - The motion sensor has no charges remaining.
const char* title = getmsg(&gMiscMessageList, &messageListItem, scanner != nullptr ? 18 : 17);
showDialogBox(title, nullptr, 0, 165, 140, _colorTable[32328], nullptr, _colorTable[32328], 0);
showDialogBox(title, nullptr, 0, 165, 140, COLOR_AMBER, nullptr, COLOR_AMBER, 0);
}
}
@@ -522,9 +522,9 @@ static void automapRenderInMapWindow(int window, int elevation, unsigned char* b
{
int color;
if ((flags & AUTOMAP_IN_GAME) != 0) {
color = _colorTable[8456];
color = COLOR_DARK_GREY;
} else {
color = _colorTable[22025];
color = COLOR_SAND;
}
windowFill(window, 0, 0, AUTOMAP_WINDOW_WIDTH, AUTOMAP_WINDOW_HEIGHT, color);
@@ -546,24 +546,24 @@ static void automapRenderInMapWindow(int window, int elevation, unsigned char* b
&& (object->flags & OBJECT_HIDDEN) == 0
&& (flags & AUTOMAP_WITH_SCANNER) != 0
&& (object->data.critter.combat.results & DAM_DEAD) == 0) {
objectColor = _colorTable[31744];
objectColor = COLOR_RED;
} else {
if ((object->flags & OBJECT_SEEN) == 0) {
continue;
}
if (object->pid == PROTO_ID_EXIT_GRID_MAP_MARKER) {
objectColor = _colorTable[32328];
objectColor = COLOR_AMBER;
} else if (objectType == OBJ_TYPE_WALL) {
objectColor = _colorTable[992];
objectColor = COLOR_GREEN;
} else if (objectType == OBJ_TYPE_SCENERY
&& (flags & AUTOMAP_WTH_HIGH_DETAILS) != 0
&& object->pid != PROTO_ID_BLOCK_HEX_AUTO_INVISO) {
objectColor = _colorTable[480];
objectColor = COLOR_DARK_GREEN;
} else if (object == gDude) {
objectColor = _colorTable[31744];
objectColor = COLOR_RED;
} else {
objectColor = _colorTable[0];
objectColor = COLOR_BLACK;
}
}
}
@@ -572,29 +572,29 @@ static void automapRenderInMapWindow(int window, int elevation, unsigned char* b
if ((flags & AUTOMAP_IN_GAME) == 0) {
switch (objectType) {
case OBJ_TYPE_ITEM:
objectColor = _colorTable[6513];
objectColor = COLOR_LIGHT_BLUE;
break;
case OBJ_TYPE_CRITTER:
objectColor = _colorTable[28672];
objectColor = COLOR_RED_2;
break;
case OBJ_TYPE_SCENERY:
objectColor = _colorTable[448];
objectColor = COLOR_LIGHT_GREEN;
break;
case OBJ_TYPE_WALL:
objectColor = _colorTable[12546];
objectColor = COLOR_DARK_BROWN;
break;
case OBJ_TYPE_MISC:
objectColor = _colorTable[31650];
objectColor = COLOR_LIGHT_GOLD;
break;
default:
objectColor = _colorTable[0];
objectColor = COLOR_BLACK;
}
}
if (objectColor != _colorTable[0]) {
if (objectColor != COLOR_BLACK) {
unsigned char* pixel = windowBuffer + pixelOffset;
if ((flags & AUTOMAP_IN_GAME) != 0) {
if (*pixel != _colorTable[992] || objectColor != _colorTable[480]) {
if (*pixel != COLOR_GREEN || objectColor != COLOR_DARK_GREEN) {
pixel[0] = objectColor;
pixel[1] = objectColor;
}
@@ -620,17 +620,17 @@ static void automapRenderInMapWindow(int window, int elevation, unsigned char* b
int textColor;
if ((flags & AUTOMAP_IN_GAME) != 0) {
textColor = _colorTable[992];
textColor = COLOR_GREEN;
} else {
textColor = _colorTable[12546];
textColor = COLOR_DARK_BROWN;
}
if (mapGetCurrentMap() != -1) {
char* areaName = mapGetCityName(mapGetCurrentMap());
windowDrawText(window, areaName, 240, 150, 380, textColor | 0x2000000);
windowDrawText(window, areaName, 240, 150, 380, textColor | DRAW_TEXT_FLAG_NO_BG);
char* mapName = mapGetName(mapGetCurrentMap(), elevation);
windowDrawText(window, mapName, 240, 150, 396, textColor | 0x2000000);
windowDrawText(window, mapName, 240, 150, 396, textColor | DRAW_TEXT_FLAG_NO_BG);
}
windowRefresh(window);
@@ -643,8 +643,8 @@ int automapRenderInPipboyWindow(int window, int map, int elevation)
{
unsigned char* windowBuffer = windowGetBuffer(window) + 640 * AUTOMAP_PIPBOY_VIEW_Y + AUTOMAP_PIPBOY_VIEW_X;
unsigned char wallColor = _colorTable[992];
unsigned char sceneryColor = _colorTable[480];
unsigned char wallColor = COLOR_GREEN;
unsigned char sceneryColor = COLOR_DARK_GREEN;
gAutomapEntry.data = (unsigned char*)internal_malloc(11024);
if (gAutomapEntry.data == nullptr) {
+4 -68
View File
@@ -24,7 +24,6 @@ static bool cacheEntryInit(CacheEntry* cacheEntry);
static bool cacheEntryFree(Cache* cache, CacheEntry* cacheEntry);
static bool cacheClean(Cache* cache);
static bool cacheResetStatistics(Cache* cache);
static bool cacheValidateHeap(Cache* cache, const char* operation, int key, int size, int heapHandleIndex, const void* data);
static bool cacheEnsureSize(Cache* cache, int size);
static bool cacheSweep(Cache* cache);
static bool cacheSetCapacity(Cache* cache, int newCapacity);
@@ -223,21 +222,10 @@ static bool cacheFetchEntryForKey(Cache* cache, int key, int* indexPtr)
do {
int size;
if (cache->sizeProc(key, &size) != 0) {
debugPrint("Cache ERROR: sizeProc failed for key %d\n", key);
break;
}
if (size < 0) {
debugPrint("Cache ERROR: invalid size %d for key %d\n", size, key);
break;
}
if (size > 0x10000) {
debugPrint("Cache INFO: key %d requested large allocation size %d\n", key, size);
}
if (!cacheEnsureSize(cache, size)) {
debugPrint("Cache INFO: cacheEnsureSize failed for key %d size %d\n", key, size);
break;
}
@@ -274,45 +262,16 @@ static bool cacheFetchEntryForKey(Cache* cache, int key, int* indexPtr)
break;
}
bool locked = false;
do {
if (!heapLock(&(cache->heap), cacheEntry->heapHandleIndex, &(cacheEntry->data))) {
break;
}
locked = true;
int requestedSize = size;
int allocatedSize = (requestedSize + 3) & ~3;
if (!cacheValidateHeap(cache, "after lock before read", key, size, cacheEntry->heapHandleIndex, cacheEntry->data)) {
break;
}
if (cache->readProc(key, &size, cacheEntry->data) != 0) {
break;
}
if (!cacheValidateHeap(cache, "after read before unlock", key, size, cacheEntry->heapHandleIndex, cacheEntry->data)) {
break;
}
if (size < 0 || size > allocatedSize) {
debugPrint("Cache ERROR: readProc returned invalid size for key %d: requested=%d allocated=%d returned=%d handle=%d data=%p\n",
key,
requestedSize,
allocatedSize,
size,
cacheEntry->heapHandleIndex,
cacheEntry->data);
break;
}
heapUnlock(&(cache->heap), cacheEntry->heapHandleIndex);
locked = false;
if (!cacheValidateHeap(cache, "after unlock", key, size, cacheEntry->heapHandleIndex, cacheEntry->data)) {
break;
}
cacheEntry->size = size;
cacheEntry->key = key;
@@ -339,9 +298,7 @@ static bool cacheFetchEntryForKey(Cache* cache, int key, int* indexPtr)
return true;
} while (0);
if (locked) {
heapUnlock(&(cache->heap), cacheEntry->heapHandleIndex);
}
heapUnlock(&(cache->heap), cacheEntry->heapHandleIndex);
} while (0);
// NOTE: Uninline.
@@ -399,9 +356,9 @@ static int cacheFindIndexForKey(Cache* cache, int key, int* indexPtr)
}
if (cmp > 0) {
l = mid + 1;
l = l + 1;
} else {
r = mid - 1;
r = r - 1;
}
} while (r >= l);
@@ -424,7 +381,6 @@ static bool cacheEntryInit(CacheEntry* cacheEntry)
cacheEntry->hits = 0;
cacheEntry->flags = 0;
cacheEntry->mru = 0;
cacheEntry->heapHandleIndex = -1;
return true;
}
@@ -433,10 +389,8 @@ static bool cacheEntryInit(CacheEntry* cacheEntry)
// 0x420740 cache_destroy_item
static bool cacheEntryFree(Cache* cache, CacheEntry* cacheEntry)
{
if (cacheEntry->heapHandleIndex != -1) {
if (cacheEntry->data != nullptr) {
heapBlockDeallocate(&(cache->heap), &(cacheEntry->heapHandleIndex));
cacheEntry->heapHandleIndex = -1;
cacheEntry->data = nullptr;
}
internal_free(cacheEntry);
@@ -491,24 +445,6 @@ static bool cacheResetStatistics(Cache* cache)
return true;
}
static bool cacheValidateHeap(Cache* cache, const char* operation, int key, int size, int heapHandleIndex, const void* data)
{
if (heapValidate(&(cache->heap))) {
return true;
}
debugPrint("Cache ERROR: heap validation failed %s: key=%d size=%d handle=%d data=%p cacheSize=%d maxSize=%d entries=%d\n",
operation,
key,
size,
heapHandleIndex,
data,
cache->size,
cache->maxSize,
cache->entriesLength);
return false;
}
// Prepare cache for storing new entry with the specified size.
//
// 0x42084C cache_make_room
+143 -143
View File
File diff suppressed because it is too large Load Diff
+28 -28
View File
@@ -269,7 +269,7 @@ static bool characterSelectorWindowInit()
int characterSelectorWindowX = (screenGetWidth() - CS_WINDOW_WIDTH) / 2;
int characterSelectorWindowY = (screenGetHeight() - CS_WINDOW_HEIGHT) / 2;
gCharacterSelectorWindow = windowCreate(characterSelectorWindowX, characterSelectorWindowY, CS_WINDOW_WIDTH, CS_WINDOW_HEIGHT, _colorTable[0], 0);
gCharacterSelectorWindow = windowCreate(characterSelectorWindowX, characterSelectorWindowY, CS_WINDOW_WIDTH, CS_WINDOW_HEIGHT, COLOR_BLACK, 0);
if (gCharacterSelectorWindow == -1) {
return characterSelectorWindowFatalError(false);
}
@@ -635,7 +635,7 @@ static bool characterSelectorWindowRenderStats()
strcpy(text, str);
length = fontGetStringWidth(text);
fontDrawText(gCharacterSelectorWindowBuffer + CS_WINDOW_WIDTH * y + CS_WINDOW_NAME_MID_X - (length / 2), text, 160, CS_WINDOW_WIDTH, _colorTable[992]);
fontDrawText(gCharacterSelectorWindowBuffer + CS_WINDOW_WIDTH * y + CS_WINDOW_NAME_MID_X - (length / 2), text, 160, CS_WINDOW_WIDTH, COLOR_GREEN);
// STRENGTH
y += vh + vh + vh;
@@ -646,13 +646,13 @@ static bool characterSelectorWindowRenderStats()
snprintf(text, sizeof(text), "%s %02d", str, value);
length = fontGetStringWidth(text);
fontDrawText(gCharacterSelectorWindowBuffer + CS_WINDOW_WIDTH * y + CS_WINDOW_PRIMARY_STAT_MID_X - length, text, length, CS_WINDOW_WIDTH, _colorTable[992]);
fontDrawText(gCharacterSelectorWindowBuffer + CS_WINDOW_WIDTH * y + CS_WINDOW_PRIMARY_STAT_MID_X - length, text, length, CS_WINDOW_WIDTH, COLOR_GREEN);
str = statGetValueDescription(value);
snprintf(text, sizeof(text), " %s", str);
length = fontGetStringWidth(text);
fontDrawText(gCharacterSelectorWindowBuffer + CS_WINDOW_WIDTH * y + CS_WINDOW_PRIMARY_STAT_MID_X, text, length, CS_WINDOW_WIDTH, _colorTable[992]);
fontDrawText(gCharacterSelectorWindowBuffer + CS_WINDOW_WIDTH * y + CS_WINDOW_PRIMARY_STAT_MID_X, text, length, CS_WINDOW_WIDTH, COLOR_GREEN);
// PERCEPTION
y += vh;
@@ -663,13 +663,13 @@ static bool characterSelectorWindowRenderStats()
snprintf(text, sizeof(text), "%s %02d", str, value);
length = fontGetStringWidth(text);
fontDrawText(gCharacterSelectorWindowBuffer + CS_WINDOW_WIDTH * y + CS_WINDOW_PRIMARY_STAT_MID_X - length, text, length, CS_WINDOW_WIDTH, _colorTable[992]);
fontDrawText(gCharacterSelectorWindowBuffer + CS_WINDOW_WIDTH * y + CS_WINDOW_PRIMARY_STAT_MID_X - length, text, length, CS_WINDOW_WIDTH, COLOR_GREEN);
str = statGetValueDescription(value);
snprintf(text, sizeof(text), " %s", str);
length = fontGetStringWidth(text);
fontDrawText(gCharacterSelectorWindowBuffer + CS_WINDOW_WIDTH * y + CS_WINDOW_PRIMARY_STAT_MID_X, text, length, CS_WINDOW_WIDTH, _colorTable[992]);
fontDrawText(gCharacterSelectorWindowBuffer + CS_WINDOW_WIDTH * y + CS_WINDOW_PRIMARY_STAT_MID_X, text, length, CS_WINDOW_WIDTH, COLOR_GREEN);
// ENDURANCE
y += vh;
@@ -680,13 +680,13 @@ static bool characterSelectorWindowRenderStats()
snprintf(text, sizeof(text), "%s %02d", str, value);
length = fontGetStringWidth(text);
fontDrawText(gCharacterSelectorWindowBuffer + CS_WINDOW_WIDTH * y + CS_WINDOW_PRIMARY_STAT_MID_X - length, text, length, CS_WINDOW_WIDTH, _colorTable[992]);
fontDrawText(gCharacterSelectorWindowBuffer + CS_WINDOW_WIDTH * y + CS_WINDOW_PRIMARY_STAT_MID_X - length, text, length, CS_WINDOW_WIDTH, COLOR_GREEN);
str = statGetValueDescription(value);
snprintf(text, sizeof(text), " %s", str);
length = fontGetStringWidth(text);
fontDrawText(gCharacterSelectorWindowBuffer + CS_WINDOW_WIDTH * y + CS_WINDOW_PRIMARY_STAT_MID_X, text, length, CS_WINDOW_WIDTH, _colorTable[992]);
fontDrawText(gCharacterSelectorWindowBuffer + CS_WINDOW_WIDTH * y + CS_WINDOW_PRIMARY_STAT_MID_X, text, length, CS_WINDOW_WIDTH, COLOR_GREEN);
// CHARISMA
y += vh;
@@ -697,13 +697,13 @@ static bool characterSelectorWindowRenderStats()
snprintf(text, sizeof(text), "%s %02d", str, value);
length = fontGetStringWidth(text);
fontDrawText(gCharacterSelectorWindowBuffer + CS_WINDOW_WIDTH * y + CS_WINDOW_PRIMARY_STAT_MID_X - length, text, length, CS_WINDOW_WIDTH, _colorTable[992]);
fontDrawText(gCharacterSelectorWindowBuffer + CS_WINDOW_WIDTH * y + CS_WINDOW_PRIMARY_STAT_MID_X - length, text, length, CS_WINDOW_WIDTH, COLOR_GREEN);
str = statGetValueDescription(value);
snprintf(text, sizeof(text), " %s", str);
length = fontGetStringWidth(text);
fontDrawText(gCharacterSelectorWindowBuffer + CS_WINDOW_WIDTH * y + CS_WINDOW_PRIMARY_STAT_MID_X, text, length, CS_WINDOW_WIDTH, _colorTable[992]);
fontDrawText(gCharacterSelectorWindowBuffer + CS_WINDOW_WIDTH * y + CS_WINDOW_PRIMARY_STAT_MID_X, text, length, CS_WINDOW_WIDTH, COLOR_GREEN);
// INTELLIGENCE
y += vh;
@@ -714,13 +714,13 @@ static bool characterSelectorWindowRenderStats()
snprintf(text, sizeof(text), "%s %02d", str, value);
length = fontGetStringWidth(text);
fontDrawText(gCharacterSelectorWindowBuffer + CS_WINDOW_WIDTH * y + CS_WINDOW_PRIMARY_STAT_MID_X - length, text, length, CS_WINDOW_WIDTH, _colorTable[992]);
fontDrawText(gCharacterSelectorWindowBuffer + CS_WINDOW_WIDTH * y + CS_WINDOW_PRIMARY_STAT_MID_X - length, text, length, CS_WINDOW_WIDTH, COLOR_GREEN);
str = statGetValueDescription(value);
snprintf(text, sizeof(text), " %s", str);
length = fontGetStringWidth(text);
fontDrawText(gCharacterSelectorWindowBuffer + CS_WINDOW_WIDTH * y + CS_WINDOW_PRIMARY_STAT_MID_X, text, length, CS_WINDOW_WIDTH, _colorTable[992]);
fontDrawText(gCharacterSelectorWindowBuffer + CS_WINDOW_WIDTH * y + CS_WINDOW_PRIMARY_STAT_MID_X, text, length, CS_WINDOW_WIDTH, COLOR_GREEN);
// AGILITY
y += vh;
@@ -731,13 +731,13 @@ static bool characterSelectorWindowRenderStats()
snprintf(text, sizeof(text), "%s %02d", str, value);
length = fontGetStringWidth(text);
fontDrawText(gCharacterSelectorWindowBuffer + CS_WINDOW_WIDTH * y + CS_WINDOW_PRIMARY_STAT_MID_X - length, text, length, CS_WINDOW_WIDTH, _colorTable[992]);
fontDrawText(gCharacterSelectorWindowBuffer + CS_WINDOW_WIDTH * y + CS_WINDOW_PRIMARY_STAT_MID_X - length, text, length, CS_WINDOW_WIDTH, COLOR_GREEN);
str = statGetValueDescription(value);
snprintf(text, sizeof(text), " %s", str);
length = fontGetStringWidth(text);
fontDrawText(gCharacterSelectorWindowBuffer + CS_WINDOW_WIDTH * y + CS_WINDOW_PRIMARY_STAT_MID_X, text, length, CS_WINDOW_WIDTH, _colorTable[992]);
fontDrawText(gCharacterSelectorWindowBuffer + CS_WINDOW_WIDTH * y + CS_WINDOW_PRIMARY_STAT_MID_X, text, length, CS_WINDOW_WIDTH, COLOR_GREEN);
// LUCK
y += vh;
@@ -748,13 +748,13 @@ static bool characterSelectorWindowRenderStats()
snprintf(text, sizeof(text), "%s %02d", str, value);
length = fontGetStringWidth(text);
fontDrawText(gCharacterSelectorWindowBuffer + CS_WINDOW_WIDTH * y + CS_WINDOW_PRIMARY_STAT_MID_X - length, text, length, CS_WINDOW_WIDTH, _colorTable[992]);
fontDrawText(gCharacterSelectorWindowBuffer + CS_WINDOW_WIDTH * y + CS_WINDOW_PRIMARY_STAT_MID_X - length, text, length, CS_WINDOW_WIDTH, COLOR_GREEN);
str = statGetValueDescription(value);
snprintf(text, sizeof(text), " %s", str);
length = fontGetStringWidth(text);
fontDrawText(gCharacterSelectorWindowBuffer + CS_WINDOW_WIDTH * y + CS_WINDOW_PRIMARY_STAT_MID_X, text, length, CS_WINDOW_WIDTH, _colorTable[992]);
fontDrawText(gCharacterSelectorWindowBuffer + CS_WINDOW_WIDTH * y + CS_WINDOW_PRIMARY_STAT_MID_X, text, length, CS_WINDOW_WIDTH, COLOR_GREEN);
y += vh; // blank line
@@ -768,13 +768,13 @@ static bool characterSelectorWindowRenderStats()
}
length = fontGetStringWidth(text);
fontDrawText(gCharacterSelectorWindowBuffer + CS_WINDOW_WIDTH * y + CS_WINDOW_SECONDARY_STAT_MID_X - length, text, length, CS_WINDOW_WIDTH, _colorTable[992]);
fontDrawText(gCharacterSelectorWindowBuffer + CS_WINDOW_WIDTH * y + CS_WINDOW_SECONDARY_STAT_MID_X - length, text, length, CS_WINDOW_WIDTH, COLOR_GREEN);
value = critterGetStat(gDude, STAT_MAXIMUM_HIT_POINTS);
snprintf(text, sizeof(text), " %d/%d", critterGetHitPoints(gDude), value);
length = fontGetStringWidth(text);
fontDrawText(gCharacterSelectorWindowBuffer + CS_WINDOW_WIDTH * y + CS_WINDOW_SECONDARY_STAT_MID_X, text, length, CS_WINDOW_WIDTH, _colorTable[992]);
fontDrawText(gCharacterSelectorWindowBuffer + CS_WINDOW_WIDTH * y + CS_WINDOW_SECONDARY_STAT_MID_X, text, length, CS_WINDOW_WIDTH, COLOR_GREEN);
// ARMOR CLASS
y += vh;
@@ -783,13 +783,13 @@ static bool characterSelectorWindowRenderStats()
strcpy(text, str);
length = fontGetStringWidth(text);
fontDrawText(gCharacterSelectorWindowBuffer + CS_WINDOW_WIDTH * y + CS_WINDOW_SECONDARY_STAT_MID_X - length, text, length, CS_WINDOW_WIDTH, _colorTable[992]);
fontDrawText(gCharacterSelectorWindowBuffer + CS_WINDOW_WIDTH * y + CS_WINDOW_SECONDARY_STAT_MID_X - length, text, length, CS_WINDOW_WIDTH, COLOR_GREEN);
value = critterGetStat(gDude, STAT_ARMOR_CLASS);
snprintf(text, sizeof(text), " %d", value);
length = fontGetStringWidth(text);
fontDrawText(gCharacterSelectorWindowBuffer + CS_WINDOW_WIDTH * y + CS_WINDOW_SECONDARY_STAT_MID_X, text, length, CS_WINDOW_WIDTH, _colorTable[992]);
fontDrawText(gCharacterSelectorWindowBuffer + CS_WINDOW_WIDTH * y + CS_WINDOW_SECONDARY_STAT_MID_X, text, length, CS_WINDOW_WIDTH, COLOR_GREEN);
// ACTION POINTS
y += vh;
@@ -801,13 +801,13 @@ static bool characterSelectorWindowRenderStats()
}
length = fontGetStringWidth(text);
fontDrawText(gCharacterSelectorWindowBuffer + CS_WINDOW_WIDTH * y + CS_WINDOW_SECONDARY_STAT_MID_X - length, text, length, CS_WINDOW_WIDTH, _colorTable[992]);
fontDrawText(gCharacterSelectorWindowBuffer + CS_WINDOW_WIDTH * y + CS_WINDOW_SECONDARY_STAT_MID_X - length, text, length, CS_WINDOW_WIDTH, COLOR_GREEN);
value = critterGetStat(gDude, STAT_MAXIMUM_ACTION_POINTS);
snprintf(text, sizeof(text), " %d", value);
length = fontGetStringWidth(text);
fontDrawText(gCharacterSelectorWindowBuffer + CS_WINDOW_WIDTH * y + CS_WINDOW_SECONDARY_STAT_MID_X, text, length, CS_WINDOW_WIDTH, _colorTable[992]);
fontDrawText(gCharacterSelectorWindowBuffer + CS_WINDOW_WIDTH * y + CS_WINDOW_SECONDARY_STAT_MID_X, text, length, CS_WINDOW_WIDTH, COLOR_GREEN);
// MELEE DAMAGE
y += vh;
@@ -816,13 +816,13 @@ static bool characterSelectorWindowRenderStats()
strcpy(text, str);
length = fontGetStringWidth(text);
fontDrawText(gCharacterSelectorWindowBuffer + CS_WINDOW_WIDTH * y + CS_WINDOW_SECONDARY_STAT_MID_X - length, text, length, CS_WINDOW_WIDTH, _colorTable[992]);
fontDrawText(gCharacterSelectorWindowBuffer + CS_WINDOW_WIDTH * y + CS_WINDOW_SECONDARY_STAT_MID_X - length, text, length, CS_WINDOW_WIDTH, COLOR_GREEN);
value = critterGetStat(gDude, STAT_MELEE_DAMAGE);
snprintf(text, sizeof(text), " %d", value);
length = fontGetStringWidth(text);
fontDrawText(gCharacterSelectorWindowBuffer + CS_WINDOW_WIDTH * y + CS_WINDOW_SECONDARY_STAT_MID_X, text, length, CS_WINDOW_WIDTH, _colorTable[992]);
fontDrawText(gCharacterSelectorWindowBuffer + CS_WINDOW_WIDTH * y + CS_WINDOW_SECONDARY_STAT_MID_X, text, length, CS_WINDOW_WIDTH, COLOR_GREEN);
y += vh; // blank line
@@ -837,13 +837,13 @@ static bool characterSelectorWindowRenderStats()
strcpy(text, str);
length = fontGetStringWidth(text);
fontDrawText(gCharacterSelectorWindowBuffer + CS_WINDOW_WIDTH * y + CS_WINDOW_SECONDARY_STAT_MID_X - length, text, length, CS_WINDOW_WIDTH, _colorTable[992]);
fontDrawText(gCharacterSelectorWindowBuffer + CS_WINDOW_WIDTH * y + CS_WINDOW_SECONDARY_STAT_MID_X - length, text, length, CS_WINDOW_WIDTH, COLOR_GREEN);
value = skillGetValue(gDude, skills[index]);
snprintf(text, sizeof(text), " %d%%", value);
length = fontGetStringWidth(text);
fontDrawText(gCharacterSelectorWindowBuffer + CS_WINDOW_WIDTH * y + CS_WINDOW_SECONDARY_STAT_MID_X, text, length, CS_WINDOW_WIDTH, _colorTable[992]);
fontDrawText(gCharacterSelectorWindowBuffer + CS_WINDOW_WIDTH * y + CS_WINDOW_SECONDARY_STAT_MID_X, text, length, CS_WINDOW_WIDTH, COLOR_GREEN);
}
// TRAITS
@@ -857,7 +857,7 @@ static bool characterSelectorWindowRenderStats()
strcpy(text, str);
length = fontGetStringWidth(text);
fontDrawText(gCharacterSelectorWindowBuffer + CS_WINDOW_WIDTH * y + CS_WINDOW_SECONDARY_STAT_MID_X - length, text, length, CS_WINDOW_WIDTH, _colorTable[992]);
fontDrawText(gCharacterSelectorWindowBuffer + CS_WINDOW_WIDTH * y + CS_WINDOW_SECONDARY_STAT_MID_X - length, text, length, CS_WINDOW_WIDTH, COLOR_GREEN);
}
fontSetCurrent(oldFont);
@@ -882,7 +882,7 @@ static bool characterSelectorWindowRenderBio()
char string[256];
while (fileReadString(string, 256, stream) && y < 260) {
fontDrawText(gCharacterSelectorWindowBuffer + CS_WINDOW_WIDTH * y + CS_WINDOW_BIO_X, string, CS_WINDOW_WIDTH - CS_WINDOW_BIO_X, CS_WINDOW_WIDTH, _colorTable[992]);
fontDrawText(gCharacterSelectorWindowBuffer + CS_WINDOW_WIDTH * y + CS_WINDOW_BIO_X, string, CS_WINDOW_WIDTH - CS_WINDOW_BIO_X, CS_WINDOW_WIDTH, COLOR_GREEN);
y += lineHeight;
}
+75
View File
@@ -33,6 +33,81 @@ void colorSetBrightness(double value);
bool _initColors();
void _colorsClose();
// Converts a 24-bit 0xRRGGBB color to a 15-bit RGB555 index used by _colorTable.
//
// _colorTable maps RGB555 indices to the nearest 8-bit palette entry so that
// callers can request a logical color without knowing the current palette layout.
//
// RGB555 packs three 5-bit channels into a 15-bit integer:
// bits 14-10 = R (red, 0-31)
// bits 9- 5 = G (green, 0-31)
// bits 4- 0 = B (blue, 0-31)
//
// Each 8-bit input channel is reduced to 5 bits by discarding the 3 least
// significant bits (>> 3), which is equivalent to dividing by 8.
// The three resulting 5-bit values are then packed into the RGB555 layout:
// R5 = (rgb >> 16 & 0xFF) >> 3 = rgb >> 19 (masked to 5 bits with & 0x1F)
// G5 = (rgb >> 8 & 0xFF) >> 3 = rgb >> 11 (masked to 5 bits with & 0x1F)
// B5 = (rgb >> 0 & 0xFF) >> 3 = rgb >> 3 (masked to 5 bits with & 0x1F)
constexpr int rgb555(int rgb)
{
return ((rgb >> 19) & 0x1F) << 10 | ((rgb >> 11) & 0x1F) << 5 | ((rgb >> 3) & 0x1F);
}
#define COLOR_LIGHT_RED _colorTable[rgb555(0xFF5252)]
#define COLOR_RED _colorTable[rgb555(0xFF0000)]
#define COLOR_RED_2 _colorTable[rgb555(0xE60000)]
#define COLOR_DARK_RED _colorTable[rgb555(0xBD1042)]
#define COLOR_WHITE _colorTable[rgb555(0xFFFFFF)]
#define COLOR_PALE_BLUE _colorTable[rgb555(0xC5D6FF)]
#define COLOR_LIGHT_GREY _colorTable[rgb555(0xA5A5A5)]
#define COLOR_LIGHT_GREY_2 _colorTable[rgb555(0x9C94A5)]
#define COLOR_GREY _colorTable[rgb555(0x8C8C8C)]
#define COLOR_GREY_2 _colorTable[rgb555(0x7B7B7B)]
#define COLOR_DARK_GREY _colorTable[rgb555(0x424242)]
#define COLOR_DARK_GREY_2 _colorTable[rgb555(0x525252)]
#define COLOR_DARK_GREY_3 _colorTable[rgb555(0x212121)]
#define COLOR_BLACK _colorTable[rgb555(0x000000)]
#define COLOR_BLACK_2 _colorTable[rgb555(0x000010)]
#define COLOR_CYAN _colorTable[rgb555(0x4AFFFF)]
#define COLOR_LIGHT_BLUE _colorTable[rgb555(0x315A8C)]
#define COLOR_BLUE _colorTable[rgb555(0x0000FF)]
#define COLOR_BLUE_2 _colorTable[rgb555(0x0000AD)]
#define COLOR_SAGE_GREEN _colorTable[rgb555(0x8CBD8C)]
#define COLOR_LIGHT_PINK _colorTable[rgb555(0xFF7B7B)]
#define COLOR_MAGENTA _colorTable[rgb555(0xFF00FF)]
#define COLOR_LIGHT_PINK_2 _colorTable[rgb555(0xF77B7B)]
#define COLOR_LIGHT_GREEN _colorTable[rgb555(0x007300)]
#define COLOR_LIGHT_GREEN_2 _colorTable[rgb555(0x429C21)]
#define COLOR_LIGHT_GREEN_3 _colorTable[rgb555(0x00C500)]
#define COLOR_GREEN _colorTable[rgb555(0x00FF00)]
#define COLOR_DARK_GREEN _colorTable[rgb555(0x007B00)]
#define COLOR_DARK_GREEN_2 _colorTable[rgb555(0x084A08)]
#define COLOR_GRASS_GREEN _colorTable[rgb555(0x7B7B29)]
#define COLOR_LIGHT_YELLOW _colorTable[rgb555(0xFFFF5A)]
#define COLOR_LIGHT_GOLD _colorTable[rgb555(0xF7EF10)]
#define COLOR_LIGHT_GOLD_2 _colorTable[rgb555(0xEFEF42)]
#define COLOR_AMBER _colorTable[rgb555(0xFF9442)]
#define COLOR_YELLOW_2 _colorTable[rgb555(0xA59C19)]
#define COLOR_SAND _colorTable[rgb555(0xAD844A)]
#define COLOR_YELLOW_GREEN _colorTable[rgb555(0xEFFF08)]
#define COLOR_DARK_YELLOW _colorTable[rgb555(0x948C19)]
#define COLOR_DARK_YELLOW_2 _colorTable[rgb555(0x736319)]
#define COLOR_DARK_YELLOW_3 _colorTable[rgb555(0x947B29)]
#define COLOR_DARK_YELLOW_4 _colorTable[rgb555(0x948C08)]
#define COLOR_LIGHT_ORANGE _colorTable[rgb555(0xFFBD7B)]
#define COLOR_DARK_BROWN _colorTable[rgb555(0x634210)]
#define COLOR_OLIVE _colorTable[rgb555(0xADAD5A)]
#define COLOR_DULL_BROWN _colorTable[rgb555(0x6B5A4A)]
} // namespace fallout
#endif /* COLOR_H */
+6 -6
View File
@@ -5527,19 +5527,19 @@ static char* hitLocationGetName(Object* critter, int hitLocation)
// 0x4261B4
static void _draw_loc_off(int a1, int a2)
{
_draw_loc_(a2, _colorTable[992]);
_draw_loc_(a2, COLOR_GREEN);
}
// 0x4261C0
static void _draw_loc_on_(int a1, int a2)
{
_draw_loc_(a2, _colorTable[31744]);
_draw_loc_(a2, COLOR_RED);
}
// 0x4261CC
static void _draw_loc_(int eventCode, int color)
{
color |= 0x3000000;
color |= (DRAW_TEXT_FLAG_REFRESH | DRAW_TEXT_FLAG_NO_BG);
if (eventCode >= 4) {
char* name = hitLocationGetName(gCalledShotCritter, _hit_loc_right[eventCode - 4]);
@@ -5578,7 +5578,7 @@ static int calledShotSelectHitLocation(Object* critter, int* hitLocation, int hi
calledShotWindowY,
CALLED_SHOT_WINDOW_WIDTH,
CALLED_SHOT_WINDOW_HEIGHT,
_colorTable[0],
COLOR_BLACK,
WINDOW_MODAL);
if (gCalledShotWindow == -1) {
return -1;
@@ -5655,14 +5655,14 @@ static int calledShotSelectHitLocation(Object* critter, int* hitLocation, int hi
btn = buttonCreate(gCalledShotWindow, 33, _call_ty[index] - 90, 128, 20, index, index, -1, index, nullptr, nullptr, nullptr, 0);
buttonSetMouseCallbacks(btn, _draw_loc_on_, _draw_loc_off, nullptr, nullptr);
_draw_loc_(index, _colorTable[992]);
_draw_loc_(index, COLOR_GREEN);
probability = _determine_to_hit(gDude, critter, _hit_loc_right[index], hitMode);
_print_tohit(windowBuffer + CALLED_SHOT_WINDOW_WIDTH * (_call_ty[index] - 86) + 453, CALLED_SHOT_WINDOW_WIDTH, probability);
btn = buttonCreate(gCalledShotWindow, 341, _call_ty[index] - 90, 128, 20, index + 4, index + 4, -1, index + 4, nullptr, nullptr, nullptr, 0);
buttonSetMouseCallbacks(btn, _draw_loc_on_, _draw_loc_off, nullptr, nullptr);
_draw_loc_(index + 4, _colorTable[992]);
_draw_loc_(index + 4, COLOR_GREEN);
}
windowRefresh(gCalledShotWindow);
+26 -61
View File
@@ -53,41 +53,7 @@ static constexpr int kChemUseAlwaysChance = 100;
static constexpr int kRandomDrugPickingArraySize = 3;
static std::unordered_set<Object*> burstDisabledCritters;
typedef struct AiMessageRange {
int start;
int end;
} AiMessageRange;
typedef struct AiPacket {
char* name;
int packet_num;
int max_dist;
int min_to_hit;
int min_hp;
int aggression;
int hurt_too_much;
int secondary_freq;
int called_freq;
int font;
int color;
int outline_color;
int chance;
AiMessageRange run;
AiMessageRange move;
AiMessageRange attack;
AiMessageRange miss;
AiMessageRange hit[HIT_LOCATION_SPECIFIC_COUNT];
int area_attack_mode;
int run_away_mode;
int best_weapon;
int distance;
int attack_who;
int chem_use;
int chem_primary_desire[AI_PACKET_CHEM_PRIMARY_DESIRE_COUNT];
int disposition;
char* body_type;
char* general_type;
} AiPacket;
static_assert(AI_PACKET_CHEM_PRIMARY_DESIRE_COUNT == sizeof(AiPacket::chem_primary_desire) / sizeof(AiPacket::chem_primary_desire[0]));
typedef struct AiRetargetData {
Object* source;
@@ -108,7 +74,6 @@ static int _cai_match_str_to_list(const char* str, const char** list, int count,
static void aiPacketInit(AiPacket* ai);
static int aiPacketRead(File* stream, AiPacket* ai);
static int aiPacketWrite(File* stream, AiPacket* ai);
static AiPacket* aiGetPacket(Object* obj);
static AiPacket* aiGetPacketByNum(int aiPacketNum);
static int _ai_magic_hands(Object* a1, Object* a2, int num);
static int _ai_check_drugs(Object* critter);
@@ -738,7 +703,7 @@ char* combat_ai_name(int packet_num)
// Get ai from object
//
// 0x4280B4
static AiPacket* aiGetPacket(Object* obj)
AiPacket* aiGetPacket(Object* obj)
{
// NOTE: Uninline.
AiPacket* ai = aiGetPacketByNum(obj->data.critter.combat.aiPacket);
@@ -1046,7 +1011,7 @@ static int _ai_check_drugs(Object* critter)
int drugPid = drug->pid;
if (itemIsHealing(drugPid)) {
if (itemRemove(critter, drug, 1) == 0) {
if (itemRemoveWithReason(critter, drug, 1, RemoveInventoryObjectHookReason::AIUseDrugOn) == 0) {
if (drugItemTakeDrug(critter, drug) == -1) {
itemAdd(critter, drug, 1);
} else {
@@ -1143,7 +1108,7 @@ static int _ai_check_drugs(Object* critter)
availableDrugs[index] = availableDrugs[*availableDrugsCountPtr - 1];
*availableDrugsCountPtr -= 1;
if (itemRemove(critter, drug, 1) == 0) {
if (itemRemoveWithReason(critter, drug, 1, RemoveInventoryObjectHookReason::AIUseDrugOn) == 0) {
if (drugItemTakeDrug(critter, drug) == -1) {
itemAdd(critter, drug, 1);
} else {
@@ -1188,7 +1153,7 @@ static int _ai_check_drugs(Object* critter)
break;
}
if (itemRemove(critter, lastItem, 1) == 0) {
if (itemRemoveWithReason(critter, lastItem, 1, RemoveInventoryObjectHookReason::AIUseDrugOn) == 0) {
if (drugItemTakeDrug(critter, lastItem) == -1) {
itemAdd(critter, lastItem, 1);
} else {
@@ -3561,6 +3526,25 @@ void _combatai_check_retaliation(Object* a1, Object* a2)
}
}
static int adjustPerceptionDistanceForDudeSneak(int maxDistance, Object* target)
{
if (target != gDude) {
return maxDistance;
}
if (dudeIsSneaking()) {
int sneak = skillGetValue(gDude, SKILL_SNEAK);
maxDistance /= 4;
if (sneak > 120) {
maxDistance -= 1;
}
} else if (dudeHasState(DUDE_STATE_SNEAKING)) {
maxDistance = maxDistance * 2 / 3;
}
return maxDistance;
}
// 0x42BA04
PerceptionResult isWithinPerceptionDetailed(Object* critter, Object* target, PerceptionType type)
{
@@ -3570,23 +3554,13 @@ PerceptionResult isWithinPerceptionDetailed(Object* critter, Object* target, Per
int distance = objectGetDistanceBetween(target, critter);
int perception = critterGetStat(critter, STAT_PERCEPTION);
int sneak = skillGetValue(target, SKILL_SNEAK);
if (_can_see(critter, target)) {
int maxDistance = perception * 5;
if ((target->flags & OBJECT_TRANS_GLASS) != 0) {
maxDistance /= 2;
}
if (target == gDude) {
if (dudeIsSneaking()) {
maxDistance /= 4;
if (sneak > 120) {
maxDistance -= 1;
}
} else if (dudeHasState(DUDE_STATE_SNEAKING)) {
maxDistance = maxDistance * 2 / 3;
}
}
maxDistance = adjustPerceptionDistanceForDudeSneak(maxDistance, target);
if (distance <= maxDistance) {
return scriptHooks_WithinPerception(critter, target, type, PERCEPTION_IN_RANGE);
@@ -3600,16 +3574,7 @@ PerceptionResult isWithinPerceptionDetailed(Object* critter, Object* target, Per
maxDistance = perception;
}
if (target == gDude) {
if (dudeIsSneaking()) {
maxDistance /= 4;
if (sneak > 120) {
maxDistance -= 1;
}
} else if (dudeHasState(DUDE_STATE_SNEAKING)) {
maxDistance = maxDistance * 2 / 3;
}
}
maxDistance = adjustPerceptionDistanceForDudeSneak(maxDistance, target);
if (distance <= maxDistance) {
return scriptHooks_WithinPerception(critter, target, type, PERCEPTION_IN_RANGE);

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