From d6a74468da1fea902558785f3f90d175f4e87808 Mon Sep 17 00:00:00 2001 From: Alexander Batalov Date: Mon, 1 May 2023 07:10:04 +0300 Subject: [PATCH 01/10] Use Xcode for macOS builds (#275) --- .github/workflows/ci-build.yml | 14 ++++++++++---- CMakeLists.txt | 33 +++++++++++++++++++++------------ os/macos/Info.plist | 4 +++- 3 files changed, 34 insertions(+), 17 deletions(-) diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml index 54fa754..aa97e0d 100644 --- a/.github/workflows/ci-build.yml +++ b/.github/workflows/ci-build.yml @@ -213,28 +213,34 @@ jobs: uses: actions/cache@v3 with: path: build - key: macos-cmake-v3 + key: macos-cmake-v4 - name: Configure run: | cmake \ -B build \ - -D CMAKE_BUILD_TYPE=RelWithDebInfo \ + -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 + - name: Pack + run: | + cd build + cpack -C RelWithDebInfo + - name: Upload uses: actions/upload-artifact@v3 with: name: fallout2-ce-macos.dmg - path: build/fallout2-ce.dmg + path: build/Fallout II Community Edition.dmg retention-days: 7 windows: diff --git a/CMakeLists.txt b/CMakeLists.txt index d7da510..5fc81b1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -318,16 +318,30 @@ if(APPLE) XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER "com.alexbatalov.fallout2-ce" XCODE_ATTRIBUTE_TARGETED_DEVICE_FAMILY "1,2" ) + + set(MACOSX_BUNDLE_BUNDLE_NAME "${EXECUTABLE_NAME}") + set(MACOSX_BUNDLE_DISPLAY_NAME "Fallout 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(RESOURCES + "os/macos/fallout2-ce.icns" + ) + + target_sources(${EXECUTABLE_NAME} PUBLIC ${RESOURCES}) + set_source_files_properties(${RESOURCES} PROPERTIES MACOSX_PACKAGE_LOCATION "Resources") + + set_target_properties(${EXECUTABLE_NAME} PROPERTIES + OUTPUT_NAME "Fallout II Community Edition" + MACOSX_BUNDLE_INFO_PLIST "${CMAKE_SOURCE_DIR}/os/macos/Info.plist" + XCODE_ATTRIBUTE_EXECUTABLE_NAME "${EXECUTABLE_NAME}" + XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER "com.alexbatalov.fallout2-ce" + ) + set(MACOSX_BUNDLE_ICON_FILE "fallout2-ce.icns") + set(MACOSX_BUNDLE_BUNDLE_NAME "Fallout II: Community Edition") + set(MACOSX_BUNDLE_DISPLAY_NAME "Fallout II") endif() set(MACOSX_BUNDLE_GUI_IDENTIFIER "com.alexbatalov.fallout2-ce") - set(MACOSX_BUNDLE_BUNDLE_NAME "${EXECUTABLE_NAME}") - set(MACOSX_BUNDLE_DISPLAY_NAME "Fallout 2") set(MACOSX_BUNDLE_SHORT_VERSION_STRING "1.2.0") set(MACOSX_BUNDLE_BUNDLE_VERSION "1.2.0") endif() @@ -360,22 +374,17 @@ if(APPLE) set(CPACK_ARCHIVE_FILE_EXTENSION "ipa") else() install(TARGETS ${EXECUTABLE_NAME} DESTINATION .) - install(CODE " - include(BundleUtilities) - fixup_bundle(${CMAKE_BINARY_DIR}/${MACOSX_BUNDLE_BUNDLE_NAME}.app \"\" \"\") - " - COMPONENT Runtime) if (CPACK_BUNDLE_APPLE_CERT_APP) install(CODE " - execute_process(COMMAND codesign --deep --force --options runtime --sign \"${CPACK_BUNDLE_APPLE_CERT_APP}\" ${CMAKE_BINARY_DIR}/${MACOSX_BUNDLE_BUNDLE_NAME}.app) + execute_process(COMMAND codesign --deep --force --options runtime --sign \"${CPACK_BUNDLE_APPLE_CERT_APP}\" ${CMAKE_BINARY_DIR}/Fallout II Community Edition.app) " COMPONENT Runtime) endif() set(CPACK_GENERATOR "DragNDrop") set(CPACK_DMG_DISABLE_APPLICATIONS_SYMLINK ON) - set(CPACK_PACKAGE_FILE_NAME "fallout2-ce") + set(CPACK_PACKAGE_FILE_NAME "Fallout II Community Edition") endif() include(CPack) diff --git a/os/macos/Info.plist b/os/macos/Info.plist index 5d5b555..38b9b8d 100644 --- a/os/macos/Info.plist +++ b/os/macos/Info.plist @@ -3,7 +3,7 @@ CFBundleDevelopmentRegion - English + en CFBundleDisplayName ${MACOSX_BUNDLE_DISPLAY_NAME} CFBundleExecutable @@ -28,6 +28,8 @@ ${MACOSX_BUNDLE_COPYRIGHT} NSHighResolutionCapable True + LSApplicationCategoryType + public.app-category.role-playing-games LSMinimumSystemVersion 10.11 SDL_FILESYSTEM_BASE_DIR_TYPE From efdc2e019982a863aa477bc6f0285c20dfe49348 Mon Sep 17 00:00:00 2001 From: Alexander Batalov Date: Mon, 1 May 2023 07:54:14 +0300 Subject: [PATCH 02/10] Bump setup-java to v3 (#278) --- .github/workflows/ci-build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml index aa97e0d..946be3c 100644 --- a/.github/workflows/ci-build.yml +++ b/.github/workflows/ci-build.yml @@ -53,7 +53,7 @@ jobs: uses: actions/checkout@v3 - name: Setup Java - uses: actions/setup-java@v2 + uses: actions/setup-java@v3 with: distribution: temurin java-version: 11 From a06097aef5f0bfef4ce3692f25550731c59f6b7f Mon Sep 17 00:00:00 2001 From: Alexander Batalov Date: Tue, 9 May 2023 18:36:20 +0300 Subject: [PATCH 03/10] Improve touch controls (#283) --- CMakeLists.txt | 2 + README.md | 6 +- src/dinput.cc | 134 ++------------------- src/game_movie.cc | 6 + src/input.cc | 9 +- src/mouse.cc | 49 ++++++++ src/touch.cc | 290 ++++++++++++++++++++++++++++++++++++++++++++++ src/touch.h | 38 ++++++ 8 files changed, 405 insertions(+), 129 deletions(-) create mode 100644 src/touch.cc create mode 100644 src/touch.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 5fc81b1..dc311d2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -271,6 +271,8 @@ target_sources(${EXECUTABLE_NAME} PUBLIC "src/sfall_lists.h" "src/sfall_opcodes.cc" "src/sfall_opcodes.h" + "src/touch.cc" + "src/touch.h" ) if(IOS) diff --git a/README.md b/README.md index e58b082..ff0aae7 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,11 @@ $ sudo apt install libsdl2-2.0-0 ### Android -> **NOTE**: Fallout 2 was designed with mouse in mind. There are many controls that require precise cursor positioning, which is not possible with fingers. When playing on Android you'll use fingers to move mouse cursor, not a character, or a map. Double tap to "click" left mouse button in the current cursor position, triple tap to "click" right mouse button. It might feel awkward at first, but it's super handy - you can play with just a thumb. This is not set in stone and might change in the future. +> **NOTE**: Fallout 2 was designed with mouse in mind. There are many controls that require precise cursor positioning, which is not possible with fingers. Current control scheme resembles trackpad usage: +- One finger moves mouse cursor around. +- Tap one finger for left mouse click. +- Tap two fingers for right mouse click (switches mouse cursor mode). +- Move two fingers to scroll current view (map view, worldmap view, inventory scrollers). > **NOTE**: From Android standpoint release and debug builds are different apps. Both apps require their own copy of game assets and have their own savegames. This is intentional. As a gamer just stick with release version and check for updates. diff --git a/src/dinput.cc b/src/dinput.cc index d84b9f3..4fae607 100644 --- a/src/dinput.cc +++ b/src/dinput.cc @@ -2,30 +2,9 @@ namespace fallout { -enum InputType { - INPUT_TYPE_MOUSE, - INPUT_TYPE_TOUCH, -} InputType; - -static int gLastInputType = INPUT_TYPE_MOUSE; - -static int gTouchMouseLastX = 0; -static int gTouchMouseLastY = 0; -static int gTouchMouseDeltaX = 0; -static int gTouchMouseDeltaY = 0; - -static int gTouchFingers = 0; -static unsigned int gTouchGestureLastTouchDownTimestamp = 0; -static unsigned int gTouchGestureLastTouchUpTimestamp = 0; -static int gTouchGestureTaps = 0; -static bool gTouchGestureHandled = false; - static int gMouseWheelDeltaX = 0; static int gMouseWheelDeltaY = 0; -extern int screenGetWidth(); -extern int screenGetHeight(); - // 0x4E0400 bool directInputInit() { @@ -71,49 +50,14 @@ bool mouseDeviceUnacquire() // 0x4E053C bool mouseDeviceGetData(MouseData* mouseState) { - if (gLastInputType == INPUT_TYPE_TOUCH) { - mouseState->x = gTouchMouseDeltaX; - mouseState->y = gTouchMouseDeltaY; - mouseState->buttons[0] = 0; - mouseState->buttons[1] = 0; - mouseState->wheelX = 0; - mouseState->wheelY = 0; - gTouchMouseDeltaX = 0; - gTouchMouseDeltaY = 0; + Uint32 buttons = SDL_GetRelativeMouseState(&(mouseState->x), &(mouseState->y)); + mouseState->buttons[0] = (buttons & SDL_BUTTON(SDL_BUTTON_LEFT)) != 0; + mouseState->buttons[1] = (buttons & SDL_BUTTON(SDL_BUTTON_RIGHT)) != 0; + mouseState->wheelX = gMouseWheelDeltaX; + mouseState->wheelY = gMouseWheelDeltaY; - if (gTouchFingers == 0) { - if (SDL_GetTicks() - gTouchGestureLastTouchUpTimestamp > 150) { - if (!gTouchGestureHandled) { - if (gTouchGestureTaps == 2) { - mouseState->buttons[0] = 1; - gTouchGestureHandled = true; - } else if (gTouchGestureTaps == 3) { - mouseState->buttons[1] = 1; - gTouchGestureHandled = true; - } - } - } - } else if (gTouchFingers == 1) { - if (SDL_GetTicks() - gTouchGestureLastTouchDownTimestamp > 150) { - if (gTouchGestureTaps == 1) { - mouseState->buttons[0] = 1; - gTouchGestureHandled = true; - } else if (gTouchGestureTaps == 2) { - mouseState->buttons[1] = 1; - gTouchGestureHandled = true; - } - } - } - } else { - Uint32 buttons = SDL_GetRelativeMouseState(&(mouseState->x), &(mouseState->y)); - mouseState->buttons[0] = (buttons & SDL_BUTTON(SDL_BUTTON_LEFT)) != 0; - mouseState->buttons[1] = (buttons & SDL_BUTTON(SDL_BUTTON_RIGHT)) != 0; - mouseState->wheelX = gMouseWheelDeltaX; - mouseState->wheelY = gMouseWheelDeltaY; - - gMouseWheelDeltaX = 0; - gMouseWheelDeltaY = 0; - } + gMouseWheelDeltaX = 0; + gMouseWheelDeltaY = 0; return true; } @@ -174,70 +118,6 @@ void handleMouseEvent(SDL_Event* event) gMouseWheelDeltaX += event->wheel.x; gMouseWheelDeltaY += event->wheel.y; } - - if (gLastInputType != INPUT_TYPE_MOUSE) { - // Reset touch data. - gTouchMouseLastX = 0; - gTouchMouseLastY = 0; - gTouchMouseDeltaX = 0; - gTouchMouseDeltaY = 0; - - gTouchFingers = 0; - gTouchGestureLastTouchDownTimestamp = 0; - gTouchGestureLastTouchUpTimestamp = 0; - gTouchGestureTaps = 0; - gTouchGestureHandled = false; - - gLastInputType = INPUT_TYPE_MOUSE; - } -} - -void handleTouchEvent(SDL_Event* event) -{ - int windowWidth = screenGetWidth(); - int windowHeight = screenGetHeight(); - - if (event->tfinger.type == SDL_FINGERDOWN) { - gTouchFingers++; - - gTouchMouseLastX = (int)(event->tfinger.x * windowWidth); - gTouchMouseLastY = (int)(event->tfinger.y * windowHeight); - gTouchMouseDeltaX = 0; - gTouchMouseDeltaY = 0; - - if (event->tfinger.timestamp - gTouchGestureLastTouchDownTimestamp > 250) { - gTouchGestureTaps = 0; - gTouchGestureHandled = false; - } - - gTouchGestureLastTouchDownTimestamp = event->tfinger.timestamp; - } else if (event->tfinger.type == SDL_FINGERMOTION) { - int prevX = gTouchMouseLastX; - int prevY = gTouchMouseLastY; - gTouchMouseLastX = (int)(event->tfinger.x * windowWidth); - gTouchMouseLastY = (int)(event->tfinger.y * windowHeight); - gTouchMouseDeltaX += gTouchMouseLastX - prevX; - gTouchMouseDeltaY += gTouchMouseLastY - prevY; - } else if (event->tfinger.type == SDL_FINGERUP) { - gTouchFingers--; - - int prevX = gTouchMouseLastX; - int prevY = gTouchMouseLastY; - gTouchMouseLastX = (int)(event->tfinger.x * windowWidth); - gTouchMouseLastY = (int)(event->tfinger.y * windowHeight); - gTouchMouseDeltaX += gTouchMouseLastX - prevX; - gTouchMouseDeltaY += gTouchMouseLastY - prevY; - - gTouchGestureTaps++; - gTouchGestureLastTouchUpTimestamp = event->tfinger.timestamp; - } - - if (gLastInputType != INPUT_TYPE_TOUCH) { - // Reset mouse data. - SDL_GetRelativeMouseState(NULL, NULL); - - gLastInputType = INPUT_TYPE_TOUCH; - } } } // namespace fallout diff --git a/src/game_movie.cc b/src/game_movie.cc index b4253a8..c8cb9aa 100644 --- a/src/game_movie.cc +++ b/src/game_movie.cc @@ -18,6 +18,7 @@ #include "settings.h" #include "svga.h" #include "text_font.h" +#include "touch.h" #include "window_manager.h" namespace fallout { @@ -249,6 +250,11 @@ int gameMoviePlay(int movie, int flags) break; } + Gesture gesture; + if (touch_get_gesture(&gesture) && gesture.state == kEnded) { + break; + } + int x; int y; _mouse_get_raw_state(&x, &y, &buttons); diff --git a/src/input.cc b/src/input.cc index 95bbc2d..ccfa5a6 100644 --- a/src/input.cc +++ b/src/input.cc @@ -11,6 +11,7 @@ #include "mouse.h" #include "svga.h" #include "text_font.h" +#include "touch.h" #include "vcr.h" #include "win32.h" @@ -1084,9 +1085,13 @@ void _GNW95_process_message() handleMouseEvent(&e); break; case SDL_FINGERDOWN: + touch_handle_start(&(e.tfinger)); + break; case SDL_FINGERMOTION: + touch_handle_move(&(e.tfinger)); + break; case SDL_FINGERUP: - handleTouchEvent(&e); + touch_handle_end(&(e.tfinger)); break; case SDL_KEYDOWN: case SDL_KEYUP: @@ -1121,6 +1126,8 @@ void _GNW95_process_message() } } + touch_process_gesture(); + if (gProgramIsActive && !keyboardIsDisabled()) { // NOTE: Uninline int tick = getTicks(); diff --git a/src/mouse.cc b/src/mouse.cc index 6e8a8bb..7815d70 100644 --- a/src/mouse.cc +++ b/src/mouse.cc @@ -6,6 +6,7 @@ #include "kb.h" #include "memory.h" #include "svga.h" +#include "touch.h" #include "vcr.h" namespace fallout { @@ -381,6 +382,54 @@ void _mouse_info() return; } + Gesture gesture; + if (touch_get_gesture(&gesture)) { + static int prevx; + static int prevy; + + switch (gesture.type) { + case kTap: + if (gesture.numberOfTouches == 1) { + _mouse_simulate_input(0, 0, MOUSE_STATE_LEFT_BUTTON_DOWN); + } else if (gesture.numberOfTouches == 2) { + _mouse_simulate_input(0, 0, MOUSE_STATE_RIGHT_BUTTON_DOWN); + } + break; + case kLongPress: + case kPan: + if (gesture.state == kBegan) { + prevx = gesture.x; + prevy = gesture.y; + } + + if (gesture.type == kLongPress) { + if (gesture.numberOfTouches == 1) { + _mouse_simulate_input(gesture.x - prevx, gesture.y - prevy, MOUSE_STATE_LEFT_BUTTON_DOWN); + } else if (gesture.numberOfTouches == 2) { + _mouse_simulate_input(gesture.x - prevx, gesture.y - prevy, MOUSE_STATE_RIGHT_BUTTON_DOWN); + } + } else if (gesture.type == kPan) { + if (gesture.numberOfTouches == 1) { + _mouse_simulate_input(gesture.x - prevx, gesture.y - prevy, 0); + } else if (gesture.numberOfTouches == 2) { + gMouseWheelX = (prevx - gesture.x) / 2; + gMouseWheelY = (gesture.y - prevy) / 2; + + if (gMouseWheelX != 0 || gMouseWheelY != 0) { + gMouseEvent |= MOUSE_EVENT_WHEEL; + _raw_buttons |= MOUSE_EVENT_WHEEL; + } + } + } + + prevx = gesture.x; + prevy = gesture.y; + break; + } + + return; + } + int x; int y; int buttons = 0; diff --git a/src/touch.cc b/src/touch.cc new file mode 100644 index 0000000..473fe70 --- /dev/null +++ b/src/touch.cc @@ -0,0 +1,290 @@ +#include "touch.h" + +#include +#include + +#include "svga.h" + +namespace fallout { + +#define TOUCH_PHASE_BEGAN 0 +#define TOUCH_PHASE_MOVED 1 +#define TOUCH_PHASE_ENDED 2 + +#define MAX_TOUCHES 10 + +#define TAP_MAXIMUM_DURATION 75 +#define PAN_MINIMUM_MOVEMENT 4 +#define LONG_PRESS_MINIMUM_DURATION 500 + +struct TouchLocation { + int x; + int y; +}; + +struct Touch { + bool used; + SDL_FingerID fingerId; + TouchLocation startLocation; + Uint32 startTimestamp; + TouchLocation currentLocation; + Uint32 currentTimestamp; + int phase; +}; + +static Touch touches[MAX_TOUCHES]; +static Gesture currentGesture; +static std::stack gestureEventsQueue; + +static int find_touch(SDL_FingerID fingerId) +{ + for (int index = 0; index < MAX_TOUCHES; index++) { + if (touches[index].fingerId == fingerId) { + return index; + } + } + return -1; +} + +static int find_unused_touch_index() +{ + for (int index = 0; index < MAX_TOUCHES; index++) { + if (!touches[index].used) { + return index; + } + } + return -1; +} + +static TouchLocation touch_get_start_location_centroid(int* indexes, int length) +{ + TouchLocation centroid; + centroid.x = 0; + centroid.y = 0; + for (int index = 0; index < length; index++) { + centroid.x += touches[indexes[index]].startLocation.x; + centroid.y += touches[indexes[index]].startLocation.y; + } + centroid.x /= length; + centroid.y /= length; + return centroid; +} + +static TouchLocation touch_get_current_location_centroid(int* indexes, int length) +{ + TouchLocation centroid; + centroid.x = 0; + centroid.y = 0; + for (int index = 0; index < length; index++) { + centroid.x += touches[indexes[index]].currentLocation.x; + centroid.y += touches[indexes[index]].currentLocation.y; + } + centroid.x /= length; + centroid.y /= length; + return centroid; +} + +void touch_handle_start(SDL_TouchFingerEvent* event) +{ + // On iOS `fingerId` is an address of underlying `UITouch` object. When + // `touchesBegan` is called this object might be reused, but with + // incresed `tapCount` (which is ignored in this implementation). + int index = find_touch(event->fingerId); + if (index == -1) { + index = find_unused_touch_index(); + } + + if (index != -1) { + Touch* touch = &(touches[index]); + touch->used = true; + touch->fingerId = event->fingerId; + touch->startTimestamp = event->timestamp; + touch->startLocation.x = static_cast(event->x * screenGetWidth()); + touch->startLocation.y = static_cast(event->y * screenGetHeight()); + touch->currentTimestamp = touch->startTimestamp; + touch->currentLocation = touch->startLocation; + touch->phase = TOUCH_PHASE_BEGAN; + } +} + +void touch_handle_move(SDL_TouchFingerEvent* event) +{ + int index = find_touch(event->fingerId); + if (index != -1) { + Touch* touch = &(touches[index]); + touch->currentTimestamp = event->timestamp; + touch->currentLocation.x = static_cast(event->x * screenGetWidth()); + touch->currentLocation.y = static_cast(event->y * screenGetHeight()); + touch->phase = TOUCH_PHASE_MOVED; + } +} + +void touch_handle_end(SDL_TouchFingerEvent* event) +{ + int index = find_touch(event->fingerId); + if (index != -1) { + Touch* touch = &(touches[index]); + touch->currentTimestamp = event->timestamp; + touch->currentLocation.x = static_cast(event->x * screenGetWidth()); + touch->currentLocation.y = static_cast(event->y * screenGetHeight()); + touch->phase = TOUCH_PHASE_ENDED; + } +} + +void touch_process_gesture() +{ + Uint32 sequenceStartTimestamp = -1; + int sequenceStartIndex = -1; + + // Find start of sequence (earliest touch). + for (int index = 0; index < MAX_TOUCHES; index++) { + if (touches[index].used) { + if (sequenceStartTimestamp > touches[index].startTimestamp) { + sequenceStartTimestamp = touches[index].startTimestamp; + sequenceStartIndex = index; + } + } + } + + if (sequenceStartIndex == -1) { + return; + } + + Uint32 sequenceEndTimestamp = -1; + if (touches[sequenceStartIndex].phase == TOUCH_PHASE_ENDED) { + sequenceEndTimestamp = touches[sequenceStartIndex].currentTimestamp; + + // Find end timestamp of sequence. + for (int index = 0; index < MAX_TOUCHES; index++) { + if (touches[index].used + && touches[index].startTimestamp >= sequenceStartTimestamp + && touches[index].startTimestamp <= sequenceEndTimestamp) { + if (touches[index].phase == TOUCH_PHASE_ENDED) { + if (sequenceEndTimestamp < touches[index].currentTimestamp) { + sequenceEndTimestamp = touches[index].currentTimestamp; + + // Start over since we can have fingers missed. + index = -1; + } + } else { + // Sequence is current. + sequenceEndTimestamp = -1; + break; + } + } + } + } + + int active[MAX_TOUCHES]; + int activeCount = 0; + + int ended[MAX_TOUCHES]; + int endedCount = 0; + + // Split participating fingers into two buckets - active fingers (currently + // on screen) and ended (lifted up). + for (int index = 0; index < MAX_TOUCHES; index++) { + if (touches[index].used + && touches[index].currentTimestamp >= sequenceStartTimestamp + && touches[index].currentTimestamp <= sequenceEndTimestamp) { + if (touches[index].phase == TOUCH_PHASE_ENDED) { + ended[endedCount++] = index; + } else { + active[activeCount++] = index; + } + + // If this sequence is over, unmark participating finger as used. + if (sequenceEndTimestamp != -1) { + touches[index].used = false; + } + } + } + + if (currentGesture.type == kPan || currentGesture.type == kLongPress) { + if (currentGesture.state != kEnded) { + // For continuous gestures we want number of fingers to remain the + // same as it was when gesture was recognized. + if (activeCount == currentGesture.numberOfTouches && endedCount == 0) { + TouchLocation centroid = touch_get_current_location_centroid(active, activeCount); + currentGesture.state = kChanged; + currentGesture.x = centroid.x; + currentGesture.y = centroid.y; + gestureEventsQueue.push(currentGesture); + } else { + currentGesture.state = kEnded; + gestureEventsQueue.push(currentGesture); + } + } + + // Reset continuous gesture if when current sequence is over. + if (currentGesture.state == kEnded && sequenceEndTimestamp != -1) { + currentGesture.type = kUnrecognized; + } + } else { + if (activeCount == 0 && endedCount != 0) { + // For taps we need all participating fingers to be both started + // and ended simultaneously (within predefined threshold). + Uint32 startEarliestTimestamp = -1; + Uint32 startLatestTimestamp = 0; + Uint32 endEarliestTimestamp = -1; + Uint32 endLatestTimestamp = 0; + + for (int index = 0; index < endedCount; index++) { + startEarliestTimestamp = std::min(startEarliestTimestamp, touches[ended[index]].startTimestamp); + startLatestTimestamp = std::max(startLatestTimestamp, touches[ended[index]].startTimestamp); + endEarliestTimestamp = std::min(endEarliestTimestamp, touches[ended[index]].currentTimestamp); + endLatestTimestamp = std::max(endLatestTimestamp, touches[ended[index]].currentTimestamp); + } + + if (startLatestTimestamp - startEarliestTimestamp <= TAP_MAXIMUM_DURATION + && endLatestTimestamp - endEarliestTimestamp <= TAP_MAXIMUM_DURATION) { + TouchLocation currentCentroid = touch_get_current_location_centroid(ended, endedCount); + + currentGesture.type = kTap; + currentGesture.state = kEnded; + currentGesture.numberOfTouches = endedCount; + currentGesture.x = currentCentroid.x; + currentGesture.y = currentCentroid.y; + gestureEventsQueue.push(currentGesture); + + // Reset tap gesture immediately. + currentGesture.type = kUnrecognized; + } + } else if (activeCount != 0 && endedCount == 0) { + TouchLocation startCentroid = touch_get_start_location_centroid(active, activeCount); + TouchLocation currentCentroid = touch_get_current_location_centroid(active, activeCount); + + // Disambiguate between pan and long press. + if (abs(currentCentroid.x - startCentroid.x) >= PAN_MINIMUM_MOVEMENT + || abs(currentCentroid.y - startCentroid.y) >= PAN_MINIMUM_MOVEMENT) { + currentGesture.type = kPan; + currentGesture.state = kBegan; + currentGesture.numberOfTouches = activeCount; + currentGesture.x = currentCentroid.x; + currentGesture.y = currentCentroid.y; + gestureEventsQueue.push(currentGesture); + } else if (SDL_GetTicks() - touches[active[0]].startTimestamp >= LONG_PRESS_MINIMUM_DURATION) { + currentGesture.type = kLongPress; + currentGesture.state = kBegan; + currentGesture.numberOfTouches = activeCount; + currentGesture.x = currentCentroid.x; + currentGesture.y = currentCentroid.y; + gestureEventsQueue.push(currentGesture); + } + } + } +} + +bool touch_get_gesture(Gesture* gesture) +{ + if (gestureEventsQueue.empty()) { + return false; + } + + *gesture = gestureEventsQueue.top(); + gestureEventsQueue.pop(); + + return true; +} + +} // namespace fallout diff --git a/src/touch.h b/src/touch.h new file mode 100644 index 0000000..2a76702 --- /dev/null +++ b/src/touch.h @@ -0,0 +1,38 @@ +#ifndef FALLOUT_TOUCH_H_ +#define FALLOUT_TOUCH_H_ + +#include + +namespace fallout { + +enum GestureType { + kUnrecognized, + kTap, + kLongPress, + kPan, +}; + +enum GestureState { + kPossible, + kBegan, + kChanged, + kEnded, +}; + +struct Gesture { + GestureType type; + GestureState state; + int numberOfTouches; + int x; + int y; +}; + +void touch_handle_start(SDL_TouchFingerEvent* event); +void touch_handle_move(SDL_TouchFingerEvent* event); +void touch_handle_end(SDL_TouchFingerEvent* event); +void touch_process_gesture(); +bool touch_get_gesture(Gesture* gesture); + +} // namespace fallout + +#endif /* FALLOUT_TOUCH_H_ */ From 6b6fa3f111e617f901d3a853cf50db4d77ac8fe4 Mon Sep 17 00:00:00 2001 From: Alexander Batalov Date: Tue, 9 May 2023 18:42:47 +0300 Subject: [PATCH 04/10] Fix code format check --- .github/workflows/ci-build.yml | 2 +- src/platform_compat.cc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml index 946be3c..63dcbb5 100644 --- a/.github/workflows/ci-build.yml +++ b/.github/workflows/ci-build.yml @@ -41,7 +41,7 @@ jobs: uses: actions/checkout@v3 - name: clang-format - run: find src -type f -exec clang-format --dry-run --Werror {} \; + run: clang-format --dry-run --Werror src/**/*.cc src/**/*.h android: name: Android diff --git a/src/platform_compat.cc b/src/platform_compat.cc index 7a8f425..031cdae 100644 --- a/src/platform_compat.cc +++ b/src/platform_compat.cc @@ -315,7 +315,7 @@ void compat_resolve_path(char* path) #ifndef _WIN32 char* pch = path; - DIR *dir; + DIR* dir; if (pch[0] == '/') { dir = opendir("/"); pch++; From 2565900f904934a9e0f7f615d3407a1e83783a39 Mon Sep 17 00:00:00 2001 From: Alexander Batalov Date: Thu, 18 May 2023 23:32:16 +0300 Subject: [PATCH 05/10] Fix clang-format args Looks like clang-format in Ubuntu does not recognize glob pattern as macOS does. Previous solution without xargs reported status code of find itself, not clang-format. --- .github/workflows/ci-build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml index 63dcbb5..492e190 100644 --- a/.github/workflows/ci-build.yml +++ b/.github/workflows/ci-build.yml @@ -41,7 +41,7 @@ jobs: uses: actions/checkout@v3 - name: clang-format - run: clang-format --dry-run --Werror src/**/*.cc src/**/*.h + run: find src -type f -name \*.cc -o -name \*.h | xargs clang-format --dry-run --Werror android: name: Android From 666e5cf62d7bbcf4b74fc3907dc94db9a5d3c22e Mon Sep 17 00:00:00 2001 From: Alexander Batalov Date: Tue, 23 May 2023 23:32:11 +0300 Subject: [PATCH 06/10] Fix mouse events processing See alexbatalov/fallout1-ce#55 --- src/dinput.cc | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/dinput.cc b/src/dinput.cc index 4fae607..a5c1dfc 100644 --- a/src/dinput.cc +++ b/src/dinput.cc @@ -50,6 +50,14 @@ bool mouseDeviceUnacquire() // 0x4E053C bool mouseDeviceGetData(MouseData* mouseState) { + // CE: This function is sometimes called outside loops calling `get_input` + // and subsequently `GNW95_process_message`, so mouse events might not be + // handled by SDL yet. + // + // TODO: Move mouse events processing into `GNW95_process_message` and + // update mouse position manually. + SDL_PumpEvents(); + Uint32 buttons = SDL_GetRelativeMouseState(&(mouseState->x), &(mouseState->y)); mouseState->buttons[0] = (buttons & SDL_BUTTON(SDL_BUTTON_LEFT)) != 0; mouseState->buttons[1] = (buttons & SDL_BUTTON(SDL_BUTTON_RIGHT)) != 0; From d641fefc1387886b7cd357b121feab4e93c3d660 Mon Sep 17 00:00:00 2001 From: Alexander Batalov Date: Tue, 23 May 2023 23:43:00 +0300 Subject: [PATCH 07/10] Fix open door sound See alexbatalov/fallout1-ce#71 --- src/proto_instance.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/proto_instance.cc b/src/proto_instance.cc index 8dd9483..0c57d8a 100644 --- a/src/proto_instance.cc +++ b/src/proto_instance.cc @@ -1766,7 +1766,7 @@ int _obj_use_door(Object* a1, Object* a2, int a3) animationRegisterCallback(a2, a2, (AnimationCallback*)_set_door_state_open, -1); } - const char* sfx = sfxBuildOpenName(a2, SCENERY_SOUND_EFFECT_CLOSED); + const char* sfx = sfxBuildOpenName(a2, SCENERY_SOUND_EFFECT_OPEN); animationRegisterPlaySoundEffect(a2, sfx, -1); animationRegisterAnimate(a2, ANIM_STAND, 0); From 61293bd39c0f680efb06023f4484f1ac5a149145 Mon Sep 17 00:00:00 2001 From: Vlad Date: Tue, 23 May 2023 22:45:18 +0200 Subject: [PATCH 08/10] Fix ruined stack in rm_mult_objs_from_inven and name remaining opcodes (#289) --- src/interpreter.cc | 80 ++++++++++++++++++++-------------------- src/interpreter_extra.cc | 6 +-- 2 files changed, 43 insertions(+), 43 deletions(-) diff --git a/src/interpreter.cc b/src/interpreter.cc index 23793ac..59534ec 100644 --- a/src/interpreter.cc +++ b/src/interpreter.cc @@ -87,17 +87,17 @@ static void opLeaveCriticalSection(Program* program); static void opEnterCriticalSection(Program* program); static void opJump(Program* program); static void opCall(Program* program); -static void op801F(Program* program); -static void op801C(Program* program); -static void op801D(Program* program); -static void op8020(Program* program); -static void op8021(Program* program); -static void op8025(Program* program); -static void op8026(Program* program); -static void op8022(Program* program); -static void op8023(Program* program); -static void op8024(Program* program); -static void op801E(Program* program); +static void opPopFlags(Program* program); +static void opPopReturn(Program* program); +static void opPopExit(Program* program); +static void opPopFlagsReturn(Program* program); +static void opPopFlagsExit(Program* program); +static void opPopFlagsReturnValExit(Program* program); +static void opPopFlagsReturnValExitExtern(Program* program); +static void opPopFlagsReturnExtern(Program* program); +static void opPopFlagsExitExtern(Program* program); +static void opPopFlagsReturnValExtern(Program* program); +static void opPopAddress(Program* program); static void opAtoD(Program* program); static void opDtoA(Program* program); static void opExitProgram(Program* program); @@ -2042,7 +2042,7 @@ static void opCall(Program* program) } // 0x46B590 -static void op801F(Program* program) +static void opPopFlags(Program* program) { program->windowId = programStackPopInteger(program); program->checkWaitFunc = (InterpretCheckWaitFunc*)programStackPopPointer(program); @@ -2051,13 +2051,13 @@ static void op801F(Program* program) // pop stack 2 -> set program address // 0x46B63C -static void op801C(Program* program) +static void opPopReturn(Program* program) { program->instructionPointer = programReturnStackPopInteger(program); } // 0x46B658 -static void op801D(Program* program) +static void opPopExit(Program* program) { program->instructionPointer = programReturnStackPopInteger(program); @@ -2065,37 +2065,37 @@ static void op801D(Program* program) } // 0x46B67C -static void op8020(Program* program) +static void opPopFlagsReturn(Program* program) { - op801F(program); + opPopFlags(program); program->instructionPointer = programReturnStackPopInteger(program); } // 0x46B698 -static void op8021(Program* program) +static void opPopFlagsExit(Program* program) { - op801F(program); + opPopFlags(program); program->instructionPointer = programReturnStackPopInteger(program); program->flags |= PROGRAM_FLAG_0x40; } // 0x46B6BC -static void op8025(Program* program) +static void opPopFlagsReturnValExit(Program* program) { ProgramValue value = programStackPopValue(program); - op801F(program); + opPopFlags(program); program->instructionPointer = programReturnStackPopInteger(program); program->flags |= PROGRAM_FLAG_0x40; programStackPushValue(program, value); } // 0x46B73C -static void op8026(Program* program) +static void opPopFlagsReturnValExitExtern(Program* program) { ProgramValue value = programStackPopValue(program); - op801F(program); + opPopFlags(program); Program* v1 = (Program*)programReturnStackPopPointer(program); v1->checkWaitFunc = (InterpretCheckWaitFunc*)programReturnStackPopPointer(program); @@ -2109,9 +2109,9 @@ static void op8026(Program* program) } // 0x46B808 -static void op8022(Program* program) +static void opPopFlagsReturnExtern(Program* program) { - op801F(program); + opPopFlags(program); Program* v1 = (Program*)programReturnStackPopPointer(program); v1->checkWaitFunc = (InterpretCheckWaitFunc*)programReturnStackPopPointer(program); @@ -2121,9 +2121,9 @@ static void op8022(Program* program) } // 0x46B86C -static void op8023(Program* program) +static void opPopFlagsExitExtern(Program* program) { - op801F(program); + opPopFlags(program); Program* v1 = (Program*)programReturnStackPopPointer(program); v1->checkWaitFunc = (InterpretCheckWaitFunc*)programReturnStackPopPointer(program); @@ -2136,11 +2136,11 @@ static void op8023(Program* program) // pop value from stack 1 and push it to script popped from stack 2 // 0x46B8D8 -static void op8024(Program* program) +static void opPopFlagsReturnValExtern(Program* program) { ProgramValue value = programStackPopValue(program); - op801F(program); + opPopFlags(program); Program* v10 = (Program*)programReturnStackPopPointer(program); v10->checkWaitFunc = (InterpretCheckWaitFunc*)programReturnStackPopPointer(program); @@ -2164,7 +2164,7 @@ static void op8024(Program* program) } // 0x46BA10 -static void op801E(Program* program) +static void opPopAddress(Program* program) { programReturnStackPopValue(program); } @@ -2540,17 +2540,17 @@ void interpreterRegisterOpcodeHandlers() interpreterRegisterOpcode(OPCODE_SWAPA, opSwapReturnStack); interpreterRegisterOpcode(OPCODE_POP, opPop); interpreterRegisterOpcode(OPCODE_DUP, opDuplicate); - interpreterRegisterOpcode(OPCODE_POP_RETURN, op801C); - interpreterRegisterOpcode(OPCODE_POP_EXIT, op801D); - interpreterRegisterOpcode(OPCODE_POP_ADDRESS, op801E); - interpreterRegisterOpcode(OPCODE_POP_FLAGS, op801F); - interpreterRegisterOpcode(OPCODE_POP_FLAGS_RETURN, op8020); - interpreterRegisterOpcode(OPCODE_POP_FLAGS_EXIT, op8021); - interpreterRegisterOpcode(OPCODE_POP_FLAGS_RETURN_EXTERN, op8022); - interpreterRegisterOpcode(OPCODE_POP_FLAGS_EXIT_EXTERN, op8023); - interpreterRegisterOpcode(OPCODE_POP_FLAGS_RETURN_VAL_EXTERN, op8024); - interpreterRegisterOpcode(OPCODE_POP_FLAGS_RETURN_VAL_EXIT, op8025); - interpreterRegisterOpcode(OPCODE_POP_FLAGS_RETURN_VAL_EXIT_EXTERN, op8026); + interpreterRegisterOpcode(OPCODE_POP_RETURN, opPopReturn); + interpreterRegisterOpcode(OPCODE_POP_EXIT, opPopExit); + interpreterRegisterOpcode(OPCODE_POP_ADDRESS, opPopAddress); + interpreterRegisterOpcode(OPCODE_POP_FLAGS, opPopFlags); + interpreterRegisterOpcode(OPCODE_POP_FLAGS_RETURN, opPopFlagsReturn); + interpreterRegisterOpcode(OPCODE_POP_FLAGS_EXIT, opPopFlagsExit); + interpreterRegisterOpcode(OPCODE_POP_FLAGS_RETURN_EXTERN, opPopFlagsReturnExtern); + interpreterRegisterOpcode(OPCODE_POP_FLAGS_EXIT_EXTERN, opPopFlagsExitExtern); + interpreterRegisterOpcode(OPCODE_POP_FLAGS_RETURN_VAL_EXTERN, opPopFlagsReturnValExtern); + interpreterRegisterOpcode(OPCODE_POP_FLAGS_RETURN_VAL_EXIT, opPopFlagsReturnValExit); + interpreterRegisterOpcode(OPCODE_POP_FLAGS_RETURN_VAL_EXIT_EXTERN, opPopFlagsReturnValExitExtern); interpreterRegisterOpcode(OPCODE_CHECK_PROCEDURE_ARGUMENT_COUNT, opCheckProcedureArgumentCount); interpreterRegisterOpcode(OPCODE_LOOKUP_PROCEDURE_BY_NAME, opLookupStringProc); interpreterRegisterOpcode(OPCODE_POP_BASE, opPopBase); diff --git a/src/interpreter_extra.cc b/src/interpreter_extra.cc index 12f3222..b2d3295 100644 --- a/src/interpreter_extra.cc +++ b/src/interpreter_extra.cc @@ -3098,8 +3098,7 @@ static void _op_inven_cmds(Program* program) break; } } else { - // FIXME: Should be inven_cmds. - scriptPredefinedError(program, "anim", SCRIPT_ERROR_OBJECT_IS_NULL); + scriptPredefinedError(program, "inven_cmds", SCRIPT_ERROR_OBJECT_IS_NULL); } programStackPushPointer(program, item); @@ -3657,7 +3656,8 @@ static void opRemoveMultipleObjectsFromInventory(Program* program) Object* owner = static_cast(programStackPopPointer(program)); if (owner == NULL || item == NULL) { - // FIXME: Ruined stack. + scriptPredefinedError(program, "rm_mult_objs_from_inven", SCRIPT_ERROR_OBJECT_IS_NULL); + programStackPushInteger(program, 0); return; } From 42c541012c3ba8a58b9441b6bc38f9b15d1dabc1 Mon Sep 17 00:00:00 2001 From: Vasilii Rogin Date: Tue, 23 May 2023 23:50:37 +0300 Subject: [PATCH 09/10] Fix crash in _map_age_dead_critters (#258) Co-authored-by: Alexander Batalov --- src/object.cc | 43 ++++++++++++++++++++++--------------------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/src/object.cc b/src/object.cc index 60b3bd3..a597a76 100644 --- a/src/object.cc +++ b/src/object.cc @@ -2133,27 +2133,18 @@ Object* objectFindFirst() { gObjectFindElevation = 0; - ObjectListNode* objectListNode; for (gObjectFindTile = 0; gObjectFindTile < HEX_GRID_SIZE; gObjectFindTile++) { - objectListNode = gObjectListHeadByTile[gObjectFindTile]; - if (objectListNode) { - break; + ObjectListNode* objectListNode = gObjectListHeadByTile[gObjectFindTile]; + while (objectListNode != NULL) { + Object* object = objectListNode->obj; + if (!artIsObjectTypeHidden(FID_TYPE(object->fid))) { + gObjectFindLastObjectListNode = objectListNode; + return object; + } + objectListNode = objectListNode->next; } } - if (gObjectFindTile == HEX_GRID_SIZE) { - gObjectFindLastObjectListNode = NULL; - return NULL; - } - - while (objectListNode != NULL) { - if (artIsObjectTypeHidden(FID_TYPE(objectListNode->obj->fid)) == 0) { - gObjectFindLastObjectListNode = objectListNode; - return objectListNode->obj; - } - objectListNode = objectListNode->next; - } - gObjectFindLastObjectListNode = NULL; return NULL; } @@ -2167,9 +2158,14 @@ Object* objectFindNext() ObjectListNode* objectListNode = gObjectFindLastObjectListNode->next; - while (gObjectFindTile < HEX_GRID_SIZE) { + while (true) { if (objectListNode == NULL) { - objectListNode = gObjectListHeadByTile[gObjectFindTile++]; + gObjectFindTile++; + if (gObjectFindTile >= HEX_GRID_SIZE) { + break; + } + + objectListNode = gObjectListHeadByTile[gObjectFindTile]; } while (objectListNode != NULL) { @@ -2219,9 +2215,14 @@ Object* objectFindNextAtElevation() ObjectListNode* objectListNode = gObjectFindLastObjectListNode->next; - while (gObjectFindTile < HEX_GRID_SIZE) { + while (true) { if (objectListNode == NULL) { - objectListNode = gObjectListHeadByTile[gObjectFindTile++]; + gObjectFindTile++; + if (gObjectFindTile >= HEX_GRID_SIZE) { + break; + } + + objectListNode = gObjectListHeadByTile[gObjectFindTile]; } while (objectListNode != NULL) { From 53a4437be951f75b10a6d4acb73137868cb59b13 Mon Sep 17 00:00:00 2001 From: Vasilii Rogin Date: Tue, 23 May 2023 23:51:04 +0300 Subject: [PATCH 10/10] Remove trailing comma after reading strParseIntWithKey (#259) --- src/string_parsers.cc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/string_parsers.cc b/src/string_parsers.cc index e0d4b62..092b87f 100644 --- a/src/string_parsers.cc +++ b/src/string_parsers.cc @@ -208,6 +208,10 @@ int strParseIntWithKey(char** stringPtr, const char* key, int* valuePtr, const c *(str + v4) = tmp2; *(str + v2) = tmp1; + if (**stringPtr == ',') { + *stringPtr = *stringPtr + 1; + } + return result; }