diff --git a/audio/bishkek fight calm.ogg b/music/bishkek fight calm.ogg similarity index 100% rename from audio/bishkek fight calm.ogg rename to music/bishkek fight calm.ogg diff --git a/audio/bishkek fight.ogg b/music/bishkek fight.ogg similarity index 100% rename from audio/bishkek fight.ogg rename to music/bishkek fight.ogg diff --git a/audio/bishkek night.ogg b/music/bishkek night.ogg similarity index 100% rename from audio/bishkek night.ogg rename to music/bishkek night.ogg diff --git a/audio/bishkek univer day.ogg b/music/bishkek univer day.ogg similarity index 100% rename from audio/bishkek univer day.ogg rename to music/bishkek univer day.ogg diff --git a/audio/main menu final.ogg b/music/main menu final.ogg similarity index 100% rename from audio/main menu final.ogg rename to music/main menu final.ogg diff --git a/audio/obshaga.ogg b/music/obshaga.ogg similarity index 100% rename from audio/obshaga.ogg rename to music/obshaga.ogg diff --git a/proj-linux/CMakeLists.txt b/proj-linux/CMakeLists.txt index 1b10e8d..61bca5c 100644 --- a/proj-linux/CMakeLists.txt +++ b/proj-linux/CMakeLists.txt @@ -290,7 +290,7 @@ endif() # =========================================== set(RUNTIME_RESOURCE_DIRS "resources" - "audio" + "music" ) foreach(resdir IN LISTS RUNTIME_RESOURCE_DIRS) diff --git a/proj-web/CMakeLists.txt b/proj-web/CMakeLists.txt index 64c3c73..f17b45d 100644 --- a/proj-web/CMakeLists.txt +++ b/proj-web/CMakeLists.txt @@ -8,7 +8,7 @@ if(NOT CMAKE_MAKE_PROGRAM AND WIN32) endif() endif() -project(bishkek-witcher LANGUAGES C CXX) +project(ShadowOverBishkekDemo LANGUAGES C CXX) option(OPTIMIZE_WEB_RESOURCES "Resize PNG images for web build (requires Python3 + Pillow)" ON) @@ -152,10 +152,10 @@ set(SOURCES ../src/render/UiQuad.h ) -add_executable(bishkek-witcher ${SOURCES}) +add_executable(ShadowOverBishkekDemo ${SOURCES}) # Настройка путей к инклудам (используем скачанные исходники) -target_include_directories(bishkek-witcher PRIVATE +target_include_directories(ShadowOverBishkekDemo PRIVATE ../src ../thirdparty/eigen-5.0.0 ../thirdparty/boost_1_90_0 @@ -170,7 +170,7 @@ set(ENABLE_COMMONCRYPTO OFF CACHE BOOL "" FORCE) add_subdirectory("../thirdparty/libzip-1.11.4" libzip-build) -target_link_libraries(bishkek-witcher PRIVATE zip z lua_static websocket.js) +target_link_libraries(ShadowOverBishkekDemo PRIVATE zip z lua_static websocket.js) # Эмскриптен-флаги set(EMSCRIPTEN_FLAGS @@ -187,7 +187,7 @@ set(EMSCRIPTEN_FLAGS "-DNETWORK" ) -target_compile_options(bishkek-witcher PRIVATE ${EMSCRIPTEN_FLAGS} "-O2") +target_compile_options(ShadowOverBishkekDemo PRIVATE ${EMSCRIPTEN_FLAGS} "-O2") # Only loading.png and the shaders used before resources.zip is ready are preloaded. # resources.zip is downloaded asynchronously at runtime and served as a separate file. @@ -205,15 +205,14 @@ set(EMSCRIPTEN_LINK_FLAGS "--preload-file ${CMAKE_CURRENT_SOURCE_DIR}/../resources/start_uni_interior.lua@resources/start_uni_interior.lua" "--preload-file ${CMAKE_CURRENT_SOURCE_DIR}/../resources/start_uni_exterior.lua@resources/start_uni_exterior.lua" "--preload-file ${CMAKE_CURRENT_SOURCE_DIR}/../resources/start_dorm.lua@resources/start_dorm.lua" - "--preload-file ${CMAKE_CURRENT_SOURCE_DIR}/../audio@audio" ) # Применяем настройки линковки -target_link_options(bishkek-witcher PRIVATE ${EMSCRIPTEN_LINK_FLAGS}) +target_link_options(ShadowOverBishkekDemo PRIVATE ${EMSCRIPTEN_LINK_FLAGS}) # Для совместимости со старыми версиями CMake, если target_link_options недостаточно string(REPLACE ";" " " EMSCRIPTEN_LINK_FLAGS_STR "${EMSCRIPTEN_LINK_FLAGS}") -set_target_properties(bishkek-witcher PROPERTIES +set_target_properties(ShadowOverBishkekDemo PROPERTIES LINK_FLAGS "${EMSCRIPTEN_LINK_FLAGS_STR}" SUFFIX ".html" ) @@ -257,23 +256,40 @@ else() ) endif() + +# music resources +set(MUSIC_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../music") +set(MUSIC_ZIP "${CMAKE_CURRENT_BINARY_DIR}/music.zip") +get_filename_component(MUSIC_PARENT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/.." ABSOLUTE) + +add_custom_command( + OUTPUT "${MUSIC_ZIP}" + COMMAND ${CMAKE_COMMAND} -E tar "cf" "${MUSIC_ZIP}" --format=zip "music" + WORKING_DIRECTORY "${MUSIC_PARENT_DIR}" + DEPENDS "${MUSIC_PARENT_DIR}/music" +) + + add_custom_target(pack_resources DEPENDS "${RESOURCES_ZIP}") -add_dependencies(bishkek-witcher pack_resources) +add_dependencies(ShadowOverBishkekDemo pack_resources) + +add_custom_target(pack_music DEPENDS "${MUSIC_ZIP}") +add_dependencies(ShadowOverBishkekDemo pack_music) # Определяем путь к директории установки (относительно папки билда) set(CMAKE_INSTALL_PREFIX "${CMAKE_BINARY_DIR}/public") # 1. Устанавливаем основной HTML файл -install(TARGETS bishkek-witcher +install(TARGETS ShadowOverBishkekDemo RUNTIME DESTINATION . ) # 2. Устанавливаем сопутствующие файлы (JS, WASM и сгенерированный архив данных) install(FILES - "${CMAKE_CURRENT_BINARY_DIR}/bishkek-witcher.js" - "${CMAKE_CURRENT_BINARY_DIR}/bishkek-witcher.wasm" - "${CMAKE_CURRENT_BINARY_DIR}/bishkek-witcher.data" + "${CMAKE_CURRENT_BINARY_DIR}/ShadowOverBishkekDemo.js" + "${CMAKE_CURRENT_BINARY_DIR}/ShadowOverBishkekDemo.wasm" + "${CMAKE_CURRENT_BINARY_DIR}/ShadowOverBishkekDemo.data" DESTINATION . ) @@ -283,8 +299,9 @@ install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/index.html" # resources.zip is served separately and downloaded asynchronously at runtime install(FILES "${RESOURCES_ZIP}" DESTINATION .) +install(FILES "${MUSIC_ZIP}" DESTINATION .) -add_custom_command(TARGET bishkek-witcher POST_BUILD +add_custom_command(TARGET ShadowOverBishkekDemo POST_BUILD COMMAND ${CMAKE_COMMAND} --install . WORKING_DIRECTORY "${CMAKE_BINARY_DIR}" COMMENT "Automatically deploying to public directory..." diff --git a/proj-web/bishkek-witcher.html b/proj-web/bishkek-witcher.html deleted file mode 100644 index 7d859d4..0000000 --- a/proj-web/bishkek-witcher.html +++ /dev/null @@ -1,5 +0,0 @@ -Emscripten-Generated Code - - - -
Downloading...
Resize canvas Lock/hide mouse pointer    
\ No newline at end of file diff --git a/proj-web/index.html b/proj-web/index.html index 5555a9f..2d21fdd 100644 --- a/proj-web/index.html +++ b/proj-web/index.html @@ -39,7 +39,7 @@ function loadGameScript() { var s = document.createElement('script'); - s.src = 'bishkek-witcher.js'; + s.src = 'ShadowOverBishkekDemo.js'; s.async = true; document.body.appendChild(s); } diff --git a/proj-web/space-game001.html b/proj-web/space-game001.html deleted file mode 100644 index 710f6f4..0000000 --- a/proj-web/space-game001.html +++ /dev/null @@ -1,2 +0,0 @@ -Emscripten-Generated Code -
Downloading...
Resize canvas Lock/hide mouse pointer    
\ No newline at end of file diff --git a/proj-web/space-game001plain.html b/proj-web/space-game001plain.html deleted file mode 100644 index e4acfc9..0000000 --- a/proj-web/space-game001plain.html +++ /dev/null @@ -1,200 +0,0 @@ - - - - - - Sky Trek Tales - - - - -
Downloading...
- - - - - - - \ No newline at end of file diff --git a/proj-windows/CMakeLists.txt b/proj-windows/CMakeLists.txt index 2ef30fa..6bbd50b 100644 --- a/proj-windows/CMakeLists.txt +++ b/proj-windows/CMakeLists.txt @@ -232,7 +232,7 @@ if (WIN32) endif() # Копирование ресурсов локально -set(RUNTIME_RESOURCE_DIRS "resources" "audio") +set(RUNTIME_RESOURCE_DIRS "resources" "music") foreach(resdir IN LISTS RUNTIME_RESOURCE_DIRS) add_custom_command(TARGET ShadowOverBishkekDemo POST_BUILD COMMAND ${CMAKE_COMMAND} -E echo "Copying ${resdir} to runtime folders..." diff --git a/resources/audio/13925__adcbicycle__21.ogg b/resources/audio/13925__adcbicycle__21.ogg new file mode 100644 index 0000000..5b76eaa --- /dev/null +++ b/resources/audio/13925__adcbicycle__21.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b9d30306535105c1070730f40aba8894d7c4b19d702bb95ef5ff2c78471b3cd5 +size 7662 diff --git a/resources/audio/255571__stradie__flipping-paper.ogg b/resources/audio/255571__stradie__flipping-paper.ogg new file mode 100644 index 0000000..cae74b7 --- /dev/null +++ b/resources/audio/255571__stradie__flipping-paper.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9442ccf84f7f2e856c16519b0cb060e1b36f4096db7c0a07889c94dd826a77bc +size 45403 diff --git a/resources/audio/398751__anthousai__door-lock-01.ogg b/resources/audio/398751__anthousai__door-lock-01.ogg new file mode 100644 index 0000000..f79dd21 --- /dev/null +++ b/resources/audio/398751__anthousai__door-lock-01.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:659d78850496656075215ccd50ae62a64b4de7e4dc13c3c0191a964822481255 +size 10364 diff --git a/resources/audio/459618__n-lerche__blow.ogg b/resources/audio/459618__n-lerche__blow.ogg new file mode 100644 index 0000000..957b415 --- /dev/null +++ b/resources/audio/459618__n-lerche__blow.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4a55d5a4968a28ae948b72d0092b6a64beeb2d14bda4c2010a12890a0a4cec03 +size 42794 diff --git a/resources/audio/537061__imafoley__message-pop-sound.ogg b/resources/audio/537061__imafoley__message-pop-sound.ogg new file mode 100644 index 0000000..c431da3 --- /dev/null +++ b/resources/audio/537061__imafoley__message-pop-sound.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:624b696ed4ffa4e72cbcbc2a7005c7f282b4baaec921bb2be1f6853c3e15b097 +size 7916 diff --git a/resources/audio/547041__cogfirestudios__hit-swing-sword-small-3.ogg b/resources/audio/547041__cogfirestudios__hit-swing-sword-small-3.ogg new file mode 100644 index 0000000..f1be8e0 --- /dev/null +++ b/resources/audio/547041__cogfirestudios__hit-swing-sword-small-3.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fd6b8506121e1ac9807a8fb2689f7c0cfca48777c08810b613ba761fc33d76c4 +size 19353 diff --git a/resources/audio/587036__antoineromo__bed-sheets.ogg b/resources/audio/587036__antoineromo__bed-sheets.ogg new file mode 100644 index 0000000..ed0d4cb --- /dev/null +++ b/resources/audio/587036__antoineromo__bed-sheets.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:12d04399abfe2441f51dc8b5e268aebc734ba707de161239aa90b808ade630da +size 35946 diff --git a/resources/audio/656126__itsthegoodstuff__paging-through-book.ogg b/resources/audio/656126__itsthegoodstuff__paging-through-book.ogg new file mode 100644 index 0000000..0f64f91 --- /dev/null +++ b/resources/audio/656126__itsthegoodstuff__paging-through-book.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ad3034bc469fba569d8569881676c27f5bbec40533d5255291bef6d379bfe4c9 +size 21567 diff --git a/resources/audio/702171__foxfire__door-opening.ogg b/resources/audio/702171__foxfire__door-opening.ogg new file mode 100644 index 0000000..2c7c861 --- /dev/null +++ b/resources/audio/702171__foxfire__door-opening.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9e725a730b1387a9df7bb0125f3dd7ccba1ca27e8422ee3c55b3bde4d8ce4124 +size 17136 diff --git a/resources/audio/751089__smallconfusion__mechanical-plastic-click-11.ogg b/resources/audio/751089__smallconfusion__mechanical-plastic-click-11.ogg new file mode 100644 index 0000000..2ca58ce --- /dev/null +++ b/resources/audio/751089__smallconfusion__mechanical-plastic-click-11.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6a548df89a87919e3fd4bde3fa8eb2da883c402b7feeae7c0603d7d98d35399a +size 5105 diff --git a/resources/audio/78564__joedeshon__alarm_clock_ticking_02.ogg b/resources/audio/78564__joedeshon__alarm_clock_ticking_02.ogg new file mode 100644 index 0000000..4d0cee4 --- /dev/null +++ b/resources/audio/78564__joedeshon__alarm_clock_ticking_02.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c1756c0ece8080c799cb6ca4911a2c610800089ec6a65c45ba69e990942b7af9 +size 104191 diff --git a/resources/audio/831642__ienba__generic-game-pick-up.ogg b/resources/audio/831642__ienba__generic-game-pick-up.ogg new file mode 100644 index 0000000..44d62da --- /dev/null +++ b/resources/audio/831642__ienba__generic-game-pick-up.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c79391d1f26978900194f9d5af1a0dc59eb62a1572f4f9011d1538a6bf09ffab +size 16067 diff --git a/resources/audio/sound_bag001.ogg b/resources/audio/sound_bag001.ogg new file mode 100644 index 0000000..be0d04b --- /dev/null +++ b/resources/audio/sound_bag001.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e64ea00f0355371d2e1b93818b0c686da6e699e1cdb4de26a1c9dac5640bd0fb +size 16054 diff --git a/resources/audio/sound_bag_close.ogg b/resources/audio/sound_bag_close.ogg new file mode 100644 index 0000000..217533a --- /dev/null +++ b/resources/audio/sound_bag_close.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bcb99f3aa14d4f53ae636fab8fadb0ed19a4be17e994088e7e41e4872002c0be +size 18569 diff --git a/resources/audio/sound_bag_open.ogg b/resources/audio/sound_bag_open.ogg new file mode 100644 index 0000000..1080147 --- /dev/null +++ b/resources/audio/sound_bag_open.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e17ae2d18d36892cbc634cca6b5523293c2737e97e0a94b577a8709823a5db04 +size 17077 diff --git a/resources/config/start_state.json b/resources/config/start_state.json index bbd0860..66288b5 100644 --- a/resources/config/start_state.json +++ b/resources/config/start_state.json @@ -1122,9 +1122,9 @@ "waypointReachRadius": 3.0, "waypoints": [] }, - "taxiCarTriggerPositionX": -1.7014118346046923e+38, - "taxiCarTriggerPositionY": -1.7014118346046923e+38, - "taxiCarTriggerPositionZ": -1.7014118346046923e+38, + "taxiCarTriggerPositionX": 0.0, + "taxiCarTriggerPositionY": 0.0, + "taxiCarTriggerPositionZ": 0.0, "taxiDefaultWaypoints": [] }, "tutorialInteractiveObjectsLocked": false @@ -1476,7 +1476,6 @@ "morning_can_open_door_index": 0, "morning_did_open_door_index": 0, "player_hold_book": false, - "player_hold_knife": false, "teacher_arrived_to_library": false, "teacher_door_opened": false, "teacher_told_about_book": false, @@ -1652,7 +1651,7 @@ } ] }, - "savedAt": "2026-07-14 14:00", + "savedAt": "2026-07-24 23:40", "taxiIsCalled": false, "tutorialJournalPickedUp": false, "tutorialJournalScreenOpened": false, diff --git a/resources/w/ui/screen_settings_nomusic_web.json b/resources/w/ui/screen_settings_nomusic_web.json new file mode 100644 index 0000000..639bf69 --- /dev/null +++ b/resources/w/ui/screen_settings_nomusic_web.json @@ -0,0 +1,131 @@ +{ + "root": { + "type": "FrameLayout", + "width": "match_parent", + "height": "match_parent", + "children": [ + { + "type": "StaticImage", + "name": "settingsBkg", + "width": 2992, + "height": 1632, + "horizontal_gravity": "center", + "vertical_gravity": "center", + "texture": "resources/w/ui/img/main/MenuBkg001_v001.png" + }, + { + "type": "LinearLayout", + "orientation": "vertical", + "horizontal_gravity": "center", + "vertical_gravity": "center", + "horizontal_align": "left", + "width": 500, + "height": 600, + "spacing": 20, + "children": [ + { + "type": "TextView", + "name": "settingsTitleText", + "width": 500, + "height": 60, + "text": "Настройки", + "fontSize": 40, + "textCentered": true, + "color": [1.0, 1.0, 1.0, 1.0] + }, + { + "type": "TextView", + "name": "noMusicText1", + "width": 500, + "height": 40, + "text": "Настройки музыки отсутствуют.", + "fontSize": 32, + "textCentered": false, + "topAligned": true, + "paddingX": 10, + "color": [1.0, 1.0, 1.0, 1.0] + }, + { + "type": "TextView", + "name": "noMusicText2", + "width": 500, + "height": 40, + "text": "Пакет с музыкой не установлен.", + "fontSize": 32, + "textCentered": false, + "topAligned": true, + "paddingX": 10, + "color": [1.0, 1.0, 1.0, 1.0] + }, + { + "type": "TextButton", + "name": "musicDownloadButton", + "width": 500, + "height": 40, + "text": "Нажмите сюда, чтобы скачать и установить музыку (30 MB).", + "fontSize": 32, + "textCentered": true, + "color": [1.0, 1.0, 1.0, 1.0] + }, + { + "type": "TextView", + "name": "soundVolumeText", + "width": 500, + "height": 40, + "text": "Громкость звука: 80", + "fontSize": 32, + "textCentered": false, + "topAligned": true, + "paddingX": 10, + "color": [1.0, 1.0, 1.0, 1.0] + }, + { + "type": "Slider", + "name": "soundVolumeSlider", + "width": 500, + "height": 40, + "orientation": "horizontal", + "value": 0.625, + "interactive": true, + "textures": { + "track": "resources/loadingProgressBarFrame.png", + "knob": "resources/loadingProgressBar.png" + } + }, + { + "type": "TextButton", + "name": "soundToggleButton", + "width": 500, + "height": 40, + "text": "Звук: ВКЛ", + "fontSize": 32, + "textCentered": true, + "color": [1.0, 1.0, 1.0, 1.0] + }, + { + "type": "TextButton", + "name": "graphicsButton", + "width": 500, + "height": 60, + "text": "Настройки графики", + "fontSize": 40, + "textCentered": true, + "color": [1.0, 1.0, 1.0, 1.0] + } + ] + }, + { + "type": "TextButton", + "name": "settingsBackButton", + "horizontal_gravity": "center", + "vertical_gravity": "bottom", + "y": 40, + "width": 200, + "height": 60, + "text": "Назад", + "fontSize": 40, + "textCentered": true + } + ] + } +} diff --git a/src/AudioPlayerAsync.cpp b/src/AudioPlayerAsync.cpp index 2f1b1bc..5feacb0 100644 --- a/src/AudioPlayerAsync.cpp +++ b/src/AudioPlayerAsync.cpp @@ -6,6 +6,13 @@ #include #endif +namespace FRG +{ + extern const char* CONST_MUSIC_ZIP_FILE; + extern const char* CONST_ZIP_FILE; + +} + #ifdef __EMSCRIPTEN__ AudioPlayerAsync::AudioPlayerAsync() {} @@ -92,9 +99,10 @@ void AudioPlayerAsync::playSoundAsync(const std::string& filePath, int loops, in } if (!sound) { -#ifdef __ANDROID__ +//#ifdef __ANDROID__ - std::vector buffer = FRG::readFile(filePath); + //Sounds are loaded from regular resource file + std::vector buffer = FRG::readFileFromZIP(filePath, FRG::CONST_ZIP_FILE); if (buffer.empty()) { FRG::logger() << "Failed to read sound file into buffer: " << filePath << std::endl; @@ -108,9 +116,9 @@ void AudioPlayerAsync::playSoundAsync(const std::string& filePath, int loops, in sound = Mix_LoadWAV_RW(rw, 1); // 1 means it will free rw automatically //FRG::logger() << "sound = Mix_LoadWAV_RW(rw, 1); just fired " << std::endl; -#else - sound = Mix_LoadWAV(filePath.c_str()); -#endif +//#else +// sound = Mix_LoadWAV(filePath.c_str()); +//#endif if (!sound) { FRG::logger() << "Failed to load sound " << filePath << ": " << Mix_GetError() << std::endl; return; @@ -144,6 +152,10 @@ void AudioPlayerAsync::playMusicAsync(const std::string& filePath, int loops) { currentTrack = filePath; if (!musicEnabled_) return; + if ((strlen(FRG::CONST_MUSIC_ZIP_FILE)>0) && !FRG::zipFileExists(FRG::CONST_MUSIC_ZIP_FILE)) { + return; + } + auto task = [this, filePath, loops]() { Mix_HaltMusic(); if (currentMusic_) { @@ -153,17 +165,17 @@ void AudioPlayerAsync::playMusicAsync(const std::string& filePath, int loops) { currentMusicData_.clear(); Mix_Music* music = nullptr; -#ifdef __ANDROID__ - currentMusicData_ = FRG::readFile(filePath); +//#ifdef __ANDROID__ + currentMusicData_ = FRG::readFileFromZIP(filePath, FRG::CONST_MUSIC_ZIP_FILE); if (!currentMusicData_.empty()) { SDL_RWops* rw = SDL_RWFromMem(currentMusicData_.data(), currentMusicData_.size()); if (rw) { music = Mix_LoadMUS_RW(rw, 1); } } -#else - music = Mix_LoadMUS(filePath.c_str()); -#endif +//#else +// music = Mix_LoadMUS(filePath.c_str()); +//#endif if (!music) { FRG::logger() << "Failed to load music " << filePath << ": " << Mix_GetError() << std::endl; @@ -268,11 +280,30 @@ void AudioPlayerAsync::setSoundVolume(int volume) { } void AudioPlayerAsync::crossFadeMusicAsync(const std::string& filePath, int fadeDurationMs, int loops) { + FRG::logger() << "crossFadeMusicAsync called step 1" << std::endl; + if (!initialized) return; + FRG::logger() << "crossFadeMusicAsync called step 1.1" << std::endl; + + if ((strlen(FRG::CONST_MUSIC_ZIP_FILE) > 0) && !FRG::zipFileExists(FRG::CONST_MUSIC_ZIP_FILE)) { + FRG::logger() << "crossFadeMusicAsync called step 1.2" << std::endl; + return; + } + FRG::logger() << "crossFadeMusicAsync called step 1.3" << std::endl; + if (filePath == currentTrack) return; + FRG::logger() << "crossFadeMusicAsync called step 1.4" << std::endl; + currentTrack = filePath; if (!musicEnabled_) return; + FRG::logger() << "crossFadeMusicAsync called step 2" << std::endl; + + + + + FRG::logger() << "crossFadeMusicAsync called step 3" << std::endl; + auto task = [this, filePath, fadeDurationMs, loops]() { #ifdef __EMSCRIPTEN__ Mix_HaltMusic(); @@ -287,17 +318,17 @@ void AudioPlayerAsync::crossFadeMusicAsync(const std::string& filePath, int fade currentMusicData_.clear(); #endif Mix_Music* music = nullptr; -#ifdef __ANDROID__ - currentMusicData_ = FRG::readFile(filePath); +//#ifdef __ANDROID__ + currentMusicData_ = FRG::readFileFromZIP(filePath, FRG::CONST_MUSIC_ZIP_FILE); if (!currentMusicData_.empty()) { SDL_RWops* rw = SDL_RWFromMem(currentMusicData_.data(), currentMusicData_.size()); if (rw) { music = Mix_LoadMUS_RW(rw, 1); } } -#else - music = Mix_LoadMUS(filePath.c_str()); -#endif +//#else +// music = Mix_LoadMUS(filePath.c_str()); +//#endif if (!music) { FRG::logger() << "Failed to load music " << filePath << ": " << Mix_GetError() << std::endl; return; diff --git a/src/Character.cpp b/src/Character.cpp index 49c4ee3..dffe04e 100644 --- a/src/Character.cpp +++ b/src/Character.cpp @@ -438,11 +438,11 @@ void Character::update(int64_t deltaMs, const CharacterResolver& resolver) { if (audioPlayer) { if (state.isPlayer) { - audioPlayer->playSoundAsync("audio/547041__cogfirestudios__hit-swing-sword-small-3.ogg"); + audioPlayer->playSoundAsync("resources/audio/547041__cogfirestudios__hit-swing-sword-small-3.ogg"); } else { - audioPlayer->playSoundAsync("audio/13925__adcbicycle__21.ogg"); + audioPlayer->playSoundAsync("resources/audio/13925__adcbicycle__21.ogg"); } } } diff --git a/src/Environment.cpp b/src/Environment.cpp index 4bdf8e1..158d72f 100644 --- a/src/Environment.cpp +++ b/src/Environment.cpp @@ -23,7 +23,7 @@ int Environment::height = CONST_DEFAULT_HEIGHT; float Environment::zoom = DEFAULT_ZOOM; -bool Environment::enableLogging = false; +bool Environment::enableLogging = true; SDL_Window* Environment::window = nullptr; diff --git a/src/Game.cpp b/src/Game.cpp index 2d22473..99f38ad 100644 --- a/src/Game.cpp +++ b/src/Game.cpp @@ -36,8 +36,10 @@ namespace FRG #ifdef EMSCRIPTEN const char* CONST_ZIP_FILE = "resources.zip"; + const char* CONST_MUSIC_ZIP_FILE = "music.zip"; #else const char* CONST_ZIP_FILE = ""; + const char* CONST_MUSIC_ZIP_FILE = ""; #endif float x = 0; @@ -47,25 +49,29 @@ namespace FRG #ifdef EMSCRIPTEN Game* Game::s_instance = nullptr; + // Коллбэк успешной загрузки resources.zip void Game::onResourcesZipLoaded(unsigned /*handle*/, void* /*userData*/, const char* /*filename*/) { - logger() << "Resources.zip loaded successfully--0" << std::endl; + logger() << "Resources.zip downloaded successfully to VFS" << std::endl; + + // Синхронизируем MemFS -> IndexedDB, чтобы файл сохранился между сессиями браузера + FRG::emscriptenSyncFs(); + if (s_instance) { s_instance->resourcesDownloadProgress = 1.0f; - // Builds the loadSteps queue (cheap, no GL work); the actual loading - // happens one step per frame in Game::update() so the browser tab - // keeps repainting/responding instead of freezing for the whole load. s_instance->setupPart2(); } } - void Game::onResourcesZipError(unsigned /*handle*/, void* /*userData*/, int /*httpStatus*/) { - logger() << "Failed to download resources.zip" << std::endl; + void Game::onResourcesZipError(unsigned /*handle*/, void* /*userData*/, int httpStatus) { + logger() << "Failed to download resources.zip, HTTP status: " << httpStatus << std::endl; + // Можно обработать ошибку или предложить пользователю перезагрузить страницу } void Game::onResourcesZipProgress(unsigned /*handle*/, void* /*userData*/, int percentComplete) { if (s_instance) s_instance->resourcesDownloadProgress = percentComplete / 100.0f; } + #endif Location* Game::currentLocation() const { @@ -133,12 +139,53 @@ namespace FRG loadingMesh.RefreshVBO(); #ifdef EMSCRIPTEN - // Asynchronously download resources.zip; setupPart2() is called on completion. - // The loading screen stays visible until the download finishes. s_instance = this; - logger() << "Load resurces step 1-" << std::endl; - emscripten_async_wget2("resources.zip", "resources.zip", "GET", nullptr, nullptr, - onResourcesZipLoaded, onResourcesZipError, onResourcesZipProgress); + + std::string resPath = FRG::getPersistentResourcePath("resources.zip"); + std::string musicPath = FRG::getPersistentResourcePath("music.zip"); + + // 1. Проверяем ресурсы (resources.zip) + if (FRG::isZipValid(resPath)) { + logger() << "[Load] Valid resources.zip found in IndexedDB cache: " << resPath << std::endl; + resourcesDownloadProgress = 1.0f; + + // Архив уже есть и цел — переходим сразу к разбору/загрузке + setupPart2(); + } + else { + logger() << "[Load] Resources.zip missing or corrupt. Downloading to: " << resPath << std::endl; + + // Скачиваем сразу в постоянный путь /offline/resources.zip + emscripten_async_wget2( + "resources.zip", // URL на сервере + resPath.c_str(), // Назначение в VFS Emscripten + "GET", + nullptr, + nullptr, + onResourcesZipLoaded, + onResourcesZipError, + onResourcesZipProgress + ); + } + /* + // 2. Проверяем музыку (music.zip) + if (FRG::isZipValid(musicPath)) { + logger() << "[Load] Valid music.zip found in IndexedDB cache." << std::endl; + } + else { + logger() << "[Load] Music.zip missing or corrupt. Downloading to: " << musicPath << std::endl; + + emscripten_async_wget2( + "music.zip", + musicPath.c_str(), + "GET", + nullptr, + nullptr, + onMusicZipLoaded, + onMusicZipError, + onMusicZipProgress + ); + }*/ #else // Builds the loadSteps queue (cheap, no GL work); the actual loading // happens one step per frame in Game::update() so the window keeps @@ -299,12 +346,12 @@ namespace FRG menuManager.onItemPickedUp(itemId); const Item* item = ItemRegistry::instance().findById(itemId); if (item) menuManager.showToast(menuManager.toastTextureItemAdded, item->name); - audioPlayer->playSoundAsync("audio/831642__ienba__generic-game-pick-up.ogg"); + audioPlayer->playSoundAsync("resources/audio/831642__ienba__generic-game-pick-up.ogg"); }; gameState.inventory.onItemRemoved = [this](const std::string& itemId) { const Item* item = ItemRegistry::instance().findById(itemId); if (item) menuManager.showToast(menuManager.toastTextureItemRemoved, item->name); - audioPlayer->playSoundAsync("audio/831642__ienba__generic-game-pick-up.ogg"); + audioPlayer->playSoundAsync("resources/audio/831642__ienba__generic-game-pick-up.ogg"); }; // Wire phone dialogue start function so MenuManager can trigger dialogues. @@ -344,7 +391,8 @@ namespace FRG performResetToInitialState(); gameState.currentLocationName = "location_dorm"; currentLocation()->scriptEngine.callLocationEnterCallback(); - this->audioPlayer->crossFadeMusicAsync("audio/obshaga.ogg"); + this->audioPlayer->crossFadeMusicAsync("music/obshaga.ogg"); + menuManager.lastMusicTrack = "music/obshaga.ogg"; }); }; @@ -392,7 +440,8 @@ namespace FRG loadSteps.push([this]() { menuManager.loadSettings(); - audioPlayer->crossFadeMusicAsync("audio/main menu final.ogg"); + audioPlayer->crossFadeMusicAsync("music/main menu final.ogg"); + menuManager.lastMusicTrack = "music/main menu final.ogg"; }); @@ -465,7 +514,21 @@ namespace FRG void Game::performResetToInitialState() { const std::string path = "resources/config/start_state.json"; - const std::string content = FRG::readTextFile(path); + + std::vector fileData; + if (strlen(CONST_ZIP_FILE) > 0) + { + fileData = readFileFromZIP(path, CONST_ZIP_FILE); + } + else + { + fileData = readFile(path); + } + + std::string content(fileData.begin(), fileData.end()); + + //const std::vector contentVector = FRG::readFileFromZIP(); + //const std::string content = FRG::readTextFile(path); if (!content.empty()) { try { nlohmann::json root = nlohmann::json::parse(content); @@ -485,10 +548,14 @@ namespace FRG rearmDormOneShotCallbacks(); return; } catch (const std::exception& e) { - std::cerr << "[reset] Failed to parse " << path << ": " << e.what() + logger() << "[reset] Failed to parse " << path << ": " << e.what() << " -- falling back to full rebuild" << std::endl; } } + else + { + logger() << "File " << path << " is empty -- falling back to full rebuild" << std::endl; + } performFullReset(); } @@ -616,10 +683,12 @@ namespace FRG } gameState.locations["uni_interior"]->requestDarklandsPlayBattleMusic = [this]() { - audioPlayer->crossFadeMusicFromPositionAsync("audio/bishkek fight.ogg"); + audioPlayer->crossFadeMusicFromPositionAsync("music/bishkek fight.ogg"); + menuManager.lastMusicTrack = "music/bishkek fight.ogg"; }; gameState.locations["uni_interior"]->requestDarklandsPlayNormalMusic = [this]() { - audioPlayer->crossFadeMusicFromPositionAsync("audio/bishkek fight calm.ogg"); + audioPlayer->crossFadeMusicFromPositionAsync("music/bishkek fight calm.ogg"); + menuManager.lastMusicTrack = "music/bishkek fight calm.ogg"; }; } @@ -1679,7 +1748,7 @@ namespace FRG return false; } - audioPlayer->playSoundAsync("audio/459618__n-lerche__blow.ogg"); + audioPlayer->playSoundAsync("resources/audio/459618__n-lerche__blow.ogg"); if (darklandsFlashActive) return false; darklandsFlashActive = true; darklandsFlashFadingIn = true; @@ -1715,7 +1784,8 @@ namespace FRG currentLocation()->scriptEngine.callTriggerNightEnterCallback(); } - audioPlayer->crossFadeMusicAsync("audio/bishkek night.ogg"); + audioPlayer->crossFadeMusicAsync("music/bishkek night.ogg"); + menuManager.lastMusicTrack = "music/bishkek night.ogg"; } else { gameState.isDarklands = !gameState.isDarklands; @@ -1725,11 +1795,13 @@ namespace FRG if (gameState.isDarklands) { - audioPlayer->crossFadeMusicAsync("audio/bishkek fight calm.ogg"); + audioPlayer->crossFadeMusicAsync("music/bishkek fight calm.ogg"); + menuManager.lastMusicTrack = "music/bishkek fight calm.ogg"; } else { - audioPlayer->crossFadeMusicAsync("audio/bishkek night.ogg"); + audioPlayer->crossFadeMusicAsync("music/bishkek night.ogg"); + menuManager.lastMusicTrack = "music/bishkek night.ogg"; } if (currentLocation()) { @@ -1845,20 +1917,24 @@ namespace FRG if (gameState.isDarklands) { - audioPlayer->crossFadeMusicAsync("audio/bishkek fight calm.ogg"); + audioPlayer->crossFadeMusicAsync("music/bishkek fight calm.ogg"); + menuManager.lastMusicTrack = "music/bishkek fight calm.ogg"; } else if (gameState.isNight) { - audioPlayer->crossFadeMusicAsync("audio/bishkek night.ogg"); + audioPlayer->crossFadeMusicAsync("music/bishkek night.ogg"); + menuManager.lastMusicTrack = "music/bishkek night.ogg"; } else { if (gameState.currentLocationName == "location_dorm") { - audioPlayer->crossFadeMusicAsync("audio/obshaga.ogg"); + audioPlayer->crossFadeMusicAsync("music/obshaga.ogg"); + menuManager.lastMusicTrack = "music/obshaga.ogg"; } else { - audioPlayer->crossFadeMusicAsync("audio/bishkek univer day.ogg"); + audioPlayer->crossFadeMusicAsync("music/bishkek univer day.ogg"); + menuManager.lastMusicTrack = "music/bishkek univer day.ogg"; } } } diff --git a/src/Game.h b/src/Game.h index d45089e..b95cd43 100644 --- a/src/Game.h +++ b/src/Game.h @@ -82,6 +82,10 @@ namespace FRG { MenuManager menuManager; void activateSlowMoEffect(); + +#ifdef EMSCRIPTEN + static Game* s_instance; +#endif private: // Unified pointer handling: mouse-left and a single touch share one path. @@ -160,7 +164,6 @@ namespace FRG { int countNonUiPointers() const; #ifdef EMSCRIPTEN - static Game* s_instance; static void onResourcesZipLoaded(unsigned handle, void* userData, const char* filename); static void onResourcesZipError(unsigned handle, void* userData, int httpStatus); static void onResourcesZipProgress(unsigned handle, void* userData, int percentComplete); diff --git a/src/GameState.cpp b/src/GameState.cpp index 409379c..13de307 100644 --- a/src/GameState.cpp +++ b/src/GameState.cpp @@ -83,29 +83,40 @@ void GameState::save(nlohmann::json& out) const void GameState::load(const nlohmann::json& in) { + //logger() << "GameState::load step 1" << std::endl; + // --- World --- currentLocationName = in.value("currentLocationName", currentLocationName); isDarklands = in.value("isDarklands", isDarklands); isNight = in.value("isNight", isNight); isDawn = in.value("isDawn", isDawn); + //() << "GameState::load step 2" << std::endl; + + // --- Player resources --- playerHp = in.value("playerHp", playerHp); playerMaxHp = in.value("playerMaxHp", playerMaxHp); money = in.value("money", money); + //logger() << "GameState::load step 3" << std::endl; + // --- Script globals --- globalInts.clear(); if (in.contains("globalInts") && in["globalInts"].is_object()) { for (const auto& [k, v] : in["globalInts"].items()) globalInts[k] = v.get(); } + //logger() << "GameState::load step 4" << std::endl; + globalFloats.clear(); if (in.contains("globalFloats") && in["globalFloats"].is_object()) { for (const auto& [k, v] : in["globalFloats"].items()) globalFloats[k] = v.get(); } + //logger() << "GameState::load step 5" << std::endl; + // --- Tutorial --- tutorialStep = static_cast(in.value("tutorialStep", static_cast(tutorialStep))); uniIntTutorialState = static_cast(in.value("uniIntTutorialState", static_cast(uniIntTutorialState))); @@ -115,15 +126,21 @@ void GameState::load(const nlohmann::json& in) tutorialJournalScreenOpened = in.value("tutorialJournalScreenOpened", tutorialJournalScreenOpened); tutorialNeedOpenTaxiScreen = in.value("tutorialNeedOpenTaxiScreen", tutorialNeedOpenTaxiScreen); + //logger() << "GameState::load step 6" << std::endl; + // --- Phone / chat --- if (in.contains("chatUnread") && in["chatUnread"].is_array()) { for (int i = 0; i < 3 && i < static_cast(in["chatUnread"].size()); ++i) chatUnread[i] = in["chatUnread"][i].get(); } + //logger() << "GameState::load step 7" << std::endl; + if (in.contains("chatPreviewMsg") && in["chatPreviewMsg"].is_array()) { for (int i = 0; i < 3 && i < static_cast(in["chatPreviewMsg"].size()); ++i) chatPreviewMsg[i] = in["chatPreviewMsg"][i].get(); } + //logger() << "GameState::load step 8" << std::endl; + if (in.contains("chatHistory") && in["chatHistory"].is_array()) { for (int i = 0; i < 3 && i < static_cast(in["chatHistory"].size()); ++i) { chatHistory[i].clear(); @@ -136,26 +153,38 @@ void GameState::load(const nlohmann::json& in) } } } + } + //logger() << "GameState::load step 8" << std::endl; // --- Inventory --- if (in.contains("inventory")) inventory.load(in["inventory"]); + //logger() << "GameState::load step 9" << std::endl; + // --- Quest journal --- if (in.contains("questJournal")) questJournal.load(in["questJournal"]); + + logger() << "GameState::load step 10" << std::endl; // --- Locations --- if (in.contains("locations") && in["locations"].is_object()) { for (const auto& [name, locJson] : in["locations"].items()) { + //logger() << "GameState::load step 10.1: " << name << std::endl; + auto it = locations.find(name); if (it != locations.end() && it->second) { it->second->load(locJson); } } } + //logger() << "GameState::load step 11" << std::endl; + // --- Taxi --- taxiIsCalled = in.value("taxiIsCalled", taxiIsCalled); + //logger() << "GameState::load step 12" << std::endl; + } } // namespace FRG diff --git a/src/Location.cpp b/src/Location.cpp index fe47f60..de5812f 100644 --- a/src/Location.cpp +++ b/src/Location.cpp @@ -2147,10 +2147,13 @@ namespace FRG void Location::load(const nlohmann::json& in) { + //logger() << "Location::load step 1" << std::endl; + // Location-level state if (in.contains("locationState")) { state.load(in["locationState"]); } + //logger() << "Location::load step 2" << std::endl; // Re-sync navigation pointer from restored index if (state.activeNavigationIndex >= 0 && @@ -2159,6 +2162,8 @@ namespace FRG navigation = &navigationMaps[state.activeNavigationIndex]; } + //logger() << "Location::load step 3" << std::endl; + // Restore trigger zone runtime state (by id) if (in.contains("triggerZoneState") && in["triggerZoneState"].is_object()) { const auto& tzs = in["triggerZoneState"]; @@ -2169,6 +2174,7 @@ namespace FRG } } } + //logger() << "Location::load step 4" << std::endl; // Restore teleport zone runtime state (by id) if (in.contains("teleportZoneState") && in["teleportZoneState"].is_object()) { @@ -2180,11 +2186,15 @@ namespace FRG } } + //logger() << "Location::load step 5" << std::endl; + // Player character state if (player && in.contains("playerState")) { player->state.load(in["playerState"]); } + //logger() << "Location::load step 6" << std::endl; + // NPC states if (in.contains("npcStates") && in["npcStates"].is_array()) { const auto& arr = in["npcStates"]; @@ -2195,6 +2205,8 @@ namespace FRG } } + //logger() << "Location::load step 7" << std::endl; + // Interactive object states if (in.contains("interactiveObjectStates") && in["interactiveObjectStates"].is_array()) { const auto& arr = in["interactiveObjectStates"]; @@ -2202,17 +2214,23 @@ namespace FRG interactiveObjects[i].state.load(arr[i]); } } + //logger() << "Location::load step 8" << std::endl; // Dialogue runtime state if (in.contains("dialogueState")) { dialogueSystem.loadDialogueState(in["dialogueState"]); } + //logger() << "Location::load step 9" << std::endl; + // Lua globals if (in.contains("scriptGlobals")) { scriptEngine.loadScriptGlobals(in["scriptGlobals"]); } + //logger() << "Location::load step 10" << std::endl; + + // Re-wire in-flight, one-shot Lua callbacks (walk-arrival / animation-complete) // that were serialized by name. Must run after scriptEngine has already run the // location's script (guaranteed: Location::setup() always runs before load()) @@ -2220,16 +2238,24 @@ namespace FRG if (player && !player->state.onArrivedCallbackName.empty()) { scriptEngine.rehydrateNamedCallback(player->state.onArrivedCallbackName, player->state.onArrivedCallback); } + + //logger() << "Location::load step 11" << std::endl; + for (auto& npc : npcs) { if (npc && !npc->state.onArrivedCallbackName.empty()) { scriptEngine.rehydrateNamedCallback(npc->state.onArrivedCallbackName, npc->state.onArrivedCallback); } } + //logger() << "Location::load step 12" << std::endl; + for (auto& intObj : interactiveObjects) { if (intObj.state.isAnimating && intObj.state.animTask && !intObj.state.animTask->onCompleteName.empty()) { scriptEngine.rehydrateNamedCallback(intObj.state.animTask->onCompleteName, intObj.state.animTask->onComplete); } } + + //logger() << "Location::load step 13" << std::endl; + } } // namespace FRG diff --git a/src/LocationState.cpp b/src/LocationState.cpp index 3de601a..d177a19 100644 --- a/src/LocationState.cpp +++ b/src/LocationState.cpp @@ -107,6 +107,8 @@ void LocationState::save(nlohmann::json& out) const void LocationState::load(const nlohmann::json& in) { + //logger() << "LocationState::load step 1" << std::endl; + cameraAzimuth = in.value("cameraAzimuth", cameraAzimuth); cameraInclination = in.value("cameraInclination", cameraInclination); @@ -122,6 +124,8 @@ void LocationState::load(const nlohmann::json& in) tutorialInteractiveObjectsLocked = in.value("tutorialInteractiveObjectsLocked", false); + //logger() << "LocationState::load step 2" << std::endl; + nlohmann::json taxiJson = in["taxi"]; taxiArrivedCallbackCalled = taxiJson.value("taxiArrivedCallbackCalled", false); @@ -136,20 +140,18 @@ void LocationState::load(const nlohmann::json& in) taxiCarJson.value("positionZ", 0.f) ); + //logger() << "LocationState::load step 3" << std::endl; + taxiCar.rotation = taxiCarJson.value("rotation", 0.f); taxiCar.velocity = taxiCarJson.value("velocity", 0.f); taxiCar.steeringAngle = taxiCarJson.value("steeringAngle", 0.f); - /*taxiCar.acceleration = taxiCarJson.value("acceleration", 20.f); - taxiCar.friction = taxiCarJson.value("friction", 6.f); - taxiCar.maxSpeed = taxiCarJson.value("maxSpeed", 24.f); - taxiCar.maxReverseSpeed = taxiCarJson.value("maxReverseSpeed", 8.f); - taxiCar.turnRate = taxiCarJson.value("turnRate", 1.8f); - taxiCar.maxSteerAngle = taxiCarJson.value("maxSteerAngle", 0.6f);*/ taxiCar.mode = static_cast(taxiCarJson.value("mode", 0)); + //logger() << "LocationState::load step 4" << std::endl; + taxiCar.waypoints.clear(); if (taxiCarJson.contains("waypoints") && taxiCarJson["waypoints"].is_array()) { for (int i = 0; i < static_cast(taxiCarJson["waypoints"].size()); ++i) @@ -160,6 +162,8 @@ void LocationState::load(const nlohmann::json& in) )); } + //logger() << "LocationState::load step 5" << std::endl; + taxiCar.currentWaypoint = taxiCarJson.value("currentWaypoint", 0); taxiCar.waypointReachRadius = taxiCarJson.value("waypointReachRadius", 3.f); taxiCar.wheelAngle = taxiCarJson.value("wheelAngle", 0.f); @@ -167,6 +171,8 @@ void LocationState::load(const nlohmann::json& in) //--------- Taxi initial state ----- + //logger() << "LocationState::load step 6" << std::endl; + nlohmann::json taxiCarInitialStateJson = taxiJson["taxiCarInitialState"]; taxiCarInitialState.position = Eigen::Vector3f( @@ -180,15 +186,12 @@ void LocationState::load(const nlohmann::json& in) taxiCarInitialState.velocity = taxiCarInitialStateJson.value("velocity", 0.f); taxiCarInitialState.steeringAngle = taxiCarInitialStateJson.value("steeringAngle", 0.f); - /* - taxiCarInitialState.acceleration = taxiCarInitialStateJson.value("acceleration", 20.f); - taxiCarInitialState.friction = taxiCarInitialStateJson.value("friction", 6.f); - taxiCarInitialState.maxSpeed = taxiCarInitialStateJson.value("maxSpeed", 24.f); - taxiCarInitialState.maxReverseSpeed = taxiCarInitialStateJson.value("maxReverseSpeed", 8.f); - taxiCarInitialState.turnRate = taxiCarInitialStateJson.value("turnRate", 1.8f); - taxiCarInitialState.maxSteerAngle = taxiCarInitialStateJson.value("maxSteerAngle", 0.6f);*/ + taxiCarInitialState.mode = static_cast(taxiCarInitialStateJson.value("mode", 0)); + //logger() << "LocationState::load step 7" << std::endl; + + taxiCarInitialState.waypoints.clear(); if (taxiCarInitialStateJson.contains("waypoints") && taxiCarInitialStateJson["waypoints"].is_array()) { for (int i = 0; i < static_cast(taxiCarInitialStateJson["waypoints"].size()); ++i) @@ -199,11 +202,14 @@ void LocationState::load(const nlohmann::json& in) )); } + //logger() << "LocationState::load step 8" << std::endl; + taxiCarInitialState.currentWaypoint = taxiCarInitialStateJson.value("currentWaypoint", 0); taxiCarInitialState.waypointReachRadius = taxiCarInitialStateJson.value("waypointReachRadius", 3.f); taxiCarInitialState.wheelAngle = taxiCarInitialStateJson.value("wheelAngle", 0.f); + taxiCarWaypoints.clear(); if (taxiJson.contains("taxiDefaultWaypoints") && taxiJson["taxiDefaultWaypoints"].is_array()) { for (int i = 0; i < static_cast(taxiJson["taxiDefaultWaypoints"].size()); ++i) @@ -214,6 +220,7 @@ void LocationState::load(const nlohmann::json& in) )); } + //logger() << "LocationState::load step 9" << std::endl; taxiCarTriggerPosition = Eigen::Vector3f( taxiJson.value("taxiCarTriggerPositionX", 0.f), @@ -221,6 +228,9 @@ void LocationState::load(const nlohmann::json& in) taxiJson.value("taxiCarTriggerPositionZ", 0.f) ); + //logger() << "LocationState::load step 10" << std::endl; + + } } // namespace FRG diff --git a/src/LocationState.h b/src/LocationState.h index e81ba1f..7f680a0 100644 --- a/src/LocationState.h +++ b/src/LocationState.h @@ -31,7 +31,7 @@ struct LocationState : public ISaveable { NpcCar taxiCarInitialState; NpcCar taxiCar; bool taxiArrivedCallbackCalled = false; - Vector3f taxiCarTriggerPosition; + Vector3f taxiCarTriggerPosition = Vector3f(0,0,0); std::vector taxiCarWaypoints; // (trigger/teleport zone runtime flags are serialised by id directly in Location::save/load) diff --git a/src/MenuManager.cpp b/src/MenuManager.cpp index d4cd7c1..976198b 100644 --- a/src/MenuManager.cpp +++ b/src/MenuManager.cpp @@ -10,8 +10,16 @@ #include #include +#ifdef __EMSCRIPTEN__ +#include +#endif + namespace FRG { +#ifdef __EMSCRIPTEN__ + //extern Game* Game::s_instance; +#endif + // Localization static const std::string EMPTY_LANGUAGE_RU = u8"(пусто)"; static const std::string EMPTY_LANGUAGE_EN = "(empty)"; @@ -19,6 +27,11 @@ namespace FRG { static const std::string BACK_TO_MAIN_MENU_RU = u8"Вернуться в меню"; static const std::string BACK_TO_MAIN_MENU_EN = "Back to Main Menu"; + static const std::string MUSIC_DOWNLOAD_ERROR_RU = u8"Ошибка скачивания музыки"; + static const std::string MUSIC_DOWNLOAD_NOT_STARTED_RU = u8"Нажмите сюда, чтобы скачать и установить музыку (27 MB)."; + static const std::string MUSIC_DOWNLOAD_PROGRESS_PREFIX_RU = u8"Загрузка музыки: "; + //static const std::string MUSIC_DOWNLOAD_COMPLETED_RU = u8"Музыка успешно загружена."; + static int questStatusPriority(Quest::QuestStatus status) { switch (status) { @@ -112,6 +125,78 @@ namespace FRG { } } +#ifdef __EMSCRIPTEN__ + // Коллбэк успешной загрузки music.zip + void MenuManager::onMusicZipLoaded(unsigned /*handle*/, void* /*userData*/, const char* /*filename*/) { + logger() << "File music.zip downloaded successfully to VFS" << std::endl; + FRG::emscriptenSyncFs(); + if (Game::s_instance != nullptr) + { + Game::s_instance->menuManager.musicDownloadProgress = 1.f; + Game::s_instance->menuManager.musicDownloadStarted = false; + Game::s_instance->menuManager.musicDownloadError = false; + logger() << "onMusicZipLoaded step 2: " << Game::s_instance->menuManager.lastMusicTrack << std::endl; + if (Game::s_instance->menuManager.lastMusicTrack != "") + { + logger() << "onMusicZipLoaded step 3" << std::endl; + + Game::s_instance->menuManager.audioPlayer_.crossFadeMusicAsync(Game::s_instance->menuManager.lastMusicTrack); + } + logger() << "onMusicZipLoaded step 4" << std::endl; + + if (Game::s_instance->menuManager.playerInSettingsNoMusic) + { + if (Game::s_instance->menuManager.playerInSettingsNoMusicGraphics) + { + Game::s_instance->menuManager.uiManager.popMenu(); + Game::s_instance->menuManager.uiManager.popMenu(); + Game::s_instance->menuManager.showSettingsScreen(); + Game::s_instance->menuManager.showSettingsGraphicsScreen(); + Game::s_instance->menuManager.uiManager.updateAllLayouts(); + Game::s_instance->menuManager.topUiManager.updateAllLayouts(); + } + else + { + Game::s_instance->menuManager.uiManager.popMenu(); + Game::s_instance->menuManager.showSettingsScreen(); + Game::s_instance->menuManager.uiManager.updateAllLayouts(); + Game::s_instance->menuManager.topUiManager.updateAllLayouts(); + } + } + Game::s_instance->menuManager.playerInSettingsNoMusic = false; + Game::s_instance->menuManager.playerInSettingsNoMusicGraphics = false; + //Game::s_instance->menuManager.uiManager.setText("musicDownloadButton", musicDownloadCompleted); + } + } + + void MenuManager::onMusicZipError(unsigned /*handle*/, void* /*userData*/, int httpStatus) { + logger() << "Failed to download music.zip, HTTP status: " << httpStatus << std::endl; + if (Game::s_instance != nullptr) + { + std::string musicDownloadError = MUSIC_DOWNLOAD_ERROR_RU; + + Game::s_instance->menuManager.musicDownloadStarted = false; + Game::s_instance->menuManager.musicDownloadError = true; + Game::s_instance->menuManager.uiManager.setText("musicDownloadButton", + musicDownloadError); + + } + } + + void MenuManager::onMusicZipProgress(unsigned /*handle*/, void* /*userData*/, int percentComplete) { + // Прогресс для музыки при необходимости + if (Game::s_instance != nullptr) + { + std::string musicDownloadProgressPrefix = MUSIC_DOWNLOAD_PROGRESS_PREFIX_RU; + + Game::s_instance->menuManager.musicDownloadProgress = percentComplete / 100.0f; + Game::s_instance->menuManager.uiManager.setText("musicDownloadButton", + musicDownloadProgressPrefix + std::to_string(percentComplete) + "%"); + + } + } +#endif + MenuManager::MenuManager(Renderer& iRenderer, GameState& gameState, AudioPlayerAsync& audioPlayer) : renderer(iRenderer), gameState_(gameState), @@ -254,7 +339,13 @@ namespace FRG { creditsScreenRoot = loadLocalizedUi("resources/w/ui/screen_credits.json", zipFile); portraitCreditsScreenRoot = loadLocalizedUi("resources/w/ui/portrait_screen_credits.json", zipFile); + +#ifdef __EMSCRIPTEN__ + settingsScreenNoMusicWebRoot = loadLocalizedUi("resources/w/ui/screen_settings_nomusic_web.json", zipFile); +#endif settingsScreenRoot = loadLocalizedUi("resources/w/ui/screen_settings.json", zipFile); + + settingsGraphicsScreenRoot = loadLocalizedUi("resources/w/ui/screen_settings_graphics.json", zipFile); languageScreenRoot = loadLocalizedUi("resources/w/ui/screen_language.json", zipFile); loadSavedGameScreenRoot = loadLocalizedUi("resources/w/ui/screen_load.json", zipFile); @@ -375,7 +466,8 @@ namespace FRG { void MenuManager::showMainMenu() { gameState_.currentLocationName.clear(); - audioPlayer_.crossFadeMusicAsync("audio/main menu final.ogg"); + audioPlayer_.crossFadeMusicAsync("music/main menu final.ogg"); + lastMusicTrack = "music/main menu final.ogg"; uiState_ = GameUiState::MainMenu; uiManager.clearMenuStack(); @@ -386,36 +478,46 @@ namespace FRG { uiManager.setTextButtonCallback("menuStartButton", [this](const std::string&) { - audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); + audioPlayer_.playSoundAsync("resources/audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); enterGameplay(); }); uiManager.setTextButtonCallback("menuAboutButton", [this](const std::string&) { showAboutScreen(); - audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); + audioPlayer_.playSoundAsync("resources/audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); }); uiManager.setTextButtonCallback("menuCreditsButton", [this](const std::string&) { showCreditsScreen(); - audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); + audioPlayer_.playSoundAsync("resources/audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); }); uiManager.setTextButtonCallback("menuSettingsButton", [this](const std::string&) { +#ifdef __EMSCRIPTEN__ + std::string musicPath = FRG::getPersistentResourcePath("music.zip"); + if (FRG::isZipValid(musicPath)) { + showSettingsScreen(); + } + else { + showSettingsNoMusicWebScreen(); + } +#else showSettingsScreen(); - audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); +#endif + audioPlayer_.playSoundAsync("resources/audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); }); uiManager.setTextButtonCallback("menuLanguageButton", [this](const std::string&) { showLanguageScreen(); - audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); + audioPlayer_.playSoundAsync("resources/audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); }); uiManager.setTextButtonCallback("menuLoadButton", [this](const std::string&) { showLoadGameScreen(); - audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); + audioPlayer_.playSoundAsync("resources/audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); }); #if !defined(EMSCRIPTEN) && !defined(__ANDROID__) uiManager.setTextButtonCallback("menuExitButton", [this](const std::string&) { - audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); + audioPlayer_.playSoundAsync("resources/audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); Environment::exitGameLoop = true; }); #else @@ -426,7 +528,8 @@ namespace FRG { void MenuManager::showMenuAfterGameCompleted() { //gameState_.currentLocationName.clear(); - audioPlayer_.crossFadeMusicAsync("audio/main menu final.ogg"); + audioPlayer_.crossFadeMusicAsync("music/main menu final.ogg"); + lastMusicTrack = "music/main menu final.ogg"; uiState_ = GameUiState::MainMenu; uiManager.clearMenuStack(); @@ -437,36 +540,36 @@ namespace FRG { uiManager.setTextButtonCallback("menuStartButton", [this](const std::string&) { - audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); + audioPlayer_.playSoundAsync("resources/audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); enterGameplay(); }); uiManager.setTextButtonCallback("menuAboutButton", [this](const std::string&) { showAboutScreen(); - audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); + audioPlayer_.playSoundAsync("resources/audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); }); uiManager.setTextButtonCallback("menuCreditsButton", [this](const std::string&) { showCreditsScreen(); - audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); + audioPlayer_.playSoundAsync("resources/audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); }); uiManager.setTextButtonCallback("menuSettingsButton", [this](const std::string&) { showSettingsScreen(); - audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); + audioPlayer_.playSoundAsync("resources/audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); }); uiManager.setTextButtonCallback("menuLanguageButton", [this](const std::string&) { showLanguageScreen(); - audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); + audioPlayer_.playSoundAsync("resources/audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); }); uiManager.setTextButtonCallback("menuLoadButton", [this](const std::string&) { showLoadGameScreen(); - audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); + audioPlayer_.playSoundAsync("resources/audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); }); #ifndef EMSCRIPTEN uiManager.setTextButtonCallback("menuExitButton", [this](const std::string&) { - audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); + audioPlayer_.playSoundAsync("resources/audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); Environment::exitGameLoop = true; }); #else @@ -479,21 +582,21 @@ namespace FRG { uiManager.setTextButtonCallback("aboutBackButton", [this](const std::string&) { uiManager.popMenu(); uiManager.updateAllLayouts(); - audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); + audioPlayer_.playSoundAsync("resources/audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); }); uiManager.setTextButtonCallback("steamButton", [this](const std::string&) { - audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); + audioPlayer_.playSoundAsync("resources/audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); SDL_OpenURL("https://store.steampowered.com/app/4801010/"); }); uiManager.setTextButtonCallback("telegramButton", [this](const std::string&) { - audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); + audioPlayer_.playSoundAsync("resources/audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); SDL_OpenURL("https://telegram.me/fishrungames"); }); uiManager.setTextButtonCallback("discordButton", [this](const std::string&) { - audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); + audioPlayer_.playSoundAsync("resources/audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); SDL_OpenURL("https://discord.gg/S8jgGHXjw"); }); } @@ -503,29 +606,29 @@ namespace FRG { uiManager.setTextButtonCallback("aboutBackButton", [this](const std::string&) { uiManager.popMenu(); uiManager.updateAllLayouts(); - audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); + audioPlayer_.playSoundAsync("resources/audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); }); uiManager.setTextButtonCallback("steamButton", [this](const std::string&) { - audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); + audioPlayer_.playSoundAsync("resources/audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); SDL_OpenURL("https://store.steampowered.com/app/4801010/"); }); #ifdef __ANDROID__ uiManager.setTextButtonCallback("websiteButton", [this](const std::string&) { - audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); + audioPlayer_.playSoundAsync("resources/audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); SDL_OpenURL("https://shadowoverbishkek.com/"); }); #endif uiManager.setTextButtonCallback("telegramButton", [this](const std::string&) { - audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); + audioPlayer_.playSoundAsync("resources/audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); SDL_OpenURL("https://telegram.me/fishrungames"); }); uiManager.setTextButtonCallback("discordButton", [this](const std::string&) { - audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); + audioPlayer_.playSoundAsync("resources/audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); SDL_OpenURL("https://discord.gg/S8jgGHXjw"); }); } @@ -561,7 +664,7 @@ namespace FRG { isPlayerInCreditsMenu = false; uiManager.popMenu(); uiManager.updateAllLayouts(); - audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); + audioPlayer_.playSoundAsync("resources/audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); }); uiManager.updateAllLayouts(); @@ -577,12 +680,12 @@ namespace FRG { saveSettings(); uiManager.popMenu(); uiManager.updateAllLayouts(); - audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); + audioPlayer_.playSoundAsync("resources/audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); }); uiManager.setTextButtonCallback("graphicsButton", [this](const std::string&) { showSettingsGraphicsScreen(); - audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); + audioPlayer_.playSoundAsync("resources/audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); }); // Set initial slider positions before registering callbacks so @@ -658,7 +761,7 @@ namespace FRG { /*uiManager.setTextButtonColor("musicToggleButton", on ? std::array{ 0.2f, 0.9f, 0.2f, 1.0f } : std::array{ 0.9f, 0.2f, 0.2f, 1.0f });*/ - audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); + audioPlayer_.playSoundAsync("resources/audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); }); uiManager.setTextButtonCallback("soundToggleButton", [this](const std::string&) { audioPlayer_.setSoundEnabled(!audioPlayer_.isSoundEnabled()); @@ -667,7 +770,7 @@ namespace FRG { /*uiManager.setTextButtonColor("soundToggleButton", on ? std::array{ 0.2f, 0.9f, 0.2f, 1.0f } : std::array{ 0.9f, 0.2f, 0.2f, 1.0f });*/ - audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); + audioPlayer_.playSoundAsync("resources/audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); }); } @@ -676,9 +779,12 @@ namespace FRG { uiManager.setTextButtonCallback("settingsBackButton", [this](const std::string&) { saveSettings(); uiManager.popMenu(); - audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); + audioPlayer_.playSoundAsync("resources/audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); uiManager.updateAllLayouts(); topUiManager.updateAllLayouts(); +#ifdef __EMSCRIPTEN__ + playerInSettingsNoMusicGraphics = false; +#endif }); @@ -718,7 +824,7 @@ namespace FRG { uiManager.setTextButtonCallback("fullScreenToggleButton", [this, refreshFullscreenToggle](const std::string&) { Environment::setFullscreen(!Environment::isFullscreen); refreshFullscreenToggle(); - audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); + audioPlayer_.playSoundAsync("resources/audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); uiManager.updateAllLayouts(); topUiManager.updateAllLayouts(); }); @@ -728,27 +834,148 @@ namespace FRG { bool newValue = !this->shadowsEnabled; shadowMapSettingsChangedFunc(newValue); refreshShadowToggle(); - audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); + audioPlayer_.playSoundAsync("resources/audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); }); } + +#ifdef __EMSCRIPTEN__ + void MenuManager::showSettingsNoMusicWebScreen() + { + playerInSettingsNoMusic = true; + uiManager.pushMenuFromSavedRoot(settingsScreenNoMusicWebRoot); + uiManager.setTextButtonCallback("settingsBackButton", [this](const std::string&) { + saveSettings(); + uiManager.popMenu(); + uiManager.updateAllLayouts(); + audioPlayer_.playSoundAsync("resources/audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); + playerInSettingsNoMusic = false; + }); + + uiManager.setTextButtonCallback("graphicsButton", [this](const std::string&) { + playerInSettingsNoMusicGraphics = true; + showSettingsGraphicsScreen(); + audioPlayer_.playSoundAsync("resources/audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); + }); + + // Set initial slider positions before registering callbacks so + // setSliderValue does not fire an unregistered callback. + const float soundFrac = audioPlayer_.getSoundVolume() / 128.0f; + uiManager.setSliderValue("soundVolumeSlider", soundFrac); + + // Localization + if (g_currentLanguage == Language::English) + { + soundVolumePrefix = "Sound volume: "; + soundToggleOn = "Sound: ON"; + soundToggleOff = "Sound: OFF"; + } + else if (g_currentLanguage == Language::Russian) + { + soundVolumePrefix = u8"Громкость звука: "; + soundToggleOn = u8"Звук: ВКЛ"; + soundToggleOff = u8"Звук: ВЫКЛ"; + } + + static std::string musicDownloadErrorString = u8"Ошибка скачивания музыки"; + static std::string musicDownloadNotStartedString = u8"Нажмите сюда, чтобы скачать\nи установить музыку (27 MB)."; + static std::string musicDownloadProgressPrefix = u8"Загрузка музыки: "; + + // musicDownloadStarted = false; + //musicDownloadError = false; + + if (musicDownloadError) + { + uiManager.setText("musicDownloadButton", musicDownloadErrorString); + + } + else + { + if (musicDownloadStarted == false) + { + uiManager.setText("musicDownloadButton", musicDownloadNotStartedString); + } + else + { + //uiManager.setText("musicDownloadButton", musicDownloadProgressString); + uiManager.setText("musicDownloadButton", + musicDownloadProgressPrefix + std::to_string(round(musicDownloadProgress*100)) + "%"); + } + } + + uiManager.setText("soundVolumeText", + soundVolumePrefix + std::to_string(audioPlayer_.getSoundVolume())); + + uiManager.setSliderCallback("soundVolumeSlider", + [this](const std::string&, float value) { + const int vol = static_cast(value * 128.0f + 0.5f); + audioPlayer_.setSoundVolume(vol); + uiManager.setText("soundVolumeText", + soundVolumePrefix + std::to_string(vol)); + }); + + // Music on/off toggle — helper to sync button text and color from current state + auto refreshSoundToggle = [this]() { + const bool on = audioPlayer_.isSoundEnabled(); + uiManager.setTextButtonText("soundToggleButton", on ? soundToggleOn : soundToggleOff); + /*uiManager.setTextButtonColor("soundToggleButton", on + ? std::array{ 0.2f, 0.9f, 0.2f, 1.0f } + : std::array{ 0.9f, 0.2f, 0.2f, 1.0f });*/ + }; + + refreshSoundToggle(); + + + uiManager.setTextButtonCallback("soundToggleButton", [this](const std::string&) { + audioPlayer_.setSoundEnabled(!audioPlayer_.isSoundEnabled()); + const bool on = audioPlayer_.isSoundEnabled(); + uiManager.setTextButtonText("soundToggleButton", on ? soundToggleOn : soundToggleOff); + /*uiManager.setTextButtonColor("soundToggleButton", on + ? std::array{ 0.2f, 0.9f, 0.2f, 1.0f } + : std::array{ 0.9f, 0.2f, 0.2f, 1.0f });*/ + audioPlayer_.playSoundAsync("resources/audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); + }); + + uiManager.setTextButtonCallback("musicDownloadButton", [this](const std::string&) { + if (musicDownloadStarted) return; // Already downloading, ignore further clicks + + musicDownloadStarted = true; + musicDownloadError = false; + + std::string musicPath = FRG::getPersistentResourcePath("music.zip"); + + emscripten_async_wget2( + "music.zip", + musicPath.c_str(), + "GET", + nullptr, + nullptr, + onMusicZipLoaded, + onMusicZipError, + onMusicZipProgress + ); + audioPlayer_.playSoundAsync("resources/audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); + }); + } +#endif + void MenuManager::showLanguageScreen() { uiManager.pushMenuFromSavedRoot(languageScreenRoot); uiManager.setTextButtonCallback("languageBackButton", [this](const std::string&) { saveSettings(); uiManager.popMenu(); uiManager.updateAllLayouts(); - audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); + audioPlayer_.playSoundAsync("resources/audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); }); uiManager.setTextButtonCallback("languageRussianButton", [this](const std::string&) { - audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); + audioPlayer_.playSoundAsync("resources/audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); FRG::g_currentLanguage = FRG::Language::Russian; reloadLocalizedGameContent(); saveSettings(); }); uiManager.setTextButtonCallback("languageEnglishButton", [this](const std::string&) { - audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); + audioPlayer_.playSoundAsync("resources/audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); FRG::g_currentLanguage = FRG::Language::English; reloadLocalizedGameContent(); saveSettings(); @@ -810,7 +1037,7 @@ namespace FRG { uiManager.setTextButtonCallback("loadBackButton", [this](const std::string&) { uiManager.popMenu(); uiManager.updateAllLayouts(); - audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); + audioPlayer_.playSoundAsync("resources/audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); }); static const char* kSlotButtons[4] = { @@ -837,7 +1064,7 @@ namespace FRG { uiManager.setTextButtonText(kSlotButtons[i], label); } uiManager.setTextButtonCallback(kSlotButtons[i], [this, slot](const std::string&) { - audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); + audioPlayer_.playSoundAsync("resources/audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); if (onLoadGame) onLoadGame(slot); enterGameplay(true); }); @@ -850,7 +1077,7 @@ namespace FRG { uiManager.setTextButtonCallback("saveBackButton", [this](const std::string&) { uiManager.popMenu(); uiManager.updateAllLayouts(); - audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); + audioPlayer_.playSoundAsync("resources/audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); }); static const char* kSlotButtons[4] = { @@ -896,7 +1123,7 @@ namespace FRG { ? emptyText : localizedLocationName[info.locationName][FRG::g_currentLanguage] + " " + info.savedAt; uiManager.setTextButtonText(buttonName, label); - audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); + audioPlayer_.playSoundAsync("resources/audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); } }); } @@ -941,7 +1168,7 @@ namespace FRG { selectInventoryItem(i); //logger() << "Callback called for button number " << i << " END" << std::endl; - audioPlayer_.playSoundAsync("audio/sound_bag001.ogg"); + audioPlayer_.playSoundAsync("resources/audio/sound_bag001.ogg"); }); } else { uiManager.setNodeVisible(btnName, false); @@ -953,7 +1180,7 @@ namespace FRG { selectInventoryItem(0); } - audioPlayer_.playSoundAsync("audio/sound_bag_open.ogg"); + audioPlayer_.playSoundAsync("resources/audio/sound_bag_open.ogg"); } void MenuManager::selectInventoryItem(int index) { @@ -1005,7 +1232,7 @@ namespace FRG { for (int i = 0; i < inventoryMenuStackDepth_; ++i) uiManager.popMenu(); inventoryMenuStackDepth_ = 0; uiManager.updateAllLayouts(); - audioPlayer_.playSoundAsync("audio/sound_bag_close.ogg"); + audioPlayer_.playSoundAsync("resources/audio/sound_bag_close.ogg"); } void MenuManager::openQuestJournal() { @@ -1021,12 +1248,12 @@ namespace FRG { uiManager.setButtonCallback("journalExitButton", [this](const std::string&) { closeQuestJournal(); - audioPlayer_.playSoundAsync("audio/656126__itsthegoodstuff__paging-through-book.ogg"); + audioPlayer_.playSoundAsync("resources/audio/656126__itsthegoodstuff__paging-through-book.ogg"); }); uiManager.setButtonCallback("journalExitButton2", [this](const std::string&) { closeQuestJournal(); - audioPlayer_.playSoundAsync("audio/656126__itsthegoodstuff__paging-through-book.ogg"); + audioPlayer_.playSoundAsync("resources/audio/656126__itsthegoodstuff__paging-through-book.ogg"); }); @@ -1041,7 +1268,7 @@ namespace FRG { for (int i = 0; i < 9; ++i) { uiManager.setTextButtonCallback(kItemNames[i], [this, i](const std::string&) { selectQuestByIndex(i); - audioPlayer_.playSoundAsync("audio/255571__stradie__flipping-paper.ogg"); + audioPlayer_.playSoundAsync("resources/audio/255571__stradie__flipping-paper.ogg"); }); } @@ -1050,7 +1277,7 @@ namespace FRG { selectQuestByIndex(0); } - audioPlayer_.playSoundAsync("audio/255571__stradie__flipping-paper.ogg"); + audioPlayer_.playSoundAsync("resources/audio/255571__stradie__flipping-paper.ogg"); } void MenuManager::closeQuestJournal() { @@ -1101,24 +1328,24 @@ namespace FRG { uiManager.setButtonCallback("phoneExitButton", [this](const std::string&) { closePhoneEntirely(); - audioPlayer_.playSoundAsync("audio/537061__imafoley__message-pop-sound.ogg"); + audioPlayer_.playSoundAsync("resources/audio/537061__imafoley__message-pop-sound.ogg"); }); uiManager.setButtonCallback("phoneMain", [this](const std::string&) {}); uiManager.setButtonCallback("phoneMessenger", [this](const std::string&) { openPhoneMessenger(); - audioPlayer_.playSoundAsync("audio/537061__imafoley__message-pop-sound.ogg"); + audioPlayer_.playSoundAsync("resources/audio/537061__imafoley__message-pop-sound.ogg"); }); uiManager.setButtonCallback("phoneBank", [this](const std::string&) { openPhoneBank(); - audioPlayer_.playSoundAsync("audio/537061__imafoley__message-pop-sound.ogg"); + audioPlayer_.playSoundAsync("resources/audio/537061__imafoley__message-pop-sound.ogg"); }); uiManager.setButtonCallback("phoneVideo", [this](const std::string&) { openPhoneVideo(); - audioPlayer_.playSoundAsync("audio/537061__imafoley__message-pop-sound.ogg"); + audioPlayer_.playSoundAsync("resources/audio/537061__imafoley__message-pop-sound.ogg"); }); uiManager.setButtonCallback("phoneTaxi", [this](const std::string&) { openPhoneTaxi(); - audioPlayer_.playSoundAsync("audio/537061__imafoley__message-pop-sound.ogg"); + audioPlayer_.playSoundAsync("resources/audio/537061__imafoley__message-pop-sound.ogg"); }); if (gameState_.isNight) @@ -1143,7 +1370,7 @@ namespace FRG { uiManager.setNodeVisible("phoneTimeDawn", false); } - audioPlayer_.playSoundAsync("audio/537061__imafoley__message-pop-sound.ogg"); + audioPlayer_.playSoundAsync("resources/audio/537061__imafoley__message-pop-sound.ogg"); } void MenuManager::openPhoneMessenger() { @@ -1161,23 +1388,23 @@ namespace FRG { uiManager.setButtonCallback("phoneExitButton", [this](const std::string&) { closePhoneEntirely(); - audioPlayer_.playSoundAsync("audio/537061__imafoley__message-pop-sound.ogg"); + audioPlayer_.playSoundAsync("resources/audio/537061__imafoley__message-pop-sound.ogg"); }); uiManager.setButtonCallback("phoneMain", [this](const std::string&) {}); uiManager.setTextButtonCallback("chat1button", [this](const std::string&) { gameState_.chatUnread[0] = false; openPhoneChatFromList(0, isPortraitMode() ? verticalPhoneChat1Root : phoneChat1Root); - audioPlayer_.playSoundAsync("audio/537061__imafoley__message-pop-sound.ogg"); + audioPlayer_.playSoundAsync("resources/audio/537061__imafoley__message-pop-sound.ogg"); }); uiManager.setTextButtonCallback("chat2button", [this](const std::string&) { gameState_.chatUnread[1] = false; openPhoneChatFromList(1, isPortraitMode() ? verticalPhoneChat2Root : phoneChat2Root); - audioPlayer_.playSoundAsync("audio/537061__imafoley__message-pop-sound.ogg"); + audioPlayer_.playSoundAsync("resources/audio/537061__imafoley__message-pop-sound.ogg"); }); uiManager.setTextButtonCallback("chat3button", [this](const std::string&) { gameState_.chatUnread[2] = false; openPhoneChatFromList(2, isPortraitMode() ? verticalPhoneChat3Root : phoneChat3Root); - audioPlayer_.playSoundAsync("audio/537061__imafoley__message-pop-sound.ogg"); + audioPlayer_.playSoundAsync("resources/audio/537061__imafoley__message-pop-sound.ogg"); }); } @@ -1213,13 +1440,13 @@ namespace FRG { uiManager.setButtonCallback("phoneExitButton", [this](const std::string&) { closePhoneEntirely(); - audioPlayer_.playSoundAsync("audio/537061__imafoley__message-pop-sound.ogg"); + audioPlayer_.playSoundAsync("resources/audio/537061__imafoley__message-pop-sound.ogg"); }); uiManager.setButtonCallback("phoneMain", [this](const std::string&) {}); uiManager.setButtonCallback("buttonBack", [this](const std::string&) { uiManager.popMenu(); uiManager.updateAllLayouts(); - audioPlayer_.playSoundAsync("audio/537061__imafoley__message-pop-sound.ogg"); + audioPlayer_.playSoundAsync("resources/audio/537061__imafoley__message-pop-sound.ogg"); }); } @@ -1229,13 +1456,13 @@ namespace FRG { uiManager.setButtonCallback("phoneExitButton", [this](const std::string&) { closePhoneEntirely(); - audioPlayer_.playSoundAsync("audio/537061__imafoley__message-pop-sound.ogg"); + audioPlayer_.playSoundAsync("resources/audio/537061__imafoley__message-pop-sound.ogg"); }); uiManager.setButtonCallback("phoneMain", [this](const std::string&) {}); uiManager.setButtonCallback("buttonBack", [this](const std::string&) { uiManager.popMenu(); uiManager.updateAllLayouts(); - audioPlayer_.playSoundAsync("audio/537061__imafoley__message-pop-sound.ogg"); + audioPlayer_.playSoundAsync("resources/audio/537061__imafoley__message-pop-sound.ogg"); }); uiManager.setButtonCallback("videoSkip", [this](const std::string&) { closePhoneEntirely(); @@ -1252,7 +1479,7 @@ namespace FRG { } else { - audioPlayer_.playSoundAsync("audio/78564__joedeshon__alarm_clock_ticking_02.ogg"); + audioPlayer_.playSoundAsync("resources/audio/78564__joedeshon__alarm_clock_ticking_02.ogg"); startNightTransitionFunc(); } } @@ -1277,13 +1504,13 @@ namespace FRG { uiManager.setButtonCallback("phoneExitButton", [this](const std::string&) { closePhoneEntirely(); - audioPlayer_.playSoundAsync("audio/537061__imafoley__message-pop-sound.ogg"); + audioPlayer_.playSoundAsync("resources/audio/537061__imafoley__message-pop-sound.ogg"); }); uiManager.setButtonCallback("phoneMain", [this](const std::string&) {}); uiManager.setButtonCallback("buttonBack", [this](const std::string&) { uiManager.popMenu(); uiManager.updateAllLayouts(); - audioPlayer_.playSoundAsync("audio/537061__imafoley__message-pop-sound.ogg"); + audioPlayer_.playSoundAsync("resources/audio/537061__imafoley__message-pop-sound.ogg"); }); uiManager.setButtonCallback("mapGo", [this](const std::string&) { gameState_.tutorialNeedOpenTaxiScreen = false; @@ -1298,17 +1525,17 @@ namespace FRG { gameState_.money -= 500; closePhoneEntirely(); if (startDialogueFunc) startDialogueFunc("dialog_taxi002"); - audioPlayer_.playSoundAsync("audio/537061__imafoley__message-pop-sound.ogg"); + audioPlayer_.playSoundAsync("resources/audio/537061__imafoley__message-pop-sound.ogg"); } else { closePhoneEntirely(); if (startDialogueFunc) startDialogueFunc("dialog_taxi004"); - audioPlayer_.playSoundAsync("audio/537061__imafoley__message-pop-sound.ogg"); + audioPlayer_.playSoundAsync("resources/audio/537061__imafoley__message-pop-sound.ogg"); } }); - audioPlayer_.playSoundAsync("audio/537061__imafoley__message-pop-sound.ogg"); + audioPlayer_.playSoundAsync("resources/audio/537061__imafoley__message-pop-sound.ogg"); } void MenuManager::openPhoneChatFromList(int chatIndex, std::shared_ptr chatRoot) { @@ -1348,12 +1575,12 @@ namespace FRG { uiManager.setButtonCallback("phoneExitButton", [this](const std::string&) { closePhoneScreenFromChat(); - audioPlayer_.playSoundAsync("audio/537061__imafoley__message-pop-sound.ogg"); + audioPlayer_.playSoundAsync("resources/audio/537061__imafoley__message-pop-sound.ogg"); }); uiManager.setButtonCallback("phoneMain", [this](const std::string&) {}); uiManager.setTextButtonCallback("chatTitleButton", [this](const std::string&) { returnToPhoneChatList(); - audioPlayer_.playSoundAsync("audio/537061__imafoley__message-pop-sound.ogg"); + audioPlayer_.playSoundAsync("resources/audio/537061__imafoley__message-pop-sound.ogg"); }); if (chatOpenCallback) { @@ -1437,37 +1664,37 @@ namespace FRG { } void MenuManager::showModalMenuScreen() { - audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); + audioPlayer_.playSoundAsync("resources/audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); uiManager.pushMenuFromSavedRoot(modalMenuRoot); uiManager.setButtonCallback("modalExitButton", [this](const std::string&) { - audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); + audioPlayer_.playSoundAsync("resources/audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); uiManager.popMenu(); uiManager.updateAllLayouts(); }); uiManager.setButtonCallback("menuCloseButton", [this](const std::string&) { - audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); + audioPlayer_.playSoundAsync("resources/audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); uiManager.popMenu(); uiManager.updateAllLayouts(); }); uiManager.setButtonCallback("menuSaveButton", [this](const std::string&) { showSaveGameScreen(); - audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); + audioPlayer_.playSoundAsync("resources/audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); }); uiManager.setButtonCallback("menuLoadButton", [this](const std::string&) { showLoadGameScreen(); - audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); + audioPlayer_.playSoundAsync("resources/audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); }); uiManager.setButtonCallback("menuSettingsButton", [this](const std::string&) { showSettingsScreen(); - audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); + audioPlayer_.playSoundAsync("resources/audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); }); uiManager.setButtonCallback("menuExitButton", [this](const std::string&) { - audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); + audioPlayer_.playSoundAsync("resources/audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); uiManager.popMenu(); showMainMenu(); }); @@ -1579,29 +1806,35 @@ namespace FRG { setupGameplayHudCallbacks(); if (gameState_.isDarklands) { - audioPlayer_.crossFadeMusicAsync("audio/bishkek fight calm.ogg"); + audioPlayer_.crossFadeMusicAsync("music/bishkek fight calm.ogg"); + lastMusicTrack = "music/bishkek fight calm.ogg"; } else if (gameState_.isNight) { - audioPlayer_.crossFadeMusicAsync("audio/bishkek night.ogg"); + audioPlayer_.crossFadeMusicAsync("music/bishkek night.ogg"); + lastMusicTrack = "music/bishkek night.ogg"; } else { - audioPlayer_.crossFadeMusicAsync("audio/bishkek univer day.ogg"); + audioPlayer_.crossFadeMusicAsync("music/bishkek univer day.ogg"); + lastMusicTrack = "music/bishkek univer day.ogg"; } } else if (locationName == "uni_interior") { applyUniIntHud(); if (gameState_.isDarklands) { - audioPlayer_.crossFadeMusicAsync("audio/bishkek fight calm.ogg"); + audioPlayer_.crossFadeMusicAsync("music/bishkek fight calm.ogg"); + lastMusicTrack = "music/bishkek fight calm.ogg"; } else if (gameState_.isNight) { - audioPlayer_.crossFadeMusicAsync("audio/bishkek night.ogg"); + audioPlayer_.crossFadeMusicAsync("music/bishkek night.ogg"); + lastMusicTrack = "music/bishkek night.ogg"; } else { - audioPlayer_.crossFadeMusicAsync("audio/bishkek univer day.ogg"); + audioPlayer_.crossFadeMusicAsync("music/bishkek univer day.ogg"); + lastMusicTrack = "music/bishkek univer day.ogg"; } } else { // Returning to dorm: reuse step5ab, suppress already-completed hints @@ -1611,11 +1844,13 @@ namespace FRG { if (gameState_.isNight) { - audioPlayer_.crossFadeMusicAsync("audio/bishkek night.ogg"); + audioPlayer_.crossFadeMusicAsync("music/bishkek night.ogg"); + lastMusicTrack = "music/bishkek night.ogg"; } else { - audioPlayer_.crossFadeMusicAsync("audio/obshaga.ogg"); + audioPlayer_.crossFadeMusicAsync("music/obshaga.ogg"); + lastMusicTrack = "music/obshaga.ogg"; } } } @@ -1844,12 +2079,12 @@ namespace FRG { uiManager.setButtonCallback("inventoryExitButton", [this](const std::string&) { closePortraitInventoryItem(); - audioPlayer_.playSoundAsync("audio/sound_bag001.ogg"); + audioPlayer_.playSoundAsync("resources/audio/sound_bag001.ogg"); }); uiManager.setButtonCallback("inventoryExitButton2", [this](const std::string&) { closePortraitInventoryItem(); - audioPlayer_.playSoundAsync("audio/sound_bag001.ogg"); + audioPlayer_.playSoundAsync("resources/audio/sound_bag001.ogg"); }); uiManager.setButtonCallback("inventoryMain", [this](const std::string&) {}); @@ -1880,12 +2115,12 @@ namespace FRG { uiManager.setButtonCallback("journalItemExitButton", [this](const std::string&) { closePortraitJournalItem(); - audioPlayer_.playSoundAsync("audio/255571__stradie__flipping-paper.ogg"); + audioPlayer_.playSoundAsync("resources/audio/255571__stradie__flipping-paper.ogg"); }); uiManager.setButtonCallback("journalItemExitButton2", [this](const std::string&) { closeQuestJournal(); - audioPlayer_.playSoundAsync("audio/656126__itsthegoodstuff__paging-through-book.ogg"); + audioPlayer_.playSoundAsync("resources/audio/656126__itsthegoodstuff__paging-through-book.ogg"); }); @@ -2085,7 +2320,7 @@ namespace FRG { recomputePhoneChatPositions(); } chatUiManager.startPopIn(nodeName, 300.0f); - audioPlayer_.playSoundAsync("audio/537061__imafoley__message-pop-sound.ogg"); + audioPlayer_.playSoundAsync("resources/audio/537061__imafoley__message-pop-sound.ogg"); } void MenuManager::setDarklandsMode(bool enabled) diff --git a/src/MenuManager.h b/src/MenuManager.h index f327ef1..b78a503 100644 --- a/src/MenuManager.h +++ b/src/MenuManager.h @@ -130,7 +130,20 @@ namespace FRG { std::shared_ptr languageLoadingEn; bool shadowsEnabled = true; + std::string lastMusicTrack; +#ifdef __EMSCRIPTEN__ + static void onMusicZipLoaded(unsigned /*handle*/, void* /*userData*/, const char* /*filename*/); + static void onMusicZipError(unsigned /*handle*/, void* /*userData*/, int httpStatus); + static void onMusicZipProgress(unsigned /*handle*/, void* /*userData*/, int percentComplete); + + float musicDownloadProgress = 0.0f; + + bool musicDownloadStarted = false; + bool musicDownloadError = false; + bool playerInSettingsNoMusic = false; + bool playerInSettingsNoMusicGraphics = false; +#endif protected: Renderer& renderer; @@ -176,6 +189,10 @@ namespace FRG { void showCreditsScreen(); void showSettingsScreen(); void showSettingsGraphicsScreen(); + +#ifdef __EMSCRIPTEN__ + void showSettingsNoMusicWebScreen(); +#endif void showLanguageScreen(); void showLoadGameScreen(); void showSaveGameScreen(); @@ -268,6 +285,9 @@ namespace FRG { std::shared_ptr creditsScreenRoot; std::shared_ptr portraitCreditsScreenRoot; +#ifdef __EMSCRIPTEN__ + std::shared_ptr settingsScreenNoMusicWebRoot; +#endif std::shared_ptr settingsScreenRoot; std::shared_ptr settingsGraphicsScreenRoot; std::shared_ptr languageScreenRoot; @@ -343,6 +363,7 @@ namespace FRG { std::string fullScreenToggleOff; std::string shadowToggleOn; std::string shadowToggleOff; + std::string musicDownloadProgressPrefix; }; diff --git a/src/utils/Utils.cpp b/src/utils/Utils.cpp index f2d550a..dadefb4 100644 --- a/src/utils/Utils.cpp +++ b/src/utils/Utils.cpp @@ -105,12 +105,20 @@ namespace FRG } std::vector readFileFromZIP(const std::string& filename, const std::string& zipfilename) { + if (zipfilename.empty()) { + logger() << "readFileFromZIP: zipfilename empty, reading from file directly..." << std::endl; + return readFile(filename); + } + #ifdef EMSCRIPTEN + + std::string webZipFilename = getPersistentResourcePath(zipfilename); + int zipErr = 0; - zip_t* archive = zip_open(zipfilename.c_str(), ZIP_RDONLY, &zipErr); + zip_t* archive = zip_open(webZipFilename.c_str(), ZIP_RDONLY, &zipErr); if (!archive) { - logger() << "Failed to open ZIP: " << zipfilename << " (error code " << zipErr << ")" << std::endl; - throw std::runtime_error("Failed to open ZIP: " + zipfilename); + logger() << "Failed to open ZIP: " << webZipFilename << " (error code " << zipErr << ")" << std::endl; + throw std::runtime_error("Failed to open ZIP: " + webZipFilename); } std::string cleanFilename = filename; @@ -120,14 +128,14 @@ namespace FRG zip_stat_init(&fileStat); if (zip_stat(archive, cleanFilename.c_str(), 0, &fileStat) != 0 || !(fileStat.valid & ZIP_STAT_SIZE)) { zip_close(archive); - logger() << "Failed to stat file in ZIP: " << cleanFilename << " in " << zipfilename << std::endl; + logger() << "Failed to stat file in ZIP: " << cleanFilename << " in " << webZipFilename << std::endl; throw std::runtime_error("Can't stat file in ZIP: " + cleanFilename); } zip_file_t* zipFile = zip_fopen(archive, cleanFilename.c_str(), 0); if (!zipFile) { zip_close(archive); - logger() << "Failed to open file in ZIP: " << cleanFilename << std::endl; + logger() << "Failed to open file in ZIP: " << cleanFilename << " in " << webZipFilename << std::endl; throw std::runtime_error("Can't open file in ZIP: " + cleanFilename); } @@ -146,11 +154,6 @@ namespace FRG return fileData; #else - if (zipfilename.empty()) { - std::cerr << "readFileFromZIP: zipfilename empty" << std::endl; - return {}; - } - int zipErr = 0; zip_t* archive = zip_open(zipfilename.c_str(), ZIP_RDONLY, &zipErr); if (!archive) { @@ -197,6 +200,39 @@ namespace FRG #endif } + bool zipFileExists(const std::string& zipfilename) + { + if (zipfilename.empty()) { + return false; + } + +#ifdef EMSCRIPTEN + std::string webZipFilename = getPersistentResourcePath(zipfilename); + + int zipErr = 0; + zip_t* archive = zip_open(webZipFilename.c_str(), ZIP_RDONLY, &zipErr); + if (!archive) { + return false; + } + + zip_close(archive); + return true; +#else + + int zipErr = 0; + zip_t* archive = zip_open(zipfilename.c_str(), ZIP_RDONLY, &zipErr); + if (!archive) { + std::cerr << "readFileFromZIP: zip_open failed for: " << zipfilename + << " (error code " << zipErr << ")" << std::endl; + return false; + } + + zip_close(archive); + + return true; +#endif + } + bool findString(const char* in, char* list) { size_t thisLength = strlen(in); @@ -481,4 +517,31 @@ namespace FRG // Если логирование отключено, возвращаем заглушку. return g_nullStream; } + + +#ifdef __EMSCRIPTEN__ + // Получение абсолютного пути к ресурсам в хранилище сохранений (/offline/resources.zip) + std::string getPersistentResourcePath(const std::string& filename) { + namespace fs = std::filesystem; + return (getSaveDirectory() / filename).string(); + } + + // Проверка целостности zip-файла по указанному пути + bool isZipValid(const std::string& fullPath) { + namespace fs = std::filesystem; + if (!fs::exists(fullPath)) { + return false; + } + + int zipErr = 0; + zip_t* archive = zip_open(fullPath.c_str(), ZIP_RDONLY, &zipErr); + if (!archive) { + logger() << "[VFS] ZIP file invalid or corrupted: " << fullPath + << " (code " << zipErr << ")" << std::endl; + return false; + } + zip_close(archive); + return true; + } +#endif }; \ No newline at end of file diff --git a/src/utils/Utils.h b/src/utils/Utils.h index be698cd..7c9f512 100644 --- a/src/utils/Utils.h +++ b/src/utils/Utils.h @@ -23,6 +23,8 @@ namespace FRG std::vector readFileFromZIP(const std::string& filename, const std::string& zipfilename); + bool zipFileExists(const std::string& zipfilename); + bool findString(const char* in, char* list); std::filesystem::path getSaveDirectory(); @@ -38,4 +40,10 @@ namespace FRG void initLogger(); std::ostream& logger(); + +#ifdef __EMSCRIPTEN__ + std::string getPersistentResourcePath(const std::string& filename); + + bool isZipValid(const std::string& fullPath); +#endif } \ No newline at end of file