From 5a3bdce186780e3a707ddc0935f1112c32d4ed9a Mon Sep 17 00:00:00 2001 From: staphen Date: Thu, 22 May 2025 22:14:26 -0400 Subject: [PATCH 1/6] Separate logic for enemy position from activeForTicks --- Source/monster.cpp | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/Source/monster.cpp b/Source/monster.cpp index 3e66523d4..643a91aa8 100644 --- a/Source/monster.cpp +++ b/Source/monster.cpp @@ -4102,7 +4102,8 @@ void ProcessMonsters() monster.hitPoints = std::min(monster.hitPoints, monster.maxHitPoints); // prevent going over max HP with part of a single regen tick } - if (IsTileVisible(monster.position.tile) && monster.activeForTicks == 0) { + const bool isMonsterVisible = IsTileVisible(monster.position.tile); + if (isMonsterVisible && monster.activeForTicks == 0) { if (monster.type().type == MT_CLEAVER) { PlaySFX(SfxID::ButcherGreeting); } @@ -4130,14 +4131,20 @@ void ProcessMonsters() assert(monster.enemy >= 0 && monster.enemy < MAX_PLRS); Player &player = Players[monster.enemy]; monster.enemyPosition = player.position.future; - if (IsTileVisible(monster.position.tile)) { - monster.activeForTicks = UINT8_MAX; + if (isMonsterVisible) { monster.position.last = player.position.future; - } else if (monster.activeForTicks != 0 && monster.type().type != MT_DIABLO) { - monster.activeForTicks--; } } } + + if ((monster.flags & MFLAG_TARGETS_MONSTER) == 0) { + if (isMonsterVisible) { + monster.activeForTicks = UINT8_MAX; + } else if (monster.activeForTicks != 0 && monster.type().type != MT_DIABLO) { + monster.activeForTicks--; + } + } + while (true) { if ((monster.flags & MFLAG_SEARCH) == 0 || !AiPlanPath(monster)) { AiProc[static_cast(monster.ai)](monster); From e57a2d7d54108973bc55249f4c4ccc44e475d03e Mon Sep 17 00:00:00 2001 From: Oleksandr Kalko Date: Sat, 24 May 2025 12:59:23 +0300 Subject: [PATCH 2/6] Android stuff * Upgrade Gradle addon to latest version * Replace deprecated syntax for "Clean" task --- android-project/build.gradle | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/android-project/build.gradle b/android-project/build.gradle index e65644f52..85846d89d 100644 --- a/android-project/build.gradle +++ b/android-project/build.gradle @@ -6,7 +6,7 @@ buildscript { google() } dependencies { - classpath 'com.android.tools.build:gradle:8.9.2' + classpath 'com.android.tools.build:gradle:8.10.0' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files @@ -20,6 +20,6 @@ allprojects { } } -task clean(type: Delete) { - delete rootProject.buildDir +tasks.register('clean', Delete) { + delete rootProject.layout.buildDirectory } From 88b39dc97d7aebb046013a6a5c3bcdf8f8235dc4 Mon Sep 17 00:00:00 2001 From: staphen Date: Sat, 24 May 2025 12:22:21 -0400 Subject: [PATCH 3/6] Make use of SDL_UserEvent::code instead of SDL_Event::type --- Source/diablo.cpp | 2 +- Source/engine/demomode.cpp | 6 +++--- Source/interfac.cpp | 26 +++++++++++++------------- Source/interfac.h | 6 ++++-- Source/player.cpp | 6 +++--- 5 files changed, 24 insertions(+), 22 deletions(-) diff --git a/Source/diablo.cpp b/Source/diablo.cpp index 2fab97a4e..624b138f0 100644 --- a/Source/diablo.cpp +++ b/Source/diablo.cpp @@ -811,7 +811,7 @@ void GameEventHandler(const SDL_Event &event, uint16_t modState) nthread_ignore_mutex(true); PaletteFadeOut(8); sound_stop(); - ShowProgress(GetCustomEvent(event.type)); + ShowProgress(GetCustomEvent(event)); RedrawEverything(); if (!HeadlessMode) { diff --git a/Source/engine/demomode.cpp b/Source/engine/demomode.cpp index 21cb4de8b..1468e9ec3 100644 --- a/Source/engine/demomode.cpp +++ b/Source/engine/demomode.cpp @@ -280,7 +280,7 @@ bool CreateSdlEvent(const DemoMsg &dmsg, SDL_Event &event, uint16_t &modState) return true; default: if (type >= DemoMsg::MinCustomEvent) { - event.type = CustomEventToSdlEvent(static_cast(type - DemoMsg::MinCustomEvent)); + CustomEventToSdlEvent(event, static_cast(type - DemoMsg::MinCustomEvent)); return true; } event.type = static_cast(0); @@ -377,7 +377,7 @@ bool CreateSdlEvent(const DemoMsg &dmsg, SDL_Event &event, uint16_t &modState) return true; default: if (type >= DemoMsg::MinCustomEvent) { - event.type = CustomEventToSdlEvent(static_cast(type - DemoMsg::MinCustomEvent)); + CustomEventToSdlEvent(event, static_cast(type - DemoMsg::MinCustomEvent)); return true; } event.type = static_cast(0); @@ -802,7 +802,7 @@ void RecordMessage(const SDL_Event &event, uint16_t modState) default: if (IsCustomEvent(event.type)) { WriteDemoMsgHeader(static_cast( - DemoMsg::MinCustomEvent + static_cast(GetCustomEvent(event.type)))); + DemoMsg::MinCustomEvent + static_cast(GetCustomEvent(event)))); } break; } diff --git a/Source/interfac.cpp b/Source/interfac.cpp index 4866ef831..caa38772d 100644 --- a/Source/interfac.cpp +++ b/Source/interfac.cpp @@ -73,8 +73,7 @@ const int BarPos[3][2] = { { 53, 37 }, { 53, 421 }, { 53, 37 } }; OptionalOwnedClxSpriteList ArtCutsceneWidescreen; -SdlEventType CustomEventsBegin = SDL_USEREVENT; -constexpr uint16_t NumCustomEvents = WM_LAST - WM_FIRST + 1; +SdlEventType CustomEventType = SDL_USEREVENT; Cutscenes GetCutSceneFromLevelType(dungeon_type type) { @@ -413,7 +412,7 @@ void DoLoad(interface_mode uMsg) if (!loadResult.has_value()) { #if SDL_PUSH_EVENT_BG_THREAD_WORKS SDL_Event event; - event.type = CustomEventToSdlEvent(WM_ERROR); + CustomEventToSdlEvent(event, WM_ERROR); event.user.data1 = new std::string(std::move(loadResult).error()); if (SDL_PushEvent(&event) < 0) { LogError("Failed to send WM_ERROR {}", SDL_GetError()); @@ -428,7 +427,7 @@ void DoLoad(interface_mode uMsg) #if SDL_PUSH_EVENT_BG_THREAD_WORKS SDL_Event event; - event.type = CustomEventToSdlEvent(WM_DONE); + CustomEventToSdlEvent(event, WM_DONE); if (SDL_PushEvent(&event) < 0) { LogError("Failed to send WM_DONE {}", SDL_GetError()); SDL_ClearError(); @@ -485,7 +484,7 @@ void ProgressEventHandler(const SDL_Event &event, uint16_t modState) DisableInputEventHandler(event, modState); if (!IsCustomEvent(event.type)) return; - const interface_mode customEvent = GetCustomEvent(event.type); + const interface_mode customEvent = GetCustomEvent(event); switch (customEvent) { case WM_PROGRESS: if (!HeadlessMode && ProgressEventHandlerState.drawnProgress != sgdwProgress && !ProgressEventHandlerState.skipRendering) { @@ -557,23 +556,24 @@ void ProgressEventHandler(const SDL_Event &event, uint16_t modState) void RegisterCustomEvents() { #ifndef USE_SDL1 - CustomEventsBegin = SDL_RegisterEvents(NumCustomEvents); + CustomEventType = SDL_RegisterEvents(1); #endif } bool IsCustomEvent(SdlEventType eventType) { - return eventType >= CustomEventsBegin && eventType < CustomEventsBegin + NumCustomEvents; + return eventType == CustomEventType; } -interface_mode GetCustomEvent(SdlEventType eventType) +interface_mode GetCustomEvent(const SDL_Event &event) { - return static_cast(eventType - CustomEventsBegin); + return static_cast(event.user.code); } -SdlEventType CustomEventToSdlEvent(interface_mode eventType) +void CustomEventToSdlEvent(SDL_Event &event, interface_mode eventType) { - return CustomEventsBegin + eventType; + event.type = CustomEventType; + event.user.code = static_cast(eventType); } void interface_msg_pump() @@ -598,7 +598,7 @@ void IncProgress(uint32_t steps) if (!HeadlessMode && sgdwProgress != prevProgress) { #if SDL_PUSH_EVENT_BG_THREAD_WORKS SDL_Event event; - event.type = CustomEventToSdlEvent(WM_PROGRESS); + CustomEventToSdlEvent(event, WM_PROGRESS); if (SDL_PushEvent(&event) < 0) { LogError("Failed to send WM_PROGRESS {}", SDL_GetError()); SDL_ClearError(); @@ -691,7 +691,7 @@ void ShowProgress(interface_mode uMsg) } #if !SDL_PUSH_EVENT_BG_THREAD_WORKS if (const int customEventType = NextCustomEvent.type.exchange(-1); customEventType != -1) { - event.type = CustomEventToSdlEvent(static_cast(customEventType)); + CustomEventToSdlEvent(event, static_cast(customEventType)); if (static_cast(customEventType) == static_cast(WM_ERROR)) { event.user.data1 = &NextCustomEvent.error; } diff --git a/Source/interfac.h b/Source/interfac.h index a5316f519..5face5d13 100644 --- a/Source/interfac.h +++ b/Source/interfac.h @@ -7,6 +7,8 @@ #include +#include + #include "utils/ui_fwd.h" namespace devilution { @@ -45,9 +47,9 @@ using SdlEventType = uint8_t; bool IsCustomEvent(SdlEventType eventType); -interface_mode GetCustomEvent(SdlEventType eventType); +interface_mode GetCustomEvent(const SDL_Event &event); -SdlEventType CustomEventToSdlEvent(interface_mode eventType); +void CustomEventToSdlEvent(SDL_Event &event, interface_mode eventType); enum Cutscenes : uint8_t { CutStart, diff --git a/Source/player.cpp b/Source/player.cpp index e5cd1d61c..0d0980c25 100644 --- a/Source/player.cpp +++ b/Source/player.cpp @@ -2861,7 +2861,7 @@ StartNewLvl(Player &player, interface_mode fom, int lvl) player._pmode = PM_NEWLVL; player._pInvincible = true; SDL_Event event; - event.type = CustomEventToSdlEvent(fom); + CustomEventToSdlEvent(event, fom); SDL_PushEvent(&event); if (gbIsMultiplayer) { NetSendCmdParam2(true, CMD_NEWLVL, fom, lvl); @@ -2887,7 +2887,7 @@ void RestartTownLvl(Player &player) if (&player == MyPlayer) { player._pInvincible = true; SDL_Event event; - event.type = CustomEventToSdlEvent(WM_DIABRETOWN); + CustomEventToSdlEvent(event, WM_DIABRETOWN); SDL_PushEvent(&event); } } @@ -2912,7 +2912,7 @@ void StartWarpLvl(Player &player, size_t pidx) player._pmode = PM_NEWLVL; player._pInvincible = true; SDL_Event event; - event.type = CustomEventToSdlEvent(WM_DIABWARPLVL); + CustomEventToSdlEvent(event, WM_DIABWARPLVL); SDL_PushEvent(&event); } } From eed05cea5ffa5b4302214b08df173b648a04b0b5 Mon Sep 17 00:00:00 2001 From: staphen Date: Sat, 24 May 2025 12:22:48 -0400 Subject: [PATCH 4/6] Restore SDL_PushEvent() on 3DS --- Source/interfac.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/interfac.cpp b/Source/interfac.cpp index caa38772d..2bfa6dbd0 100644 --- a/Source/interfac.cpp +++ b/Source/interfac.cpp @@ -40,7 +40,7 @@ namespace devilution { namespace { -#if (defined(__APPLE__) || defined(__3DS__)) && defined(USE_SDL1) +#if defined(__APPLE__) && defined(USE_SDL1) // On Tiger PPC, SDL_PushEvent from a background thread appears to do nothing. #define SDL_PUSH_EVENT_BG_THREAD_WORKS 0 #else From e071612518b5568341dd06607917e1ec2627d95c Mon Sep 17 00:00:00 2001 From: staphen Date: Sun, 25 May 2025 00:13:55 -0400 Subject: [PATCH 5/6] Fix path separator in DAPI Server.cpp includes --- Source/dapi/Server.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/dapi/Server.cpp b/Source/dapi/Server.cpp index 0dac51512..eba31d230 100644 --- a/Source/dapi/Server.cpp +++ b/Source/dapi/Server.cpp @@ -1,7 +1,7 @@ #include #include "Server.h" -#include "qol\stash.h" +#include "qol/stash.h" From 44cdd7a0272e622b8be7f4e4e14767e37e974ded Mon Sep 17 00:00:00 2001 From: staphen Date: Sun, 25 May 2025 00:15:53 -0400 Subject: [PATCH 6/6] Add CMake option to opt-in to DAPI --- CMake/Definitions.cmake | 1 + CMake/Dependencies.cmake | 8 ++-- CMake/VcPkgManifestFeatures.cmake | 3 ++ CMakeLists.txt | 2 + CMakeSettings.json | 24 +++++++++- Packaging/windows/CMakePresets.json | 4 ++ Source/CMakeLists.txt | 69 ++++++++++++++++------------- Source/diablo.cpp | 11 ++++- vcpkg.json | 8 ++-- 9 files changed, 91 insertions(+), 39 deletions(-) diff --git a/CMake/Definitions.cmake b/CMake/Definitions.cmake index ab85b1f8f..1db7245be 100644 --- a/CMake/Definitions.cmake +++ b/CMake/Definitions.cmake @@ -19,6 +19,7 @@ foreach( DEVILUTIONX_RESAMPLER_SDL DEVILUTIONX_PALETTE_TRANSPARENCY_BLACK_16_LUT SCREEN_READER_INTEGRATION + DAPI_SERVER UNPACKED_MPQS UNPACKED_SAVES DEVILUTIONX_WINDOWS_NO_WCHAR diff --git a/CMake/Dependencies.cmake b/CMake/Dependencies.cmake index 030781d8c..0968a42f2 100644 --- a/CMake/Dependencies.cmake +++ b/CMake/Dependencies.cmake @@ -276,7 +276,9 @@ if(GPERF) message("INFO: ${GPERFTOOLS_LIBRARIES}") endif() -find_package(SFML 3.0 COMPONENTS Network CONFIG QUIET) -if(NOT SFML_FOUND) - add_subdirectory(3rdParty/sfml) +if(DAPI_SERVER) + find_package(SFML 3.0 COMPONENTS Network CONFIG QUIET) + if(NOT SFML_FOUND) + add_subdirectory(3rdParty/sfml) + endif() endif() diff --git a/CMake/VcPkgManifestFeatures.cmake b/CMake/VcPkgManifestFeatures.cmake index de7359fa8..0ce00f29b 100644 --- a/CMake/VcPkgManifestFeatures.cmake +++ b/CMake/VcPkgManifestFeatures.cmake @@ -10,6 +10,9 @@ endif() if(USE_GETTEXT_FROM_VCPKG) list(APPEND VCPKG_MANIFEST_FEATURES "translations") endif() +if(DAPI_SERVER) + list(APPEND VCPKG_MANIFEST_FEATURES "dapi") +endif() if(BUILD_TESTING) list(APPEND VCPKG_MANIFEST_FEATURES "tests") endif() diff --git a/CMakeLists.txt b/CMakeLists.txt index d750b0a3e..d98f610cd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -37,6 +37,8 @@ cmake_dependent_option(PACKET_ENCRYPTION "Encrypt network packets" ON "NOT NONET if(CMAKE_TOOLCHAIN_FILE MATCHES "vcpkg.cmake$") option(USE_GETTEXT_FROM_VCPKG "Add vcpkg dependency for gettext[tools] for compiling translations" OFF) endif() +option(DAPI_SERVER "Build with DAPI server component for gameplay automation" OFF) +mark_as_advanced(DAPI_SERVER) option(BUILD_TESTING "Build tests." ON) # These must be included after the options above but before the `project` call. diff --git a/CMakeSettings.json b/CMakeSettings.json index 2041366d8..9a13ba0e0 100644 --- a/CMakeSettings.json +++ b/CMakeSettings.json @@ -12,7 +12,7 @@ "variables": [ { "name": "DISCORD_INTEGRATION", - "value": "False", + "value": "True", "type": "BOOL" } ] @@ -39,6 +39,28 @@ } ] }, + { + "name": "x64-Debug-DAPI", + "generator": "Ninja", + "configurationType": "Debug", + "buildRoot": "${workspaceRoot}\\build\\${name}", + "installRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\install\\${name}", + "inheritEnvironments": [ "msvc_x64" ], + "intelliSenseMode": "windows-msvc-x64", + "enableClangTidyCodeAnalysis": true, + "variables": [ + { + "name": "DISCORD_INTEGRATION", + "value": "False", + "type": "BOOL" + }, + { + "name": "DAPI_SERVER", + "value": "True", + "type": "BOOL" + } + ] + }, { "name": "x64-Debug-SDL1", "generator": "Ninja", diff --git a/Packaging/windows/CMakePresets.json b/Packaging/windows/CMakePresets.json index c59a2c30c..55b092cfa 100644 --- a/Packaging/windows/CMakePresets.json +++ b/Packaging/windows/CMakePresets.json @@ -36,6 +36,10 @@ "DISABLE_LTO": { "type": "BOOL", "value": "ON" + }, + "DAPI_SERVER": { + "type": "BOOL", + "value": "ON" } } }, diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt index 09b565d48..83f4d2e67 100644 --- a/Source/CMakeLists.txt +++ b/Source/CMakeLists.txt @@ -649,14 +649,16 @@ if(DISCORD_INTEGRATION) ) endif() -list(APPEND libdevilutionx_SRCS - dapi/Server.cpp - dapi/Backend/DAPIBackendCore/DAPIProtoClient.cpp - dapi/Backend/Messages/command.proto - dapi/Backend/Messages/data.proto - dapi/Backend/Messages/game.proto - dapi/Backend/Messages/init.proto - dapi/Backend/Messages/message.proto) +if(DAPI_SERVER) + list(APPEND libdevilutionx_SRCS + dapi/Server.cpp + dapi/Backend/DAPIBackendCore/DAPIProtoClient.cpp + dapi/Backend/Messages/command.proto + dapi/Backend/Messages/data.proto + dapi/Backend/Messages/game.proto + dapi/Backend/Messages/init.proto + dapi/Backend/Messages/message.proto) +endif() if(SCREEN_READER_INTEGRATION) list(APPEND libdevilutionx_SRCS @@ -684,7 +686,6 @@ target_link_dependencies(libdevilutionx PUBLIC libsmackerdec ${LUA_LIBRARIES} sol2::sol2 - SFML::Network tl unordered_dense::unordered_dense libdevilutionx_assets @@ -752,6 +753,10 @@ if(DISCORD_INTEGRATION) target_link_libraries(libdevilutionx PRIVATE discord discord_game_sdk) endif() +if(DAPI_SERVER) + target_link_libraries(libdevilutionx PRIVATE SFML::Network) +endif() + if(SCREEN_READER_INTEGRATION) if(WIN32) target_compile_definitions(libdevilutionx PRIVATE Tolk) @@ -807,25 +812,27 @@ elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang") endif() endif() -find_package(Protobuf REQUIRED) - -target_link_libraries(libdevilutionx PUBLIC protobuf::libprotobuf-lite) -find_package(absl REQUIRED) -set(PROTO_BINARY_DIR "${CMAKE_BINARY_DIR}/generated") - -file(MAKE_DIRECTORY ${PROTO_BINARY_DIR}) - -target_include_directories(libdevilutionx PRIVATE ${Protobuf_INCLUDE_DIRS}) - -# Generate the protobuf files into the 'generated' directory within the build tree -protobuf_generate( - TARGET libdevilutionx - IMPORT_DIRS "${CMAKE_CURRENT_SOURCE_DIR}/dapi/Backend/Messages" - PROTOC_OUT_DIR "${PROTO_BINARY_DIR}" -) - -# Make sure the generated protobuf files are correctly included in the build -target_include_directories(libdevilutionx PUBLIC "$") -include_directories("${PROTO_BINARY_DIR}/dapi/Backend/Messages") - -target_include_directories(libdevilutionx PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/dapi/Backend/DAPIBackendCore") +if(DAPI_SERVER) + find_package(Protobuf REQUIRED) + + target_link_libraries(libdevilutionx PUBLIC protobuf::libprotobuf-lite) + find_package(absl REQUIRED) + set(PROTO_BINARY_DIR "${CMAKE_BINARY_DIR}/generated") + + file(MAKE_DIRECTORY ${PROTO_BINARY_DIR}) + + target_include_directories(libdevilutionx PRIVATE ${Protobuf_INCLUDE_DIRS}) + + # Generate the protobuf files into the 'generated' directory within the build tree + protobuf_generate( + TARGET libdevilutionx + IMPORT_DIRS "${CMAKE_CURRENT_SOURCE_DIR}/dapi/Backend/Messages" + PROTOC_OUT_DIR "${PROTO_BINARY_DIR}" + ) + + # Make sure the generated protobuf files are correctly included in the build + target_include_directories(libdevilutionx PUBLIC "$") + include_directories("${PROTO_BINARY_DIR}/dapi/Backend/Messages") + + target_include_directories(libdevilutionx PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/dapi/Backend/DAPIBackendCore") +endif() diff --git a/Source/diablo.cpp b/Source/diablo.cpp index 1aea08810..3cbb64f89 100644 --- a/Source/diablo.cpp +++ b/Source/diablo.cpp @@ -26,7 +26,6 @@ #include "controls/keymapper.hpp" #include "controls/plrctrls.h" #include "controls/remap_keyboard.h" -#include "dapi/Server.h" #include "diablo.h" #include "diablo_msg.hpp" #include "discord/discord.h" @@ -108,6 +107,10 @@ #include "controls/touch/renderers.h" #endif +#ifdef DAPI_SERVER +#include "dapi/Server.h" +#endif + #ifdef __vita__ #include "platform/vita/touch.h" #endif @@ -133,7 +136,9 @@ clicktype sgbMouseDown; uint16_t gnTickDelay = 50; char gszProductName[64] = "DevilutionX vUnknown"; +#ifdef DAPI_SERVER DAPI::Server dapiServer; +#endif #ifdef _DEBUG bool DebugDisableNetworkTimeout = false; @@ -1459,11 +1464,15 @@ void UpdateMonsterLights() void GameLogic() { if (!ProcessInput()) { +#ifdef DAPI_SERVER if (gmenu_is_active()) dapiServer.update(); // For game menu commands +#endif return; } +#ifdef DAPI_SERVER dapiServer.update(); +#endif if (gbProcessPlayers) { gGameLogicStep = GameLogicStep::ProcessPlayers; ProcessPlayers(); diff --git a/vcpkg.json b/vcpkg.json index 759e23cce..5136ac7a7 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -4,9 +4,7 @@ "dependencies": [ "fmt", "bzip2", - "lua", - "protobuf", - "sfml" + "lua" ], "builtin-baseline": "533a5fda5c0646d1771345fb572e759283444d5f", "features": { @@ -31,6 +29,10 @@ } ] }, + "dapi": { + "description": "Build DAPI server for gameplay automation", + "dependencies": [ "protobuf", "sfml" ] + }, "tests": { "description": "Build tests", "dependencies": [ "gtest", "benchmark" ]