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