Compare commits

..
Author SHA1 Message Date
google-labs-jules[bot] a59d88f9f5 feat: Implement interface tag functions
I've implemented new sfall script functions to control interface tags (boxes appearing above the interface like SNEAK, LEVEL).

New functions:
- `add_iface_tag()`: Adds a new custom interface tag box. Returns the ID of the new tag (5 to 130) or -1 if the limit (126 custom tags) is reached.
- `show_iface_tag(tag_id)`: Activates a specified tag.
  - For predefined tags: 0 (SNEAK), 3 (LEVEL), 4 (ADDICT), modifies player state.
  - For custom tags (5+): Activates the custom box.
- `hide_iface_tag(tag_id)`: Deactivates a specified tag.
  - For predefined tags: Clears player state.
  - For custom tags: Deactivates the custom box.
- `is_iface_tag_active(tag_id)`: Returns 1 if the tag is active, 0 otherwise.
  - Checks game state for predefined tags: 0 (SNEAK), 1 (POISONED), 2 (RADIATED), 3 (LEVEL), 4 (ADDICT).
  - Checks status for custom tags (5+).
- `set_iface_tag_text(tag_id, text, color)`: Sets the display text (max 19 chars) and color for a custom interface tag.

I've made changes to `interface.h`, `interface.cc`, `sfall_opcodes.cc`, and `sfall_metarules.cc` to implement these features. I also created a new file `sfall_testing/gl_test_iface_tags.ssl` to test all the new functionalities.
2025-06-22 04:43:38 +00:00
177 changed files with 4126 additions and 16475 deletions
+6 -47
View File
@@ -14,16 +14,6 @@ jobs:
runs-on: ubuntu-latest
permissions: write-all
steps:
- name: start deployment
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository
uses: bobheadxi/deployments@v1
id: deployment
with:
ref: ${{ github.event.pull_request.head.sha }}
step: start
debug: true
env: preview-${{ github.event.pull_request.number }}
- name: Clone
uses: actions/checkout@v4
@@ -86,10 +76,6 @@ jobs:
SITE_BASE: ${{ steps.set-path.outputs.SITE_BASE }}
F2_GAME_VERSION: Game=${{github.event.pull_request.head.sha || github.sha }}
- name: Place hash file
if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository)
run: echo "$GITHUB_SHA" > fallout2-ce-ems/dist/site_hash.txt
- name: Install rsync (required for the next step)
if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository)
run: |
@@ -105,37 +91,10 @@ jobs:
clean: false
single-commit: true
- name: Check that site is deployed
if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository)
run: |
URL="$SITE_ORIGIN""$SITE_BASE""site_hash.txt"
echo "Expecting $GITHUB_SHA from $URL"
for i in `seq 1 80`; do
got="$(curl -fsSL --max-time 10 "$URL" | tr -d '\r\n' || true)"
if [ "$got" = "$GITHUB_SHA" ]; then
echo "âś… Match on attempt $i: ${got}"
exit 0
fi
echo "Received $got, waiting"
sleep 5
done
exit 1
env:
SITE_ORIGIN: https://${{ github.repository_owner }}.github.io
SITE_BASE: ${{ steps.set-path.outputs.SITE_BASE }}
- name: Update deployment status
- name: Comment preview URL on PR
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository
uses: bobheadxi/deployments@v1
with:
step: finish
status: success
debug: true
env: ${{ steps.deployment.outputs.env }}
deployment_id: ${{ steps.deployment.outputs.deployment_id }}
ref: ${{ github.event.pull_request.head.sha }}
env_url: https://${{ github.repository_owner }}.github.io/fallout2-ce/PR-${{ github.event.pull_request.number }}/
run: |
gh pr comment ${{ github.event.pull_request.number }} \
--body "🚀 WebAssembly PR Preview for ${{github.event.pull_request.head.sha || github.sha }} is available at: https://${{ github.repository_owner }}.github.io/fallout2-ce/PR-${{ github.event.pull_request.number }}/"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+8 -8
View File
@@ -87,7 +87,7 @@ jobs:
uses: actions/cache@v4
with:
path: os/android/app/.cxx
key: android-cmake-v101
key: android-cmake-v2
- name: Setup signing config
if: env.KEYSTORE_FILE_BASE64 != '' && env.KEYSTORE_PROPERTIES_FILE_BASE64 != ''
@@ -128,7 +128,7 @@ jobs:
uses: actions/cache@v4
with:
path: out
key: ios-cmake-v101
key: ios-cmake-v5
- name: Configure
run: |
@@ -183,7 +183,7 @@ jobs:
uses: actions/cache@v4
with:
path: out
key: linux-${{ matrix.arch }}-cmake-v101
key: linux-${{ matrix.arch }}-cmake-v3
- name: Configure
run: |
@@ -288,7 +288,7 @@ jobs:
uses: actions/cache@v4
with:
path: out
key: macos-cmake-v101
key: macos-cmake-v6
- name: Configure
run: |
@@ -328,16 +328,16 @@ jobs:
- name: Clone
uses: actions/checkout@v4
- name: Install Visual Studio 2022 Build Tools with v141_xp
- name: Install Visual Studio 2019 Build Tools with v141_xp
shell: cmd
run: |
choco install visualstudio2022buildtools -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'"
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-v102
key: windows-${{ matrix.arch }}-cmake-v3
- name: Configure
shell: cmd
@@ -432,4 +432,4 @@ jobs:
tag_name: continious
draft: false
prerelease: true
name: Fallout2-CE Continuous Build
name: Fallout2-CE Continuous Build
+1 -10
View File
@@ -12,7 +12,6 @@ jobs:
runs-on: ubuntu-latest
permissions:
contents: write
deployments: write
steps:
- name: Checkout gh-pages branch
uses: actions/checkout@v4
@@ -44,12 +43,4 @@ jobs:
else
git commit --amend -m "đźš® Remove preview for closed PR #${{ github.event.pull_request.number }}"
git push --force
fi
- name: mark environment as deactivated
uses: bobheadxi/deployments@v1
with:
step: deactivate-env
env: preview-${{ github.event.pull_request.number }}
desc: Environment was pruned
debug: true
fi
+1 -34
View File
@@ -286,19 +286,12 @@ target_sources(${EXECUTABLE_NAME} PUBLIC
"src/sfall_metarules.h"
"src/sfall_opcodes.cc"
"src/sfall_opcodes.h"
"src/sfall_script_hooks.cc"
"src/sfall_arrays.cc"
"src/sfall_arrays.h"
"src/sfall_callbacks.cc"
"src/sfall_callbacks.h"
"src/touch.cc"
"src/touch.h"
"src/scan_unimplemented.cc"
"third_party/lodepng/lodepng.cpp"
)
target_include_directories(${EXECUTABLE_NAME} PUBLIC "third_party/lodepng")
if(IOS)
target_sources(${EXECUTABLE_NAME} PUBLIC
"src/platform/ios/paths.h"
@@ -319,8 +312,7 @@ endif()
if(WIN32)
target_link_libraries(${EXECUTABLE_NAME}
winmm
debug libcpmtd
optimized libcpmt
libcpmt
)
endif()
@@ -509,28 +501,3 @@ if(CMAKE_SYSTEM_NAME MATCHES "Emscripten")
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -g")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -g")
endif()
# Optional path to which the built executable and PDB will be copied after build.
# Can be set via -DCOPY_TO_PATH=/some/path or through the CMake/CLion GUI.
set(COPY_TO_PATH "" CACHE PATH "Destination directory for copying the executable and PDB after build")
if(DEFINED COPY_TO_PATH AND NOT "${COPY_TO_PATH}" STREQUAL "")
add_custom_command(TARGET ${EXECUTABLE_NAME} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E make_directory "${COPY_TO_PATH}"
COMMAND ${CMAKE_COMMAND} -E copy_if_different
"$<TARGET_FILE:${EXECUTABLE_NAME}>"
"${COPY_TO_PATH}/"
COMMAND ${CMAKE_COMMAND} -E echo "Copied executable to ${COPY_TO_PATH}"
)
if(MSVC)
add_custom_command(TARGET ${EXECUTABLE_NAME} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different
"$<TARGET_PDB_FILE:${EXECUTABLE_NAME}>"
"${COPY_TO_PATH}/"
COMMAND ${CMAKE_COMMAND} -E echo "Copied PDB to ${COPY_TO_PATH}"
)
message(STATUS "Will copy executable and PDB to: ${COPY_TO_PATH}")
else()
message(STATUS "Will copy executable to: ${COPY_TO_PATH}")
endif()
endif()
+2 -2
View File
@@ -28,7 +28,7 @@
"inherits": [
"windows-base"
],
"generator": "Visual Studio 17 2022",
"generator": "Visual Studio 16 2019",
"architecture": "Win32",
"cacheVariables": {
"CMAKE_GENERATOR_TOOLSET": "v141_xp",
@@ -42,7 +42,7 @@
"inherits": [
"windows-base"
],
"generator": "Visual Studio 17 2022",
"generator": "Visual Studio 16 2019",
"architecture": "x64",
"cacheVariables": {
"CMAKE_MSVC_RUNTIME_LIBRARY": "MultiThreaded$<$<CONFIG:Debug>:Debug>"
+1 -12
View File
@@ -44,7 +44,7 @@ $ sudo apt install libsdl2-2.0-0
- Alternatively you can use Fallout 2 from Macplay/The Omni Group as a base - you need to extract game assets from the original bundle. Mount CD/DMG, right click `Fallout 2` -> `Show Package Contents`, navigate to `Contents/Resources`. Copy `GameData` folder somewhere, for example `/Applications/Fallout2`.
- Or if you're a Terminal user and have Homebrew installed you can extract the needed files from the GoG installer. **Note**: You must use "Offline backup game installer", not the main game installer.
- Or if you're a Terminal user and have Homebrew installed you can extract the needed files from the GoG installer:
```console
$ brew install innoextract
@@ -126,17 +126,6 @@ IFACE_BAR_SIDES_ORI=0
;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
[STATIC_SCREENS]
;if SPLASH_SCRN_SIZE=0 - Splash screen shows at original size if it fits, otherwise scales down while preserving aspect ratio.
;if SPLASH_SCRN_SIZE=1 - Splash screen scales to fit the screen while preserving aspect ratio.
;if SPLASH_SCRN_SIZE=2 - Splash screen stretches to fill the entire screen.
SPLASH_SCRN_SIZE=0
[MAPS]
;if IGNORE_MAP_EDGES=0 - Hi-Res map scroll edges are enabled.
;if IGNORE_MAP_EDGES=1 - Hi-Res map scroll edges are ignored.
IGNORE_MAP_EDGES=0
```
Recommendations:
-1
View File
@@ -1,2 +1 @@
*.int
sfall
-33
View File
@@ -1,33 +0,0 @@
#include "test_utils.h"
#include "sfall.h"
procedure start begin
display_msg("Testing iface_tag functions...");
// assumes no iface tags active in the save game
// SNEAK
show_iface_tag(0);
call assertTrue("show_iface_tag(SNEAK)", is_iface_tag_active(0));
hide_iface_tag(0);
call assertFalse("hide_iface_tag(SNEAK)", is_iface_tag_active(0));
// LEVEL
show_iface_tag(3);
call assertTrue("show_iface_tag(LEVEL)", is_iface_tag_active(3));
hide_iface_tag(3);
call assertFalse("hide_iface_tag(LEVEL)", is_iface_tag_active(3));
// ADDICTED
show_iface_tag(4);
call assertTrue("show_iface_tag(ADDICTED)", is_iface_tag_active(4));
hide_iface_tag(4);
call assertFalse("hide_iface_tag(ADDICTED)", is_iface_tag_active(4));
// POISONED
call assertFalse("hide_iface_tag(POISONED)", is_iface_tag_active(1));
// RADIATED
call assertFalse("hide_iface_tag(RADIATED)", is_iface_tag_active(2));
call report_test_results("iface_tag");
end
+189
View File
@@ -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
-69
View File
@@ -1,69 +0,0 @@
#include "lib.arrays.h" // For arrays_equal, len_array, etc.
#include "test_utils.h" // For assertEquals, VALTYPE_STR, report_test_results
// To test: copy test.ini into the game folder before running
procedure ini_test_suite begin
variable result_array, expected_array, key_type, value_type, count, key_val_pair;
display_msg("--- Testing ini functions");
// Test Case 1: Valid file, valid section
result_array := get_ini_section("test.ini", "ValidSection");
expected_array := {"Key1": "Value1", "Key2": "2"};
call assertEquals("TC1 Size", len_array(result_array), 2);
call assertEquals("TC1 Content", arrays_equal(result_array, expected_array), true);
count := 0;
foreach key_val_pair in result_array begin
count += 1;
call assertEquals("TC1 Key Type " + count, typeof(key_val_pair[0]), VALTYPE_STR);
call assertEquals("TC1 Value Type " + count, typeof(key_val_pair[1]), VALTYPE_STR);
end
// basic fetchers
call assertEquals("TC2 Specific Key1 Value", result_array["Key1"], "Value1");
call assertEquals("TC2 get_ini_setting string", get_ini_setting("test.ini|ValidSection|Key1"), 0);
call assertEquals("TC2 get_ini_string string", get_ini_string("test.ini|ValidSection|Key1"), "Value1");
call assertEquals("TC2 get_ini_setting int", get_ini_setting("test.ini|ValidSection|Key2"), 2);
call assertEquals("TC2 get_ini_string int", get_ini_string("test.ini|ValidSection|Key2"), "2");
// INI file not found
result_array := get_ini_section("nonexistent.ini", "AnySection");
call assertEquals("TC3 Size", len_array(result_array), 0);
call assertEquals("TC3 Is Map", array_key(result_array, -1), 1); // Should still be an associative array
call assertEquals("TC3 get_ini_setting", get_ini_setting("notexist.ini|ValidSection|Key2"), 0);
call assertEquals("TC3 get_ini_string", get_ini_string("notexist.ini|ValidSection|Key2"), "");
// Valid file, section not found
result_array := get_ini_section("test.ini", "NonExistentSection");
call assertEquals("TC4 Size", len_array(result_array), 0);
call assertEquals("TC4 Is Map", array_key(result_array, -1), 1);
call assertEquals("TC4 get_ini_setting", get_ini_setting("test.ini|NonExistentSection|Key2"), 0);
call assertEquals("TC4 get_ini_string", get_ini_string("test.ini|NonExistentSection|Key2"), "");
// Valid file, empty section
result_array := get_ini_section("test.ini", "EmptySection");
call assertEquals("TC5 Size", len_array(result_array), 0);
call assertEquals("TC5 Is Map", array_key(result_array, -1), 1);
call assertEquals("TC5 get_ini_setting", get_ini_setting("test.ini|EmptySection|Key2"), 0);
call assertEquals("TC5 get_ini_string", get_ini_string("test.ini|EmptySection|Key2"), "");
// set ini setting
set_ini_setting("test_set.ini|ValidSection|Key3", "OldValue");
call assertEquals("TC6 set_ini_setting 1", get_ini_string("test_set.ini|ValidSection|Key3"), "OldValue");
set_ini_setting("test_set.ini|ValidSection|Key3", "NewValue");
call assertEquals("TC6 set_ini_setting 2", get_ini_string("test_set.ini|ValidSection|Key3"), "NewValue");
set_ini_setting("test_set.ini|ValidSection|Key3", 2);
call assertEquals("TC6 set_ini_setting int", get_ini_setting("test_set.ini|ValidSection|Key3"), 2);
// get_init_sections
result_array := get_ini_sections("test.ini");
call assertEquals("TC7 get_ini_sections size", len_array(result_array), 3);
call assertTrue("TC7 get_ini_sections", scan_array(result_array, "TestMisc") >= 0);
call report_test_results("ini");
end
procedure start begin
call ini_test_suite();
end
-14
View File
@@ -1,14 +0,0 @@
#include "sfall.h"
// this is the basic message_box function used by RPU and Et Tu
#define message_box_warning(text) message_box4(text, 0x01, 134, 145)
procedure start begin
// I don't see a practice way to auto test message_box, so this is just a way to do manual testing
variable ret = message_box1("Basic Dialog\nwith multiple lines\nand such.");
display_msg("chose: " + ret);
message_box2("Basic Dialog\nwith default flags.", -1); // -1 means use default flags
message_box2("Basic Dialog\nsmall.", 0x02);
message_box_warning("Missing config values\n(just testing).");
end
-30
View File
@@ -1,30 +0,0 @@
#include "test_utils.h"
#include "sfall.h"
procedure start begin
display_msg("Testing metarule_exist functions...");
call assertEquals("string_format", metarule_exist("string_format"), 1);
call assertEquals("metarule_exist", metarule_exist("metarule_exist"), 1);
// This will fail on sFall
call assertEquals("opcode_exists", metarule_exist("opcode_exists"), 1);
if metarule_exist("opcode_exists") then begin // And example if how to use it
#define opcode_exists(opcode) sfall_func1("opcode_exists", opcode)
#define OP_GET_UPTIME 0x81B3
#define OP_NOOP 0x8000
#define OP_WRITE_INT 0x81D1
call assertEquals("opcode_exists OP_GET_UPTIME", opcode_exists(OP_GET_UPTIME), 1);
call assertEquals("opcode_exists OP_NOOP", opcode_exists(OP_NOOP), 1);
// Probably will never be implemented on fallout2-ce
call assertEquals("opcode_exists OP_WRITE_INT", opcode_exists(OP_WRITE_INT), 0);
call assertEquals("opcode_exists 0xFFFF", opcode_exists(0xFFFF), 0);
end else begin
display_msg("Opcode exists metarule is not available, skipping tests.");
end
call report_test_results("metarule_exist");
end
-7
View File
@@ -1,7 +0,0 @@
#include "sfall.h"
procedure start begin
// this isn't a unit test, just a way to easily test different values manually
display_msg("Setting world map mult");
set_map_time_multi(1.0);
end
-120
View File
@@ -1,120 +0,0 @@
#include "../sfall/sfall.h"
#include "../sfall/dik.h"
#include "../sfall/define_lite.h"
#include "../test_utils.h"
variable last_game_mode;
variable gamemodechange_assertions_enabled := false;
variable gamemodechange_reported := false;
procedure test_with_args(variable arg1) begin
display_msg("test_w_arg" + arg1);
end
procedure test_wo_args begin
display_msg("test_wo_args");
end
procedure tohit_handler begin
variable
attacker := get_sfall_arg_at(1),
target := get_sfall_arg_at(2),
chanceRaw := get_sfall_arg_at(7);
display_msg(string_format("tohit %s -> %s (%d)", obj_name(attacker), obj_name(target), chanceRaw));
if (attacker == dude_obj) then
set_sfall_return(100);
else if (target == dude_obj) then
set_sfall_return(0);
end
procedure useobj_handler begin
variable
user := get_sfall_arg,
item := get_sfall_arg;
display_msg(string_format("useobj %s (%s)", obj_name(user), obj_name(item)));
set_sfall_return(2);
end
procedure useobjon_handler begin
variable args := get_sfall_args,
target := args[0],
user := args[1],
item := args[2];
display_msg(string_format("useobjon %s (%s) -> %s [len: %d]", obj_name(user), obj_name(item), obj_name(target), len_array(args)));
set_sfall_return(0);
end
procedure keypress_handler begin
variable
pressed := get_sfall_arg_at(0),
key := get_sfall_arg_at(1);
display_msg(string_format("keypress %d dx: %d", pressed, key));
if (not pressed) then return;
if (key == DIK_F2) then begin
display_msg(string_format("tile %d, elev %d, map %d", tile_under_cursor, elevation(dude_obj), cur_map_index));
end if (key == DIK_F3) then begin
if (key_pressed(DIK_LCONTROL)) then begin
variable cr := obj_under_cursor(true, false);
if (cr) then
critter_dmg(cr, 1000, DMG_BYPASS_ARMOR);
end else
move_to(dude_obj, tile_under_cursor, elevation(dude_obj));
end
end
procedure gamemodechange_handler begin
variable
event_type := get_sfall_arg_at(0),
previous_mode := get_sfall_arg_at(1),
current_mode := get_game_mode;
display_msg(string_format("gamemodechange event=%d prev=%d cur=%d", event_type, previous_mode, current_mode));
if (not gamemodechange_assertions_enabled) then begin
last_game_mode := current_mode;
return;
end
call assertTrue("gamemodechange event_type is 0 or 1", event_type == 0 or event_type == 1);
call assertEquals("gamemodechange previous mode", previous_mode, last_game_mode);
if (event_type == 0) then
call assertNotEquals("gamemodechange mode changed", current_mode, previous_mode);
else if (not gamemodechange_reported) then begin
gamemodechange_reported := true;
call report_test_results("hook_gamemodechange");
end
last_game_mode := current_mode;
end
procedure start begin
//call test_with_args(777);
display_msg("start hook test");
if (not game_loaded) then return;
add_obj_to_inven(dude_obj, create_object_sid(40, 0, 0, -1));
add_obj_to_inven(dude_obj, create_object_sid(76, 0, 0, -1));
add_obj_to_inven(dude_obj, create_object_sid(85, 0, 0, -1));
last_game_mode := get_game_mode;
gamemodechange_assertions_enabled := true;
register_hook_proc(HOOK_KEYPRESS, keypress_handler);
register_hook_proc(HOOK_TOHIT, tohit_handler);
register_hook_proc(HOOK_USEOBJ, useobj_handler);
register_hook_proc(HOOK_USEOBJON, useobjon_handler);
register_hook_proc(HOOK_GAMEMODECHANGE, gamemodechange_handler);
end
procedure map_enter_p_proc begin
display_msg("Map Enter hook test");
end
-16
View File
@@ -1,16 +0,0 @@
[ValidSection]
Key1=Value1
Key2=2
[SectionWithSpaces]
LeadingSpaceKey = LeadingSpaceValue
TrailingSpaceKey = TrailingSpaceValue
Both Sides Space Key = Both Sides Space Value
SimpleKey=SimpleValue
[EmptySection]
; This is TestMisc, it's not empty but won't be directly tested for content here.
[TestMisc]
Alpha=Beta
Gamma=Delta
-22
View File
@@ -7,28 +7,6 @@ variable test_suite_errors := 0;
variable test_suite_verbose := false;
variable test_suite_assertions := 0;
procedure assertTrue(variable desc, variable a) begin
test_suite_assertions++;
if (not a) then begin
display_msg("Assertion failed \""+desc+"\": is not true");
test_suite_errors ++;
end else if (test_suite_verbose) then begin
display_msg("Assert \""+desc+"\" ok");
end
end
procedure assertFalse(variable desc, variable a) begin
test_suite_assertions++;
if (a) then begin
display_msg("Assertion failed \""+desc+"\": is not false");
test_suite_errors ++;
end else if (test_suite_verbose) then begin
display_msg("Assert \""+desc+"\" ok");
end
end
procedure assertEquals(variable desc, variable a, variable b) begin
test_suite_assertions++;
+45 -32
View File
@@ -103,7 +103,7 @@ static int hideProjectile(void* _, void* projectile);
// 0x410468
int actionKnockdown(Object* obj, int* anim, int maxDistance, int rotation, int delay)
{
if (critterFlagCheck(obj->pid, CRITTER_NO_KNOCKBACK)) {
if (_critter_flag_check(obj->pid, CRITTER_NO_KNOCKBACK)) {
return -1;
}
@@ -208,7 +208,7 @@ int _pick_death(Object* attacker, Object* defender, Object* weapon, int damage,
int violenceLevel = settings.preferences.violence_level;
if (critterFlagCheck(defender->pid, CRITTER_SPECIAL_DEATH)) {
if (_critter_flag_check(defender->pid, CRITTER_SPECIAL_DEATH)) {
return _check_death(defender, ANIM_EXPLODED_TO_NOTHING, VIOLENCE_LEVEL_NORMAL, hitFromFront);
}
@@ -285,7 +285,7 @@ int _check_death(Object* obj, int anim, int minViolenceLevel, bool hitFromFront)
// 0x4108C8
int _internal_destroy(Object* _, Object* toDestroy)
{
return objectDestroy(toDestroy);
return _obj_destroy(toDestroy);
}
// TODO: Check very carefully, lots of conditions and jumps.
@@ -297,12 +297,12 @@ void _show_damage_to_object(Object* defender, int damage, int flags, Object* wea
int fid;
const char* sfx_name;
if (critterFlagCheck(defender->pid, CRITTER_NO_KNOCKBACK)) {
if (_critter_flag_check(defender->pid, CRITTER_NO_KNOCKBACK)) {
knockbackDistance = 0;
}
anim = FID_ANIM_TYPE(defender->fid);
if (!critterIsProne(defender)) {
if (!_critter_is_prone(defender)) {
if ((flags & DAM_DEAD) != 0) {
fid = buildFid(OBJ_TYPE_MISC, 10, 0, 0, 0);
if (fid == attacker->fid) {
@@ -447,7 +447,7 @@ void _show_damage_to_object(Object* defender, int damage, int flags, Object* wea
if (weapon != nullptr) {
if ((flags & DAM_EXPLODE) != 0) {
animationRegisterCallbackForced(defender, weapon, (AnimationCallback*)objectDrop, -1);
animationRegisterCallbackForced(defender, weapon, (AnimationCallback*)_obj_drop, -1);
fid = buildFid(OBJ_TYPE_MISC, 10, 0, 0, 0);
animationRegisterSetFid(weapon, fid, 0);
animationRegisterAnimateAndHide(weapon, ANIM_STAND, 0);
@@ -459,7 +459,7 @@ void _show_damage_to_object(Object* defender, int damage, int flags, Object* wea
} else if ((flags & DAM_DESTROY) != 0) {
animationRegisterCallbackForced(defender, weapon, (AnimationCallback*)_internal_destroy, -1);
} else if ((flags & DAM_DROP) != 0) {
animationRegisterCallbackForced(defender, weapon, (AnimationCallback*)objectDrop, -1);
animationRegisterCallbackForced(defender, weapon, (AnimationCallback*)_obj_drop, -1);
}
}
@@ -488,7 +488,7 @@ int _show_death(Object* obj, int anim)
}
}
if (!critterFlagCheck(obj->pid, CRITTER_FLAT)) {
if (!_critter_flag_check(obj->pid, CRITTER_FLAT)) {
obj->flags |= OBJECT_NO_BLOCK;
if (_obj_toggle_flat(obj, &tempRect) == 0) {
rectUnion(&dirtyRect, &tempRect, &dirtyRect);
@@ -499,7 +499,7 @@ int _show_death(Object* obj, int anim)
rectUnion(&dirtyRect, &tempRect, &dirtyRect);
}
if (anim >= 30 && anim <= 31 && !critterFlagCheck(obj->pid, CRITTER_SPECIAL_DEATH) && !critterFlagCheck(obj->pid, CRITTER_NO_DROP)) {
if (anim >= 30 && anim <= 31 && !_critter_flag_check(obj->pid, CRITTER_SPECIAL_DEATH) && !_critter_flag_check(obj->pid, CRITTER_NO_DROP)) {
itemDropAll(obj, obj->tile);
}
@@ -1032,7 +1032,9 @@ int _action_climb_ladder(Object* critter, Object* ladder)
reg_anim_begin(animationRequestOptions);
int tile = tileGetTileInDirection(ladder->tile, ROTATION_SE, 1);
if (actionPoints != -1 || objectWithinWalkDistance(critter, ladder)) {
int walkDistance = 5;
configGetInt(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_USE_WALK_DISTANCE, &walkDistance);
if (actionPoints != -1 || objectGetDistanceBetween(critter, ladder) < walkDistance) {
animationRegisterMoveToTile(critter, tile, ladder->elevation, actionPoints, 0);
} else {
animationRegisterRunToTile(critter, tile, ladder->elevation, actionPoints, 0);
@@ -1040,7 +1042,7 @@ int _action_climb_ladder(Object* critter, Object* ladder)
animationRegisterCallbackForced(critter, ladder, (AnimationCallback*)_is_next_to, -1);
animationRegisterRotateToTile(critter, ladder->tile);
animationRegisterCallbackForced(critter, ladder, (AnimationCallback*)checkSceneryUseActionPointCost, -1);
animationRegisterCallbackForced(critter, ladder, (AnimationCallback*)_check_scenery_ap_cost, -1);
int weaponAnimationCode = (critter->fid & 0xF000) >> 12;
if (weaponAnimationCode != 0) {
@@ -1052,7 +1054,7 @@ int _action_climb_ladder(Object* critter, Object* ladder)
const char* climbingSfx = sfxBuildCharName(critter, ANIM_CLIMB_LADDER, CHARACTER_SOUND_EFFECT_UNUSED);
animationRegisterPlaySoundEffect(critter, climbingSfx, -1);
animationRegisterAnimate(critter, ANIM_CLIMB_LADDER, 0);
animationRegisterCallback(critter, ladder, (AnimationCallback*)objectUse, -1);
animationRegisterCallback(critter, ladder, (AnimationCallback*)_obj_use, -1);
if (weaponAnimationCode != 0) {
animationRegisterTakeOutWeapon(critter, weaponAnimationCode, -1);
@@ -1099,7 +1101,9 @@ int _action_use_an_item_on_object(Object* user, Object* targetObj, Object* item)
reg_anim_begin(animationRequestOptions);
if (actionPoints != -1 || objectWithinWalkDistance(user, targetObj)) {
int walkDistance = 5;
configGetInt(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_USE_WALK_DISTANCE, &walkDistance);
if (actionPoints != -1 || objectGetDistanceBetween(user, targetObj) < walkDistance) {
animationRegisterMoveToObject(user, targetObj, actionPoints, 0);
} else {
animationRegisterRunToObject(user, targetObj, -1, 0);
@@ -1108,7 +1112,7 @@ int _action_use_an_item_on_object(Object* user, Object* targetObj, Object* item)
animationRegisterCallbackForced(user, targetObj, (AnimationCallback*)_is_next_to, -1);
if (item == nullptr) {
animationRegisterCallback(user, targetObj, (AnimationCallback*)checkSceneryUseActionPointCost, -1);
animationRegisterCallback(user, targetObj, (AnimationCallback*)_check_scenery_ap_cost, -1);
}
int weaponAnimCode = (user->fid & 0xF000) >> 12;
@@ -1120,9 +1124,9 @@ int _action_use_an_item_on_object(Object* user, Object* targetObj, Object* item)
int anim;
int objectType = FID_TYPE(targetObj->fid);
if (objectType == OBJ_TYPE_CRITTER && critterIsProne(targetObj)) {
if (objectType == OBJ_TYPE_CRITTER && _critter_is_prone(targetObj)) {
anim = ANIM_MAGIC_HANDS_GROUND;
} else if (objectType == OBJ_TYPE_SCENERY && (proto->scenery.extendedFlags & PROTO_EXT_FLAG_MAGIC_HANDS_GROUND) != 0) {
} else if (objectType == OBJ_TYPE_SCENERY && (proto->scenery.extendedFlags & 0x01) != 0) {
anim = ANIM_MAGIC_HANDS_GROUND;
} else {
anim = ANIM_MAGIC_HANDS_MIDDLE;
@@ -1134,9 +1138,9 @@ int _action_use_an_item_on_object(Object* user, Object* targetObj, Object* item)
if (item != nullptr) {
// TODO: Get rid of cast.
animationRegisterCallback3(user, targetObj, item, (AnimationCallback3*)objectUseItemOn, -1);
animationRegisterCallback3(user, targetObj, item, (AnimationCallback3*)_obj_use_item_on, -1);
} else {
animationRegisterCallback(user, targetObj, (AnimationCallback*)objectUse, -1);
animationRegisterCallback(user, targetObj, (AnimationCallback*)_obj_use, -1);
}
if (weaponAnimCode != 0) {
@@ -1169,12 +1173,15 @@ int actionPickUp(Object* critter, Object* item)
}
}
int walkDistance = 5;
configGetInt(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_USE_WALK_DISTANCE, &walkDistance);
if (isInCombat()) {
reg_anim_begin(ANIMATION_REQUEST_RESERVED);
animationRegisterMoveToObject(critter, item, critter->data.critter.combat.ap, 0);
} else {
reg_anim_begin(critter == gDude ? ANIMATION_REQUEST_RESERVED : ANIMATION_REQUEST_UNRESERVED);
if (!objectWithinWalkDistance(critter, item)) {
if (objectGetDistanceBetween(critter, item) >= walkDistance) {
animationRegisterRunToObject(critter, item, -1, 0);
} else {
animationRegisterMoveToObject(critter, item, -1, 0);
@@ -1182,7 +1189,7 @@ int actionPickUp(Object* critter, Object* item)
}
animationRegisterCallbackForced(critter, item, (AnimationCallback*)_is_next_to, -1);
animationRegisterCallback(critter, item, (AnimationCallback*)checkSceneryUseActionPointCost, -1);
animationRegisterCallback(critter, item, (AnimationCallback*)_check_scenery_ap_cost, -1);
Proto* itemProto;
protoGetProto(item->pid, &itemProto);
@@ -1207,7 +1214,7 @@ int actionPickUp(Object* critter, Object* item)
animationRegisterPlaySoundEffect(item, sfx, actionFrame);
}
animationRegisterCallback(critter, item, (AnimationCallback*)objectPickup, actionFrame);
animationRegisterCallback(critter, item, (AnimationCallback*)_obj_pickup, actionFrame);
} else {
int weaponAnimationCode = (critter->fid & 0xF000) >> 12;
if (weaponAnimationCode != 0) {
@@ -1235,7 +1242,7 @@ int actionPickUp(Object* critter, Object* item)
}
if (item->frame != 1) {
animationRegisterCallback(critter, item, (AnimationCallback*)objectUseContainer, actionFrame);
animationRegisterCallback(critter, item, (AnimationCallback*)_obj_use_container, actionFrame);
}
if (weaponAnimationCode != 0) {
@@ -1263,7 +1270,7 @@ int _action_loot_container(Object* critter, Object* container)
}
// SFALL: Fix for trying to loot corpses with the "NoSteal" flag.
if (critterFlagCheck(container->pid, CRITTER_NO_STEAL)) {
if (_critter_flag_check(container->pid, CRITTER_NO_STEAL)) {
return -1;
}
@@ -1274,13 +1281,16 @@ int _action_loot_container(Object* critter, Object* container)
}
}
int walkDistance = 5;
configGetInt(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_USE_WALK_DISTANCE, &walkDistance);
if (isInCombat()) {
reg_anim_begin(ANIMATION_REQUEST_RESERVED);
animationRegisterMoveToObject(critter, container, critter->data.critter.combat.ap, 0);
} else {
reg_anim_begin(critter == gDude ? ANIMATION_REQUEST_RESERVED : ANIMATION_REQUEST_UNRESERVED);
if (objectWithinWalkDistance(critter, container)) {
if (objectGetDistanceBetween(critter, container) < walkDistance) {
animationRegisterMoveToObject(critter, container, -1, 0);
} else {
animationRegisterRunToObject(critter, container, -1, 0);
@@ -1288,7 +1298,7 @@ int _action_loot_container(Object* critter, Object* container)
}
animationRegisterCallbackForced(critter, container, (AnimationCallback*)_is_next_to, -1);
animationRegisterCallback(critter, container, (AnimationCallback*)checkSceneryUseActionPointCost, -1);
animationRegisterCallback(critter, container, (AnimationCallback*)_check_scenery_ap_cost, -1);
animationRegisterCallback(critter, container, (AnimationCallback*)scriptsRequestLooting, -1);
return reg_anim_end();
}
@@ -1478,13 +1488,16 @@ int actionUseSkill(Object* user, Object* target, int skill)
}
}
int walkDistance = 5;
configGetInt(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_USE_WALK_DISTANCE, &walkDistance);
if (isInCombat()) {
reg_anim_begin(ANIMATION_REQUEST_RESERVED);
animationRegisterMoveToObject(performer, target, performer->data.critter.combat.ap, 0);
} else {
reg_anim_begin(user == gDude ? ANIMATION_REQUEST_RESERVED : ANIMATION_REQUEST_UNRESERVED);
if (target != gDude) {
if (!objectWithinWalkDistance(performer, target)) {
if (objectGetDistanceBetween(performer, target) >= walkDistance) {
animationRegisterRunToObject(performer, target, -1, 0);
} else {
animationRegisterMoveToObject(performer, target, -1, 0);
@@ -1494,7 +1507,7 @@ int actionUseSkill(Object* user, Object* target, int skill)
animationRegisterCallbackForced(performer, target, (AnimationCallback*)_is_next_to, -1);
int anim = (FID_TYPE(target->fid) == OBJ_TYPE_CRITTER && critterIsProne(target)) ? ANIM_MAGIC_HANDS_GROUND : ANIM_MAGIC_HANDS_MIDDLE;
int anim = (FID_TYPE(target->fid) == OBJ_TYPE_CRITTER && _critter_is_prone(target)) ? ANIM_MAGIC_HANDS_GROUND : ANIM_MAGIC_HANDS_MIDDLE;
int fid = buildFid(OBJ_TYPE_CRITTER, performer->fid & 0xFFF, anim, 0, performer->rotation + 1);
CacheEntry* artHandle;
@@ -1506,7 +1519,7 @@ int actionUseSkill(Object* user, Object* target, int skill)
animationRegisterAnimate(performer, anim, -1);
// TODO: Get rid of casts.
animationRegisterCallback3(performer, target, (void*)(uintptr_t)skill, (AnimationCallback3*)objectUseSkillOn, -1);
animationRegisterCallback3(performer, target, (void*)(uintptr_t)skill, (AnimationCallback3*)_obj_use_skill_on, -1);
return reg_anim_end();
}
@@ -1753,7 +1766,7 @@ int _report_explosion(Attack* attack, Object* sourceObj)
xp += critterGetExp(attack->defender);
}
} else {
critterSetWhoHitMe(attack->defender, sourceObj);
_critter_set_who_hit_me(attack->defender, sourceObj);
anyDefender = attack->defender;
}
}
@@ -1766,7 +1779,7 @@ int _report_explosion(Attack* attack, Object* sourceObj)
xp += critterGetExp(critter);
}
} else {
critterSetWhoHitMe(critter, sourceObj);
_critter_set_who_hit_me(critter, sourceObj);
if (anyDefender == nullptr) {
anyDefender = critter;
@@ -1972,7 +1985,7 @@ int _report_dmg(Attack* attack, Object* _)
// 0x413660
int _compute_dmg_damage(int min, int max, Object* obj, int* knockbackDistancePtr, int damageType)
{
if (!critterFlagCheck(obj->pid, CRITTER_NO_KNOCKBACK)) {
if (!_critter_flag_check(obj->pid, CRITTER_NO_KNOCKBACK)) {
knockbackDistancePtr = nullptr;
}
@@ -2046,7 +2059,7 @@ int actionPush(Object* obj, Object* target)
}
int sid;
if (objectGetSid(target, &sid) == 0) {
if (_obj_sid(target, &sid) == 0) {
scriptSetObjects(sid, obj, target);
scriptExecProc(sid, SCRIPT_PROC_PUSH);
+28 -30
View File
@@ -1418,7 +1418,7 @@ static int animationRunSequence(int animationSequenceIndex)
rc = _anim_animate(animationDescription->owner, animationDescription->anim, animationSequenceIndex, ANIM_SAD_FOREVER);
break;
case ANIM_KIND_ROTATE_TO_TILE:
if (!critterIsProne(animationDescription->owner)) {
if (!_critter_is_prone(animationDescription->owner)) {
int rotation = tileGetRotationTo(animationDescription->owner->tile, animationDescription->tile);
_dude_stand(animationDescription->owner, rotation, -1);
}
@@ -1637,7 +1637,7 @@ static int _anim_set_end(int animationSequenceIndex)
}
}
if ((animationSequence->flags & ANIM_SEQ_NO_STAND) == 0 && !critterIsProne(owner)) {
if ((animationSequence->flags & ANIM_SEQ_NO_STAND) == 0 && !_critter_is_prone(owner)) {
_dude_stand(owner, owner->rotation, -1);
}
}
@@ -1713,18 +1713,16 @@ static bool canUseDoor(Object* critter, Object* door)
}
// 0x415EE8
int _make_path(Object* object, int from, int to, unsigned char* rotations, int requireEmptyDest)
int _make_path(Object* object, int from, int to, unsigned char* rotations, int a5)
{
return pathfinderFindPath(object, from, to, rotations, requireEmptyDest, _obj_blocking_at);
return pathfinderFindPath(object, from, to, rotations, a5, _obj_blocking_at);
}
// TODO: move pathfinding into another unit
// If requireEmptyDest == 1, will not return a path if the destination tile is blocked (according to `callback`).
// Returns path length, or 0 if no path is available.
// 0x415EFC
int pathfinderFindPath(Object* object, int from, int to, unsigned char* rotations, int requireEmptyDest, PathBuilderCallback* callback)
int pathfinderFindPath(Object* object, int from, int to, unsigned char* rotations, int a5, PathBuilderCallback* callback)
{
if (requireEmptyDest) {
if (a5) {
if (callback(object, to, object->elevation) != nullptr) {
return 0;
}
@@ -1755,7 +1753,7 @@ int pathfinderFindPath(Object* object, int from, int to, unsigned char* rotation
int toScreenX;
int toScreenY;
tileToScreenXY(to, &toScreenX, &toScreenY);
tileToScreenXY(to, &toScreenX, &toScreenY, object->elevation);
int closedPathNodeListLength = 0;
int openPathNodeListLength = 1;
@@ -1840,7 +1838,7 @@ int pathfinderFindPath(Object* object, int from, int to, unsigned char* rotation
int newX;
int newY;
tileToScreenXY(tile, &newX, &newY);
tileToScreenXY(tile, &newX, &newY, object->elevation);
v27->estimate = _idist(newX, newY, toScreenX, toScreenY);
v27->cost = temp.cost + 50;
@@ -1940,11 +1938,11 @@ static int _tile_idistance(int tile1, int tile2)
{
int x1;
int y1;
tileToScreenXY(tile1, &x1, &y1);
tileToScreenXY(tile1, &x1, &y1, gElevation);
int x2;
int y2;
tileToScreenXY(tile2, &x2, &y2);
tileToScreenXY(tile2, &x2, &y2, gElevation);
return _idist(x1, y1, x2, y2);
}
@@ -1977,13 +1975,13 @@ int _make_straight_path_func(Object* obj, int from, int to, StraightPathNode* st
int fromX;
int fromY;
tileToScreenXY(from, &fromX, &fromY);
tileToScreenXY(from, &fromX, &fromY, obj->elevation);
fromX += 16;
fromY += 8;
int toX;
int toY;
tileToScreenXY(to, &toX, &toY);
tileToScreenXY(to, &toX, &toY, obj->elevation);
toX += 16;
toY += 8;
@@ -2021,7 +2019,7 @@ int _make_straight_path_func(Object* obj, int from, int to, StraightPathNode* st
if (ddx <= ddy) {
int middle = ddx - ddy / 2;
while (true) {
tile = tileFromScreenXY(tileX, tileY);
tile = tileFromScreenXY(tileX, tileY, obj->elevation);
v22 += 1;
if (v22 == a6) {
@@ -2034,7 +2032,7 @@ int _make_straight_path_func(Object* obj, int from, int to, StraightPathNode* st
pathNode->tile = tile;
pathNode->elevation = obj->elevation;
tileToScreenXY(tile, &fromX, &fromY);
tileToScreenXY(tile, &fromX, &fromY, obj->elevation);
pathNode->x = tileX - fromX - 16;
pathNode->y = tileY - fromY - 8;
}
@@ -2074,7 +2072,7 @@ int _make_straight_path_func(Object* obj, int from, int to, StraightPathNode* st
} else {
int middle = ddy - ddx / 2;
while (true) {
tile = tileFromScreenXY(tileX, tileY);
tile = tileFromScreenXY(tileX, tileY, obj->elevation);
v22 += 1;
if (v22 == a6) {
@@ -2087,7 +2085,7 @@ int _make_straight_path_func(Object* obj, int from, int to, StraightPathNode* st
pathNode->tile = tile;
pathNode->elevation = obj->elevation;
tileToScreenXY(tile, &fromX, &fromY);
tileToScreenXY(tile, &fromX, &fromY, obj->elevation);
pathNode->x = tileX - fromX - 16;
pathNode->y = tileY - fromY - 8;
}
@@ -2136,7 +2134,7 @@ int _make_straight_path_func(Object* obj, int from, int to, StraightPathNode* st
pathNode->tile = tile;
pathNode->elevation = obj->elevation;
tileToScreenXY(tile, &fromX, &fromY);
tileToScreenXY(tile, &fromX, &fromY, obj->elevation);
pathNode->x = tileX - fromX - 16;
pathNode->y = tileY - fromY - 8;
}
@@ -2201,13 +2199,13 @@ int _make_stair_path(Object* object, int from, int fromElevation, int to, int to
int fromX;
int fromY;
tileToScreenXY(from, &fromX, &fromY);
tileToScreenXY(from, &fromX, &fromY, fromElevation);
fromX += 16;
fromY += 8;
int toX;
int toY;
tileToScreenXY(to, &toX, &toY);
tileToScreenXY(to, &toX, &toY, toElevation);
toX += 16;
toY += 8;
@@ -2250,7 +2248,7 @@ int _make_stair_path(Object* object, int from, int fromElevation, int to, int to
if (ddx > ddy) {
int middle = ddy - ddx / 2;
while (true) {
tile = tileFromScreenXY(tileX, tileY);
tile = tileFromScreenXY(tileX, tileY, elevation);
iteration += 1;
if (iteration == 16) {
@@ -2263,7 +2261,7 @@ int _make_stair_path(Object* object, int from, int fromElevation, int to, int to
pathNode->tile = tile;
pathNode->elevation = elevation;
tileToScreenXY(tile, &fromX, &fromY);
tileToScreenXY(tile, &fromX, &fromY, elevation);
pathNode->x = tileX - fromX - 16;
pathNode->y = tileY - fromY - 8;
}
@@ -2297,7 +2295,7 @@ int _make_stair_path(Object* object, int from, int fromElevation, int to, int to
} else {
int middle = ddx - ddy / 2;
while (true) {
tile = tileFromScreenXY(tileX, tileY);
tile = tileFromScreenXY(tileX, tileY, elevation);
iteration += 1;
if (iteration == 16) {
@@ -2310,7 +2308,7 @@ int _make_stair_path(Object* object, int from, int fromElevation, int to, int to
pathNode->tile = tile;
pathNode->elevation = elevation;
tileToScreenXY(tile, &fromX, &fromY);
tileToScreenXY(tile, &fromX, &fromY, elevation);
pathNode->x = tileX - fromX - 16;
pathNode->y = tileY - fromY - 8;
}
@@ -2353,7 +2351,7 @@ int _make_stair_path(Object* object, int from, int fromElevation, int to, int to
pathNode->tile = tile;
pathNode->elevation = elevation;
tileToScreenXY(tile, &fromX, &fromY);
tileToScreenXY(tile, &fromX, &fromY, elevation);
pathNode->x = tileX - fromX - 16;
pathNode->y = tileY - fromY - 8;
}
@@ -2613,7 +2611,7 @@ static void _object_move(int index)
}
nextTile = -1;
} else {
objectUseDoor(object, obstacle, 0);
_obj_use_door(object, obstacle, 0);
}
}
@@ -2962,7 +2960,7 @@ int _check_move(int* actionPointsPtr)
int y;
mouseGetPosition(&x, &y);
int tile = tileFromScreenXY(x, y);
int tile = tileFromScreenXY(x, y, gElevation);
if (tile == -1) {
return -1;
}
@@ -3219,7 +3217,7 @@ void _dude_standup(Object* a1)
// 0x4185EC
static int actionRotate(Object* obj, int delta, int animationSequenceIndex)
{
if (!critterIsProne(obj)) {
if (!_critter_is_prone(obj)) {
int rotation = obj->rotation + delta;
if (rotation >= ROTATION_COUNT) {
rotation = ROTATION_NE;
@@ -3293,7 +3291,7 @@ static int _check_gravity(int tile, int elevation)
for (; elevation > 0; elevation--) {
int x;
int y;
tileToScreenXY(tile, &x, &y);
tileToScreenXY(tile, &x, &y, elevation);
int squareTile = squareTileFromScreenXY(x + 2, y + 8, elevation);
int fid = buildFid(OBJ_TYPE_TILE, _square[elevation]->field_0[squareTile] & 0xFFF, 0, 0, 0);
+2 -2
View File
@@ -143,8 +143,8 @@ int animationRegisterToggleOutline(Object* object, bool outline, int delay);
int animationRegisterPlaySoundEffect(Object* owner, const char* soundEffectName, int delay);
int animationRegisterAnimateForever(Object* owner, int anim, int delay);
int animationRegisterPing(int flags, int delay);
int _make_path(Object* object, int from, int to, unsigned char* rotations, int requireEmptyDest);
int pathfinderFindPath(Object* object, int from, int to, unsigned char* rotations, int requireEmptyDest, PathBuilderCallback* callback);
int _make_path(Object* object, int from, int to, unsigned char* a4, int a5);
int pathfinderFindPath(Object* object, int from, int to, unsigned char* rotations, int a5, PathBuilderCallback* callback);
int _make_straight_path(Object* object, int from, int to, StraightPathNode* straightPathNodeList, Object** obstaclePtr, int a6);
int _make_straight_path_func(Object* object, int from, int to, StraightPathNode* straightPathNodeList, Object** obstaclePtr, int a6, PathBuilderCallback* callback);
void _object_animate();
+53 -64
View File
@@ -1,6 +1,5 @@
#include "art.h"
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -542,72 +541,72 @@ int artCopyFileName(int objectType, int id, char* dest)
}
// 0x419314
int _art_get_code(int animation, int weaponType, char* weaponCodePtr, char* animationCodePtr)
int _art_get_code(int animation, int weaponType, char* a3, char* a4)
{
if (weaponType < 0 || weaponType >= WEAPON_ANIMATION_COUNT) {
return -1;
}
if (animation >= ANIM_TAKE_OUT && animation <= ANIM_FIRE_CONTINUOUS) {
*animationCodePtr = 'c' + (animation - ANIM_TAKE_OUT);
*a4 = 'c' + (animation - ANIM_TAKE_OUT);
if (weaponType == WEAPON_ANIMATION_NONE) {
return -1;
}
*weaponCodePtr = 'd' + (weaponType - 1);
*a3 = 'd' + (weaponType - 1);
return 0;
} else if (animation == ANIM_PRONE_TO_STANDING) {
*animationCodePtr = 'h';
*weaponCodePtr = 'c';
*a4 = 'h';
*a3 = 'c';
return 0;
} else if (animation == ANIM_BACK_TO_STANDING) {
*animationCodePtr = 'j';
*weaponCodePtr = 'c';
*a4 = 'j';
*a3 = 'c';
return 0;
} else if (animation == ANIM_CALLED_SHOT_PIC) {
*animationCodePtr = 'a';
*weaponCodePtr = 'n';
*a4 = 'a';
*a3 = 'n';
return 0;
} else if (animation >= FIRST_SF_DEATH_ANIM) {
*animationCodePtr = 'a' + (animation - FIRST_SF_DEATH_ANIM);
*weaponCodePtr = 'r';
*a4 = 'a' + (animation - FIRST_SF_DEATH_ANIM);
*a3 = 'r';
return 0;
} else if (animation >= FIRST_KNOCKDOWN_AND_DEATH_ANIM) {
*animationCodePtr = 'a' + (animation - FIRST_KNOCKDOWN_AND_DEATH_ANIM);
*weaponCodePtr = 'b';
*a4 = 'a' + (animation - FIRST_KNOCKDOWN_AND_DEATH_ANIM);
*a3 = 'b';
return 0;
} else if (animation == ANIM_THROW_ANIM) {
if (weaponType == WEAPON_ANIMATION_KNIFE) {
// knife
*weaponCodePtr = 'd';
*animationCodePtr = 'm';
*a3 = 'd';
*a4 = 'm';
} else if (weaponType == WEAPON_ANIMATION_SPEAR) {
// spear
*weaponCodePtr = 'g';
*animationCodePtr = 'm';
*a3 = 'g';
*a4 = 'm';
} else {
// other -> probably rock or grenade
*weaponCodePtr = 'a';
*animationCodePtr = 's';
*a3 = 'a';
*a4 = 's';
}
return 0;
} else if (animation == ANIM_DODGE_ANIM) {
if (weaponType <= 0) {
*weaponCodePtr = 'a';
*animationCodePtr = 'n';
*a3 = 'a';
*a4 = 'n';
} else {
*weaponCodePtr = 'd' + (weaponType - 1);
*animationCodePtr = 'e';
*a3 = 'd' + (weaponType - 1);
*a4 = 'e';
}
return 0;
}
*animationCodePtr = 'a' + animation;
*a4 = 'a' + animation;
if (animation <= ANIM_WALK && weaponType > 0) {
*weaponCodePtr = 'd' + (weaponType - 1);
*a3 = 'd' + (weaponType - 1);
return 0;
}
*weaponCodePtr = 'a';
*a3 = 'a';
return 0;
}
@@ -615,51 +614,53 @@ int _art_get_code(int animation, int weaponType, char* weaponCodePtr, char* anim
// 0x419428
char* artBuildFilePath(int fid)
{
int baseFid = fid;
int rotation = FID_ROTATION(fid);
int v1, v2, v3, v4, v5, type, v8, v10;
char v9, v11, v12;
int aliasFid = artAliasFid(fid);
if (aliasFid != -1) {
baseFid = aliasFid;
v2 = fid;
v10 = (fid & 0x70000000) >> 28;
v1 = artAliasFid(fid);
if (v1 != -1) {
v2 = v1;
}
*_art_name = '\0';
int frmId = baseFid & 0xFFF;
int animType = FID_ANIM_TYPE(baseFid);
int weaponCode = (baseFid & 0xF000) >> 12;
int objectType = FID_TYPE(baseFid);
v3 = v2 & 0xFFF;
v4 = FID_ANIM_TYPE(v2);
v5 = (v2 & 0xF000) >> 12;
type = FID_TYPE(v2);
if (objectType < OBJ_TYPE_ITEM || objectType >= OBJ_TYPE_COUNT) {
if (type < OBJ_TYPE_ITEM || type >= OBJ_TYPE_COUNT) {
return nullptr;
}
if (frmId >= gArtListDescriptions[objectType].fileNamesLength) {
if (v3 >= gArtListDescriptions[type].fileNamesLength) {
return nullptr;
}
int fileNameOffset = frmId * 13;
v8 = v3 * 13;
if (objectType == OBJ_TYPE_CRITTER) {
char critterWeaponCode;
char critterAnimationCode;
if (_art_get_code(animType, weaponCode, &critterWeaponCode, &critterAnimationCode) == -1) {
if (type == 1) {
if (_art_get_code(v4, v5, &v11, &v12) == -1) {
return nullptr;
}
if (rotation != 0) {
snprintf(_art_name, sizeof(_art_name), "%s%s%s\\%s%c%c.fr%c", _cd_path_base, "art\\", gArtListDescriptions[OBJ_TYPE_CRITTER].name, gArtListDescriptions[OBJ_TYPE_CRITTER].fileNames + fileNameOffset, critterWeaponCode, critterAnimationCode, rotation + 47);
if (v10) {
snprintf(_art_name, sizeof(_art_name), "%s%s%s\\%s%c%c.fr%c", _cd_path_base, "art\\", gArtListDescriptions[1].name, gArtListDescriptions[1].fileNames + v8, v11, v12, v10 + 47);
} else {
snprintf(_art_name, sizeof(_art_name), "%s%s%s\\%s%c%c.frm", _cd_path_base, "art\\", gArtListDescriptions[OBJ_TYPE_CRITTER].name, gArtListDescriptions[OBJ_TYPE_CRITTER].fileNames + fileNameOffset, critterWeaponCode, critterAnimationCode);
snprintf(_art_name, sizeof(_art_name), "%s%s%s\\%s%c%c.frm", _cd_path_base, "art\\", gArtListDescriptions[1].name, gArtListDescriptions[1].fileNames + v8, v11, v12);
}
} else if (objectType == OBJ_TYPE_HEAD) {
char headSuffix = _head2[animType];
if (headSuffix == 'f') {
snprintf(_art_name, sizeof(_art_name), "%s%s%s\\%s%c%c%d.frm", _cd_path_base, "art\\", gArtListDescriptions[OBJ_TYPE_HEAD].name, gArtListDescriptions[OBJ_TYPE_HEAD].fileNames + fileNameOffset, _head1[animType], 102, weaponCode);
} else if (type == 8) {
v9 = _head2[v4];
if (v9 == 'f') {
snprintf(_art_name, sizeof(_art_name), "%s%s%s\\%s%c%c%d.frm", _cd_path_base, "art\\", gArtListDescriptions[8].name, gArtListDescriptions[8].fileNames + v8, _head1[v4], 102, v5);
} else {
snprintf(_art_name, sizeof(_art_name), "%s%s%s\\%s%c%c.frm", _cd_path_base, "art\\", gArtListDescriptions[OBJ_TYPE_HEAD].name, gArtListDescriptions[OBJ_TYPE_HEAD].fileNames + fileNameOffset, _head1[animType], headSuffix);
snprintf(_art_name, sizeof(_art_name), "%s%s%s\\%s%c%c.frm", _cd_path_base, "art\\", gArtListDescriptions[8].name, gArtListDescriptions[8].fileNames + v8, _head1[v4], v9);
}
} else {
snprintf(_art_name, sizeof(_art_name), "%s%s%s\\%s", _cd_path_base, "art\\", gArtListDescriptions[objectType].name, gArtListDescriptions[objectType].fileNames + fileNameOffset);
snprintf(_art_name, sizeof(_art_name), "%s%s%s\\%s", _cd_path_base, "art\\", gArtListDescriptions[type].name, gArtListDescriptions[type].fileNames + v8);
}
return _art_name;
@@ -701,13 +702,8 @@ static int artReadList(const char* path, char** artListPtr, int* artListSizePtr)
artList[12] = '\0';
artList += 13;
count--;
}
// Sanity check. There was a bug with uncompressed database file seek
assert(count == 0);
fileClose(stream);
return 0;
@@ -1010,13 +1006,6 @@ static void artCacheFreeImpl(void* ptr)
internal_free(ptr);
}
/* FID Structure:
3 bits for rotation
4 bits for object type
8 bits for animation type
4 bits for weapon code
12 bits for frame ID
*/
static int buildFidInternal(unsigned short frmId, unsigned char weaponCode, unsigned char animType, unsigned char objectType, unsigned char rotation)
{
return ((rotation << 28) & 0x70000000) | (objectType << 24) | ((animType << 16) & 0xFF0000) | ((weaponCode << 12) & 0xF000) | (frmId & 0xFFF);

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