mirror of
https://github.com/fallout2-ce/fallout2-ce.git
synced 2026-07-27 16:47:11 -07:00
Compare commits
81
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a59d88f9f5 | ||
|
|
7767aabdce | ||
|
|
9cb2b83db1 | ||
|
|
75ca12e07c | ||
|
|
f85e8ba67b | ||
|
|
07876ddaf9 | ||
|
|
7288f8eeb7 | ||
|
|
1a46d0f59f | ||
|
|
7c2dfa9af1 | ||
|
|
d681e7be69 | ||
|
|
8294e15d3c | ||
|
|
4278d1a1d0 | ||
|
|
b2675ad1ac | ||
|
|
5dafa019c9 | ||
|
|
8888fc46d6 | ||
|
|
001bdbee5c | ||
|
|
40471e5a0b | ||
|
|
ad776876a1 | ||
|
|
224f1088f2 | ||
|
|
05b692f403 | ||
|
|
190906bb5f | ||
|
|
41dd2ccd81 | ||
|
|
04e93cabd7 | ||
|
|
a31af0671d | ||
|
|
72df28f53a | ||
|
|
7e3bd36f86 | ||
|
|
94ce0fbe11 | ||
|
|
a368f27ad4 | ||
|
|
d8d411e847 | ||
|
|
5eef9af7a9 | ||
|
|
0419efad8f | ||
|
|
21f432afbb | ||
|
|
aed05c0daf | ||
|
|
8a8b54d4b7 | ||
|
|
8ee543ec16 | ||
|
|
161bb82f30 | ||
|
|
22bd1ffbba | ||
|
|
f46f1c78d4 | ||
|
|
3d0e206825 | ||
|
|
47a9891e0e | ||
|
|
f1e5cde84f | ||
|
|
015909fdef | ||
|
|
1e91b401f1 | ||
|
|
29a0be50f4 | ||
|
|
9e3ce4cd8d | ||
|
|
592882b7b8 | ||
|
|
cbd7b45736 | ||
|
|
220fb8e190 | ||
|
|
5a29fc1815 | ||
|
|
a083779fb2 | ||
|
|
95d254cd45 | ||
|
|
0d8f4cc711 | ||
|
|
aabc971dd5 | ||
|
|
b069e3b941 | ||
|
|
823f72492f | ||
|
|
e5f3734e08 | ||
|
|
e37522d388 | ||
|
|
07a361f63a | ||
|
|
515bec4c82 | ||
|
|
42bfdc7343 | ||
|
|
0fd5e59575 | ||
|
|
0d0578f111 | ||
|
|
dacda99b98 | ||
|
|
dfb0463b02 | ||
|
|
1903e04c6e | ||
|
|
abec3681b0 | ||
|
|
5a9f8fb46c | ||
|
|
3659c834ae | ||
|
|
196553ec46 | ||
|
|
57f1229a51 | ||
|
|
413c843ff5 | ||
|
|
995102d515 | ||
|
|
dd867446ea | ||
|
|
4f91e38cc0 | ||
|
|
d8dc05597b | ||
|
|
7d9e1612c6 | ||
|
|
848bd97e42 | ||
|
|
5c5512277d | ||
|
|
016f853c0a | ||
|
|
9edbf0dd72 | ||
|
|
36af4b2ea8 |
@@ -89,6 +89,7 @@ jobs:
|
||||
folder: fallout2-ce-ems/dist
|
||||
target-folder: ${{ steps.set-path.outputs.DEST_DIR }}
|
||||
clean: false
|
||||
single-commit: true
|
||||
|
||||
- name: Comment preview URL on PR
|
||||
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository
|
||||
|
||||
@@ -313,7 +313,7 @@ jobs:
|
||||
windows:
|
||||
name: Windows (${{ matrix.arch }})
|
||||
|
||||
runs-on: windows-2019
|
||||
runs-on: windows-2025
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@@ -328,19 +328,28 @@ jobs:
|
||||
- name: Clone
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install Visual Studio 2019 Build Tools with v141_xp
|
||||
shell: cmd
|
||||
run: |
|
||||
choco install visualstudio2019buildtools -y --package-parameters="'--add Microsoft.VisualStudio.Workload.VCTools --add Microsoft.VisualStudio.Component.VC.v141.x86.x64 --add Microsoft.VisualStudio.Component.VC.v141.xp --add Microsoft.VisualStudio.Component.VC.Tools.x86.x64 --add Microsoft.VisualStudio.Component.Windows10SDK.10240 --add Microsoft.VisualStudio.ComponentGroup.NativeDesktop.WinXP --add Microsoft.VisualStudio.Component.WinXP'"
|
||||
|
||||
- name: Cache cmake build
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: out
|
||||
key: windows-${{ matrix.arch }}-cmake-v2
|
||||
key: windows-${{ matrix.arch }}-cmake-v3
|
||||
|
||||
- name: Configure
|
||||
shell: cmd
|
||||
run: |
|
||||
cmake --preset windows-${{ matrix.arch }}
|
||||
call "C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\Common7\Tools\VsDevCmd.bat" ^
|
||||
&& cmake --preset windows-${{ matrix.arch }}
|
||||
|
||||
- name: Build
|
||||
shell: cmd
|
||||
run: |
|
||||
cmake --build --preset windows-${{ matrix.arch }}-release
|
||||
call "C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\Common7\Tools\VsDevCmd.bat" ^
|
||||
&& cmake --build --preset windows-${{ matrix.arch }}-release
|
||||
|
||||
- name: Change executable file name
|
||||
run: |
|
||||
|
||||
@@ -29,5 +29,18 @@ jobs:
|
||||
run: |
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
git commit -am "đźš® Remove preview for closed PR #${{ github.event.pull_request.number }}"
|
||||
git push
|
||||
|
||||
CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
|
||||
echo "Current branch: $CURRENT_BRANCH"
|
||||
|
||||
if [ "$CURRENT_BRANCH" != "gh-pages" ]; then
|
||||
echo "❌ Refusing to force-push to non-gh-pages branch: $CURRENT_BRANCH"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if git diff --cached --quiet; then
|
||||
echo "No changes to commit"
|
||||
else
|
||||
git commit --amend -m "đźš® Remove preview for closed PR #${{ github.event.pull_request.number }}"
|
||||
git push --force
|
||||
fi
|
||||
@@ -243,7 +243,7 @@ jobs:
|
||||
windows:
|
||||
name: Windows (${{ matrix.arch }})
|
||||
|
||||
runs-on: windows-2019
|
||||
runs-on: windows-2025
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@@ -258,6 +258,11 @@ jobs:
|
||||
- name: Clone
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install Visual Studio 2019 Build Tools with v141_xp
|
||||
shell: cmd
|
||||
run: |
|
||||
choco install visualstudio2019buildtools -y --package-parameters="'--add Microsoft.VisualStudio.Workload.VCTools --add Microsoft.VisualStudio.Component.VC.v141.x86.x64 --add Microsoft.VisualStudio.Component.VC.v141.xp --add Microsoft.VisualStudio.Component.VC.Tools.x86.x64 --add Microsoft.VisualStudio.Component.Windows10SDK.10240 --add Microsoft.VisualStudio.ComponentGroup.NativeDesktop.WinXP --add Microsoft.VisualStudio.Component.WinXP'"
|
||||
|
||||
- name: Cache cmake build
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
@@ -265,19 +270,16 @@ jobs:
|
||||
key: windows-${{ matrix.arch }}-cmake-v1
|
||||
|
||||
- name: Configure
|
||||
shell: cmd
|
||||
run: |
|
||||
cmake \
|
||||
-B build \
|
||||
-G "Visual Studio 16 2019" \
|
||||
-A ${{ matrix.generator-platform }} \
|
||||
# EOL
|
||||
call "C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\Common7\Tools\VsDevCmd.bat" ^
|
||||
&& cmake -B build -G "Visual Studio 16 2019" -A ${{ matrix.generator-platform }}
|
||||
|
||||
- name: Build
|
||||
shell: cmd
|
||||
run: |
|
||||
cmake \
|
||||
--build build \
|
||||
--config RelWithDebInfo \
|
||||
# EOL
|
||||
call "C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\Common7\Tools\VsDevCmd.bat" ^
|
||||
&& cmake --build build --config RelWithDebInfo
|
||||
|
||||
- name: Upload
|
||||
run: |
|
||||
|
||||
@@ -125,7 +125,7 @@ IFACE_BAR_SIDES_ORI=0
|
||||
;This will increase the width of the interface bar expanding the area used to display text.
|
||||
;if IFACE_BAR_WIDTH=640 - Interface bar will remain at it's original width.
|
||||
;if IFACE_BAR_WIDTH=800 - Interface bar will use 800pix wide asset from f2_res.dat.
|
||||
IFACE_BAR_WIDTH=640
|
||||
;IFACE_BAR_WIDTH=640
|
||||
```
|
||||
|
||||
Recommendations:
|
||||
|
||||
+2
-1
@@ -4,7 +4,8 @@
|
||||
[Main]
|
||||
|
||||
;Set to one to hide areas outside map bounds when using higher than 640x480 resolution, and to zero to disable
|
||||
;HiResMode=1
|
||||
;EnableHighResolutionStencil=1
|
||||
|
||||
|
||||
;XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
||||
[Misc]
|
||||
|
||||
+32
-14
@@ -4,30 +4,48 @@
|
||||
|
||||
This folder contains scripts which can be used for sFall opcodes.
|
||||
|
||||
## Build script using sFall Script Editor
|
||||
## Building
|
||||
|
||||
1. Get "Fallout sFall Script Editor v4.1.7.RC1", for example from https://nuclear-grot.ucoz.net/forum/12-20-1
|
||||
Get latest `compile.exe` from [modders pack](https://sourceforge.net/projects/sfall/files/Modders%20pack/) or from [repository](https://github.com/sfall-team/sslc) and run it this way:
|
||||
|
||||
2. Run script editor, add "sfall_headers" into "Location folder of headers files" in the options
|
||||
```sh
|
||||
compile.exe -q -p -l -O2 -d -s -n -I<sfall_headers_id> <script_name.ssl>
|
||||
```
|
||||
|
||||
3. Open any script here and compile it
|
||||
(sfall headers can be found in modderspack)
|
||||
|
||||
|
||||
### Build script using sFall Script Editor
|
||||
|
||||
- Get "Fallout sFall Script Editor v4.1.7.RC1", for example from https://nuclear-grot.ucoz.net/forum/12-20-1
|
||||
|
||||
- Run script editor, add "sfall_headers" into "Location folder of headers files" in the options
|
||||
|
||||
- Open any script here and compile it
|
||||
|
||||
- This shoud create compiled script <file.int>
|
||||
|
||||
|
||||
### Build script using VSCode extension
|
||||
|
||||
- Install [VSCode Extension](https://marketplace.visualstudio.com/items?itemName=BGforge.bgforge-mls)
|
||||
|
||||
4. This shoud create compiled script <file.int>
|
||||
|
||||
|
||||
## Run test script
|
||||
|
||||
1. Move compiled .int file into game folder as `mods/tests/SCRIPTS/OBJ_DUDE.INT` (case-sensitive!)
|
||||
1. Move compiled `.int` file into game folder as `data/scripts/gl_<script_name>.int`
|
||||
|
||||
2. Change `mods/mods_order.txt` file: add a line `tests` there
|
||||
2. Change `ddraw.ini` and add this section:
|
||||
```ini
|
||||
[Scripts]
|
||||
GlobalScriptPaths=data/scripts/gl*.int
|
||||
```
|
||||
|
||||
(or add new path using comma as separator)
|
||||
|
||||
Note that on non-Windows it have to be `/` as folder separator
|
||||
|
||||
3. Run game, check that game displays message about tests
|
||||
|
||||
4. Comment tests line in `mods/mods_order.txt` in order to enable game back
|
||||
|
||||
|
||||
|
||||
## TODO
|
||||
|
||||
- Use custom global script instead of `obj_dude`
|
||||
- Add note about compile.exe and https://marketplace.visualstudio.com/items?itemName=BGforge.bgforge-mls
|
||||
|
||||
@@ -1,30 +1,8 @@
|
||||
#include "lib.arrays.h"
|
||||
|
||||
#include "test_utils.h"
|
||||
// #include "define_lite.h"
|
||||
|
||||
|
||||
variable test_suite_errors := 0;
|
||||
variable test_suite_verbose := true;
|
||||
|
||||
#ifndef assertEquals
|
||||
procedure assertEquals(variable desc, variable a, variable b) begin
|
||||
if (a != b or typeof(a) != typeof(b)) then begin
|
||||
display_msg("Assertion failed \""+desc+"\": "+a+" != "+b);
|
||||
test_suite_errors ++;
|
||||
end else if (test_suite_verbose) then begin
|
||||
display_msg("Assert \""+desc+"\" ok");
|
||||
end
|
||||
end
|
||||
|
||||
procedure assertNotEquals(variable desc, variable a, variable b) begin
|
||||
if (a == b) then begin
|
||||
display_msg("Assertion failed \""+desc+"\": "+a+" == "+b);
|
||||
test_suite_errors ++;
|
||||
end else if (test_suite_verbose) then begin
|
||||
display_msg("Assert \""+desc+"\" ok");
|
||||
end
|
||||
end
|
||||
#endif
|
||||
|
||||
#define ARRAY_MAX_STRING (255)
|
||||
#define ARRAY_MAX_SIZE (100000)
|
||||
@@ -187,6 +165,8 @@ procedure array_test_suite begin
|
||||
*/
|
||||
|
||||
display_msg("All tests finished with "+test_suite_errors+" errors.");
|
||||
|
||||
call report_test_results("arrays");
|
||||
end
|
||||
|
||||
|
||||
@@ -195,7 +175,7 @@ end
|
||||
|
||||
|
||||
|
||||
procedure map_enter_p_proc begin
|
||||
procedure start begin
|
||||
display_msg("Starting tests");
|
||||
call array_test_suite();
|
||||
end
|
||||
@@ -0,0 +1,189 @@
|
||||
#include "constants.h"
|
||||
#include "headers/define_extra.h" // For sfall functions if not in constants
|
||||
|
||||
procedure start;
|
||||
procedure description_proc; // For visual checks during game
|
||||
|
||||
variable new_tag1_id = -1;
|
||||
variable new_tag2_id = -1;
|
||||
variable max_tags_test_id = -1;
|
||||
|
||||
procedure start begin
|
||||
// Test Predefined Tags
|
||||
debug_msg("Testing Predefined Interface Tags...");
|
||||
|
||||
// SNEAK (0)
|
||||
debug_msg("Activating SNEAK (0)...");
|
||||
show_iface_tag(0);
|
||||
if (is_iface_tag_active(0)) then
|
||||
debug_msg("SNEAK is active. (Visual check needed)");
|
||||
else
|
||||
debug_msg("ERROR: SNEAK failed to activate or is_iface_tag_active(0) failed.");
|
||||
hide_iface_tag(0);
|
||||
if (not is_iface_tag_active(0)) then
|
||||
debug_msg("SNEAK is inactive.");
|
||||
else
|
||||
debug_msg("ERROR: SNEAK failed to deactivate or is_iface_tag_active(0) failed after hide.");
|
||||
|
||||
// POISONED (1) - Test requires making the player poisoned by other means to truly verify show/hide effect on game state
|
||||
// For now, just test is_iface_tag_active if it reflects direct calls or underlying state.
|
||||
// The opcodes show_iface_tag/hide_iface_tag are not meant to directly poison/cure the player for tags 1 and 2.
|
||||
// They are for custom tags or the specific PC flags (0,3,4). is_iface_tag_active *should* work for 1 and 2.
|
||||
debug_msg("Checking POISONED (1) (depends on game state): " + is_iface_tag_active(1));
|
||||
// Similar for RADIATED (2)
|
||||
debug_msg("Checking RADIATED (2) (depends on game state): " + is_iface_tag_active(2));
|
||||
|
||||
|
||||
// LEVEL (3)
|
||||
debug_msg("Activating LEVEL (3)...");
|
||||
show_iface_tag(3);
|
||||
if (is_iface_tag_active(3)) then
|
||||
debug_msg("LEVEL is active. (Visual check needed)");
|
||||
else
|
||||
debug_msg("ERROR: LEVEL failed to activate or is_iface_tag_active(3) failed.");
|
||||
hide_iface_tag(3);
|
||||
if (not is_iface_tag_active(3)) then
|
||||
debug_msg("LEVEL is inactive.");
|
||||
else
|
||||
debug_msg("ERROR: LEVEL failed to deactivate or is_iface_tag_active(3) failed after hide.");
|
||||
|
||||
// ADDICT (4)
|
||||
debug_msg("Activating ADDICT (4)...");
|
||||
show_iface_tag(4);
|
||||
if (is_iface_tag_active(4)) then
|
||||
debug_msg("ADDICT is active. (Visual check needed)");
|
||||
else
|
||||
debug_msg("ERROR: ADDICT failed to activate or is_iface_tag_active(4) failed.");
|
||||
hide_iface_tag(4);
|
||||
if (not is_iface_tag_active(4)) then
|
||||
debug_msg("ADDICT is inactive.");
|
||||
else
|
||||
debug_msg("ERROR: ADDICT failed to deactivate or is_iface_tag_active(4) failed after hide.");
|
||||
|
||||
debug_msg("Predefined tag tests complete.");
|
||||
debug_msg("---");
|
||||
|
||||
// Test Custom Tags
|
||||
debug_msg("Testing Custom Interface Tags...");
|
||||
|
||||
// Add first custom tag
|
||||
new_tag1_id = add_iface_tag;
|
||||
if (new_tag1_id != -1) then
|
||||
debug_msg("Added custom tag 1 with ID: " + new_tag1_id + " (expected 5 if first)");
|
||||
else
|
||||
debug_msg("ERROR: Failed to add custom tag 1.");
|
||||
return; // Stop if first add fails
|
||||
|
||||
// Show it
|
||||
show_iface_tag(new_tag1_id);
|
||||
if (is_iface_tag_active(new_tag1_id)) then
|
||||
debug_msg("Custom tag 1 is active. (Visual check: should show 'NEW' in green)");
|
||||
else
|
||||
debug_msg("ERROR: Custom tag 1 failed to activate or is_iface_tag_active failed.");
|
||||
|
||||
// Set its text and color
|
||||
set_iface_tag_text(new_tag1_id, "HELLO TAG1", 2); // Color 2 (White from issue description's reference)
|
||||
debug_msg("Set text for custom tag 1 to 'HELLO TAG1' (color white). (Visual check needed)");
|
||||
// Verify text length truncation (19 chars)
|
||||
set_iface_tag_text(new_tag1_id, "ThisIsAVeryLongTextStringIndeed", 3); // Color 3 (Yellow)
|
||||
debug_msg("Set text for custom tag 1 to 'ThisIsAVeryLongTextStringIndeed' (color yellow) - should be truncated. (Visual check needed)");
|
||||
|
||||
|
||||
// Add second custom tag
|
||||
new_tag2_id = add_iface_tag;
|
||||
if (new_tag2_id != -1) then
|
||||
debug_msg("Added custom tag 2 with ID: " + new_tag2_id + " (expected 6 if second)");
|
||||
else
|
||||
debug_msg("ERROR: Failed to add custom tag 2.");
|
||||
// Continue to test tag1 hide
|
||||
|
||||
if (new_tag2_id != -1) then
|
||||
show_iface_tag(new_tag2_id);
|
||||
set_iface_tag_text(new_tag2_id, "AWESOME", 6); // Color 6 (GoodColor/Green)
|
||||
debug_msg("Custom tag 2 ('AWESOME', green) should be active. (Visual check needed)");
|
||||
end
|
||||
|
||||
// Hide first custom tag
|
||||
hide_iface_tag(new_tag1_id);
|
||||
if (not is_iface_tag_active(new_tag1_id)) then
|
||||
debug_msg("Custom tag 1 is now inactive.");
|
||||
else
|
||||
debug_msg("ERROR: Custom tag 1 failed to deactivate.");
|
||||
|
||||
if (new_tag2_id != -1 and is_iface_tag_active(new_tag2_id)) then
|
||||
debug_msg("Custom tag 2 should still be active. (Visual check needed)");
|
||||
else if (new_tag2_id != -1) then
|
||||
debug_msg("ERROR: Custom tag 2 became inactive or failed check.");
|
||||
end
|
||||
|
||||
// Test max tags
|
||||
debug_msg("Attempting to add maximum custom tags (up to 126)...");
|
||||
variable i;
|
||||
variable current_tags = 2; // Assuming new_tag1_id and new_tag2_id were successful (IDs 5 and 6)
|
||||
if (new_tag1_id == -1) then current_tags = 0;
|
||||
if (new_tag1_id != -1 and new_tag2_id == -1) then current_tags = 1;
|
||||
|
||||
|
||||
for (i = current_tags; i < 126; i++) begin
|
||||
max_tags_test_id = add_iface_tag;
|
||||
if (max_tags_test_id == -1) then begin
|
||||
debug_msg("ERROR: Failed to add tag at iteration " + i + ". Max tags might be less than 126 or error in add_iface_tag.");
|
||||
break;
|
||||
end
|
||||
// Minimal show/set_text to ensure they are processed
|
||||
show_iface_tag(max_tags_test_id);
|
||||
set_iface_tag_text(max_tags_test_id, "Tag " + max_tags_test_id, 0); // Default color
|
||||
end
|
||||
if (i == 126) then
|
||||
debug_msg("Successfully added up to 126 custom tags (IDs 5 through 130). Last ID: " + max_tags_test_id);
|
||||
else
|
||||
debug_msg("Stopped adding tags at count " + i + ". Last successful ID: " + max_tags_test_id);
|
||||
end
|
||||
|
||||
// Test adding one more tag (should fail if 126 was the limit)
|
||||
max_tags_test_id = add_iface_tag;
|
||||
if (max_tags_test_id == -1) then
|
||||
debug_msg("Correctly failed to add tag beyond limit (127th attempt).");
|
||||
else
|
||||
debug_msg("ERROR: Added tag beyond 126 limit! ID: " + max_tags_test_id);
|
||||
end
|
||||
|
||||
// Test invalid IDs
|
||||
debug_msg("Testing invalid tag IDs...");
|
||||
show_iface_tag(-1); // Invalid
|
||||
show_iface_tag(1000); // Likely invalid (beyond max added)
|
||||
hide_iface_tag(-2);
|
||||
hide_iface_tag(1001);
|
||||
debug_msg("is_iface_tag_active(-3): " + is_iface_tag_active(-3));
|
||||
debug_msg("is_iface_tag_active(1002): " + is_iface_tag_active(1002));
|
||||
set_iface_tag_text(-4, "INVALID", 0);
|
||||
set_iface_tag_text(1003, "INVALID", 0);
|
||||
debug_msg("Invalid ID tests complete (check console for debug prints from C++).");
|
||||
|
||||
debug_msg("---");
|
||||
debug_msg("Interface Tag testing complete. Check console and game screen.");
|
||||
debug_msg("Will keep some tags active for 10 seconds for visual check then clear...");
|
||||
|
||||
// Keep some tags visible for a moment if run in game
|
||||
// This part is for easier visual checking if you load a game with this script.
|
||||
// For automated testing, the debug_msg output is key.
|
||||
if (game_loaded) then begin
|
||||
if (new_tag1_id != -1) then hide_iface_tag(new_tag1_id); // ensure it's hidden from previous test
|
||||
if (new_tag2_id != -1) then show_iface_tag(new_tag2_id); // ensure tag2 is visible
|
||||
set_iface_tag_text(new_tag2_id, "Testing Done!", 4); // Peanut butter color
|
||||
game_time_advance(game_ticks(10)); // Wait 10 seconds
|
||||
if (new_tag2_id != -1) then hide_iface_tag(new_tag2_id);
|
||||
debug_msg("Visual check period ended.");
|
||||
end
|
||||
end
|
||||
|
||||
// This can be used with `debug_map_scripts` or similar to run on map enter
|
||||
// procedure map_enter_p_proc begin call start; end
|
||||
// procedure map_update_p_proc begin call start; end // If you want it to run repeatedly (not recommended for this test script)
|
||||
|
||||
// For `description_proc` usage with `debug(F5)`:
|
||||
// Create a global script that calls `start` from `description_proc`
|
||||
// Example:
|
||||
// procedure description_proc begin
|
||||
// if (game_loaded) then call start;
|
||||
// end
|
||||
@@ -0,0 +1,71 @@
|
||||
#include "test_utils.h"
|
||||
#include "sfall.h"
|
||||
|
||||
procedure start begin
|
||||
|
||||
display_msg("Testing math functions...");
|
||||
|
||||
call assertFloat("sin(0)", sin(0), 0.0);
|
||||
call assertFloat("sin(PI/2)", sin(1.5708), 1.0);
|
||||
call assertFloat("sin(PI)", sin(3.1416), 0.0);
|
||||
call assertFloat("sin(-PI/2)", sin(-1.5708), -1.0);
|
||||
|
||||
call assertFloat("cos(0)", cos(0), 1.0);
|
||||
call assertFloat("cos(PI/2)", cos(1.5708), 0.0);
|
||||
call assertFloat("cos(PI)", cos(3.1416), -1.0);
|
||||
call assertFloat("cos(-PI)", cos(-3.1416), -1.0);
|
||||
|
||||
call assertFloat("tan(0)", tan(0), 0.0);
|
||||
call assertFloat("tan(PI/4)", tan(0.7854), 1.0);
|
||||
call assertFloat("tan(-PI/4)", tan(-0.7854), -1.0);
|
||||
|
||||
call assertFloat("arctan(0, 1)", arctan(0, 1), 0.0);
|
||||
call assertFloat("arctan(1, 1)", arctan(1, 1), 0.7854);
|
||||
call assertFloat("arctan(-1, 1)", arctan(-1, 1), -0.7854);
|
||||
call assertFloat("arctan(1, 0)", arctan(1, 0), 1.5708);
|
||||
|
||||
call assertFloat("log(1)", log(1), 0.0);
|
||||
call assertFloat("log(e)", log(2.7183), 1.0);
|
||||
call assertFloat("log(10)", log(10), 2.3026);
|
||||
|
||||
call assertFloat("exponent(0)", exponent(0), 1.0);
|
||||
call assertFloat("exponent(1)", exponent(1), 2.7183);
|
||||
call assertFloat("exponent(2)", exponent(2), 7.3891);
|
||||
call assertFloat("exponent(-1)", exponent(-1), 0.3679);
|
||||
|
||||
call assertEquals("ceil(2.1)", ceil(2.1), 3);
|
||||
call assertEquals("ceil(2.0)", ceil(2.0), 2);
|
||||
call assertEquals("ceil(-2.1)", ceil(-2.1), -2);
|
||||
call assertEquals("ceil(-2.9)", ceil(-2.9), -2);
|
||||
|
||||
call assertEquals("floor2(2.1)", floor2(2.1), 2);
|
||||
call assertEquals("floor2(2.9)", floor2(2.9), 2);
|
||||
call assertEquals("floor2(-2.1)", floor2(-2.1), -3);
|
||||
call assertEquals("floor2(-2.9)", floor2(-2.9), -3);
|
||||
|
||||
call assertEquals("2^3", 2^3, 8);
|
||||
call assertEquals("4^2", 4^2, 16);
|
||||
call assertEquals("3^0", 3^0, 1);
|
||||
call assertFloat("2^-1", 2^-1, 0); // note: int truncation
|
||||
call assertFloat("2^-1", 2^-1.0, 0.5);
|
||||
call assertFloat("4^0.5", 4^0.5, 2.0);
|
||||
call assertFloat("8^(1/3)", 8^(1.0/3.0), 2.0);
|
||||
call assertFloat("2.5^2", 2.5^2, 6.25);
|
||||
|
||||
call assertEquals("round(2.3)", round(2.3), 2);
|
||||
call assertEquals("round(2.5)", round(2.5), 3);
|
||||
call assertEquals("round(2.7)", round(2.7), 3);
|
||||
call assertEquals("round(-2.3)", round(-2.3), -2);
|
||||
call assertEquals("round(-2.5)", round(-2.5), -3);
|
||||
call assertEquals("round(-2.7)", round(-2.7), -3);
|
||||
call assertEquals("round(0.0)", round(0.0), 0);
|
||||
call assertEquals("round(1.0)", round(1.0), 1);
|
||||
call assertEquals("round(<int>)", round(1), 1);
|
||||
|
||||
call assertFloat("sin(arctan(1, 1))", sin(arctan(1, 1)), 0.7071);
|
||||
call assertFloat("cos(arctan(1, 1))", cos(arctan(1, 1)), 0.7071);
|
||||
call assertFloat("log(ceil(2.7))", log(ceil(2.7)), 1.0986);
|
||||
call assertFloat("exponent(log(5))", exponent(log(5)), 5.0);
|
||||
|
||||
call report_test_results("math");
|
||||
end
|
||||
@@ -0,0 +1,15 @@
|
||||
#include "test_utils.h"
|
||||
#include "sfall.h"
|
||||
|
||||
procedure start begin
|
||||
// TODO: move this to a more suitable place, once we have more functions implemented
|
||||
// These are in the "Other" category in the sfall documentation
|
||||
display_msg("Testing misc functions...");
|
||||
variable hand := active_hand;
|
||||
toggle_active_hand;
|
||||
call assertEquals("active_hand toggled", active_hand, 1 - hand);
|
||||
toggle_active_hand;
|
||||
call assertEquals("active_hand toggled", active_hand, hand);
|
||||
|
||||
call report_test_results("misc");
|
||||
end
|
||||
@@ -0,0 +1,82 @@
|
||||
#include "test_utils.h"
|
||||
#include "sfall.h"
|
||||
|
||||
#define REPEAT_64 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
#define REPEAT_512 REPEAT_64 REPEAT_64 REPEAT_64 REPEAT_64 REPEAT_64 REPEAT_64 REPEAT_64 REPEAT_64
|
||||
#define REPEAT_1024 REPEAT_512 REPEAT_512
|
||||
|
||||
procedure start begin
|
||||
|
||||
display_msg("Testing sprintf...");
|
||||
|
||||
call assertEquals("sprintf hello world", sprintf("Hello %s", "World"), "Hello World");
|
||||
|
||||
call assertEquals("sprintf empty string", sprintf("", 123), "");
|
||||
|
||||
call assertEquals("sprintf number string", sprintf("%d", 123), "123");
|
||||
|
||||
call assertEquals("sprintf number inside string", sprintf("Hello %d !", 123), "Hello 123 !");
|
||||
|
||||
call assertEquals("sprintf ascii code", sprintf("Hello %c !", 123), "Hello { !");
|
||||
|
||||
call assertEquals("sprintf float string", sprintf("Hello %f !", 3.4), "Hello 3.400000 !");
|
||||
|
||||
call assertEquals("sprintf float string", sprintf("Hello %d !", 10/4), "Hello 2 !");
|
||||
|
||||
call assertEquals("sprintf two strings", sprintf("Hello %s %s", "World"), "Hello World World");
|
||||
|
||||
call assertEquals("Long string", sprintf(REPEAT_1024"x", 1), "Error");
|
||||
|
||||
call assertEquals("Not so long", sprintf(REPEAT_1024, 1), REPEAT_1024);
|
||||
|
||||
|
||||
call assertEquals("string_format1 hello world", string_format1("Hello %s!", "World"), "Hello World!");
|
||||
call assertEquals("string_format1 auto hello world", string_format("Hello %s!", "World"), "Hello World!");
|
||||
|
||||
call assertEquals("string_format2 hello world", string_format2("Hello %s%s!", "Wo","rld"), "Hello World!");
|
||||
call assertEquals("string_format2 auto hello world", string_format("Hello %s%s!", "Wo","rld"), "Hello World!");
|
||||
|
||||
call assertEquals("string_format3 hello world", string_format3("Hell%s %s%s!", "o", "Wo","rld"), "Hello World!");
|
||||
call assertEquals("string_format3 auto hello world", string_format("Hell%s %s%s!", "o", "Wo","rld"), "Hello World!");
|
||||
|
||||
call assertEquals("string_format4 hello world", string_format4("Hel%s%s %s%s!", "l", "o", "Wo","rld"), "Hello World!");
|
||||
call assertEquals("string_format4 auto hello world", string_format("Hel%s%s %s%s!", "l", "o", "Wo","rld"), "Hello World!");
|
||||
|
||||
call assertEquals("string_format5 hello world", string_format5("He%s%s%s %s%s!", "l", "l", "o", "Wo","rld"), "Hello World!");
|
||||
call assertEquals("string_format5 auto hello world", string_format("He%s%s%s %s%s!", "l", "l", "o", "Wo","rld"), "Hello World!");
|
||||
|
||||
call assertEquals("string_format6 hello world", string_format6("H%s%s%s%s %s%s!", "e", "l", "l", "o", "Wo","rld"), "Hello World!");
|
||||
call assertEquals("string_format6 auto hello world", string_format("H%s%s%s%s %s%s!", "e", "l", "l", "o", "Wo","rld"), "Hello World!");
|
||||
|
||||
call assertEquals("string_format7 hello world", string_format7("%s%s%s%s%s %s%s!", "H", "e", "l", "l", "o", "Wo","rld"), "Hello World!");
|
||||
call assertEquals("string_format7 auto hello world", string_format("%s%s%s%s%s %s%s!", "H", "e", "l", "l", "o", "Wo","rld"), "Hello World!");
|
||||
|
||||
call assertEquals("formatted string length 1024", strlen(sprintf(REPEAT_1024, 1)), 1024);
|
||||
|
||||
call assertEquals("formatted string length 5119", strlen(
|
||||
string_format7(
|
||||
"%s%s%s%s%s%s%s",
|
||||
REPEAT_1024,
|
||||
REPEAT_1024,
|
||||
REPEAT_1024,
|
||||
REPEAT_1024,
|
||||
REPEAT_1024,
|
||||
REPEAT_1024,
|
||||
REPEAT_1024)), 5119);
|
||||
|
||||
call assertEquals("string_tolower", string_tolower("miXeD CaSe"), "mixed case");
|
||||
call assertEquals("string_toupper", string_toupper("miXeD CaSe"), "MIXED CASE");
|
||||
|
||||
call assertEquals("string_find ok", string_find("Hello World", "World"), 6);
|
||||
call assertEquals("string_find missing", string_find("Hello World", "Zebra"), -1);
|
||||
call assertEquals("string_find_from", string_find_from("Hello World", "o", 6), 7);
|
||||
|
||||
call assertEquals("string_compare ok", string_compare("Hello World", "HeLlO WoRld"), 1);
|
||||
call assertEquals("string_compare neq", string_compare("Hello World", "HeLlO WoRld!"), 0);
|
||||
call assertEquals("string_compare_locale", string_compare_locale("Hello World", "HeLlO WoRld", 866), 1); // TODO: would be nice to compare cyrillic
|
||||
|
||||
call assertEquals("charcode", charcode("A"), 65);
|
||||
call assertEquals("charcode empty", charcode(""), 0);
|
||||
|
||||
call report_test_results("sprintf");
|
||||
end
|
||||
@@ -0,0 +1,162 @@
|
||||
#include "define_lite.h"
|
||||
#include "sfall.h"
|
||||
#include "test_utils.h"
|
||||
#include "command_lite.h"
|
||||
|
||||
procedure test_stat_pc(variable stat_id) begin
|
||||
display_msg("Stat " + stat_id + " = " +
|
||||
get_pc_base_stat(stat_id) + " + " +
|
||||
get_pc_extra_stat(stat_id));
|
||||
|
||||
|
||||
call assertNotEquals("Not interesting stat, base stat is zero", get_pc_base_stat(stat_id), 0);
|
||||
|
||||
variable old_base_stat := get_pc_base_stat(stat_id);
|
||||
variable old_extra_stat := get_pc_extra_stat(stat_id);
|
||||
|
||||
set_pc_base_stat(stat_id, old_base_stat + 2);
|
||||
set_pc_extra_stat(stat_id, old_extra_stat + 4);
|
||||
// display_msg("Stat after update " + stat_id + " = " +
|
||||
// get_pc_base_stat(stat_id) + " + " +
|
||||
// get_pc_extra_stat(stat_id));
|
||||
if (stat_id > STAT_lu) then begin
|
||||
call assertNotEquals("Base stat was updated while it shouldn't",
|
||||
get_pc_base_stat(stat_id), old_base_stat + 2);
|
||||
end else begin
|
||||
call assertEquals("Base stat updated",
|
||||
get_pc_base_stat(stat_id), old_base_stat + 2);
|
||||
end
|
||||
|
||||
set_pc_base_stat(stat_id, old_base_stat);
|
||||
set_pc_extra_stat(stat_id, old_extra_stat);
|
||||
|
||||
// display_msg("Stat after reset " + stat_id + " = " +
|
||||
// get_pc_base_stat(stat_id) + " + " +
|
||||
// get_pc_extra_stat(stat_id));
|
||||
|
||||
call assertEquals("Base stat reset",
|
||||
get_pc_base_stat(stat_id), old_base_stat);
|
||||
|
||||
call assertEquals("Extra stat reset",
|
||||
get_pc_extra_stat(stat_id), old_extra_stat);
|
||||
end
|
||||
|
||||
procedure test_stat_critter(variable critter, variable stat_id) begin
|
||||
display_msg("Stat " + stat_id + " = " +
|
||||
get_critter_base_stat(critter, stat_id) + " + " +
|
||||
get_critter_extra_stat(critter, stat_id));
|
||||
|
||||
call assertNotEquals("Not interesting stat, base stat is zero",
|
||||
get_critter_base_stat(critter, stat_id), 0);
|
||||
|
||||
variable old_base_stat := get_critter_base_stat(critter, stat_id);
|
||||
variable old_extra_stat := get_critter_extra_stat(critter, stat_id);
|
||||
|
||||
set_critter_base_stat(critter, stat_id, old_base_stat + 2);
|
||||
set_critter_extra_stat(critter, stat_id, old_extra_stat + 4);
|
||||
// display_msg("Stat after update " + stat_id + " = " +
|
||||
// get_critter_base_stat(critter, stat_id) + " + " +
|
||||
// get_critter_extra_stat(critter, stat_id));
|
||||
if (stat_id > STAT_lu) then begin
|
||||
call assertNotEquals("Base stat was updated while it shouldn't",
|
||||
get_critter_base_stat(critter, stat_id), old_base_stat + 2);
|
||||
end else begin
|
||||
call assertEquals("Base stat updated",
|
||||
get_critter_base_stat(critter, stat_id), old_base_stat + 2);
|
||||
end
|
||||
|
||||
set_critter_base_stat(critter, stat_id, old_base_stat);
|
||||
set_critter_extra_stat(critter, stat_id, old_extra_stat);
|
||||
// display_msg("Stat after reset " + stat_id + " = " +
|
||||
// get_critter_base_stat(critter, stat_id) + " + " +
|
||||
// get_critter_extra_stat(critter, stat_id));
|
||||
call assertEquals("Base stat reset",
|
||||
get_critter_base_stat(critter, stat_id), old_base_stat);
|
||||
call assertEquals("Extra stat reset",
|
||||
get_critter_extra_stat(critter, stat_id), old_extra_stat);
|
||||
end
|
||||
|
||||
procedure test_derived_base_stat_critter(variable critter) begin
|
||||
display_msg("Testing derived stats on " + obj_name(critter));
|
||||
|
||||
variable old_agility;
|
||||
old_agility := get_critter_base_stat(critter, STAT_ag);
|
||||
|
||||
call assertNotEquals("Initial agility is 10", old_agility, 10);
|
||||
|
||||
call assertNotEquals("Action points is 10 while agility is not 10", get_critter_base_stat(critter, STAT_max_move_points), 10);
|
||||
|
||||
set_critter_base_stat(critter, STAT_ag, 10);
|
||||
|
||||
call assertEquals("Action points updated to 10",
|
||||
get_critter_base_stat(critter, STAT_max_move_points), 10);
|
||||
|
||||
set_critter_base_stat(critter, STAT_ag, old_agility);
|
||||
|
||||
call assertNotEquals("Action points updated from 10",
|
||||
get_critter_base_stat(critter, STAT_max_move_points), 10);
|
||||
|
||||
end
|
||||
|
||||
procedure start begin
|
||||
display_msg("Testing stats...");
|
||||
test_suite_errors := 0;
|
||||
|
||||
call test_stat_pc(STAT_max_hit_points);
|
||||
|
||||
call test_stat_pc(STAT_lu);
|
||||
|
||||
|
||||
variable critter;
|
||||
variable closest_critter := dude_obj;
|
||||
foreach critter in list_as_array(LIST_CRITTERS) begin
|
||||
if (is_critter_dead(critter)) then begin
|
||||
continue;
|
||||
end
|
||||
if tile_distance(dude_tile, tile_num(critter)) == 0 then begin
|
||||
continue;
|
||||
end
|
||||
if (tile_distance(dude_tile, tile_num(critter)) <
|
||||
tile_distance(dude_tile, tile_num(closest_critter))) or
|
||||
(closest_critter == dude_obj)
|
||||
then begin
|
||||
closest_critter := critter;
|
||||
end
|
||||
end
|
||||
|
||||
// display_msg("Closest " + obj_name(closest_critter) + " distance: " +
|
||||
// tile_distance(dude_tile, tile_num(closest_critter)));
|
||||
|
||||
call test_stat_critter(dude_obj, STAT_max_hit_points);
|
||||
call test_stat_critter(dude_obj, STAT_lu);
|
||||
|
||||
if closest_critter != dude_obj then begin
|
||||
display_msg("Testing stats for " + obj_name(closest_critter));
|
||||
call test_stat_critter(closest_critter, STAT_max_hit_points);
|
||||
call test_stat_critter(closest_critter, STAT_lu);
|
||||
end else begin
|
||||
display_msg("FAIL: No other critters found to test stats");
|
||||
test_suite_errors++;
|
||||
end
|
||||
|
||||
|
||||
if
|
||||
get_critter_base_stat(dude_obj, STAT_lu) ==
|
||||
get_critter_base_stat(closest_critter, STAT_lu)
|
||||
and
|
||||
get_critter_extra_stat(dude_obj, STAT_max_hit_points) ==
|
||||
get_critter_extra_stat(closest_critter, STAT_max_hit_points) then begin
|
||||
display_msg("FAIL: Very sus eq values for dude and closest critter");
|
||||
test_suite_errors++;
|
||||
end
|
||||
|
||||
|
||||
call test_derived_base_stat_critter(dude_obj);
|
||||
call test_derived_base_stat_critter(closest_critter);
|
||||
|
||||
call report_test_results("stats");
|
||||
|
||||
|
||||
// set_pc_base_stat(STAT_lu, 10);
|
||||
// set_pc_extra_stat(STAT_max_hit_points, 500);
|
||||
end
|
||||
@@ -0,0 +1,112 @@
|
||||
#ifndef TEST_UTILS_H
|
||||
#define TEST_UTILS_H
|
||||
|
||||
#include "define_lite.h"
|
||||
|
||||
variable test_suite_errors := 0;
|
||||
variable test_suite_verbose := false;
|
||||
variable test_suite_assertions := 0;
|
||||
|
||||
procedure assertEquals(variable desc, variable a, variable b) begin
|
||||
test_suite_assertions++;
|
||||
|
||||
if (a != b or typeof(a) != typeof(b)) then begin
|
||||
display_msg("FAIL \""+desc+"\": "+a+" != "+b);
|
||||
test_suite_errors ++;
|
||||
end else if (test_suite_verbose) then begin
|
||||
display_msg("ok \""+desc+"\"");
|
||||
end
|
||||
end
|
||||
|
||||
procedure assertNotEquals(variable desc, variable a, variable b) begin
|
||||
test_suite_assertions++;
|
||||
|
||||
if (a == b) then begin
|
||||
display_msg("FAIL \""+desc+"\": "+a+" == "+b);
|
||||
test_suite_errors ++;
|
||||
end else if (test_suite_verbose) then begin
|
||||
display_msg("ok \""+desc+"\"");
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
procedure assertFloat(variable desc, variable a, variable b, variable tolerance = 0.001) begin
|
||||
test_suite_assertions++;
|
||||
|
||||
variable diff := abs(a - b);
|
||||
if (diff > tolerance) then begin
|
||||
display_msg("FAIL \""+desc+"\": "+a+" != "+b+" (diff: "+diff+")");
|
||||
test_suite_errors ++;
|
||||
end else if (test_suite_verbose) then begin
|
||||
display_msg("ok \""+desc+"\"");
|
||||
end
|
||||
end
|
||||
|
||||
procedure report_test_results(variable desc) begin
|
||||
|
||||
display_msg("DONE " + desc + " " +
|
||||
(test_suite_assertions-test_suite_errors) + "/" + test_suite_assertions + "");
|
||||
display_msg("================");
|
||||
|
||||
|
||||
#define GL_VAR_TESTING_INFO_ARRAY_ID "TestArra"
|
||||
#define TEST_ARR_IDENTIFIER_KEY "__testing_info_array__"
|
||||
variable arr_id = get_sfall_global_int(GL_VAR_TESTING_INFO_ARRAY_ID);
|
||||
if arr_id != 0 then begin
|
||||
// Let's check that it is actually our testing array
|
||||
if array_key(arr_id, -1) == 0 then begin
|
||||
// It is not even associative array, so it is not our testing array
|
||||
arr_id = 0;
|
||||
end else begin
|
||||
if arr_id[TEST_ARR_IDENTIFIER_KEY] != 1 then begin
|
||||
// It is associative array, but it is not our testing array
|
||||
arr_id = 0;
|
||||
end
|
||||
end
|
||||
end
|
||||
if arr_id == 0 then begin
|
||||
arr_id = create_array(-1, 0);
|
||||
arr_id[TEST_ARR_IDENTIFIER_KEY] = 1;
|
||||
set_sfall_global(GL_VAR_TESTING_INFO_ARRAY_ID, arr_id);
|
||||
end
|
||||
|
||||
|
||||
#define TEST_SUITES "Test suites"
|
||||
#define TEST_ASSERTIONS_TOTAL "Tests assertings total"
|
||||
#define TEST_ASSERTIONS_FAILED "Tests assertings failed"
|
||||
|
||||
if arr_id[TEST_SUITES] == 0 then begin
|
||||
arr_id[TEST_SUITES] = create_array(-1, 0);
|
||||
end
|
||||
|
||||
//set_sfall_global("test_suite_errors", test_suite_errors);
|
||||
|
||||
arr_id[TEST_ASSERTIONS_TOTAL] += test_suite_assertions;
|
||||
arr_id[TEST_ASSERTIONS_FAILED] += test_suite_errors;
|
||||
arr_id[TEST_SUITES][desc] += 1;
|
||||
|
||||
variable total_suites_names = "";
|
||||
variable total_suites_count = 0;
|
||||
|
||||
variable key, item;
|
||||
foreach (key: item in arr_id[TEST_SUITES]) begin
|
||||
total_suites_count += item;
|
||||
if (total_suites_names != "") then begin
|
||||
total_suites_names += ", ";
|
||||
end
|
||||
total_suites_names += key;
|
||||
end
|
||||
|
||||
|
||||
variable assertions_stats_str = (arr_id[TEST_ASSERTIONS_TOTAL]-arr_id[TEST_ASSERTIONS_FAILED]) +
|
||||
"/" + arr_id[TEST_ASSERTIONS_TOTAL];
|
||||
if (arr_id[TEST_ASSERTIONS_FAILED] == 0) then begin
|
||||
float_msg(dude_obj, "Tested " + total_suites_count + " cases: " + total_suites_names + ". " +
|
||||
assertions_stats_str + " assertions passed!", FLOAT_MSG_GREEN);
|
||||
end else begin
|
||||
float_msg(dude_obj, "Tested " + total_suites_count + " cases: " + total_suites_names + ". " +
|
||||
assertions_stats_str + " assertions passed", FLOAT_MSG_RED);
|
||||
end
|
||||
end
|
||||
|
||||
#endif
|
||||
@@ -3046,6 +3046,10 @@ void _dude_fidget()
|
||||
return;
|
||||
}
|
||||
|
||||
if (GameMode::getCurrentGameMode() == GameMode::kWorldmap) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isInCombat()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1769,7 +1769,7 @@ static int characterEditorWindowInit()
|
||||
|
||||
btn = buttonCreate(
|
||||
gCharacterEditorWindow,
|
||||
343,
|
||||
344, // one pixel right shift to align with background
|
||||
454,
|
||||
_editorFrmImages[EDITOR_GRAPHIC_LITTLE_RED_BUTTON_UP].getWidth(),
|
||||
_editorFrmImages[EDITOR_GRAPHIC_LITTLE_RED_BUTTON_UP].getHeight(),
|
||||
@@ -1787,7 +1787,7 @@ static int characterEditorWindowInit()
|
||||
|
||||
btn = buttonCreate(
|
||||
gCharacterEditorWindow,
|
||||
552,
|
||||
553, // one pixel right shift to align with background
|
||||
454,
|
||||
_editorFrmImages[EDITOR_GRAPHIC_LITTLE_RED_BUTTON_UP].getWidth(),
|
||||
_editorFrmImages[EDITOR_GRAPHIC_LITTLE_RED_BUTTON_UP].getHeight(),
|
||||
@@ -1805,7 +1805,7 @@ static int characterEditorWindowInit()
|
||||
|
||||
btn = buttonCreate(
|
||||
gCharacterEditorWindow,
|
||||
455,
|
||||
456, // one pixel right shift to align with background
|
||||
454,
|
||||
_editorFrmImages[23].getWidth(),
|
||||
_editorFrmImages[23].getHeight(),
|
||||
|
||||
+36
-4
@@ -69,6 +69,7 @@ static bool gSoundEffectsEnabled = false;
|
||||
static int _gsound_active_effect_counter;
|
||||
|
||||
// 0x518E50
|
||||
// background music
|
||||
static Sound* gBackgroundSound = nullptr;
|
||||
|
||||
// 0x518E54
|
||||
@@ -593,9 +594,32 @@ int backgroundSoundGetDuration()
|
||||
return soundGetDuration(gBackgroundSound);
|
||||
}
|
||||
|
||||
// [fileName] is base file name, without path and extension.
|
||||
//
|
||||
// 0x45067C
|
||||
/*
|
||||
[fileName] is base file name, without path and extension.
|
||||
|
||||
a2
|
||||
10 = don't auto play sound after load (unused?)
|
||||
11 = set read limit before soundLoad, !11 = set after soundLoad
|
||||
12 = usually used when value is not 11
|
||||
a3 relates to sound type
|
||||
13 = MEMORY
|
||||
14 = STREAMING
|
||||
a4 relates to sound flags
|
||||
15 = fire and forget
|
||||
16 = looping
|
||||
|
||||
examples:
|
||||
backgroundSoundLoad("akiss", 12, 14, 15) (endgame)
|
||||
backgroundSoundLoad("10labone", 11, 14, 16); (endgame)
|
||||
backgroundSoundLoad(fileName, a2, 14, 16); (map music) // 11 = main menu music, 12 = map/world map music
|
||||
backgroundSoundLoad("wind2", 12, 13, 16); (map load sound)
|
||||
|
||||
these use the last settings for a3/a4 that were passed to backgroundSoundLoad
|
||||
backgroundSoundRestart(11); (end of of script)
|
||||
backgroundSoundRestart(12); (game init, volume change)
|
||||
|
||||
0x45067C
|
||||
*/
|
||||
int backgroundSoundLoad(const char* fileName, int a2, int a3, int a4)
|
||||
{
|
||||
int rc;
|
||||
@@ -750,7 +774,7 @@ int _gsound_background_play_level_music(const char* fileName, int a2)
|
||||
{
|
||||
int gaplessMusic = 0;
|
||||
configGetInt(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_GAPLESS_MUSIC, &gaplessMusic);
|
||||
if (gaplessMusic) {
|
||||
if (backgoundSoundIsPlaying() && gaplessMusic) {
|
||||
if (!strcmp(fileName, gBackgroundSoundFileName)) {
|
||||
return 0;
|
||||
}
|
||||
@@ -802,6 +826,12 @@ void backgroundSoundResume()
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: could be made more precise by querying the sound, checking volume, &c.
|
||||
bool backgoundSoundIsPlaying()
|
||||
{
|
||||
return gBackgroundSound != nullptr;
|
||||
}
|
||||
|
||||
// NOTE: Inlined.
|
||||
//
|
||||
// 0x450B78
|
||||
@@ -1688,6 +1718,8 @@ void soundEffectCallback(void* userData, int a2)
|
||||
}
|
||||
|
||||
// 0x451ADC
|
||||
// a2 relates to sound type
|
||||
// a3 relates to sound flags
|
||||
int _gsound_background_allocate(Sound** soundPtr, int a2, int a3)
|
||||
{
|
||||
int soundFlags = SOUND_FLAG_0x02 | SOUND_16BIT;
|
||||
|
||||
@@ -53,6 +53,7 @@ void backgroundSoundDelete();
|
||||
void backgroundSoundRestart(int value);
|
||||
void backgroundSoundPause();
|
||||
void backgroundSoundResume();
|
||||
bool backgoundSoundIsPlaying();
|
||||
int speechIsEnabled();
|
||||
void speechSetVolume(int value);
|
||||
int speechGetVolume();
|
||||
|
||||
+184
-14
@@ -258,6 +258,9 @@ static unsigned char* gInterfaceWindowBuffer;
|
||||
// 0x59D40C
|
||||
static unsigned char gInterfaceActionPointsBarBackground[90 * 5];
|
||||
|
||||
CustomIndicatorBox gCustomIndicatorBoxes[MAX_CUSTOM_INDICATOR_BOXES];
|
||||
static int gActiveCustomTagsCount = 0;
|
||||
|
||||
// Should the game window stretch all the way to the bottom or sit at the top of the interface bar (default)
|
||||
bool gInterfaceBarMode = false;
|
||||
|
||||
@@ -291,7 +294,7 @@ static FrmImage _yellowLightFrmImage;
|
||||
static FrmImage _redLightFrmImage;
|
||||
|
||||
int gInterfaceBarContentOffset = 0;
|
||||
int gInterfaceBarWidth = -1;
|
||||
int gInterfaceBarWidth = 800; // will fall back to 640 if screen width is too narrow or asset is absent
|
||||
bool gInterfaceBarIsCustom = false;
|
||||
static Art* gCustomInterfaceBarBackground = nullptr;
|
||||
|
||||
@@ -581,6 +584,13 @@ int interfaceInit()
|
||||
// SFALL
|
||||
sidePanelsInit();
|
||||
|
||||
for (int i = 0; i < MAX_CUSTOM_INDICATOR_BOXES; ++i) {
|
||||
gCustomIndicatorBoxes[i].isActive = false;
|
||||
gCustomIndicatorBoxes[i].text[0] = '\0';
|
||||
gCustomIndicatorBoxes[i].color = 0;
|
||||
}
|
||||
gActiveCustomTagsCount = 0;
|
||||
|
||||
gInterfaceBarEnabled = true;
|
||||
gInterfaceBarInitialized = false;
|
||||
gInterfaceBarHidden = true;
|
||||
@@ -697,6 +707,10 @@ void interfaceFree()
|
||||
customInterfaceBarExit();
|
||||
|
||||
interfaceBarFree();
|
||||
// TODO: Consider if a dedicated function to free custom tag resources is needed.
|
||||
// For now, gCustomIndicatorBoxes is a global static array, so no dynamic memory to free.
|
||||
// Resetting active tags count.
|
||||
gActiveCustomTagsCount = 0;
|
||||
}
|
||||
|
||||
// 0x45E860
|
||||
@@ -2362,6 +2376,64 @@ int indicatorBarRefresh()
|
||||
windowRefresh(gIndicatorBarWindow);
|
||||
}
|
||||
|
||||
// START Custom Tag Handling in indicatorBarRefresh
|
||||
int customTagStartSlot = count; // Where to start adding custom tags in gIndicatorSlots
|
||||
for (int i = 0; i < gActiveCustomTagsCount && customTagStartSlot < INDICATOR_SLOTS_COUNT; ++i) {
|
||||
if (gCustomIndicatorBoxes[i].isActive) {
|
||||
// Using a convention: custom tag IDs are INDICATOR_COUNT + i
|
||||
gIndicatorSlots[customTagStartSlot++] = INDICATOR_COUNT + i;
|
||||
}
|
||||
}
|
||||
count = customTagStartSlot; // Update total count of displayed indicators
|
||||
|
||||
if (count > 1 && (gIndicatorSlots[0] < INDICATOR_COUNT || (count > 0 && gIndicatorSlots[0] >= INDICATOR_COUNT && gIndicatorSlots[1] < INDICATOR_COUNT))) { // Only sort if there are standard indicators or a mix
|
||||
// qsort might behave unexpectedly if all elements are custom tags due to how IDs are structured.
|
||||
// Custom tags are already ordered by their addition.
|
||||
// We only need to sort the standard part, or if custom tags are mixed in a way that needs sorting relative to standard ones.
|
||||
// For simplicity, if there are any standard tags, sort the whole array up to the original count.
|
||||
// Custom tags will be appended after this sorted list of standard tags.
|
||||
// This logic might need refinement if standard and custom tags need to interleave based on some criteria.
|
||||
// For now, custom tags are appended after standard ones.
|
||||
int standardIndicatorsCount = 0;
|
||||
for(int i=0; i < count; ++i) {
|
||||
if(gIndicatorSlots[i] < INDICATOR_COUNT) standardIndicatorsCount++;
|
||||
}
|
||||
if(standardIndicatorsCount > 1) {
|
||||
qsort(gIndicatorSlots, standardIndicatorsCount, sizeof(*gIndicatorSlots), indicatorBoxCompareByPosition);
|
||||
}
|
||||
// After sorting standard indicators, append custom ones.
|
||||
// This re-iterates the custom tag addition logic, but ensures they come after sorted standard tags.
|
||||
customTagStartSlot = standardIndicatorsCount;
|
||||
for (int i = 0; i < gActiveCustomTagsCount && customTagStartSlot < INDICATOR_SLOTS_COUNT; ++i) {
|
||||
if (gCustomIndicatorBoxes[i].isActive) {
|
||||
gIndicatorSlots[customTagStartSlot++] = INDICATOR_COUNT + i;
|
||||
}
|
||||
}
|
||||
count = customTagStartSlot;
|
||||
}
|
||||
|
||||
|
||||
if (gIndicatorBarWindow != -1 && count == 0) { // If no indicators (standard or custom), destroy window.
|
||||
windowDestroy(gIndicatorBarWindow);
|
||||
gIndicatorBarWindow = -1;
|
||||
} else if (count > 0) {
|
||||
if (gIndicatorBarWindow == -1) { // Create window if it doesn't exist and there are indicators
|
||||
Rect interfaceBarWindowRect;
|
||||
windowGetRect(gInterfaceBarWindow, &interfaceBarWindowRect);
|
||||
gIndicatorBarWindow = windowCreate(interfaceBarWindowRect.left,
|
||||
screenGetHeight() - INTERFACE_BAR_HEIGHT - INDICATOR_BOX_HEIGHT,
|
||||
(INDICATOR_BOX_WIDTH - INDICATOR_BOX_CONNECTOR_WIDTH) * count, // Adjust width based on actual count
|
||||
INDICATOR_BOX_HEIGHT,
|
||||
_colorTable[0],
|
||||
0);
|
||||
} else { // If window exists, adjust its width if necessary
|
||||
windowResize(gIndicatorBarWindow, (INDICATOR_BOX_WIDTH - INDICATOR_BOX_CONNECTOR_WIDTH) * count, INDICATOR_BOX_HEIGHT);
|
||||
}
|
||||
indicatorBarRender(count); // Render all (standard + custom)
|
||||
windowRefresh(gIndicatorBarWindow);
|
||||
}
|
||||
// END Custom Tag Handling in indicatorBarRefresh
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
@@ -2421,17 +2493,62 @@ static void indicatorBarRender(int count)
|
||||
int connectorWidthCompensation = INDICATOR_BOX_CONNECTOR_WIDTH;
|
||||
|
||||
for (int index = 0; index < count; index++) {
|
||||
int indicator = gIndicatorSlots[index];
|
||||
IndicatorDescription* indicatorDescription = &(gIndicatorDescriptions[indicator]);
|
||||
int indicatorId = gIndicatorSlots[index];
|
||||
|
||||
blitBufferToBufferTrans(indicatorDescription->data + connectorWidthCompensation,
|
||||
INDICATOR_BOX_WIDTH - connectorWidthCompensation,
|
||||
INDICATOR_BOX_HEIGHT,
|
||||
INDICATOR_BOX_WIDTH,
|
||||
windowBuffer + x, windowWidth);
|
||||
if (indicatorId >= INDICATOR_COUNT) { // This is a custom tag
|
||||
int customTagIndex = indicatorId - INDICATOR_COUNT;
|
||||
if (customTagIndex < gActiveCustomTagsCount && gCustomIndicatorBoxes[customTagIndex].isActive) {
|
||||
CustomIndicatorBox* customBox = &gCustomIndicatorBoxes[customTagIndex];
|
||||
|
||||
// Prepare a temporary IndicatorDescription-like structure or directly use customBox properties
|
||||
// For simplicity, let's assume a generic background for custom tags for now
|
||||
// and render text directly. A more advanced approach might involve
|
||||
// creating a temporary prerendered box similar to standard indicators.
|
||||
|
||||
// Fallback generic box (e.g. using SNEAK's background, but ideally a new generic one)
|
||||
// This is a placeholder. Proper handling would involve a generic custom box graphic.
|
||||
unsigned char* boxArt = gIndicatorDescriptions[INDICATOR_SNEAK].data; // Placeholder
|
||||
|
||||
// Create a temporary buffer for this custom tag's rendering
|
||||
unsigned char tempBoxData[INDICATOR_BOX_WIDTH * INDICATOR_BOX_HEIGHT];
|
||||
memcpy(tempBoxData, boxArt, INDICATOR_BOX_WIDTH * INDICATOR_BOX_HEIGHT);
|
||||
|
||||
// Render custom text onto this temporary buffer
|
||||
// Need to set font and color appropriately
|
||||
int oldFont = fontGetCurrent();
|
||||
fontSetCurrent(101); // Assuming same font as standard indicators
|
||||
|
||||
// Calculate text position (centering)
|
||||
int textY = (INDICATOR_BOX_HEIGHT - fontGetLineHeight()) / 2; // Adjusted for actual box height
|
||||
int textX = (INDICATOR_BOX_WIDTH - fontGetStringWidth(customBox->text)) / 2;
|
||||
|
||||
// Ensure textX is not negative if text is too long (it should be truncated already, but as a safeguard)
|
||||
if (textX < 0) textX = 0;
|
||||
|
||||
fontDrawText(tempBoxData + INDICATOR_BOX_WIDTH * textY + textX,
|
||||
customBox->text,
|
||||
INDICATOR_BOX_WIDTH, // Max width for text area
|
||||
INDICATOR_BOX_WIDTH, // Stride
|
||||
customBox->color); // Use color from custom tag
|
||||
|
||||
fontSetCurrent(oldFont); // Restore original font
|
||||
|
||||
blitBufferToBufferTrans(tempBoxData + connectorWidthCompensation,
|
||||
INDICATOR_BOX_WIDTH - connectorWidthCompensation,
|
||||
INDICATOR_BOX_HEIGHT,
|
||||
INDICATOR_BOX_WIDTH, // Source buffer stride
|
||||
windowBuffer + x, windowWidth);
|
||||
}
|
||||
} else { // This is a standard indicator
|
||||
IndicatorDescription* indicatorDescription = &(gIndicatorDescriptions[indicatorId]);
|
||||
blitBufferToBufferTrans(indicatorDescription->data + connectorWidthCompensation,
|
||||
INDICATOR_BOX_WIDTH - connectorWidthCompensation,
|
||||
INDICATOR_BOX_HEIGHT,
|
||||
INDICATOR_BOX_WIDTH,
|
||||
windowBuffer + x, windowWidth);
|
||||
}
|
||||
|
||||
connectorWidthCompensation = 0;
|
||||
|
||||
unconnectedIndicatorsWidth += INDICATOR_BOX_WIDTH;
|
||||
x = unconnectedIndicatorsWidth - INDICATOR_BOX_CONNECTOR_WIDTH * connections;
|
||||
connections++;
|
||||
@@ -2483,12 +2600,15 @@ bool indicatorBarHide()
|
||||
static void customInterfaceBarInit()
|
||||
{
|
||||
gInterfaceBarContentOffset = gInterfaceBarWidth - 640;
|
||||
if (gInterfaceBarContentOffset > 0) {
|
||||
if (screenGetWidth() > 640 && gInterfaceBarWidth <= screenGetWidth()) {
|
||||
char path[COMPAT_MAX_PATH];
|
||||
snprintf(path, sizeof(path), "art\\intrface\\HR_IFACE_%d.FRM", gInterfaceBarWidth);
|
||||
|
||||
if (gInterfaceBarContentOffset > 0 && screenGetWidth() > 640) {
|
||||
char path[COMPAT_MAX_PATH];
|
||||
snprintf(path, sizeof(path), "art\\intrface\\HR_IFACE_%d.FRM", gInterfaceBarWidth);
|
||||
|
||||
gCustomInterfaceBarBackground = artLoad(path);
|
||||
gCustomInterfaceBarBackground = artLoad(path);
|
||||
} else {
|
||||
debugPrint("\nINTRFACE: Custom interface bar width (%d) is greater than screen width (%d). Using default interface bar.\n", gInterfaceBarWidth, screenGetWidth());
|
||||
}
|
||||
}
|
||||
|
||||
if (gCustomInterfaceBarBackground != nullptr) {
|
||||
@@ -2650,4 +2770,54 @@ bool interface_get_current_attack_mode(int* hit_mode)
|
||||
return true;
|
||||
}
|
||||
|
||||
int interfaceAddCustomTag() {
|
||||
if (gActiveCustomTagsCount >= MAX_CUSTOM_INDICATOR_BOXES) {
|
||||
return -1; // No slot available
|
||||
}
|
||||
// Find the first non-active slot conceptually, but since we use gActiveCustomTagsCount,
|
||||
// we just append to the current list of active ones.
|
||||
// The actual "slot" in gCustomIndicatorBoxes is gActiveCustomTagsCount.
|
||||
int tagId = gActiveCustomTagsCount;
|
||||
|
||||
gCustomIndicatorBoxes[tagId].isActive = true; // Mark as active conceptually
|
||||
// Default text/color can be set here if desired, e.g.:
|
||||
strncpy(gCustomIndicatorBoxes[tagId].text, "NEW", 19);
|
||||
gCustomIndicatorBoxes[tagId].text[19] = '\0';
|
||||
gCustomIndicatorBoxes[tagId].color = _colorTable[992]; // Default to green, like SNEAK
|
||||
|
||||
gActiveCustomTagsCount++;
|
||||
// Note: indicatorBarRefresh() is not called here;
|
||||
// it's typically called after a batch of changes or by show/hide.
|
||||
return tagId;
|
||||
}
|
||||
|
||||
void interfaceShowCustomTag(int tagId) {
|
||||
if (tagId < 0 || tagId >= gActiveCustomTagsCount) return;
|
||||
gCustomIndicatorBoxes[tagId].isActive = true;
|
||||
indicatorBarRefresh();
|
||||
}
|
||||
|
||||
void interfaceHideCustomTag(int tagId) {
|
||||
if (tagId < 0 || tagId >= gActiveCustomTagsCount) return;
|
||||
gCustomIndicatorBoxes[tagId].isActive = false;
|
||||
indicatorBarRefresh();
|
||||
}
|
||||
|
||||
bool interfaceIsCustomTagActive(int tagId) {
|
||||
if (tagId < 0 || tagId >= gActiveCustomTagsCount) return false;
|
||||
return gCustomIndicatorBoxes[tagId].isActive;
|
||||
}
|
||||
|
||||
void interfaceSetCustomTagText(int tagId, const char* text, int color) {
|
||||
if (tagId < 0 || tagId >= gActiveCustomTagsCount) return;
|
||||
|
||||
strncpy(gCustomIndicatorBoxes[tagId].text, text, 19);
|
||||
gCustomIndicatorBoxes[tagId].text[19] = '\0'; // Ensure null termination
|
||||
gCustomIndicatorBoxes[tagId].color = color;
|
||||
|
||||
if (gCustomIndicatorBoxes[tagId].isActive) {
|
||||
indicatorBarRefresh();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace fallout
|
||||
|
||||
@@ -12,6 +12,22 @@ namespace fallout {
|
||||
#define INTERFACE_BAR_WIDTH 640
|
||||
#define INTERFACE_BAR_HEIGHT 100
|
||||
|
||||
// Minimum radiation amount to display RADIATED indicator.
|
||||
#define RADATION_INDICATOR_THRESHOLD 65
|
||||
// Minimum poison amount to display POISONED indicator.
|
||||
#define POISON_INDICATOR_THRESHOLD 0
|
||||
|
||||
#define MAX_CUSTOM_INDICATOR_BOXES 126
|
||||
|
||||
typedef struct CustomIndicatorBox {
|
||||
char text[20]; // 19 chars + null terminator
|
||||
int color; // Could be an enum or int mapped to color values
|
||||
bool isActive;
|
||||
// Potentially add a specific ID if needed, though array index might suffice
|
||||
} CustomIndicatorBox;
|
||||
|
||||
extern CustomIndicatorBox gCustomIndicatorBoxes[MAX_CUSTOM_INDICATOR_BOXES];
|
||||
|
||||
typedef enum InterfaceItemAction {
|
||||
INTERFACE_ITEM_ACTION_DEFAULT = -1,
|
||||
INTERFACE_ITEM_ACTION_USE,
|
||||
@@ -63,6 +79,12 @@ bool indicatorBarShow();
|
||||
bool indicatorBarHide();
|
||||
bool interface_get_current_attack_mode(int* hit_mode);
|
||||
|
||||
int interfaceAddCustomTag();
|
||||
void interfaceShowCustomTag(int tagId);
|
||||
void interfaceHideCustomTag(int tagId);
|
||||
bool interfaceIsCustomTagActive(int tagId);
|
||||
void interfaceSetCustomTagText(int tagId, const char* text, int color);
|
||||
|
||||
unsigned char* customInterfaceBarGetBackgroundImageData();
|
||||
|
||||
} // namespace fallout
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user