From 76287dda46094019a4789a54f9b8c585d3a9df4b Mon Sep 17 00:00:00 2001 From: Vladislav Khorev Date: Tue, 14 Jul 2026 19:41:36 +0300 Subject: [PATCH] Added Steam support for Windows --- proj-windows/CMakeLists.txt | 227 +++++++++++------ src/main.cpp | 491 +++++++++++++++++++----------------- 2 files changed, 412 insertions(+), 306 deletions(-) diff --git a/proj-windows/CMakeLists.txt b/proj-windows/CMakeLists.txt index 9c1c1ec..019deba 100644 --- a/proj-windows/CMakeLists.txt +++ b/proj-windows/CMakeLists.txt @@ -1,16 +1,19 @@ cmake_minimum_required(VERSION 3.16) -project(witcher001 LANGUAGES C CXX) +project(ShadowOverBishkekDemo LANGUAGES C CXX) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) +# Опция для интеграции Steamworks SDK (по умолчанию выключена) +option(STEAMSDK "Enable Steamworks SDK integration" OFF) + include(${CMAKE_CURRENT_SOURCE_DIR}/../cmake/ThirdParty.cmake) # =========================================== -# Основной проект witcher001 +# Основной проект ShadowOverBishkekDemo # =========================================== -add_executable(witcher001 +add_executable(ShadowOverBishkekDemo ../src/main.cpp ../src/Game.cpp ../src/Game.h @@ -42,34 +45,34 @@ add_executable(witcher001 ../src/SparkEmitter.h ../src/TeleportZone.h ../src/TeleportZone.cpp - ../src/utils/TaskManager.cpp - ../src/utils/TaskManager.h - ../src/render/FrameBuffer.cpp - ../src/render/FrameBuffer.h - ../src/render/ShadowMap.cpp - ../src/render/ShadowMap.h + ../src/utils/TaskManager.cpp + ../src/utils/TaskManager.h + ../src/render/FrameBuffer.cpp + ../src/render/FrameBuffer.h + ../src/render/ShadowMap.cpp + ../src/render/ShadowMap.h ../src/UiManager.cpp ../src/UiManager.h - ../src/render/TextRenderer.h - ../src/render/TextRenderer.cpp - ../src/MenuManager.h - ../src/MenuManager.cpp - ../src/Location.h - ../src/Location.cpp - ../src/LocationState.h - ../src/LocationState.cpp - ../src/LocationEditor.h - ../src/LocationEditor.cpp - ../src/NpcCar.h - ../src/NpcCar.cpp + ../src/render/TextRenderer.h + ../src/render/TextRenderer.cpp + ../src/MenuManager.h + ../src/MenuManager.cpp + ../src/Location.h + ../src/Location.cpp + ../src/LocationState.h + ../src/LocationState.cpp + ../src/LocationEditor.h + ../src/LocationEditor.cpp + ../src/NpcCar.h + ../src/NpcCar.cpp ../src/GameConstants.h ../src/GameConstants.cpp ../src/GameState.h ../src/GameState.cpp ../src/ScriptEngine.h ../src/ScriptEngine.cpp - ../src/navigation/PathFinder.h - ../src/navigation/PathFinder.cpp + ../src/navigation/PathFinder.h + ../src/navigation/PathFinder.cpp ../src/items/GameObjectLoader.h ../src/items/GameObjectLoader.cpp ../src/items/Item.h @@ -79,73 +82,121 @@ add_executable(witcher001 ../src/items/InteractiveObject.h ../src/items/InteractiveObject.cpp ../src/items/InteractiveObjectState.h - ../src/dialogue/DialogueTypes.h - ../src/dialogue/DialogueDatabase.h - ../src/dialogue/DialogueDatabase.cpp - ../src/dialogue/DialogueRuntime.h - ../src/dialogue/DialogueRuntime.cpp - ../src/dialogue/DialogueOverlay.h - ../src/dialogue/DialogueOverlay.cpp - ../src/dialogue/DialogueSystem.h - ../src/dialogue/DialogueSystem.cpp - ../src/dialogue/TranslationDatabase.h - ../src/dialogue/TranslationDatabase.cpp - ../src/quest/QuestTypes.h - ../src/quest/QuestJournal.h - ../src/quest/QuestJournal.cpp - ../src/cutscene/CutsceneTypes.h - ../src/cutscene/CutsceneDatabase.h - ../src/cutscene/CutsceneDatabase.cpp - ../src/cutscene/CutsceneRuntime.h - ../src/cutscene/CutsceneRuntime.cpp - ../src/cutscene/CutsceneOverlay.h - ../src/cutscene/CutsceneOverlay.cpp - ../src/render/UiQuad.h + ../src/dialogue/DialogueTypes.h + ../src/dialogue/DialogueDatabase.h + ../src/dialogue/DialogueDatabase.cpp + ../src/dialogue/DialogueRuntime.h + ../src/dialogue/DialogueRuntime.cpp + ../src/dialogue/DialogueOverlay.h + ../src/dialogue/DialogueOverlay.cpp + ../src/dialogue/DialogueSystem.h + ../src/dialogue/DialogueSystem.cpp + ../src/dialogue/TranslationDatabase.h + ../src/dialogue/TranslationDatabase.cpp + ../src/quest/QuestTypes.h + ../src/quest/QuestJournal.h + ../src/quest/QuestJournal.cpp + ../src/cutscene/CutsceneTypes.h + ../src/cutscene/CutsceneDatabase.h + ../src/cutscene/CutsceneDatabase.cpp + ../src/cutscene/CutsceneRuntime.h + ../src/cutscene/CutsceneRuntime.cpp + ../src/cutscene/CutsceneOverlay.h + ../src/cutscene/CutsceneOverlay.cpp + ../src/render/UiQuad.h ) # Установка проекта по умолчанию для Visual Studio -set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT witcher001) +set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT ShadowOverBishkekDemo) # include-пути проекта -target_include_directories(witcher001 PRIVATE +target_include_directories(ShadowOverBishkekDemo PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/../src" ) -set_target_properties(witcher001 PROPERTIES - OUTPUT_NAME "witcher001" +set_target_properties(ShadowOverBishkekDemo PROPERTIES + OUTPUT_NAME "ShadowOverBishkekDemo" ) # Определения препроцессора: # PNG_ENABLED – включает код PNG в TextureManager # SDL_MAIN_HANDLED – отключает переопределение main -> SDL_main -target_compile_definitions(witcher001 PRIVATE +target_compile_definitions(ShadowOverBishkekDemo PRIVATE WIN32_LEAN_AND_MEAN PNG_ENABLED SDL_MAIN_HANDLED -# DEBUG_LIGHT -# SHOW_PATH +# DEBUG_LIGHT +# SHOW_PATH ) # Линкуем с SDL2main, если он вообще установлен -target_link_libraries(witcher001 PRIVATE SDL2main_external_lib) +target_link_libraries(ShadowOverBishkekDemo PRIVATE SDL2main_external_lib) # Линкуем сторонние библиотеки -target_link_libraries(witcher001 PRIVATE +target_link_libraries(ShadowOverBishkekDemo PRIVATE SDL2_external_lib libpng_external_lib zlib_external_lib libzip_external_lib - freetype_external_lib - SDL2_ttf_external_lib + freetype_external_lib + SDL2_ttf_external_lib eigen_external_lib - boost_external_lib + boost_external_lib sol2_external_lib SDL2_mixer_external_lib ) # Линкуем OpenGL (Windows) if(WIN32) - target_link_libraries(witcher001 PRIVATE opengl32) + target_link_libraries(ShadowOverBishkekDemo PRIVATE opengl32) +endif() + +# =========================================== +# Интеграция Steamworks SDK (если STEAMSDK=ON) +# =========================================== +if(STEAMSDK) + message(STATUS "Steamworks SDK integration is ENABLED.") + + # Макрос STEAMSDK для условной компиляции в C++ коде (#ifdef STEAMSDK) + target_compile_definitions(ShadowOverBishkekDemo PRIVATE STEAMSDK) + + set(STEAM_SDK_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../thirdparty/steamworks_sdk_164/sdk") + + # Подключение заголовочных файлов Steamworks + target_include_directories(ShadowOverBishkekDemo PRIVATE + "${STEAM_SDK_DIR}/public" + "${STEAM_SDK_DIR}/public/steam" + ) + + # Определение разрядности сборки (x86 vs x64) для выбора версий lib и dll + if(CMAKE_SIZEOF_VOID_P EQUAL 8) + set(STEAM_LIB_DIR "${STEAM_SDK_DIR}/redistributable_bin/win64") + set(STEAM_LIB_NAME "steam_api64") + set(STEAM_DLL_NAME "steam_api64.dll") + else() + set(STEAM_LIB_DIR "${STEAM_SDK_DIR}/redistributable_bin") + set(STEAM_LIB_NAME "steam_api") + set(STEAM_DLL_NAME "steam_api.dll") + endif() + + # Поиск библиотеки линковки (.lib) + find_library(STEAM_LIBRARY + NAMES ${STEAM_LIB_NAME} + PATHS ${STEAM_LIB_DIR} + NO_DEFAULT_PATH + ) + + if(STEAM_LIBRARY) + target_link_libraries(ShadowOverBishkekDemo PRIVATE ${STEAM_LIBRARY}) + message(STATUS "Found Steam API library: ${STEAM_LIBRARY}") + else() + message(FATAL_ERROR "Steam API library (${STEAM_LIB_NAME}) NOT found in ${STEAM_LIB_DIR}!") + endif() + + # Генерация временного файла steam_appid.txt с ID демо-версии (4945840) + file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/steam_appid.txt" "4945840") +else() + message(STATUS "Steamworks SDK integration is DISABLED.") endif() # =========================================== @@ -155,19 +206,19 @@ if (WIN32) # SDL2: в Debug - SDL2d.dll, в Release - SDL2.dll set(SDL2_DLL_SRC "$,${SDL2_INSTALL_DIR}/bin/SDL2d.dll,${SDL2_INSTALL_DIR}/bin/SDL2.dll>") - set(SDL2_DLL_DST "$,$/SDL2d.dll,$/SDL2.dll>") + set(SDL2_DLL_DST "$,$/SDL2d.dll,$/SDL2.dll>") set(LIBZIP_DLL_SRC "$,${LIBZIP_BASE_DIR}-Debug/bin/zip.dll,${LIBZIP_BASE_DIR}-Release/bin/zip.dll>") - - set(ZLIB_DLL_SRC "$,${ZLIB_INSTALL_DIR}/bin/zd.dll,${ZLIB_INSTALL_DIR}/bin/z.dll>") - set(ZLIB_DLL_DST "$,$/zd.dll,$/z.dll>") - - set(SDL2TTF_DLL_SRC "$,${SDL2TTF_BASE_DIR}-Debug/bin/SDL2_ttfd.dll,${SDL2TTF_BASE_DIR}-Release/bin/SDL2_ttf.dll>") + + set(ZLIB_DLL_SRC "$,${ZLIB_INSTALL_DIR}/bin/zd.dll,${ZLIB_INSTALL_DIR}/bin/z.dll>") + set(ZLIB_DLL_DST "$,$/zd.dll,$/z.dll>") + + set(SDL2TTF_DLL_SRC "$,${SDL2TTF_BASE_DIR}-Debug/bin/SDL2_ttfd.dll,${SDL2TTF_BASE_DIR}-Release/bin/SDL2_ttf.dll>") set(SDL2MIXER_DLL_SRC "$,${SDL2MIXER_BASE_DIR}-Debug/bin/SDL2_mixerd.dll,${SDL2MIXER_BASE_DIR}-Release/bin/SDL2_mixer.dll>") -add_custom_command(TARGET witcher001 POST_BUILD + add_custom_command(TARGET ShadowOverBishkekDemo POST_BUILD COMMAND ${CMAKE_COMMAND} -E echo "Copying DLLs to output folder..." # Копируем SDL2 (целевое имя всегда SDL2.dll) @@ -178,23 +229,39 @@ add_custom_command(TARGET witcher001 POST_BUILD # Копируем LIBZIP (целевое имя всегда zip.dll) COMMAND ${CMAKE_COMMAND} -E copy_if_different "${LIBZIP_DLL_SRC}" - "$/zip.dll" - - COMMAND ${CMAKE_COMMAND} -E copy_if_different + "$/zip.dll" + + COMMAND ${CMAKE_COMMAND} -E copy_if_different "${ZLIB_DLL_SRC}" "${ZLIB_DLL_DST}" - - COMMAND ${CMAKE_COMMAND} -E copy_if_different - "${SDL2TTF_DLL_SRC}" - "$" + + COMMAND ${CMAKE_COMMAND} -E copy_if_different + "${SDL2TTF_DLL_SRC}" + "$" COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_CURRENT_SOURCE_DIR}/../thirdparty/SDL_mixer-release-2.8.0/install-$/bin/SDL2_mixer$<$:d>.dll" - - # This is only for profiling: - #"${CMAKE_CURRENT_SOURCE_DIR}/../thirdparty/SDL_mixer-release-2.8.0/install-Release/bin/SDL2_mixer$<$:d>.dll" - "$/SDL2_mixer$<$:d>.dll" + "$/SDL2_mixer$<$:d>.dll" ) + + # Если включен Steam, добавляем шаги копирования DLL и appid в этот же POST_BUILD + if(STEAMSDK) + add_custom_command(TARGET ShadowOverBishkekDemo POST_BUILD + COMMAND ${CMAKE_COMMAND} -E echo "Copying Steam SDK components..." + # Копирование dll к исполняемому файлу + COMMAND ${CMAKE_COMMAND} -E copy_if_different + "${STEAM_LIB_DIR}/${STEAM_DLL_NAME}" + "$/${STEAM_DLL_NAME}" + # Копирование steam_appid.txt к исполняемому файлу + COMMAND ${CMAKE_COMMAND} -E copy_if_different + "${CMAKE_CURRENT_BINARY_DIR}/steam_appid.txt" + "$/steam_appid.txt" + # Дублируем steam_appid.txt в корень сборки (полезно при отладке прямо из MSVC) + COMMAND ${CMAKE_COMMAND} -E copy_if_different + "${CMAKE_CURRENT_BINARY_DIR}/steam_appid.txt" + "${CMAKE_BINARY_DIR}/steam_appid.txt" + ) + endif() endif() # =========================================== @@ -204,20 +271,20 @@ endif() # Какие папки с ресурсами нужно копировать set(RUNTIME_RESOURCE_DIRS "resources" - "audio" + "audio" ) # Копируем ресурсы и шейдеры в папку exe и в корень build/ foreach(resdir IN LISTS RUNTIME_RESOURCE_DIRS) - add_custom_command(TARGET witcher001 POST_BUILD + add_custom_command(TARGET ShadowOverBishkekDemo POST_BUILD COMMAND ${CMAKE_COMMAND} -E echo "Copying ${resdir} to runtime folders..." # 1) туда, где лежит exe (build/Debug, build/Release и т.п.) COMMAND ${CMAKE_COMMAND} -E copy_directory "${CMAKE_SOURCE_DIR}/../${resdir}" - "$/${resdir}" + "$/${resdir}" # 2) в корень build, если захочешь запускать из этой папки COMMAND ${CMAKE_COMMAND} -E copy_directory "${CMAKE_SOURCE_DIR}/../${resdir}" "${CMAKE_BINARY_DIR}/${resdir}" ) -endforeach() +endforeach() \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index eeb2600..2bcc9ae 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -11,133 +11,127 @@ #include #endif - -// For Emscripten the game is heap-allocated so it can be destroyed and -// re-created when the WebGL context is lost and restored (e.g. fullscreen). -// For Android and Desktop a plain global value is used (no context loss). -#ifdef EMSCRIPTEN -ZL::Game* g_game = nullptr; -//static SDL_Window* win_x = nullptr; -static SDL_GLContext glContext_x; // Не указатель, а сам объект (который суть void*) - -#else -ZL::Game game; +#ifdef STEAMSDK +#include "steam_api.h" #endif -#ifdef EMSCRIPTEN +#if defined(EMSCRIPTEN) +static SDL_GLContext glContext_x; +#endif + +// Единый указатель на объект игры для всех платформ +ZL::Game* g_game = nullptr; + + void MainLoop() { g_game->update(); } -#else -void MainLoop() { - game.update(); -} -#endif + #ifdef EMSCRIPTEN EM_BOOL onWebGLContextLost(int /*eventType*/, const void* /*reserved*/, void* /*userData*/) { - return EM_TRUE; + return EM_TRUE; } EM_BOOL onWebGLContextRestored(int /*eventType*/, const void* /*reserved*/, void* /*userData*/) { - return EM_TRUE; + return EM_TRUE; } static void applyResize(int logicalW, int logicalH) { - // Получаем коэффициент плотности пикселей (например, 2.625 на Pixel или 3.0 на Samsung) - double dpr = emscripten_get_device_pixel_ratio(); - //double dpr = 1; // low quality + // Получаем коэффициент плотности пикселей (например, 2.625 на Pixel или 3.0 на Samsung) + double dpr = emscripten_get_device_pixel_ratio(); + //double dpr = 1; // low quality - // Вычисляем реальные физические пиксели - int physicalW = static_cast(logicalW * dpr); - int physicalH = static_cast(logicalH * dpr); + // Вычисляем реальные физические пиксели + int physicalW = static_cast(logicalW * dpr); + int physicalH = static_cast(logicalH * dpr); - // Устанавливаем размер внутреннего буфера канваса - emscripten_set_canvas_element_size("#canvas", physicalW, physicalH); + // Устанавливаем размер внутреннего буфера канваса + emscripten_set_canvas_element_size("#canvas", physicalW, physicalH); - // Сообщаем SDL о новом размере. - // ВАЖНО: SDL2 в Emscripten ожидает здесь именно физические пиксели - // для корректной работы последующих вызовов glViewport. - if (ZL::Environment::window) { - SDL_SetWindowSize(ZL::Environment::window, physicalW, physicalH); - } + // Сообщаем SDL о новом размере. + // ВАЖНО: SDL2 в Emscripten ожидает здесь именно физические пиксели + // для корректной работы последующих вызовов glViewport. + if (ZL::Environment::window) { + SDL_SetWindowSize(ZL::Environment::window, physicalW, physicalH); + } - // Пушим событие, чтобы движок пересчитал матрицы проекции - SDL_Event e = {}; - e.type = SDL_WINDOWEVENT; - e.window.event = SDL_WINDOWEVENT_RESIZED; - e.window.data1 = physicalW; - e.window.data2 = physicalH; - SDL_PushEvent(&e); + // Пушим событие, чтобы движок пересчитал матрицы проекции + SDL_Event e = {}; + e.type = SDL_WINDOWEVENT; + e.window.event = SDL_WINDOWEVENT_RESIZED; + e.window.data1 = physicalW; + e.window.data2 = physicalH; + SDL_PushEvent(&e); - std::cout << "Resized, new size: " << logicalW << "x" << logicalH - << " (physical: " << physicalW << "x" << physicalH + std::cout << "Resized, new size: " << logicalW << "x" << logicalH + << " (physical: " << physicalW << "x" << physicalH << ", DPR: " << dpr << ")" << std::endl; } EM_BOOL onWindowResized(int /*eventType*/, const EmscriptenUiEvent* e, void* /*userData*/) { - // Use the event's window dimensions — querying the canvas element would - // return its old fixed size (e.g. 1280x720) before it has been resized. - applyResize(e->windowInnerWidth, e->windowInnerHeight); - return EM_FALSE; + // Use the event's window dimensions — querying the canvas element would + // return its old fixed size (e.g. 1280x720) before it has been resized. + applyResize(e->windowInnerWidth, e->windowInnerHeight); + return EM_FALSE; } EM_BOOL onFullscreenChanged(int /*eventType*/, const EmscriptenFullscreenChangeEvent* e, void* /*userData*/) { - ZL::Environment::isFullscreen = e->isFullscreen; + ZL::Environment::isFullscreen = e->isFullscreen; - // Вместо window.innerWidth, попробуйте запросить размер целевого элемента - // так как после перехода в FS именно он растягивается на весь экран. - double clientW, clientH; - emscripten_get_element_css_size("#canvas", &clientW, &clientH); - applyResize(clientW, clientH); - return EM_FALSE; + // Вместо window.innerWidth, попробуйте запросить размер целевого элемента + // так как после перехода в FS именно он растягивается на весь экран. + double clientW, clientH; + emscripten_get_element_css_size("#canvas", &clientW, &clientH); + applyResize(clientW, clientH); + return EM_FALSE; } int main(int argc, char* argv[]) { - SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS); + SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); // Для WebGL 2.0 + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); // Для WebGL 2.0 - ZL::Environment::window = SDL_CreateWindow("Game", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 1280, 720, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE); - glContext_x = SDL_GL_CreateContext(ZL::Environment::window); + ZL::Environment::window = SDL_CreateWindow("Game", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 1280, 720, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE); + glContext_x = SDL_GL_CreateContext(ZL::Environment::window); + + SDL_GL_MakeCurrent(ZL::Environment::window, glContext_x); - SDL_GL_MakeCurrent(ZL::Environment::window, glContext_x); - g_game = new ZL::Game(); g_game->setup(); - + emscripten_set_webglcontextlost_callback("#canvas", nullptr, EM_TRUE, onWebGLContextLost); - emscripten_set_webglcontextrestored_callback("#canvas", nullptr, EM_TRUE, onWebGLContextRestored); + emscripten_set_webglcontextrestored_callback("#canvas", nullptr, EM_TRUE, onWebGLContextRestored); - // Keep Environment::width/height in sync when the canvas is resized. - emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, nullptr, EM_FALSE, onWindowResized); - emscripten_set_fullscreenchange_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, nullptr, EM_FALSE, onFullscreenChanged); - - // 2. ИНИЦИАЛИЗАЦИЯ РАЗМЕРОВ: - // Получаем реальные размеры окна браузера на момент запуска - int canvasW = EM_ASM_INT({ return window.innerWidth; }); - int canvasH = EM_ASM_INT({ return window.innerHeight; }); + // Keep Environment::width/height in sync when the canvas is resized. + emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, nullptr, EM_FALSE, onWindowResized); + emscripten_set_fullscreenchange_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, nullptr, EM_FALSE, onFullscreenChanged); + + // 2. ИНИЦИАЛИЗАЦИЯ РАЗМЕРОВ: + // Получаем реальные размеры окна браузера на момент запуска + int canvasW = EM_ASM_INT({ return window.innerWidth; }); + int canvasH = EM_ASM_INT({ return window.innerHeight; }); + + // Вызываем вашу функцию — она сама применит DPR, выставит физический размер + // канваса и отправит SDL_WINDOWEVENT_RESIZED для настройки проекции. + applyResize(canvasW, canvasH); - // Вызываем вашу функцию — она сама применит DPR, выставит физический размер - // канваса и отправит SDL_WINDOWEVENT_RESIZED для настройки проекции. - applyResize(canvasW, canvasH); - // Prevent mouse clicks from generating fake SDL_FINGERDOWN events (desktop browser) SDL_SetHint(SDL_HINT_MOUSE_TOUCH_EVENTS, "0"); // Prevent touch events from generating fake SDL_MOUSEBUTTONDOWN events (mobile browser), // since we now handle SDL_FINGERDOWN directly for multi-touch support. SDL_SetHint(SDL_HINT_TOUCH_MOUSE_EVENTS, "0"); - ZL::emscriptenInitFileSystem(); + ZL::emscriptenInitFileSystem(); - emscripten_set_main_loop(MainLoop, 0, 1); - - - return 0; + emscripten_set_main_loop(MainLoop, 0, 1); + + + return 0; } #endif @@ -147,82 +141,83 @@ int main(int argc, char* argv[]) { extern "C" int SDL_main(int argc, char* argv[]) { // Инициализация SDL перед получением информации о дисплее - if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS) != 0) { - __android_log_print(ANDROID_LOG_ERROR, "Game", "SDL init failed: %s", SDL_GetError()); - return 1; - } + if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS) != 0) { + __android_log_print(ANDROID_LOG_ERROR, "Game", "SDL init failed: %s", SDL_GetError()); + return 1; + } - SDL_DisplayMode displayMode; - if (SDL_GetCurrentDisplayMode(0, &displayMode) != 0) { - __android_log_print(ANDROID_LOG_ERROR, "Game", "SDL_GetCurrentDisplayMode failed: %s", SDL_GetError()); - SDL_Quit(); - return 1; - } - ZL::Environment::width = displayMode.w; - ZL::Environment::height = displayMode.h; + SDL_DisplayMode displayMode; + if (SDL_GetCurrentDisplayMode(0, &displayMode) != 0) { + __android_log_print(ANDROID_LOG_ERROR, "Game", "SDL_GetCurrentDisplayMode failed: %s", SDL_GetError()); + SDL_Quit(); + return 1; + } + ZL::Environment::width = displayMode.w; + ZL::Environment::height = displayMode.h; - __android_log_print(ANDROID_LOG_INFO, "Game", "Display resolution: %dx%d", - ZL::Environment::width, ZL::Environment::height); + __android_log_print(ANDROID_LOG_INFO, "Game", "Display resolution: %dx%d", + ZL::Environment::width, ZL::Environment::height); SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); - SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 5); - SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 6); - SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 5); - SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 16); - SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); + SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 5); + SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 6); + SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 5); + SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 16); + SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); - ZL::Environment::window = SDL_CreateWindow( - "Shadow Over Bishkek", - SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, - ZL::Environment::width, ZL::Environment::height, - SDL_WINDOW_FULLSCREEN | SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN - ); + ZL::Environment::window = SDL_CreateWindow( + "Shadow Over Bishkek", + SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, + ZL::Environment::width, ZL::Environment::height, + SDL_WINDOW_FULLSCREEN | SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN + ); if (!ZL::Environment::window) { - __android_log_print(ANDROID_LOG_ERROR, "Game", "Failed to create window: %s", SDL_GetError()); - SDL_Quit(); - return 1; - } + __android_log_print(ANDROID_LOG_ERROR, "Game", "Failed to create window: %s", SDL_GetError()); + SDL_Quit(); + return 1; + } SDL_GLContext ctx = SDL_GL_CreateContext(ZL::Environment::window); - if (!ctx) { - __android_log_print(ANDROID_LOG_ERROR, "Game", "SDL_GL_CreateContext failed: %s", SDL_GetError()); - SDL_DestroyWindow(ZL::Environment::window); - SDL_Quit(); - return 1; - } + if (!ctx) { + __android_log_print(ANDROID_LOG_ERROR, "Game", "SDL_GL_CreateContext failed: %s", SDL_GetError()); + SDL_DestroyWindow(ZL::Environment::window); + SDL_Quit(); + return 1; + } - if (SDL_GL_MakeCurrent(ZL::Environment::window, ctx) != 0) { - __android_log_print(ANDROID_LOG_ERROR, "Game", "SDL_GL_MakeCurrent failed: %s", SDL_GetError()); - SDL_GL_DeleteContext(ctx); - SDL_DestroyWindow(ZL::Environment::window); - SDL_Quit(); - return 1; - } + if (SDL_GL_MakeCurrent(ZL::Environment::window, ctx) != 0) { + __android_log_print(ANDROID_LOG_ERROR, "Game", "SDL_GL_MakeCurrent failed: %s", SDL_GetError()); + SDL_GL_DeleteContext(ctx); + SDL_DestroyWindow(ZL::Environment::window); + SDL_Quit(); + return 1; + } - // Настройка VSync - if (SDL_GL_SetSwapInterval(1) < 0) { - __android_log_print(ANDROID_LOG_WARN, "Game", "Unable to set VSync: %s", SDL_GetError()); - } + // Настройка VSync + if (SDL_GL_SetSwapInterval(1) < 0) { + __android_log_print(ANDROID_LOG_WARN, "Game", "Unable to set VSync: %s", SDL_GetError()); + } - // Настройка игры - try { - game.setup(); - } catch (const std::exception &e) { - __android_log_print(ANDROID_LOG_ERROR, "Game", "Game setup failed: %s", e.what()); - return 1; - } + // Настройка игры + try { + game.setup(); + } + catch (const std::exception& e) { + __android_log_print(ANDROID_LOG_ERROR, "Game", "Game setup failed: %s", e.what()); + return 1; + } - while (!game.shouldExit()) { - game.update(); - SDL_Delay(16); - } + while (!game.shouldExit()) { + game.update(); + SDL_Delay(16); + } - return 0; + return 0; } @@ -231,113 +226,157 @@ extern "C" int SDL_main(int argc, char* argv[]) { #if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__linux__) -int main(int argc, char *argv[]) { - try - { +int main(int argc, char* argv[]) { - SDL_SetHint(SDL_HINT_WINDOWS_DPI_AWARENESS, "permonitorv2"); - //SDL_SetHint(SDL_HINT_WINDOWS_DPI_SCALING, "1"); + SDL_GLContext ctx = nullptr; + try + { +#ifdef STEAMSDK + // Передаем явный App ID демо-версии + if (SteamAPI_RestartAppIfNecessary(4945840)) { + return 0; + } - constexpr int CONST_WIDTH = ZL::Environment::CONST_DEFAULT_WIDTH; - constexpr int CONST_HEIGHT = ZL::Environment::CONST_DEFAULT_HEIGHT; + if (!SteamAPI_Init()) { + std::cerr << "SteamAPI_Init() failed! Steam client might not be running.\n"; + return 1; + } +#endif - ZL::Environment::width = CONST_WIDTH; - ZL::Environment::height = CONST_HEIGHT; - if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS) != 0) { - SDL_Log("SDL init failed: %s", SDL_GetError()); - return 1; - } + SDL_SetHint(SDL_HINT_WINDOWS_DPI_AWARENESS, "permonitorv2"); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); + constexpr int CONST_WIDTH = ZL::Environment::CONST_DEFAULT_WIDTH; + constexpr int CONST_HEIGHT = ZL::Environment::CONST_DEFAULT_HEIGHT; - // Read the persisted fullscreen preference (if any) before creating the - // window, so it opens directly in the right mode instead of flashing - // windowed -> fullscreen once settings are loaded later during setupPart2(). - { - const std::string settingsContent = ZL::readSavedTextFile("settings.json"); - if (!settingsContent.empty()) { - try { - const nlohmann::json settingsRoot = nlohmann::json::parse(settingsContent); - if (settingsRoot.contains("fullscreen")) { - ZL::Environment::isFullscreen = settingsRoot["fullscreen"].get(); - } - if (settingsRoot.contains("isHighDPIEnabled")) { - ZL::Environment::isHighDPIEnabled = settingsRoot["isHighDPIEnabled"].get(); - } - } catch (const std::exception& e) { - std::cerr << "[settings] Failed to parse settings.json: " << e.what() << std::endl; - } - } - } + ZL::Environment::width = CONST_WIDTH; + ZL::Environment::height = CONST_HEIGHT; + if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS) != 0) { + SDL_Log("SDL init failed: %s", SDL_GetError()); + return 1; + } - Uint32 windowFlags = SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN | SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_RESIZABLE; - if (ZL::Environment::isFullscreen) { - windowFlags |= SDL_WINDOW_FULLSCREEN_DESKTOP; - } - - //ZL::Environment::isFullscreen = false; + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); + + // Read the persisted fullscreen preference (if any) before creating the + // window, so it opens directly in the right mode instead of flashing + // windowed -> fullscreen once settings are loaded later during setupPart2(). + { + + const std::string settingsContent = ZL::readSavedTextFile("settings.json"); + if (!settingsContent.empty()) { + try { + const nlohmann::json settingsRoot = nlohmann::json::parse(settingsContent); + if (settingsRoot.contains("fullscreen")) { + ZL::Environment::isFullscreen = settingsRoot["fullscreen"].get(); + } + + if (settingsRoot.contains("isHighDPIEnabled")) { + ZL::Environment::isHighDPIEnabled = settingsRoot["isHighDPIEnabled"].get(); + } + + } + catch (const std::exception& e) { + std::cerr << "[settings] Failed to parse settings.json: " << e.what() << std::endl; + } + } + } + + + Uint32 windowFlags = SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN | SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_RESIZABLE; + if (ZL::Environment::isFullscreen) { + windowFlags |= SDL_WINDOW_FULLSCREEN_DESKTOP; + } + + SDL_DisplayMode dm; + if (SDL_GetCurrentDisplayMode(0, &dm) == 0) { + std::cout << "Desktop resolution: " << dm.w << "x" << dm.h << " @ " << dm.refresh_rate << "Hz\n"; + } + else { + SDL_Log("SDL_GetCurrentDisplayMode failed: %s", SDL_GetError()); + } - SDL_DisplayMode dm; - if (SDL_GetCurrentDisplayMode(0, &dm) == 0) { - std::cout << "Desktop resolution: " << dm.w << "x" << dm.h << " @ " << dm.refresh_rate << "Hz\n"; - } - else { - SDL_Log("SDL_GetCurrentDisplayMode failed: %s", SDL_GetError()); - } - int windowWidth = ZL::Environment::width; int windowHeight = ZL::Environment::height; - //In case if the game is launched on a small screen if (dm.w <= 1280 && dm.h <= 720) { - windowWidth = round(dm.w / 1.5); + windowWidth = round(dm.w / 1.5); windowHeight = round(dm.h / 1.5); } - ZL::Environment::window = SDL_CreateWindow( - "Shadow Over Bishkek", - SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, - windowWidth, windowHeight, - windowFlags - ); + ZL::Environment::window = SDL_CreateWindow( + "Shadow Over Bishkek Demo", + SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, + windowWidth, windowHeight, + windowFlags + ); - SDL_GLContext ctx = SDL_GL_CreateContext(ZL::Environment::window); - SDL_GL_MakeCurrent(ZL::Environment::window, ctx); + ctx = SDL_GL_CreateContext(ZL::Environment::window); + SDL_GL_MakeCurrent(ZL::Environment::window, ctx); - // On HiDPI displays the drawable (physical) size is larger than the - // logical window size. Use the drawable size so the GL framebuffer - // covers every physical pixel and the image stays sharp. - if (ZL::Environment::isHighDPIEnabled) - { - int drawW, drawH; - SDL_GL_GetDrawableSize(ZL::Environment::window, &drawW, &drawH); - ZL::Environment::width = drawW; - ZL::Environment::height = drawH; - std::cout << "HiDPI enabled, drawable size: " << drawW << "x" << drawH << std::endl; - } - else - { + if (ZL::Environment::isHighDPIEnabled) { + int drawW, drawH; + SDL_GL_GetDrawableSize(ZL::Environment::window, &drawW, &drawH); + ZL::Environment::width = drawW; + ZL::Environment::height = drawH; + std::cout << "HiDPI enabled, drawable size: " << drawW << "x" << drawH << std::endl; + } + else { int winW, winH; - SDL_GetWindowSize(ZL::Environment::window, &winW, &winH); - ZL::Environment::width = winW; - ZL::Environment::height = winH; + SDL_GetWindowSize(ZL::Environment::window, &winW, &winH); + ZL::Environment::width = winW; + ZL::Environment::height = winH; std::cout << "HiDPI disabled, window size: " << winW << "x" << winH << std::endl; - } + } - game.setup(); + // Динамическое создание объекта игры + g_game = new ZL::Game(); + g_game->setup(); - while (!game.shouldExit()) { - game.update(); - SDL_Delay(2); - } - } - catch (const std::exception& e) - { - std::cout << e.what() << std::endl; - } + while (!g_game->shouldExit()) { + g_game->update(); + SDL_Delay(2); - return 0; +#ifdef STEAMSDK + SteamAPI_RunCallbacks(); +#endif + } + + // Явное уничтожение объекта игры ДО завершения работы Steamworks + delete g_game; + g_game = nullptr; + } + catch (const std::exception& e) + { + std::cerr << "Exception caught: " << e.what() << std::endl; + if (g_game) { + delete g_game; + g_game = nullptr; + } + } + catch (...) + { + std::cerr << "Unknown exception happened: " << std::endl; + if (g_game) { + delete g_game; + g_game = nullptr; + } + } + +#ifdef STEAMSDK + // Деинициализация Steamworks происходит только тогда, когда все объекты игры гарантированно уничтожены + SteamAPI_Shutdown(); +#endif + + // Очистка ресурсов SDL + if (ctx) { + SDL_GL_DeleteContext(ctx); + } + if (ZL::Environment::window) { + SDL_DestroyWindow(ZL::Environment::window); + } + SDL_Quit(); + return 0; } #endif