diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml index 25382f2..54fa754 100644 --- a/.github/workflows/ci-build.yml +++ b/.github/workflows/ci-build.yml @@ -31,6 +31,18 @@ jobs: - name: cppcheck run: cppcheck --std=c++17 src/ + code-format: + name: Code format check + + runs-on: ubuntu-latest + + steps: + - name: Clone + uses: actions/checkout@v3 + + - name: clang-format + run: find src -type f -exec clang-format --dry-run --Werror {} \; + android: name: Android @@ -78,7 +90,7 @@ jobs: ios: name: iOS - runs-on: macos-11 + runs-on: macos-12 steps: - name: Clone @@ -88,30 +100,31 @@ jobs: uses: actions/cache@v3 with: path: build - key: ios-cmake-v1 + key: ios-cmake-v2 - name: Configure run: | cmake \ -B build \ - -D CMAKE_BUILD_TYPE=RelWithDebInfo \ -D CMAKE_TOOLCHAIN_FILE=cmake/toolchain/ios.toolchain.cmake \ -D ENABLE_BITCODE=0 \ -D PLATFORM=OS64 \ + -G Xcode \ + -D CMAKE_XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY='' \ # EOL - name: Build run: | cmake \ --build build \ + --config RelWithDebInfo \ -j $(sysctl -n hw.physicalcpu) \ - --target package \ # EOL - # TODO: Should be a part of packaging. - - name: Prepare for uploading + - name: Pack run: | - cp build/fallout2-ce.zip build/fallout2-ce.ipa + cd build + cpack -C RelWithDebInfo - name: Upload uses: actions/upload-artifact@v3 diff --git a/CMakeLists.txt b/CMakeLists.txt index 3cf9e53..d7da510 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -303,21 +303,30 @@ if (WIN32) endif() if(APPLE) - target_sources(${EXECUTABLE_NAME} PUBLIC "os/macos/fallout2-ce.icns") - set_source_files_properties("os/macos/fallout2-ce.icns" PROPERTIES MACOSX_PACKAGE_LOCATION "Resources") - if(IOS) - target_sources(${EXECUTABLE_NAME} PUBLIC "os/ios/LaunchScreen.storyboard") - set_source_files_properties("os/ios/LaunchScreen.storyboard" PROPERTIES MACOSX_PACKAGE_LOCATION "Resources") - set_target_properties(${EXECUTABLE_NAME} PROPERTIES MACOSX_BUNDLE_INFO_PLIST "${CMAKE_SOURCE_DIR}/os/ios/Info.plist") - set_target_properties(${EXECUTABLE_NAME} PROPERTIES XCODE_ATTRIBUTE_TARGETED_DEVICE_FAMILY "1,2") + set(RESOURCES + "os/ios/AppIcon.xcassets" + "os/ios/LaunchScreen.storyboard" + ) + + target_sources(${EXECUTABLE_NAME} PUBLIC ${RESOURCES}) + set_source_files_properties(${RESOURCES} PROPERTIES MACOSX_PACKAGE_LOCATION "Resources") + + set_target_properties(${EXECUTABLE_NAME} PROPERTIES + MACOSX_BUNDLE_INFO_PLIST "${CMAKE_SOURCE_DIR}/os/ios/Info.plist" + XCODE_ATTRIBUTE_ASSETCATALOG_COMPILER_APPICON_NAME "AppIcon" + XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER "com.alexbatalov.fallout2-ce" + XCODE_ATTRIBUTE_TARGETED_DEVICE_FAMILY "1,2" + ) else() set_target_properties(${EXECUTABLE_NAME} PROPERTIES MACOSX_BUNDLE_INFO_PLIST "${CMAKE_SOURCE_DIR}/os/macos/Info.plist") + target_sources(${EXECUTABLE_NAME} PUBLIC "os/macos/fallout2-ce.icns") + set_source_files_properties("os/macos/fallout2-ce.icns" PROPERTIES MACOSX_PACKAGE_LOCATION "Resources") + set(MACOSX_BUNDLE_ICON_FILE "fallout2-ce.icns") endif() set(MACOSX_BUNDLE_GUI_IDENTIFIER "com.alexbatalov.fallout2-ce") set(MACOSX_BUNDLE_BUNDLE_NAME "${EXECUTABLE_NAME}") - set(MACOSX_BUNDLE_ICON_FILE "fallout2-ce.icns") set(MACOSX_BUNDLE_DISPLAY_NAME "Fallout 2") set(MACOSX_BUNDLE_SHORT_VERSION_STRING "1.2.0") set(MACOSX_BUNDLE_BUNDLE_VERSION "1.2.0") @@ -348,6 +357,7 @@ if(APPLE) set(CPACK_GENERATOR "ZIP") set(CPACK_INCLUDE_TOPLEVEL_DIRECTORY OFF) set(CPACK_PACKAGE_FILE_NAME "fallout2-ce") + set(CPACK_ARCHIVE_FILE_EXTENSION "ipa") else() install(TARGETS ${EXECUTABLE_NAME} DESTINATION .) install(CODE " diff --git a/os/ios/AppIcon.xcassets/AppIcon.appiconset/AppIcon.png b/os/ios/AppIcon.xcassets/AppIcon.appiconset/AppIcon.png new file mode 100644 index 0000000..cf365dc Binary files /dev/null and b/os/ios/AppIcon.xcassets/AppIcon.appiconset/AppIcon.png differ diff --git a/os/ios/AppIcon.xcassets/AppIcon.appiconset/Contents.json b/os/ios/AppIcon.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..cefcc87 --- /dev/null +++ b/os/ios/AppIcon.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,14 @@ +{ + "images" : [ + { + "filename" : "AppIcon.png", + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/os/ios/AppIcon.xcassets/Contents.json b/os/ios/AppIcon.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/os/ios/AppIcon.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/os/ios/Info.plist b/os/ios/Info.plist index 3eedf54..81b2bfd 100644 --- a/os/ios/Info.plist +++ b/os/ios/Info.plist @@ -8,8 +8,6 @@ ${MACOSX_BUNDLE_DISPLAY_NAME} CFBundleExecutable ${MACOSX_BUNDLE_EXECUTABLE_NAME} - CFBundleIconFile - ${MACOSX_BUNDLE_ICON_FILE} CFBundleIdentifier ${MACOSX_BUNDLE_GUI_IDENTIFIER} CFBundleInfoDictionaryVersion @@ -22,8 +20,6 @@ ${MACOSX_BUNDLE_SHORT_VERSION_STRING} CFBundleVersion ${MACOSX_BUNDLE_BUNDLE_VERSION} - LSApplicationCategoryType - public.app-category.role-playing-games LSMinimumSystemVersion 11.0 LSRequiresIPhoneOS @@ -32,11 +28,6 @@ True UIApplicationSupportsIndirectInputEvents - UIDeviceFamily - - 1 - 2 - UIFileSharingEnabled UILaunchStoryboardName diff --git a/src/animation.h b/src/animation.h index 540d738..4e146a4 100644 --- a/src/animation.h +++ b/src/animation.h @@ -92,7 +92,7 @@ typedef enum AnimationType { LAST_SF_DEATH_ANIM = ANIM_FALL_FRONT_BLOOD_SF, } AnimationType; -#define FID_ANIM_TYPE(value) ((value) & 0xFF0000) >> 16 +#define FID_ANIM_TYPE(value) ((value)&0xFF0000) >> 16 // Signature of animation callback accepting 2 parameters. typedef int(AnimationCallback)(void* a1, void* a2); diff --git a/src/combat.cc b/src/combat.cc index f7aafaf..cde7b7d 100644 --- a/src/combat.cc +++ b/src/combat.cc @@ -168,7 +168,7 @@ static bool _combat_call_display = false; // Accuracy modifiers for hit locations. // // 0x510954 -static const int _hit_location_penalty[HIT_LOCATION_COUNT] = { +static int hit_location_penalty_default[HIT_LOCATION_COUNT] = { -40, -30, -30, @@ -180,6 +180,8 @@ static const int _hit_location_penalty[HIT_LOCATION_COUNT] = { 0, }; +static int hit_location_penalty[HIT_LOCATION_COUNT]; + // Critical hit tables for every kill type. // // 0x510978 @@ -2029,6 +2031,7 @@ int combatInit() burstModInit(); unarmedInit(); damageModInit(); + combat_reset_hit_location_penalty(); return 0; } @@ -2058,6 +2061,7 @@ void combatReset() // SFALL criticalsReset(); + combat_reset_hit_location_penalty(); } // 0x420E14 @@ -3831,7 +3835,7 @@ static int attackCompute(Attack* attack) roll = _compute_spray(attack, accuracy, &ammoQuantity, &v26, anim); } else { int chance = critterGetStat(attack->attacker, STAT_CRITICAL_CHANCE); - roll = randomRoll(accuracy, chance - _hit_location_penalty[attack->defenderHitLocation], NULL); + roll = randomRoll(accuracy, chance - hit_location_penalty[attack->defenderHitLocation], NULL); } if (roll == ROLL_FAILURE) { @@ -4417,9 +4421,9 @@ static int attackDetermineToHit(Object* attacker, int tile, Object* defender, in } if (isRangedWeapon) { - accuracy += _hit_location_penalty[hitLocation]; + accuracy += hit_location_penalty[hitLocation]; } else { - accuracy += _hit_location_penalty[hitLocation] / 2; + accuracy += hit_location_penalty[hitLocation] / 2; } if (defender != NULL && (defender->flags & OBJECT_MULTIHEX) != 0) { @@ -6798,4 +6802,27 @@ static void damageModCalculateYaam(DamageCalculationContext* context) } } +int combat_get_hit_location_penalty(int hit_location) +{ + if (hit_location >= 0 && hit_location < HIT_LOCATION_COUNT) { + return hit_location_penalty[hit_location]; + } else { + return 0; + } +} + +void combat_set_hit_location_penalty(int hit_location, int penalty) +{ + if (hit_location >= 0 && hit_location < HIT_LOCATION_COUNT) { + hit_location_penalty[hit_location] = penalty; + } +} + +void combat_reset_hit_location_penalty() +{ + for (int hit_location = 0; hit_location < HIT_LOCATION_COUNT; hit_location++) { + hit_location_penalty[hit_location] = hit_location_penalty_default[hit_location]; + } +} + } // namespace fallout diff --git a/src/combat.h b/src/combat.h index 5775eae..4dc0430 100644 --- a/src/combat.h +++ b/src/combat.h @@ -71,6 +71,9 @@ int unarmedGetKickHitMode(bool isSecondary); bool unarmedIsPenetrating(int hitMode); bool damageModGetBonusHthDamageFix(); bool damageModGetDisplayBonusDamage(); +int combat_get_hit_location_penalty(int hit_location); +void combat_set_hit_location_penalty(int hit_location, int penalty); +void combat_reset_hit_location_penalty(); static inline bool isInCombat() { diff --git a/src/critter.cc b/src/critter.cc index cd896ab..4439eb0 100644 --- a/src/critter.cc +++ b/src/critter.cc @@ -996,8 +996,8 @@ bool _critter_is_prone(Object* critter) int anim = FID_ANIM_TYPE(critter->fid); return (critter->data.critter.combat.results & (DAM_KNOCKED_OUT | DAM_KNOCKED_DOWN)) != 0 - || (anim >= FIRST_KNOCKDOWN_AND_DEATH_ANIM && anim <= LAST_KNOCKDOWN_AND_DEATH_ANIM) - || (anim >= FIRST_SF_DEATH_ANIM && anim <= LAST_SF_DEATH_ANIM); + || (anim >= FIRST_KNOCKDOWN_AND_DEATH_ANIM && anim <= LAST_KNOCKDOWN_AND_DEATH_ANIM) + || (anim >= FIRST_SF_DEATH_ANIM && anim <= LAST_SF_DEATH_ANIM); } // critter_body_type diff --git a/src/input.cc b/src/input.cc index e18cb02..95bbc2d 100644 --- a/src/input.cc +++ b/src/input.cc @@ -1026,24 +1026,24 @@ static void buildNormalizedQwertyKeys() keys[SDL_SCANCODE_F13] = -1; keys[SDL_SCANCODE_F14] = -1; keys[SDL_SCANCODE_F15] = -1; - //keys[DIK_KANA] = -1; - //keys[DIK_CONVERT] = -1; - //keys[DIK_NOCONVERT] = -1; - //keys[DIK_YEN] = -1; + // keys[DIK_KANA] = -1; + // keys[DIK_CONVERT] = -1; + // keys[DIK_NOCONVERT] = -1; + // keys[DIK_YEN] = -1; keys[SDL_SCANCODE_KP_EQUALS] = -1; - //keys[DIK_PREVTRACK] = -1; - //keys[DIK_AT] = -1; - //keys[DIK_COLON] = -1; - //keys[DIK_UNDERLINE] = -1; - //keys[DIK_KANJI] = -1; + // keys[DIK_PREVTRACK] = -1; + // keys[DIK_AT] = -1; + // keys[DIK_COLON] = -1; + // keys[DIK_UNDERLINE] = -1; + // keys[DIK_KANJI] = -1; keys[SDL_SCANCODE_STOP] = -1; - //keys[DIK_AX] = -1; - //keys[DIK_UNLABELED] = -1; + // keys[DIK_AX] = -1; + // keys[DIK_UNLABELED] = -1; keys[SDL_SCANCODE_KP_ENTER] = SDL_SCANCODE_KP_ENTER; keys[SDL_SCANCODE_RCTRL] = SDL_SCANCODE_RCTRL; keys[SDL_SCANCODE_KP_COMMA] = -1; keys[SDL_SCANCODE_KP_DIVIDE] = SDL_SCANCODE_KP_DIVIDE; - //keys[DIK_SYSRQ] = 84; + // keys[DIK_SYSRQ] = 84; keys[SDL_SCANCODE_RALT] = SDL_SCANCODE_RALT; keys[SDL_SCANCODE_HOME] = SDL_SCANCODE_HOME; keys[SDL_SCANCODE_UP] = SDL_SCANCODE_UP; diff --git a/src/interface.cc b/src/interface.cc index 3204faa..1415892 100644 --- a/src/interface.cc +++ b/src/interface.cc @@ -2616,4 +2616,37 @@ static void sidePanelsDraw(const char* path, int win, bool isLeading) internal_free(image); } +// NOTE: Follows Sfall implementation of `GetCurrentAttackMode`. It slightly +// differs from `interfaceGetCurrentHitMode` (can return one of `reload` hit +// modes, the default is `punch`). +// +// 0x45EF6C +bool interface_get_current_attack_mode(int* hit_mode) +{ + if (gInterfaceBarWindow == -1) { + return false; + } + + switch (gInterfaceItemStates[gInterfaceCurrentHand].action) { + case INTERFACE_ITEM_ACTION_PRIMARY_AIMING: + case INTERFACE_ITEM_ACTION_PRIMARY: + *hit_mode = gInterfaceItemStates[gInterfaceCurrentHand].primaryHitMode; + break; + case INTERFACE_ITEM_ACTION_SECONDARY_AIMING: + case INTERFACE_ITEM_ACTION_SECONDARY: + *hit_mode = gInterfaceItemStates[gInterfaceCurrentHand].secondaryHitMode; + break; + case INTERFACE_ITEM_ACTION_RELOAD: + *hit_mode = gInterfaceCurrentHand == HAND_LEFT + ? HIT_MODE_LEFT_WEAPON_RELOAD + : HIT_MODE_RIGHT_WEAPON_RELOAD; + break; + default: + *hit_mode = HIT_MODE_PUNCH; + break; + } + + return true; +} + } // namespace fallout diff --git a/src/interface.h b/src/interface.h index 07772df..acec2df 100644 --- a/src/interface.h +++ b/src/interface.h @@ -69,6 +69,7 @@ void interfaceBarEndButtonsRenderRedLights(); int indicatorBarRefresh(); bool indicatorBarShow(); bool indicatorBarHide(); +bool interface_get_current_attack_mode(int* hit_mode); unsigned char* customInterfaceBarGetBackgroundImageData(); diff --git a/src/interpreter.cc b/src/interpreter.cc index 8729fd5..23793ac 100644 --- a/src/interpreter.cc +++ b/src/interpreter.cc @@ -3268,4 +3268,29 @@ bool ProgramValue::isEmpty() return true; } +// Matches Sfall implementation. +bool ProgramValue::isInt() +{ + return opcode == VALUE_TYPE_INT; +} + +// Matches Sfall implementation. +bool ProgramValue::isFloat() +{ + return opcode == VALUE_TYPE_FLOAT; +} + +// Matches Sfall implementation. +float ProgramValue::asFloat() +{ + switch (opcode) { + case VALUE_TYPE_INT: + return static_cast(integerValue); + case VALUE_TYPE_FLOAT: + return floatValue; + default: + return 0.0; + } +} + } // namespace fallout diff --git a/src/interpreter.h b/src/interpreter.h index 91d27bb..f84347f 100644 --- a/src/interpreter.h +++ b/src/interpreter.h @@ -149,6 +149,9 @@ typedef struct ProgramValue { }; bool isEmpty(); + bool isInt(); + bool isFloat(); + float asFloat(); } ProgramValue; typedef std::vector ProgramStack; diff --git a/src/interpreter_extra.cc b/src/interpreter_extra.cc index 1cce7b4..12f3222 100644 --- a/src/interpreter_extra.cc +++ b/src/interpreter_extra.cc @@ -2987,7 +2987,7 @@ static void opGetMessageString(Program* program) int messageListIndex = programStackPopInteger(program); char* string; - if (messageIndex >= 1) { + if (messageIndex >= 0) { string = _scr_get_msg_str_speech(messageListIndex, messageIndex, 1); if (string == NULL) { debugPrint("\nError: No message file EXISTS!: index %d, line %d", messageListIndex, messageIndex); diff --git a/src/inventory.cc b/src/inventory.cc index eaac208..351584c 100644 --- a/src/inventory.cc +++ b/src/inventory.cc @@ -2694,7 +2694,7 @@ void inventoryOpenUseItemOn(Object* a1) inventoryWindowOpenContextMenu(keyCode, INVENTORY_WINDOW_TYPE_USE_ITEM_ON); } else { int inventoryItemIndex = _pud->length - (_stack_offset[_curr_stack] + keyCode - 1000 + 1); - // SFALL: Fix crash when clicking on empty space in the inventory list + // SFALL: Fix crash when clicking on empty space in the inventory list // opened by "Use Inventory Item On" (backpack) action icon if (inventoryItemIndex < _pud->length && inventoryItemIndex >= 0) { InventoryItem* inventoryItem = &(_pud->items[inventoryItemIndex]); diff --git a/src/kb.cc b/src/kb.cc index 552b1fe..b9253c2 100644 --- a/src/kb.cc +++ b/src/kb.cc @@ -1400,11 +1400,11 @@ static void keyboardBuildFrenchConfiguration() gLogicalKeyEntries[SDL_SCANCODE_BACKSLASH].rmenu = -1; gLogicalKeyEntries[SDL_SCANCODE_BACKSLASH].ctrl = -1; - //gLogicalKeyEntries[DIK_OEM_102].unmodified = KEY_LESS; - //gLogicalKeyEntries[DIK_OEM_102].shift = KEY_GREATER; - //gLogicalKeyEntries[DIK_OEM_102].lmenu = -1; - //gLogicalKeyEntries[DIK_OEM_102].rmenu = -1; - //gLogicalKeyEntries[DIK_OEM_102].ctrl = -1; + // gLogicalKeyEntries[DIK_OEM_102].unmodified = KEY_LESS; + // gLogicalKeyEntries[DIK_OEM_102].shift = KEY_GREATER; + // gLogicalKeyEntries[DIK_OEM_102].lmenu = -1; + // gLogicalKeyEntries[DIK_OEM_102].rmenu = -1; + // gLogicalKeyEntries[DIK_OEM_102].ctrl = -1; switch (gKeyboardLayout) { case KEYBOARD_LAYOUT_QWERTY: @@ -1583,11 +1583,11 @@ static void keyboardBuildGermanConfiguration() gLogicalKeyEntries[SDL_SCANCODE_BACKSLASH].rmenu = -1; gLogicalKeyEntries[SDL_SCANCODE_BACKSLASH].ctrl = -1; - //gLogicalKeyEntries[DIK_OEM_102].unmodified = KEY_LESS; - //gLogicalKeyEntries[DIK_OEM_102].shift = KEY_GREATER; - //gLogicalKeyEntries[DIK_OEM_102].lmenu = -1; - //gLogicalKeyEntries[DIK_OEM_102].rmenu = KEY_166; - //gLogicalKeyEntries[DIK_OEM_102].ctrl = -1; + // gLogicalKeyEntries[DIK_OEM_102].unmodified = KEY_LESS; + // gLogicalKeyEntries[DIK_OEM_102].shift = KEY_GREATER; + // gLogicalKeyEntries[DIK_OEM_102].lmenu = -1; + // gLogicalKeyEntries[DIK_OEM_102].rmenu = KEY_166; + // gLogicalKeyEntries[DIK_OEM_102].ctrl = -1; switch (gKeyboardLayout) { case KEYBOARD_LAYOUT_FRENCH: @@ -1684,11 +1684,11 @@ static void keyboardBuildItalianConfiguration() gLogicalKeyEntries[SDL_SCANCODE_GRAVE].rmenu = -1; gLogicalKeyEntries[SDL_SCANCODE_GRAVE].ctrl = -1; - //gLogicalKeyEntries[DIK_OEM_102].unmodified = KEY_LESS; - //gLogicalKeyEntries[DIK_OEM_102].shift = KEY_GREATER; - //gLogicalKeyEntries[DIK_OEM_102].lmenu = -1; - //gLogicalKeyEntries[DIK_OEM_102].rmenu = -1; - //gLogicalKeyEntries[DIK_OEM_102].ctrl = -1; + // gLogicalKeyEntries[DIK_OEM_102].unmodified = KEY_LESS; + // gLogicalKeyEntries[DIK_OEM_102].shift = KEY_GREATER; + // gLogicalKeyEntries[DIK_OEM_102].lmenu = -1; + // gLogicalKeyEntries[DIK_OEM_102].rmenu = -1; + // gLogicalKeyEntries[DIK_OEM_102].ctrl = -1; gLogicalKeyEntries[SDL_SCANCODE_1].unmodified = KEY_1; gLogicalKeyEntries[SDL_SCANCODE_1].shift = KEY_EXCLAMATION; @@ -1896,11 +1896,11 @@ static void keyboardBuildSpanishConfiguration() gLogicalKeyEntries[SDL_SCANCODE_RIGHTBRACKET].rmenu = KEY_BRACKET_RIGHT; gLogicalKeyEntries[SDL_SCANCODE_RIGHTBRACKET].ctrl = -1; - //gLogicalKeyEntries[DIK_OEM_102].unmodified = KEY_LESS; - //gLogicalKeyEntries[DIK_OEM_102].shift = KEY_GREATER; - //gLogicalKeyEntries[DIK_OEM_102].lmenu = -1; - //gLogicalKeyEntries[DIK_OEM_102].rmenu = -1; - //gLogicalKeyEntries[DIK_OEM_102].ctrl = -1; + // gLogicalKeyEntries[DIK_OEM_102].unmodified = KEY_LESS; + // gLogicalKeyEntries[DIK_OEM_102].shift = KEY_GREATER; + // gLogicalKeyEntries[DIK_OEM_102].lmenu = -1; + // gLogicalKeyEntries[DIK_OEM_102].rmenu = -1; + // gLogicalKeyEntries[DIK_OEM_102].ctrl = -1; gLogicalKeyEntries[SDL_SCANCODE_SEMICOLON].unmodified = KEY_241; gLogicalKeyEntries[SDL_SCANCODE_SEMICOLON].shift = KEY_209; diff --git a/src/mouse.cc b/src/mouse.cc index 09e4334..6e8a8bb 100644 --- a/src/mouse.cc +++ b/src/mouse.cc @@ -54,7 +54,7 @@ static unsigned char* _mouse_fptr = NULL; static double gMouseSensitivity = 1.0; // 0x51E2AC -static int gMouseButtonsState = 0; +static int last_buttons = 0; // 0x6AC790 static bool gCursorIsHidden; @@ -415,7 +415,7 @@ void _mouse_info() } x = 0; y = 0; - buttons = gMouseButtonsState; + buttons = last_buttons; } _mouse_simulate_input(x, y, buttons); @@ -447,7 +447,7 @@ void _mouse_simulate_input(int delta_x, int delta_y, int buttons) return; } - if (delta_x || delta_y || buttons != gMouseButtonsState) { + if (delta_x || delta_y || buttons != last_buttons) { if (gVcrState == 0) { if (_vcr_buffer_index == VCR_BUFFER_CAPACITY - 1) { vcrDump(); @@ -464,13 +464,13 @@ void _mouse_simulate_input(int delta_x, int delta_y, int buttons) _vcr_buffer_index++; } } else { - if (gMouseButtonsState == 0) { + if (last_buttons == 0) { if (!_mouse_idling) { _mouse_idle_start_time = getTicks(); _mouse_idling = 1; } - gMouseButtonsState = 0; + last_buttons = 0; _raw_buttons = 0; gMouseEvent = 0; @@ -479,7 +479,7 @@ void _mouse_simulate_input(int delta_x, int delta_y, int buttons) } _mouse_idling = 0; - gMouseButtonsState = buttons; + last_buttons = buttons; previousEvent = gMouseEvent; gMouseEvent = 0; @@ -703,4 +703,9 @@ void convertMouseWheelToArrowKey(int* keyCodePtr) } } +int mouse_get_last_buttons() +{ + return last_buttons; +} + } // namespace fallout diff --git a/src/mouse.h b/src/mouse.h index 6b091c5..667250f 100644 --- a/src/mouse.h +++ b/src/mouse.h @@ -52,6 +52,7 @@ void mouseGetPositionInWindow(int win, int* x, int* y); bool mouseHitTestInWindow(int win, int left, int top, int right, int bottom); void mouseGetWheel(int* x, int* y); void convertMouseWheelToArrowKey(int* keyCodePtr); +int mouse_get_last_buttons(); } // namespace fallout diff --git a/src/obj_types.h b/src/obj_types.h index c1d1867..8638f90 100644 --- a/src/obj_types.h +++ b/src/obj_types.h @@ -29,7 +29,7 @@ enum { OBJ_TYPE_COUNT, }; -#define FID_TYPE(value) ((value) & 0xF000000) >> 24 +#define FID_TYPE(value) ((value)&0xF000000) >> 24 #define PID_TYPE(value) (value) >> 24 #define SID_TYPE(value) (value) >> 24 diff --git a/src/proto.cc b/src/proto.cc index 86d767b..efb05b0 100644 --- a/src/proto.cc +++ b/src/proto.cc @@ -249,6 +249,12 @@ int _proto_list_str(int pid, char* proto_path) return 0; } +// 0x49E984 +size_t proto_size(int type) +{ + return type >= 0 && type < OBJ_TYPE_COUNT ? _proto_sizes[type] : 0; +} + // 0x49E99C bool _proto_action_can_use(int pid) { @@ -1704,12 +1710,10 @@ static int _proto_load_pid(int pid, Proto** protoPtr) return 0; } -// allocate memory for proto of given type and adds it to proto cache +// 0x4A1D98 static int _proto_find_free_subnode(int type, Proto** protoPtr) { - size_t size = (type >= 0 && type < 11) ? _proto_sizes[type] : 0; - - Proto* proto = (Proto*)internal_malloc(size); + Proto* proto = (Proto*)internal_malloc(proto_size(type)); *protoPtr = proto; if (proto == NULL) { return -1; diff --git a/src/proto.h b/src/proto.h index c7f481d..d6f13e6 100644 --- a/src/proto.h +++ b/src/proto.h @@ -104,6 +104,7 @@ extern char* _proto_none_str; void _proto_make_path(char* path, int pid); int _proto_list_str(int pid, char* proto_path); +size_t proto_size(int type); bool _proto_action_can_use(int pid); bool _proto_action_can_use_on(int pid); bool _proto_action_can_talk_to(int pid); diff --git a/src/scripts.cc b/src/scripts.cc index 3ac7e4b..7a35d39 100644 --- a/src/scripts.cc +++ b/src/scripts.cc @@ -29,6 +29,7 @@ #include "proto.h" #include "proto_instance.h" #include "queue.h" +#include "sfall_config.h" #include "stat.h" #include "svga.h" #include "tile.h" @@ -265,6 +266,15 @@ static bool _set; // 0x667750 static char _tempStr1[20]; +static int gStartYear; +static int gStartMonth; +static int gStartDay; + +static int gMovieTimerArtimer1; +static int gMovieTimerArtimer2; +static int gMovieTimerArtimer3; +static int gMovieTimerArtimer4; + // TODO: Make unsigned. // // Returns game time in ticks (1/10 second). @@ -278,9 +288,9 @@ int gameTimeGetTime() // 0x4A3338 void gameTimeGetDate(int* monthPtr, int* dayPtr, int* yearPtr) { - int year = (gGameTime / GAME_TIME_TICKS_PER_DAY + 24) / 365 + 2241; - int month = 6; - int day = (gGameTime / GAME_TIME_TICKS_PER_DAY + 24) % 365; + int year = (gGameTime / GAME_TIME_TICKS_PER_DAY + gStartDay) / 365 + gStartYear; + int month = gStartMonth; + int day = (gGameTime / GAME_TIME_TICKS_PER_DAY + gStartDay) % 365; while (1) { int daysInMonth = gGameTimeDaysPerMonth[month]; @@ -439,7 +449,7 @@ int _scriptsCheckGameEvents(int* moviePtr, int window) movieFlags = GAME_MOVIE_FADE_IN | GAME_MOVIE_STOP_MUSIC; endgame = true; } else { - if (day >= 360 || gameGetGlobalVar(GVAR_FALLOUT_2) >= 3) { + if (day >= gMovieTimerArtimer4 || gameGetGlobalVar(GVAR_FALLOUT_2) >= 3) { movie = MOVIE_ARTIMER4; if (!gameMovieIsSeen(MOVIE_ARTIMER4)) { adjustRep = true; @@ -447,13 +457,13 @@ int _scriptsCheckGameEvents(int* moviePtr, int window) wmAreaSetVisibleState(CITY_DESTROYED_ARROYO, 1, 1); wmAreaMarkVisitedState(CITY_DESTROYED_ARROYO, 2); } - } else if (day >= 270 && gameGetGlobalVar(GVAR_FALLOUT_2) != 3) { + } else if (day >= gMovieTimerArtimer3 && gameGetGlobalVar(GVAR_FALLOUT_2) != 3) { adjustRep = true; movie = MOVIE_ARTIMER3; - } else if (day >= 180 && gameGetGlobalVar(GVAR_FALLOUT_2) != 3) { + } else if (day >= gMovieTimerArtimer2 && gameGetGlobalVar(GVAR_FALLOUT_2) != 3) { adjustRep = true; movie = MOVIE_ARTIMER2; - } else if (day >= 90 && gameGetGlobalVar(GVAR_FALLOUT_2) != 3) { + } else if (day >= gMovieTimerArtimer1 && gameGetGlobalVar(GVAR_FALLOUT_2) != 3) { adjustRep = true; movie = MOVIE_ARTIMER1; } @@ -1522,6 +1532,15 @@ int scriptsInit() messageListRepositorySetStandardMessageList(STANDARD_MESSAGE_LIST_SCRIPT, &gScrMessageList); + configGetInt(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_START_YEAR, &gStartYear); + configGetInt(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_START_MONTH, &gStartMonth); + configGetInt(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_START_DAY, &gStartDay); + + configGetInt(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_MOVIE_TIMER_ARTIMER1, &gMovieTimerArtimer1); + configGetInt(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_MOVIE_TIMER_ARTIMER2, &gMovieTimerArtimer2); + configGetInt(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_MOVIE_TIMER_ARTIMER3, &gMovieTimerArtimer3); + configGetInt(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_MOVIE_TIMER_ARTIMER4, &gMovieTimerArtimer4); + return 0; } diff --git a/src/sfall_config.cc b/src/sfall_config.cc index c3c8f7e..c0f4b6c 100644 --- a/src/sfall_config.cc +++ b/src/sfall_config.cc @@ -27,6 +27,9 @@ bool sfallConfigInit(int argc, char** argv) configSetString(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_DUDE_NATIVE_LOOK_JUMPSUIT_FEMALE_KEY, ""); configSetString(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_DUDE_NATIVE_LOOK_TRIBAL_MALE_KEY, ""); configSetString(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_DUDE_NATIVE_LOOK_TRIBAL_FEMALE_KEY, ""); + configSetInt(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_START_YEAR, 2241); + configSetInt(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_START_MONTH, 6); + configSetInt(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_START_DAY, 24); configSetInt(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_MAIN_MENU_BIG_FONT_COLOR_KEY, 0); configSetInt(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_MAIN_MENU_CREDITS_OFFSET_X_KEY, 0); configSetInt(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_MAIN_MENU_CREDITS_OFFSET_Y_KEY, 0); @@ -50,6 +53,10 @@ bool sfallConfigInit(int argc, char** argv) configSetInt(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_BURST_MOD_TARGET_MULTIPLIER_KEY, SFALL_CONFIG_BURST_MOD_DEFAULT_TARGET_MULTIPLIER); configSetInt(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_BURST_MOD_TARGET_DIVISOR_KEY, SFALL_CONFIG_BURST_MOD_DEFAULT_TARGET_DIVISOR); configSetString(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_EXTRA_MESSAGE_LISTS_KEY, ""); + configSetInt(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_MOVIE_TIMER_ARTIMER1, 90); + configSetInt(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_MOVIE_TIMER_ARTIMER2, 180); + configSetInt(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_MOVIE_TIMER_ARTIMER3, 270); + configSetInt(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_MOVIE_TIMER_ARTIMER4, 360); char path[COMPAT_MAX_PATH]; char* executable = argv[0]; diff --git a/src/sfall_config.h b/src/sfall_config.h index 26d85cc..86aa7a7 100644 --- a/src/sfall_config.h +++ b/src/sfall_config.h @@ -13,6 +13,9 @@ namespace fallout { #define SFALL_CONFIG_DUDE_NATIVE_LOOK_JUMPSUIT_FEMALE_KEY "FemaleDefaultModel" #define SFALL_CONFIG_DUDE_NATIVE_LOOK_TRIBAL_MALE_KEY "MaleStartModel" #define SFALL_CONFIG_DUDE_NATIVE_LOOK_TRIBAL_FEMALE_KEY "FemaleStartModel" +#define SFALL_CONFIG_START_YEAR "StartYear" +#define SFALL_CONFIG_START_MONTH "StartMonth" +#define SFALL_CONFIG_START_DAY "StartDay" #define SFALL_CONFIG_MAIN_MENU_BIG_FONT_COLOR_KEY "MainMenuBigFontColour" #define SFALL_CONFIG_MAIN_MENU_CREDITS_OFFSET_X_KEY "MainMenuCreditsOffsetX" #define SFALL_CONFIG_MAIN_MENU_CREDITS_OFFSET_Y_KEY "MainMenuCreditsOffsetY" @@ -42,6 +45,10 @@ namespace fallout { #define SFALL_CONFIG_PLASTIC_EXPLOSIVE_MIN_DAMAGE_KEY "PlasticExplosive_DmgMin" #define SFALL_CONFIG_PLASTIC_EXPLOSIVE_MAX_DAMAGE_KEY "PlasticExplosive_DmgMax" #define SFALL_CONFIG_EXPLOSION_EMITS_LIGHT_KEY "ExplosionsEmitLight" +#define SFALL_CONFIG_MOVIE_TIMER_ARTIMER1 "MovieTimer_artimer1" +#define SFALL_CONFIG_MOVIE_TIMER_ARTIMER2 "MovieTimer_artimer2" +#define SFALL_CONFIG_MOVIE_TIMER_ARTIMER3 "MovieTimer_artimer3" +#define SFALL_CONFIG_MOVIE_TIMER_ARTIMER4 "MovieTimer_artimer4" #define SFALL_CONFIG_CITY_REPUTATION_LIST_KEY "CityRepsList" #define SFALL_CONFIG_UNARMED_FILE_KEY "UnarmedFile" #define SFALL_CONFIG_DAMAGE_MOD_FORMULA_KEY "DamageFormula" diff --git a/src/sfall_opcodes.cc b/src/sfall_opcodes.cc index d746f93..103d29f 100644 --- a/src/sfall_opcodes.cc +++ b/src/sfall_opcodes.cc @@ -1,19 +1,25 @@ #include "sfall_opcodes.h" +#include "animation.h" #include "art.h" #include "combat.h" #include "debug.h" #include "game.h" +#include "input.h" #include "interface.h" #include "interpreter.h" #include "item.h" #include "message.h" #include "mouse.h" #include "object.h" +#include "proto.h" +#include "scripts.h" #include "sfall_global_vars.h" #include "sfall_lists.h" #include "stat.h" #include "svga.h" +#include "tile.h" +#include "worldmap.h" namespace fallout { @@ -39,6 +45,17 @@ static void opReadByte(Program* program) programStackPushInteger(program, value); } +// set_pc_base_stat +static void op_set_pc_base_stat(Program* program) +{ + // CE: Implementation is different. Sfall changes value directly on the + // dude's proto, without calling |critterSetBaseStat|. This function has + // important call to update derived stats, which is not present in Sfall. + int value = programStackPopInteger(program); + int stat = programStackPopInteger(program); + critterSetBaseStat(gDude, stat, value); +} + // set_pc_extra_stat static void opSetPcBonusStat(Program* program) { @@ -50,6 +67,16 @@ static void opSetPcBonusStat(Program* program) critterSetBonusStat(gDude, stat, value); } +// get_pc_base_stat +static void op_get_pc_base_stat(Program* program) +{ + // CE: Implementation is different. Sfall obtains value directly from + // dude's proto. This can have unforeseen consequences when dealing with + // current stats. + int stat = programStackPopInteger(program); + programStackPushInteger(program, critterGetBaseStat(gDude, stat)); +} + // get_pc_extra_stat static void opGetPcBonusStat(Program* program) { @@ -58,6 +85,28 @@ static void opGetPcBonusStat(Program* program) programStackPushInteger(program, value); } +// get_year +static void op_get_year(Program* program) +{ + int year; + gameTimeGetDate(nullptr, nullptr, &year); + programStackPushInteger(program, year); +} + +// in_world_map +static void op_in_world_map(Program* program) +{ + programStackPushInteger(program, GameMode::isInGameMode(GameMode::kWorldmap) ? 1 : 0); +} + +// set_world_map_pos +static void op_set_world_map_pos(Program* program) +{ + int y = programStackPopInteger(program); + int x = programStackPopInteger(program); + wmSetPartyWorldPos(x, y); +} + // active_hand static void opGetCurrentHand(Program* program) { @@ -100,6 +149,103 @@ static void opGetGameMode(Program* program) programStackPushInteger(program, GameMode::getCurrentGameMode()); } +// get_uptime +static void op_get_uptime(Program* program) +{ + programStackPushInteger(program, getTicks()); +} + +// set_car_current_town +static void op_set_car_current_town(Program* program) +{ + int area = programStackPopInteger(program); + wmCarSetCurrentArea(area); +} + +// get_bodypart_hit_modifier +static void op_get_bodypart_hit_modifier(Program* program) +{ + int hit_location = programStackPopInteger(program); + programStackPushInteger(program, combat_get_hit_location_penalty(hit_location)); +} + +// set_bodypart_hit_modifier +static void op_set_bodypart_hit_modifier(Program* program) +{ + int penalty = programStackPopInteger(program); + int hit_location = programStackPopInteger(program); + combat_set_hit_location_penalty(hit_location, penalty); +} + +// sqrt +static void op_sqrt(Program* program) +{ + ProgramValue programValue = programStackPopValue(program); + programStackPushFloat(program, sqrtf(programValue.asFloat())); +} + +// abs +static void op_abs(Program* program) +{ + ProgramValue programValue = programStackPopValue(program); + + if (programValue.isInt()) { + programStackPushInteger(program, abs(programValue.integerValue)); + } else { + programStackPushFloat(program, abs(programValue.asFloat())); + } +} + +// get_proto_data +static void op_get_proto_data(Program* program) +{ + size_t offset = static_cast(programStackPopInteger(program)); + int pid = programStackPopInteger(program); + + Proto* proto; + if (protoGetProto(pid, &proto) != 0) { + debugPrint("op_get_proto_data: bad proto %d", pid); + programStackPushInteger(program, -1); + return; + } + + // CE: Make sure the requested offset is within memory bounds and is + // properly aligned. + if (offset + sizeof(int) > proto_size(PID_TYPE(pid)) || offset % sizeof(int) != 0) { + debugPrint("op_get_proto_data: bad offset %d", offset); + programStackPushInteger(program, -1); + return; + } + + int value = *reinterpret_cast(reinterpret_cast(proto) + offset); + programStackPushInteger(program, value); +} + +// set_proto_data +static void op_set_proto_data(Program* program) +{ + int value = programStackPopInteger(program); + size_t offset = static_cast(programStackPopInteger(program)); + int pid = programStackPopInteger(program); + + Proto* proto; + if (protoGetProto(pid, &proto) != 0) { + debugPrint("op_set_proto_data: bad proto %d", pid); + programStackPushInteger(program, -1); + return; + } + + // CE: Make sure the requested offset is within memory bounds and is + // properly aligned. + if (offset + sizeof(int) > proto_size(PID_TYPE(pid)) || offset % sizeof(int) != 0) { + debugPrint("op_set_proto_data: bad offset %d", offset); + programStackPushInteger(program, -1); + return; + } + + *reinterpret_cast(reinterpret_cast(proto) + offset) = value; +} + // list_begin static void opListBegin(Program* program) { @@ -251,6 +397,14 @@ static void opGetMouseY(Program* program) programStackPushInteger(program, y); } +// get_mouse_buttons +static void op_get_mouse_buttons(Program* program) +{ + // CE: Implementation is slightly different - it does not handle middle + // mouse button. + programStackPushInteger(program, mouse_get_last_buttons()); +} + // get_screen_width static void opGetScreenWidth(Program* program) { @@ -263,6 +417,17 @@ static void opGetScreenHeight(Program* program) programStackPushInteger(program, screenGetHeight()); } +// get_attack_type +static void op_get_attack_type(Program* program) +{ + int hit_mode; + if (interface_get_current_attack_mode(&hit_mode)) { + programStackPushInteger(program, hit_mode); + } else { + programStackPushInteger(program, -1); + } +} + // atoi static void opParseInt(Program* program) { @@ -270,6 +435,24 @@ static void opParseInt(Program* program) programStackPushInteger(program, static_cast(strtol(string, nullptr, 0))); } +// atof +static void op_atof(Program* program) +{ + const char* string = programStackPopString(program); + programStackPushFloat(program, static_cast(atof(string))); +} + +// tile_under_cursor +static void op_tile_under_cursor(Program* program) +{ + int x; + int y; + mouseGetPosition(&x, &y); + + int tile = tileFromScreenXY(x, y, gElevation); + programStackPushInteger(program, tile); +} + // strlen static void opGetStringLength(Program* program) { @@ -277,6 +460,22 @@ static void opGetStringLength(Program* program) programStackPushInteger(program, static_cast(strlen(string))); } +// pow (^) +static void op_power(Program* program) +{ + ProgramValue expValue = programStackPopValue(program); + ProgramValue baseValue = programStackPopValue(program); + + // CE: Implementation is slightly different, check. + float result = powf(baseValue.asFloat(), expValue.asFloat()); + + if (baseValue.isInt() && expValue.isInt()) { + programStackPushInteger(program, static_cast(result)); + } else { + programStackPushFloat(program, result); + } +} + // message_str_game static void opGetMessage(Program* program) { @@ -298,6 +497,61 @@ static void opRound(Program* program) programStackPushInteger(program, integerValue); } +enum BlockType { + BLOCKING_TYPE_BLOCK, + BLOCKING_TYPE_SHOOT, + BLOCKING_TYPE_AI, + BLOCKING_TYPE_SIGHT, + BLOCKING_TYPE_SCROLL, +}; + +PathBuilderCallback* get_blocking_func(int type) +{ + switch (type) { + case BLOCKING_TYPE_SHOOT: + return _obj_shoot_blocking_at; + case BLOCKING_TYPE_AI: + return _obj_ai_blocking_at; + case BLOCKING_TYPE_SIGHT: + return _obj_sight_blocking_at; + default: + return _obj_blocking_at; + } +} + +// obj_blocking_line +static void op_make_straight_path(Program* program) +{ + int type = programStackPopInteger(program); + int dest = programStackPopInteger(program); + Object* object = static_cast(programStackPopPointer(program)); + + int flags = type == BLOCKING_TYPE_SHOOT ? 32 : 0; + + Object* obstacle = nullptr; + _make_straight_path_func(object, object->tile, dest, nullptr, &obstacle, flags, get_blocking_func(type)); + programStackPushPointer(program, obstacle); +} + +// obj_blocking_tile +static void op_obj_blocking_at(Program* program) +{ + int type = programStackPopInteger(program); + int elevation = programStackPopInteger(program); + int tile = programStackPopInteger(program); + + PathBuilderCallback* func = get_blocking_func(type); + Object* obstacle = func(NULL, tile, elevation); + if (obstacle != NULL) { + if (type == BLOCKING_TYPE_SHOOT) { + if ((obstacle->flags & OBJECT_SHOOT_THRU) != 0) { + obstacle = nullptr; + } + } + } + programStackPushPointer(program, obstacle); +} + // art_exists static void opArtExists(Program* program) { @@ -305,15 +559,50 @@ static void opArtExists(Program* program) programStackPushInteger(program, artExists(fid)); } +// div (/) +static void op_div(Program* program) +{ + ProgramValue divisorValue = programStackPopValue(program); + ProgramValue dividendValue = programStackPopValue(program); + + if (divisorValue.integerValue == 0) { + debugPrint("Division by zero"); + + // TODO: Looks like execution is not halted in Sfall's div, check. + programStackPushInteger(program, 0); + return; + } + + if (dividendValue.isFloat() || divisorValue.isFloat()) { + programStackPushFloat(program, dividendValue.asFloat() / divisorValue.asFloat()); + } else { + // Unsigned divison. + programStackPushInteger(program, static_cast(dividendValue.integerValue) / static_cast(divisorValue.integerValue)); + } +} + void sfallOpcodesInit() { interpreterRegisterOpcode(0x8156, opReadByte); + interpreterRegisterOpcode(0x815A, op_set_pc_base_stat); interpreterRegisterOpcode(0x815B, opSetPcBonusStat); + interpreterRegisterOpcode(0x815C, op_get_pc_base_stat); interpreterRegisterOpcode(0x815D, opGetPcBonusStat); + interpreterRegisterOpcode(0x8163, op_get_year); + interpreterRegisterOpcode(0x8170, op_in_world_map); + interpreterRegisterOpcode(0x8172, op_set_world_map_pos); interpreterRegisterOpcode(0x8193, opGetCurrentHand); interpreterRegisterOpcode(0x819D, opSetGlobalVar); interpreterRegisterOpcode(0x819E, opGetGlobalInt); interpreterRegisterOpcode(0x81AF, opGetGameMode); + interpreterRegisterOpcode(0x81B3, op_get_uptime); + interpreterRegisterOpcode(0x81B6, op_set_car_current_town); + interpreterRegisterOpcode(0x81DF, op_get_bodypart_hit_modifier); + interpreterRegisterOpcode(0x81E0, op_set_bodypart_hit_modifier); + interpreterRegisterOpcode(0x81EC, op_sqrt); + interpreterRegisterOpcode(0x81ED, op_abs); + interpreterRegisterOpcode(0x8204, op_get_proto_data); + interpreterRegisterOpcode(0x8205, op_set_proto_data); interpreterRegisterOpcode(0x820D, opListBegin); interpreterRegisterOpcode(0x820E, opListNext); interpreterRegisterOpcode(0x820F, opListEnd); @@ -326,13 +615,21 @@ void sfallOpcodesInit() interpreterRegisterOpcode(0x821A, opSetWeaponAmmoCount); interpreterRegisterOpcode(0x821C, opGetMouseX); interpreterRegisterOpcode(0x821D, opGetMouseY); + interpreterRegisterOpcode(0x821E, op_get_mouse_buttons); interpreterRegisterOpcode(0x8220, opGetScreenWidth); interpreterRegisterOpcode(0x8221, opGetScreenHeight); + interpreterRegisterOpcode(0x8228, op_get_attack_type); interpreterRegisterOpcode(0x8237, opParseInt); + interpreterRegisterOpcode(0x8238, op_atof); + interpreterRegisterOpcode(0x824B, op_tile_under_cursor); interpreterRegisterOpcode(0x824F, opGetStringLength); + interpreterRegisterOpcode(0x8263, op_power); interpreterRegisterOpcode(0x826B, opGetMessage); interpreterRegisterOpcode(0x8267, opRound); + interpreterRegisterOpcode(0x826E, op_make_straight_path); + interpreterRegisterOpcode(0x826F, op_obj_blocking_at); interpreterRegisterOpcode(0x8274, opArtExists); + interpreterRegisterOpcode(0x827F, op_div); } void sfallOpcodesExit() diff --git a/src/tile.cc b/src/tile.cc index a7e98aa..af68a39 100644 --- a/src/tile.cc +++ b/src/tile.cc @@ -5,6 +5,7 @@ #include #include +#include #include "art.h" #include "color.h" @@ -49,11 +50,16 @@ typedef struct UpsideDownTriangle { int field_8; } UpsideDownTriangle; +struct roof_fill_task { + int x; + int y; +}; + static void tileSetBorder(int windowWidth, int windowHeight, int hexGridWidth, int hexGridHeight); static void tileRefreshMapper(Rect* rect, int elevation); static void tileRefreshGame(Rect* rect, int elevation); -static void roof_fill_on(int x, int y, int elevation); -static void roof_fill_off(int x, int y, int elevation); +static void roof_fill_push_task_if_in_bounds(std::stack& tasks_stack, int x, int y); +static void roof_fill_off_process_task(std::stack& tasks_stack, int elevation, bool on); static void tileRenderRoof(int fid, int x, int y, Rect* rect, int light); static void _draw_grid(int tile, int elevation, Rect* rect); static void tileRenderFloor(int fid, int x, int y, Rect* rect); @@ -1258,27 +1264,39 @@ void tileRenderRoofsInRect(Rect* rect, int elevation) } } -// 0x4B22D0 -static void roof_fill_on(int x, int y, int elevation) +static void roof_fill_push_task_if_in_bounds(std::stack& tasks_stack, int x, int y) { if (x >= 0 && x < gSquareGridWidth && y >= 0 && y < gSquareGridHeight) { - int squareTileIndex = gSquareGridWidth * y + x; - int squareTile = gTileSquares[elevation]->field_0[squareTileIndex]; - int roof = (squareTile >> 16) & 0xFFFF; + tasks_stack.push(roof_fill_task { x, y }); + }; +}; - int id = roof & 0xFFF; - if (buildFid(OBJ_TYPE_TILE, id, 0, 0, 0) != buildFid(OBJ_TYPE_TILE, 1, 0, 0, 0)) { - int flag = (roof & 0xF000) >> 12; - if ((flag & 0x01) != 0) { +static void roof_fill_off_process_task(std::stack& tasks_stack, int elevation, bool on) +{ + auto [x, y] = tasks_stack.top(); + tasks_stack.pop(); + + int squareTileIndex = gSquareGridWidth * y + x; + int squareTile = gTileSquares[elevation]->field_0[squareTileIndex]; + int roof = (squareTile >> 16) & 0xFFFF; + + int id = roof & 0xFFF; + if (buildFid(OBJ_TYPE_TILE, id, 0, 0, 0) != buildFid(OBJ_TYPE_TILE, 1, 0, 0, 0)) { + int flag = (roof & 0xF000) >> 12; + + if (on ? ((flag & 0x01) != 0) : ((flag & 0x03) == 0)) { + if (on) { flag &= ~0x01; - - gTileSquares[elevation]->field_0[squareTileIndex] = (squareTile & 0xFFFF) | (((flag << 12) | id) << 16); - - roof_fill_on(x - 1, y, elevation); - roof_fill_on(x + 1, y, elevation); - roof_fill_on(x, y - 1, elevation); - roof_fill_on(x, y + 1, elevation); + } else { + flag |= 0x01; } + + gTileSquares[elevation]->field_0[squareTileIndex] = (squareTile & 0xFFFF) | (((flag << 12) | id) << 16); + + roof_fill_push_task_if_in_bounds(tasks_stack, x - 1, y); + roof_fill_push_task_if_in_bounds(tasks_stack, x + 1, y); + roof_fill_push_task_if_in_bounds(tasks_stack, x, y - 1); + roof_fill_push_task_if_in_bounds(tasks_stack, x, y + 1); } } } @@ -1286,35 +1304,12 @@ static void roof_fill_on(int x, int y, int elevation) // 0x4B23D4 void tile_fill_roof(int x, int y, int elevation, bool on) { - if (on) { - roof_fill_on(x, y, elevation); - } else { - roof_fill_off(x, y, elevation); - } -} + std::stack tasks_stack; -// 0x4B23DC -static void roof_fill_off(int x, int y, int elevation) -{ - if (x >= 0 && x < gSquareGridWidth && y >= 0 && y < gSquareGridHeight) { - int squareTileIndex = gSquareGridWidth * y + x; - int squareTile = gTileSquares[elevation]->field_0[squareTileIndex]; - int roof = (squareTile >> 16) & 0xFFFF; + roof_fill_push_task_if_in_bounds(tasks_stack, x, y); - int id = roof & 0xFFF; - if (buildFid(OBJ_TYPE_TILE, id, 0, 0, 0) != buildFid(OBJ_TYPE_TILE, 1, 0, 0, 0)) { - int flag = (roof & 0xF000) >> 12; - if ((flag & 0x03) == 0) { - flag |= 0x01; - - gTileSquares[elevation]->field_0[squareTileIndex] = (squareTile & 0xFFFF) | (((flag << 12) | id) << 16); - - roof_fill_off(x - 1, y, elevation); - roof_fill_off(x + 1, y, elevation); - roof_fill_off(x, y - 1, elevation); - roof_fill_off(x, y + 1, elevation); - } - } + while (!tasks_stack.empty()) { + roof_fill_off_process_task(tasks_stack, elevation, on); } } diff --git a/src/worldmap.cc b/src/worldmap.cc index a02914f..9b6ff3d 100644 --- a/src/worldmap.cc +++ b/src/worldmap.cc @@ -6592,4 +6592,15 @@ void wmBlinkRndEncounterIcon(bool special) wmGenData.encounterIconIsVisible = false; } +void wmSetPartyWorldPos(int x, int y) +{ + wmGenData.worldPosX = x; + wmGenData.worldPosY = y; +} + +void wmCarSetCurrentArea(int area) +{ + wmGenData.currentCarAreaId = area; +} + } // namespace fallout diff --git a/src/worldmap.h b/src/worldmap.h index 1180d70..901b0a6 100644 --- a/src/worldmap.h +++ b/src/worldmap.h @@ -277,6 +277,9 @@ int wmSetMapMusic(int mapIdx, const char* name); int wmMatchAreaContainingMapIdx(int mapIdx, int* areaIdxPtr); int wmTeleportToArea(int areaIdx); +void wmSetPartyWorldPos(int x, int y); +void wmCarSetCurrentArea(int area); + } // namespace fallout #endif /* WORLD_MAP_H */