From 7df68febcacc149f71c9ccf0dda0c1b526b35c8b Mon Sep 17 00:00:00 2001 From: Vladislav Khorev Date: Sat, 25 Jul 2026 16:56:02 +0300 Subject: [PATCH] Fixing minor stuff, working on Web version --- audio/13925__adcbicycle__21.ogg | 3 - audio/255571__stradie__flipping-paper.ogg | 3 - audio/398751__anthousai__door-lock-01.ogg | 3 - audio/459618__n-lerche__blow.ogg | 3 - audio/537061__imafoley__message-pop-sound.ogg | 3 - ...ogfirestudios__hit-swing-sword-small-3.ogg | 3 - audio/587036__antoineromo__bed-sheets.ogg | 3 - ...__itsthegoodstuff__paging-through-book.ogg | 3 - audio/702171__foxfire__door-opening.ogg | 3 - ...confusion__mechanical-plastic-click-11.ogg | 3 - ...564__joedeshon__alarm_clock_ticking_02.ogg | 3 - audio/831642__ienba__generic-game-pick-up.ogg | 3 - audio/sound_bag001.ogg | 3 - audio/sound_bag_close.ogg | 3 - audio/sound_bag_open.ogg | 3 - optimize_web_resources.py | 28 +++- proj-web/CMakeLists.txt | 10 ++ proj-web/README.md | 11 ++ proj-web/favicon.ico | Bin 0 -> 32038 bytes resources/dialogue/translate_dialogues.json | 6 +- .../dialogue/uni_interior_dialogues_010.json | 10 +- resources/w/ui/screen_about_android.json | 4 +- resources/w/ui/screen_about_web.json | 80 ++++++++++ resources/w/ui/screen_settings.json | 4 +- .../w/ui/screen_settings_nomusic_web.json | 20 +-- resources/w/ui_en/img/inv/Inventory001.png | 3 + resources/w/ui_en/screen_about_android.json | 4 +- resources/w/ui_en/screen_about_web.json | 80 ++++++++++ resources/w/ui_en/screen_phone_chat1.json | 10 -- resources/w/ui_en/screen_settings.json | 4 +- .../w/ui_en/screen_settings_nomusic_web.json | 119 +++++++++++++++ src/Environment.cpp | 1 - src/Game.cpp | 2 + src/MenuManager.cpp | 68 +++++++-- src/MenuManager.h | 8 + src/UiManager.cpp | 8 +- src/main.cpp | 139 ++++++++++-------- src/utils/Utils.cpp | 5 + 38 files changed, 502 insertions(+), 167 deletions(-) delete mode 100644 audio/13925__adcbicycle__21.ogg delete mode 100644 audio/255571__stradie__flipping-paper.ogg delete mode 100644 audio/398751__anthousai__door-lock-01.ogg delete mode 100644 audio/459618__n-lerche__blow.ogg delete mode 100644 audio/537061__imafoley__message-pop-sound.ogg delete mode 100644 audio/547041__cogfirestudios__hit-swing-sword-small-3.ogg delete mode 100644 audio/587036__antoineromo__bed-sheets.ogg delete mode 100644 audio/656126__itsthegoodstuff__paging-through-book.ogg delete mode 100644 audio/702171__foxfire__door-opening.ogg delete mode 100644 audio/751089__smallconfusion__mechanical-plastic-click-11.ogg delete mode 100644 audio/78564__joedeshon__alarm_clock_ticking_02.ogg delete mode 100644 audio/831642__ienba__generic-game-pick-up.ogg delete mode 100644 audio/sound_bag001.ogg delete mode 100644 audio/sound_bag_close.ogg delete mode 100644 audio/sound_bag_open.ogg create mode 100644 proj-web/favicon.ico create mode 100644 resources/w/ui/screen_about_web.json create mode 100644 resources/w/ui_en/img/inv/Inventory001.png create mode 100644 resources/w/ui_en/screen_about_web.json create mode 100644 resources/w/ui_en/screen_settings_nomusic_web.json diff --git a/audio/13925__adcbicycle__21.ogg b/audio/13925__adcbicycle__21.ogg deleted file mode 100644 index 5b76eaa..0000000 --- a/audio/13925__adcbicycle__21.ogg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b9d30306535105c1070730f40aba8894d7c4b19d702bb95ef5ff2c78471b3cd5 -size 7662 diff --git a/audio/255571__stradie__flipping-paper.ogg b/audio/255571__stradie__flipping-paper.ogg deleted file mode 100644 index cae74b7..0000000 --- a/audio/255571__stradie__flipping-paper.ogg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9442ccf84f7f2e856c16519b0cb060e1b36f4096db7c0a07889c94dd826a77bc -size 45403 diff --git a/audio/398751__anthousai__door-lock-01.ogg b/audio/398751__anthousai__door-lock-01.ogg deleted file mode 100644 index f79dd21..0000000 --- a/audio/398751__anthousai__door-lock-01.ogg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:659d78850496656075215ccd50ae62a64b4de7e4dc13c3c0191a964822481255 -size 10364 diff --git a/audio/459618__n-lerche__blow.ogg b/audio/459618__n-lerche__blow.ogg deleted file mode 100644 index 957b415..0000000 --- a/audio/459618__n-lerche__blow.ogg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4a55d5a4968a28ae948b72d0092b6a64beeb2d14bda4c2010a12890a0a4cec03 -size 42794 diff --git a/audio/537061__imafoley__message-pop-sound.ogg b/audio/537061__imafoley__message-pop-sound.ogg deleted file mode 100644 index c431da3..0000000 --- a/audio/537061__imafoley__message-pop-sound.ogg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:624b696ed4ffa4e72cbcbc2a7005c7f282b4baaec921bb2be1f6853c3e15b097 -size 7916 diff --git a/audio/547041__cogfirestudios__hit-swing-sword-small-3.ogg b/audio/547041__cogfirestudios__hit-swing-sword-small-3.ogg deleted file mode 100644 index f1be8e0..0000000 --- a/audio/547041__cogfirestudios__hit-swing-sword-small-3.ogg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:fd6b8506121e1ac9807a8fb2689f7c0cfca48777c08810b613ba761fc33d76c4 -size 19353 diff --git a/audio/587036__antoineromo__bed-sheets.ogg b/audio/587036__antoineromo__bed-sheets.ogg deleted file mode 100644 index ed0d4cb..0000000 --- a/audio/587036__antoineromo__bed-sheets.ogg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:12d04399abfe2441f51dc8b5e268aebc734ba707de161239aa90b808ade630da -size 35946 diff --git a/audio/656126__itsthegoodstuff__paging-through-book.ogg b/audio/656126__itsthegoodstuff__paging-through-book.ogg deleted file mode 100644 index 0f64f91..0000000 --- a/audio/656126__itsthegoodstuff__paging-through-book.ogg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ad3034bc469fba569d8569881676c27f5bbec40533d5255291bef6d379bfe4c9 -size 21567 diff --git a/audio/702171__foxfire__door-opening.ogg b/audio/702171__foxfire__door-opening.ogg deleted file mode 100644 index 2c7c861..0000000 --- a/audio/702171__foxfire__door-opening.ogg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9e725a730b1387a9df7bb0125f3dd7ccba1ca27e8422ee3c55b3bde4d8ce4124 -size 17136 diff --git a/audio/751089__smallconfusion__mechanical-plastic-click-11.ogg b/audio/751089__smallconfusion__mechanical-plastic-click-11.ogg deleted file mode 100644 index 2ca58ce..0000000 --- a/audio/751089__smallconfusion__mechanical-plastic-click-11.ogg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6a548df89a87919e3fd4bde3fa8eb2da883c402b7feeae7c0603d7d98d35399a -size 5105 diff --git a/audio/78564__joedeshon__alarm_clock_ticking_02.ogg b/audio/78564__joedeshon__alarm_clock_ticking_02.ogg deleted file mode 100644 index 4d0cee4..0000000 --- a/audio/78564__joedeshon__alarm_clock_ticking_02.ogg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c1756c0ece8080c799cb6ca4911a2c610800089ec6a65c45ba69e990942b7af9 -size 104191 diff --git a/audio/831642__ienba__generic-game-pick-up.ogg b/audio/831642__ienba__generic-game-pick-up.ogg deleted file mode 100644 index 44d62da..0000000 --- a/audio/831642__ienba__generic-game-pick-up.ogg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c79391d1f26978900194f9d5af1a0dc59eb62a1572f4f9011d1538a6bf09ffab -size 16067 diff --git a/audio/sound_bag001.ogg b/audio/sound_bag001.ogg deleted file mode 100644 index be0d04b..0000000 --- a/audio/sound_bag001.ogg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e64ea00f0355371d2e1b93818b0c686da6e699e1cdb4de26a1c9dac5640bd0fb -size 16054 diff --git a/audio/sound_bag_close.ogg b/audio/sound_bag_close.ogg deleted file mode 100644 index 217533a..0000000 --- a/audio/sound_bag_close.ogg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:bcb99f3aa14d4f53ae636fab8fadb0ed19a4be17e994088e7e41e4872002c0be -size 18569 diff --git a/audio/sound_bag_open.ogg b/audio/sound_bag_open.ogg deleted file mode 100644 index 1080147..0000000 --- a/audio/sound_bag_open.ogg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e17ae2d18d36892cbc634cca6b5523293c2737e97e0a94b577a8709823a5db04 -size 17077 diff --git a/optimize_web_resources.py b/optimize_web_resources.py index 773c0da..531c13e 100644 --- a/optimize_web_resources.py +++ b/optimize_web_resources.py @@ -27,7 +27,18 @@ except ImportError: # Paths relative to the resources directory — these files are never resized. EXCEPTIONS = { "loading.png", + "loading_land003_en.png", + "loading_land003_ru.png", "black.png", + "transparent.png", + "w/blue.png", + "w/red.png", + "w/spark.png", + "w/star.png", + "w/star_red.png", + "w/white.png", + "w/ui/img/main/aboutPage003android.png", + "w/ui_en/img/main/aboutPage003android.png" } # Path prefixes (relative to resources root, forward-slash) that get 8x reduction. @@ -35,13 +46,28 @@ HIGH_REDUCTION_PREFIXES = ( "w/ui/img", ) +LOW_REDUCTION_POSTFIXES = ( + "texAtlas002.png", + "Building_work014.png", + "Building_Floors_tex004.png", + "Building_Walls_tex006.png", + "asphalt003small.png", + "Ext_Building004.png", + "Plane3_tex002.png", + "darklands_building002.png", + "Staircase001.png", + "Staircase_Obj002.png" +) def _resize_factor(rel: str) -> int: norm = rel.replace("\\", "/") for prefix in HIGH_REDUCTION_PREFIXES: if norm.startswith(prefix): return 8 - return 4 + for prefix in LOW_REDUCTION_POSTFIXES: + if norm.endswith(prefix): + return 2 + return 8 def _next_pot(n: int) -> int: diff --git a/proj-web/CMakeLists.txt b/proj-web/CMakeLists.txt index f17b45d..14284cb 100644 --- a/proj-web/CMakeLists.txt +++ b/proj-web/CMakeLists.txt @@ -15,6 +15,12 @@ option(OPTIMIZE_WEB_RESOURCES "Resize PNG images for web build (requires Python3 set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) +if (CMAKE_BUILD_TYPE STREQUAL "Release") + # Максимальная оптимизация кода и LTO (Link-Time Optimization) + add_compile_options(-O3 -flto) + add_link_options(-O3 -flto) +endif() + # --- АВТО-ЗАГРУЗКА ЗАВИСИМОСТЕЙ --- include("${CMAKE_CURRENT_SOURCE_DIR}/../cmake/FetchDependencies.cmake") # Теперь гарантированно есть папка ../thirdparty со всеми исходниками @@ -297,6 +303,10 @@ install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/index.html" DESTINATION . ) +install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/favicon.ico" + DESTINATION . +) + # resources.zip is served separately and downloaded asynchronously at runtime install(FILES "${RESOURCES_ZIP}" DESTINATION .) install(FILES "${MUSIC_ZIP}" DESTINATION .) diff --git a/proj-web/README.md b/proj-web/README.md index d3706c8..3600be5 100644 --- a/proj-web/README.md +++ b/proj-web/README.md @@ -37,3 +37,14 @@ Run: ``` emrun --no_browser --port 8080 public ``` + +# Release build + + +``` +mkdir build_release +cd build_release +emcmake cmake -G Ninja -DCMAKE_BUILD_TYPE=Release .. +cmake --build . --config Release +cmake --install . --prefix ../dist +``` diff --git a/proj-web/favicon.ico b/proj-web/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..69c678321432450e35584527596ba60b413f0367 GIT binary patch literal 32038 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 literal 0 HcmV?d00001 diff --git a/resources/dialogue/translate_dialogues.json b/resources/dialogue/translate_dialogues.json index 22f7d5e..1453cdd 100644 --- a/resources/dialogue/translate_dialogues.json +++ b/resources/dialogue/translate_dialogues.json @@ -2276,9 +2276,9 @@ "en": "And maybe she will even give me some difficult assignment." }, { - "key": "Я даже уверена, она меня ищет. Она хочет мне какое-то задание дать.", - "ru": "Я даже уверена, она меня ищет. Она хочет мне какое-то задание дать.", - "en": "I'm even sure she's looking for me. She wants to give me some kind of assignment." + "key": "Я уверена, она меня ищет. Она хочет мне какое-то задание дать.", + "ru": "Я уверена, она меня ищет. Она хочет мне какое-то задание дать.", + "en": "I'm sure she's looking for me. She wants to give me some kind of assignment." }, { "key": "Пока ты не принесешь мне нож из учительской, никуда я тебя не выпущу. Ошо!", diff --git a/resources/dialogue/uni_interior_dialogues_010.json b/resources/dialogue/uni_interior_dialogues_010.json index e0122e3..820a87f 100644 --- a/resources/dialogue/uni_interior_dialogues_010.json +++ b/resources/dialogue/uni_interior_dialogues_010.json @@ -293,14 +293,6 @@ "speaker": "Бекзат", "portrait": "resources/dialogue/portrait_hero_neutral.png", "text": "Почему?", - "next": "line_30" - }, - { - "id": "line_30", - "type": "Line", - "speaker": "Айпери", - "portrait": "resources/dialogue/portrait_aiperi.png", - "text": "Она опять будет скафнить.", "next": "line_25" }, { @@ -308,7 +300,7 @@ "type": "Line", "speaker": "Айпери", "portrait": "resources/dialogue/portrait_aiperi.png", - "text": "Я даже уверена, она меня ищет. Она хочет мне какое-то задание дать.", + "text": "Я уверена, она меня ищет. Она хочет мне какое-то задание дать.", "next": "line_26" }, { diff --git a/resources/w/ui/screen_about_android.json b/resources/w/ui/screen_about_android.json index 1234216..03acce8 100644 --- a/resources/w/ui/screen_about_android.json +++ b/resources/w/ui/screen_about_android.json @@ -24,13 +24,13 @@ }, { "type": "TextButton", - "name": "websiteButton", + "name": "reviewButton", "horizontal_gravity": "center", "vertical_gravity": "center", "y": 200, "width": 200, "height": 60, - "text": "Сайт игры Тень над Бишкеком", + "text": "Оставьте отзыв в Google Play", "fontSize": 36, "color": [0.55, 0.9, 1.0, 1.0], "textCentered": true diff --git a/resources/w/ui/screen_about_web.json b/resources/w/ui/screen_about_web.json new file mode 100644 index 0000000..1234216 --- /dev/null +++ b/resources/w/ui/screen_about_web.json @@ -0,0 +1,80 @@ +{ + "root": { + "type": "FrameLayout", + "width": "match_parent", + "height": "match_parent", + "children": [ + { + "type": "StaticImage", + "name": "aboutBkg", + "width": 2992, + "height": 1632, + "horizontal_gravity": "center", + "vertical_gravity": "center", + "texture": "resources/w/ui/img/main/MenuBkg001_v001.png" + }, + { + "type": "StaticImage", + "name": "aboutPicture", + "width": 720, + "height": 720, + "horizontal_gravity": "center", + "vertical_gravity": "center", + "texture": "resources/w/ui/img/main/aboutPage003android.png" + }, + { + "type": "TextButton", + "name": "websiteButton", + "horizontal_gravity": "center", + "vertical_gravity": "center", + "y": 200, + "width": 200, + "height": 60, + "text": "Сайт игры Тень над Бишкеком", + "fontSize": 36, + "color": [0.55, 0.9, 1.0, 1.0], + "textCentered": true + }, + { + "type": "TextButton", + "name": "telegramButton", + "horizontal_gravity": "center", + "vertical_gravity": "center", + "y": -240, + "x": -150, + "width": 200, + "height": 60, + "text": "Telegram-канал", + "fontSize": 40, + "color": [0.55, 0.9, 1.0, 1.0], + "textCentered": true + }, + { + "type": "TextButton", + "name": "discordButton", + "horizontal_gravity": "center", + "vertical_gravity": "center", + "y": -240, + "x": 150, + "width": 200, + "height": 60, + "text": "Наш Discord", + "fontSize": 40, + "color": [0.55, 0.9, 1.0, 1.0], + "textCentered": true + }, + { + "type": "TextButton", + "name": "aboutBackButton", + "horizontal_gravity": "center", + "vertical_gravity": "bottom", + "y": 40, + "width": 200, + "height": 60, + "text": "Назад", + "fontSize": 40, + "textCentered": true + } + ] + } +} diff --git a/resources/w/ui/screen_settings.json b/resources/w/ui/screen_settings.json index 596eb7a..e1059d4 100644 --- a/resources/w/ui/screen_settings.json +++ b/resources/w/ui/screen_settings.json @@ -43,7 +43,7 @@ "textCentered": false, "topAligned": true, "paddingX": 10, - "color": [1.0, 1.0, 1.0, 1.0] + "color": [0.7, 0.7, 0.7, 1.0] }, { "type": "Slider", @@ -68,7 +68,7 @@ "textCentered": false, "topAligned": true, "paddingX": 10, - "color": [1.0, 1.0, 1.0, 1.0] + "color": [0.7, 0.7, 0.7, 1.0] }, { "type": "Slider", diff --git a/resources/w/ui/screen_settings_nomusic_web.json b/resources/w/ui/screen_settings_nomusic_web.json index 639bf69..32617b5 100644 --- a/resources/w/ui/screen_settings_nomusic_web.json +++ b/resources/w/ui/screen_settings_nomusic_web.json @@ -38,31 +38,19 @@ "name": "noMusicText1", "width": 500, "height": 40, - "text": "Настройки музыки отсутствуют.", + "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] + "color": [0.7, 0.7, 0.7, 1.0] }, { "type": "TextButton", "name": "musicDownloadButton", "width": 500, "height": 40, - "text": "Нажмите сюда, чтобы скачать и установить музыку (30 MB).", + "text": "Скачать пакет с музыкой (27 MB).", "fontSize": 32, "textCentered": true, "color": [1.0, 1.0, 1.0, 1.0] @@ -77,7 +65,7 @@ "textCentered": false, "topAligned": true, "paddingX": 10, - "color": [1.0, 1.0, 1.0, 1.0] + "color": [0.7, 0.7, 0.7, 1.0] }, { "type": "Slider", diff --git a/resources/w/ui_en/img/inv/Inventory001.png b/resources/w/ui_en/img/inv/Inventory001.png new file mode 100644 index 0000000..7d70dca --- /dev/null +++ b/resources/w/ui_en/img/inv/Inventory001.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f063e0977b210010b530468b0210fe3407e26d104c6a45cd9493340c1df0ca44 +size 269501 diff --git a/resources/w/ui_en/screen_about_android.json b/resources/w/ui_en/screen_about_android.json index c929238..8409f41 100644 --- a/resources/w/ui_en/screen_about_android.json +++ b/resources/w/ui_en/screen_about_android.json @@ -24,13 +24,13 @@ }, { "type": "TextButton", - "name": "websiteButton", + "name": "reviewButton", "horizontal_gravity": "center", "vertical_gravity": "center", "y": 200, "width": 200, "height": 60, - "text": "Shadow over Bishkek website", + "text": "Leave a review on Google Play", "fontSize": 36, "color": [0.55, 0.9, 1.0, 1.0], "textCentered": true diff --git a/resources/w/ui_en/screen_about_web.json b/resources/w/ui_en/screen_about_web.json new file mode 100644 index 0000000..c929238 --- /dev/null +++ b/resources/w/ui_en/screen_about_web.json @@ -0,0 +1,80 @@ +{ + "root": { + "type": "FrameLayout", + "width": "match_parent", + "height": "match_parent", + "children": [ + { + "type": "StaticImage", + "name": "aboutBkg", + "width": 2992, + "height": 1632, + "horizontal_gravity": "center", + "vertical_gravity": "center", + "texture": "resources/w/ui/img/main/MenuBkg001_v001.png" + }, + { + "type": "StaticImage", + "name": "aboutPicture", + "width": 720, + "height": 720, + "horizontal_gravity": "center", + "vertical_gravity": "center", + "texture": "resources/w/ui_en/img/main/aboutPage003android.png" + }, + { + "type": "TextButton", + "name": "websiteButton", + "horizontal_gravity": "center", + "vertical_gravity": "center", + "y": 200, + "width": 200, + "height": 60, + "text": "Shadow over Bishkek website", + "fontSize": 36, + "color": [0.55, 0.9, 1.0, 1.0], + "textCentered": true + }, + { + "type": "TextButton", + "name": "telegramButton", + "horizontal_gravity": "center", + "vertical_gravity": "center", + "y": -240, + "x": -150, + "width": 200, + "height": 60, + "text": "Telegram channel", + "fontSize": 40, + "color": [0.55, 0.9, 1.0, 1.0], + "textCentered": true + }, + { + "type": "TextButton", + "name": "discordButton", + "horizontal_gravity": "center", + "vertical_gravity": "center", + "y": -240, + "x": 150, + "width": 200, + "height": 60, + "text": "Our Discord", + "fontSize": 40, + "color": [0.55, 0.9, 1.0, 1.0], + "textCentered": true + }, + { + "type": "TextButton", + "name": "aboutBackButton", + "horizontal_gravity": "center", + "vertical_gravity": "bottom", + "y": 40, + "width": 200, + "height": 60, + "text": "Back", + "fontSize": 40, + "textCentered": true + } + ] + } +} diff --git a/resources/w/ui_en/screen_phone_chat1.json b/resources/w/ui_en/screen_phone_chat1.json index dab6051..8729e40 100644 --- a/resources/w/ui_en/screen_phone_chat1.json +++ b/resources/w/ui_en/screen_phone_chat1.json @@ -43,16 +43,6 @@ "width": "match_parent", "height": "match_parent" }, - { - "type": "StaticImage", - "name": "chatTopCover", - "width": 446.25, - "height": 70.82, - "x": 0, - "y": -50.82, - "horizontal_gravity": "center", - "texture": "resources/w/ui/img/phone/chat_top_cover001.png" - }, { "type": "TextButton", "name": "chatTitleButton", diff --git a/resources/w/ui_en/screen_settings.json b/resources/w/ui_en/screen_settings.json index 21614e8..082a296 100644 --- a/resources/w/ui_en/screen_settings.json +++ b/resources/w/ui_en/screen_settings.json @@ -43,7 +43,7 @@ "textCentered": false, "topAligned": true, "paddingX": 10, - "color": [1.0, 1.0, 1.0, 1.0] + "color": [0.7, 0.7, 0.7, 1.0] }, { "type": "Slider", @@ -68,7 +68,7 @@ "textCentered": false, "topAligned": true, "paddingX": 10, - "color": [1.0, 1.0, 1.0, 1.0] + "color": [0.7, 0.7, 0.7, 1.0] }, { "type": "Slider", diff --git a/resources/w/ui_en/screen_settings_nomusic_web.json b/resources/w/ui_en/screen_settings_nomusic_web.json new file mode 100644 index 0000000..c2a91b8 --- /dev/null +++ b/resources/w/ui_en/screen_settings_nomusic_web.json @@ -0,0 +1,119 @@ +{ + "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": "Settings", + "fontSize": 40, + "textCentered": true, + "color": [1.0, 1.0, 1.0, 1.0] + }, + { + "type": "TextView", + "name": "noMusicText1", + "width": 500, + "height": 40, + "text": "There is no music.", + "fontSize": 32, + "textCentered": false, + "topAligned": true, + "paddingX": 10, + "color": [0.7, 0.7, 0.7, 1.0] + }, + { + "type": "TextButton", + "name": "musicDownloadButton", + "width": 500, + "height": 40, + "text": "Download music package (27 MB).", + "fontSize": 32, + "textCentered": true, + "color": [1.0, 1.0, 1.0, 1.0] + }, + { + "type": "TextView", + "name": "soundVolumeText", + "width": 500, + "height": 40, + "text": "", + "fontSize": 32, + "textCentered": false, + "topAligned": true, + "paddingX": 10, + "color": [0.7, 0.7, 0.7, 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": "Graphics settings", + "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": "Back", + "fontSize": 40, + "textCentered": true + } + ] + } +} diff --git a/src/Environment.cpp b/src/Environment.cpp index 158d72f..6f39cc8 100644 --- a/src/Environment.cpp +++ b/src/Environment.cpp @@ -22,7 +22,6 @@ int Environment::width = CONST_DEFAULT_WIDTH; int Environment::height = CONST_DEFAULT_HEIGHT; float Environment::zoom = DEFAULT_ZOOM; - bool Environment::enableLogging = true; SDL_Window* Environment::window = nullptr; diff --git a/src/Game.cpp b/src/Game.cpp index 99f38ad..a4f8ade 100644 --- a/src/Game.cpp +++ b/src/Game.cpp @@ -1291,6 +1291,8 @@ namespace FRG menuManager.onOrientationChanged(); } + menuManager.refreshFullScreenToggleButton(); + logger() << "Window resized (Render Size): " << Environment::width << "x" << Environment::height << std::endl; } diff --git a/src/MenuManager.cpp b/src/MenuManager.cpp index 976198b..d2523a7 100644 --- a/src/MenuManager.cpp +++ b/src/MenuManager.cpp @@ -28,9 +28,12 @@ namespace FRG { 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_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 const std::string MUSIC_DOWNLOAD_ERROR_EN = u8"Music download error"; + static const std::string MUSIC_DOWNLOAD_NOT_STARTED_EN = u8"Download music package (27 MB)."; + static const std::string MUSIC_DOWNLOAD_PROGRESS_PREFIX_EN = u8"Music download: "; static int questStatusPriority(Quest::QuestStatus status) { @@ -246,7 +249,7 @@ namespace FRG { void MenuManager::loadUiRoots(const std::string& zipFile) { hudRoot = loadLocalizedUi("resources/w/ui/hud_step0.json", zipFile); -#ifdef __ANDROID__ +#if defined(__ANDROID__) hudStep1Root = loadLocalizedUi("resources/w/ui/hud_step1android.json", zipFile); hudStep2Root = loadLocalizedUi("resources/w/ui/hud_step2android.json", zipFile); #else @@ -288,7 +291,10 @@ namespace FRG { hudUniIntDarkFullRoot = loadLocalizedUi("resources/w/ui/hud_uni_int_dark_full.json", zipFile); hudUniExtDarkRoot = loadLocalizedUi("resources/w/ui/hud_uni_ext_dark.json", zipFile); - hudCutsceneRoot_ = loadLocalizedUi("resources/w/ui/hud_cutscene.json", zipFile); + //hudCutsceneRoot_ = loadLocalizedUi("resources/w/ui/hud_cutscene.json", zipFile); + hudCutsceneRoot_ = loadUiFromFile("resources/w/ui/hud_cutscene.json", renderer, zipFile); + + hudTopHintRoot_ = loadLocalizedUi("resources/w/ui/hud_top_hint01.json", zipFile); phoneMainRoot = loadLocalizedUi("resources/w/ui/screen_phone.json", zipFile); @@ -329,12 +335,18 @@ namespace FRG { portraitQuestJournalListRoot = loadLocalizedUi("resources/w/ui/portrait_screen_journal.json", zipFile); portraitQuestJournalItemRoot = loadLocalizedUi("resources/w/ui/portrait_screen_journal_item.json", zipFile); -#ifdef __ANDROID__ +#if defined(__ANDROID__) || defined(__EMSCRIPTEN__) mainMenuRoot = loadLocalizedUi("resources/w/ui/screen_main_menu_android.json", zipFile); - aboutScreenRoot = loadLocalizedUi("resources/w/ui/screen_about_android.json", zipFile); #else mainMenuRoot = loadLocalizedUi("resources/w/ui/screen_main_menu.json", zipFile); - aboutScreenRoot = loadLocalizedUi("resources/w/ui/screen_about.json", zipFile); +#endif + +#if defined(__ANDROID__) + aboutScreenRoot = loadLocalizedUi("resources/w/ui/screen_about_android.json", zipFile); +#elif defined(__EMSCRIPTEN__) + aboutScreenRoot = loadLocalizedUi("resources/w/ui/screen_about_web.json", zipFile); +#else + aboutScreenRoot = loadLocalizedUi("resources/w/ui/screen_about.json", zipFile); #endif creditsScreenRoot = loadLocalizedUi("resources/w/ui/screen_credits.json", zipFile); @@ -615,13 +627,19 @@ namespace FRG { }); -#ifdef __ANDROID__ +#ifdef __EMSCRIPTEN__ uiManager.setTextButtonCallback("websiteButton", [this](const std::string&) { audioPlayer_.playSoundAsync("resources/audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); SDL_OpenURL("https://shadowoverbishkek.com/"); }); #endif +#ifdef __ANDROID__ + uiManager.setTextButtonCallback("reviewButton", [this](const std::string&) { + audioPlayer_.playSoundAsync("resources/audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); + SDL_OpenURL("https://play.google.com/store/apps/details?id=fishrungames.shadowoverbishkek"); + }); +#endif uiManager.setTextButtonCallback("telegramButton", [this](const std::string&) { audioPlayer_.playSoundAsync("resources/audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); SDL_OpenURL("https://telegram.me/fishrungames"); @@ -838,6 +856,23 @@ namespace FRG { }); } + void MenuManager::refreshFullScreenToggleButton() + { + // Localization + if (g_currentLanguage == Language::English) + { + fullScreenToggleOn = "Full Screen: ON"; + fullScreenToggleOff = "Full Screen: OFF"; + } + else if (g_currentLanguage == Language::Russian) + { + fullScreenToggleOn = u8"Полноэкранный режим: ВКЛ"; + fullScreenToggleOff = u8"Полноэкранный режим: ВЫКЛ"; + } + + const bool on = Environment::isFullscreen; + uiManager.setTextButtonText("fullScreenToggleButton", on ? fullScreenToggleOn : fullScreenToggleOff); + } #ifdef __EMSCRIPTEN__ void MenuManager::showSettingsNoMusicWebScreen() @@ -863,23 +898,30 @@ namespace FRG { const float soundFrac = audioPlayer_.getSoundVolume() / 128.0f; uiManager.setSliderValue("soundVolumeSlider", soundFrac); + static std::string musicDownloadErrorString;// = MUSIC_DOWNLOAD_ERROR_RU; + static std::string musicDownloadNotStartedString;// = MUSIC_DOWNLOAD_NOT_STARTED_RU; + static std::string musicDownloadProgressPrefix;// = MUSIC_DOWNLOAD_PROGRESS_PREFIX_RU; + // Localization if (g_currentLanguage == Language::English) { soundVolumePrefix = "Sound volume: "; soundToggleOn = "Sound: ON"; soundToggleOff = "Sound: OFF"; + musicDownloadErrorString = MUSIC_DOWNLOAD_ERROR_EN; + musicDownloadNotStartedString = MUSIC_DOWNLOAD_NOT_STARTED_EN; + musicDownloadProgressPrefix = MUSIC_DOWNLOAD_PROGRESS_PREFIX_EN; } else if (g_currentLanguage == Language::Russian) { soundVolumePrefix = u8"Громкость звука: "; soundToggleOn = u8"Звук: ВКЛ"; soundToggleOff = u8"Звук: ВЫКЛ"; + musicDownloadErrorString = MUSIC_DOWNLOAD_ERROR_RU; + musicDownloadNotStartedString = MUSIC_DOWNLOAD_NOT_STARTED_RU; + musicDownloadProgressPrefix = MUSIC_DOWNLOAD_PROGRESS_PREFIX_RU; } - static std::string musicDownloadErrorString = u8"Ошибка скачивания музыки"; - static std::string musicDownloadNotStartedString = u8"Нажмите сюда, чтобы скачать\nи установить музыку (27 MB)."; - static std::string musicDownloadProgressPrefix = u8"Загрузка музыки: "; // musicDownloadStarted = false; //musicDownloadError = false; @@ -969,13 +1011,17 @@ namespace FRG { }); uiManager.setTextButtonCallback("languageRussianButton", [this](const std::string&) { +#ifndef __EMSCRIPTEN__ audioPlayer_.playSoundAsync("resources/audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); +#endif FRG::g_currentLanguage = FRG::Language::Russian; reloadLocalizedGameContent(); saveSettings(); }); uiManager.setTextButtonCallback("languageEnglishButton", [this](const std::string&) { +#ifndef __EMSCRIPTEN__ audioPlayer_.playSoundAsync("resources/audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); +#endif FRG::g_currentLanguage = FRG::Language::English; reloadLocalizedGameContent(); saveSettings(); diff --git a/src/MenuManager.h b/src/MenuManager.h index b78a503..a64f68c 100644 --- a/src/MenuManager.h +++ b/src/MenuManager.h @@ -129,7 +129,14 @@ namespace FRG { std::shared_ptr languageLoadingRu; std::shared_ptr languageLoadingEn; + + void refreshFullScreenToggleButton(); + +#ifdef __EMSCRIPTEN__ + bool shadowsEnabled = false; +#else bool shadowsEnabled = true; +#endif std::string lastMusicTrack; #ifdef __EMSCRIPTEN__ @@ -189,6 +196,7 @@ namespace FRG { void showCreditsScreen(); void showSettingsScreen(); void showSettingsGraphicsScreen(); + #ifdef __EMSCRIPTEN__ void showSettingsNoMusicWebScreen(); diff --git a/src/UiManager.cpp b/src/UiManager.cpp index d204f86..64283d0 100644 --- a/src/UiManager.cpp +++ b/src/UiManager.cpp @@ -798,14 +798,14 @@ namespace FRG { else { auto buf = readFileFromZIP(path, zipFile); if (buf.empty()) { - std::cerr << "UiManager: failed to read " << path << " from zip " << zipFile << std::endl; + logger() << "UiManager: failed to read " << path << " from zip " << zipFile << std::endl; throw std::runtime_error("Failed to load UI file: " + path); } content.assign(buf.begin(), buf.end()); } } catch (const std::exception& e) { - std::cerr << "UiManager: failed to open " << path << " : " << e.what() << std::endl; + logger() << "UiManager: failed to open " << path << " : " << e.what() << std::endl; throw std::runtime_error("Failed to load UI file: " + path); } @@ -814,12 +814,12 @@ namespace FRG { j = json::parse(content); } catch (const std::exception& e) { - std::cerr << "UiManager: json parse error: " << e.what() << std::endl; + logger() << "UiManager: json parse error: " << e.what() << std::endl; throw std::runtime_error("Failed to load UI file: " + path); } if (!j.contains("root") || !j["root"].is_object()) { - std::cerr << "UiManager: root node missing or invalid" << std::endl; + logger() << "UiManager: root node missing or invalid" << std::endl; throw std::runtime_error("Failed to load UI file: " + path); } diff --git a/src/main.cpp b/src/main.cpp index 8ad905c..119d7e9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -20,15 +20,48 @@ FRG::Game* g_game = nullptr; +// 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(). + +void readSettings() +{ + + const std::string settingsContent = FRG::readSavedTextFile("settings.json"); + if (!settingsContent.empty()) { + try { + const nlohmann::json settingsRoot = nlohmann::json::parse(settingsContent); + if (settingsRoot.contains("fullscreen")) { + FRG::Environment::isFullscreen = settingsRoot["fullscreen"].get(); + } + + if (settingsRoot.contains("isHighDPIEnabled")) { + FRG::Environment::isHighDPIEnabled = settingsRoot["isHighDPIEnabled"].get(); + } + + if (settingsRoot.contains("enableLogging")) { + FRG::Environment::enableLogging = settingsRoot["enableLogging"].get(); + } + + } + catch (const std::exception& e) { + std::cerr << "[settings] Failed to parse settings.json: " << e.what() << std::endl; + } + } +} + #if defined(EMSCRIPTEN) static SDL_GLContext glContext_x; static bool g_isFsLoaded = false; // Флаг готовности +static bool g_pendingFullscreenResize = false; + // Макрос EMSCRIPTEN_KEEPALIVE экспортирует функцию в глобальный объект Module extern "C" void EMSCRIPTEN_KEEPALIVE onFileSystemLoaded() { // 1. Файлы загружены. Теперь можно читать настройки и логи // Если функции readSettings() нет для Emscripten, форсируем логгер: - FRG::Environment::enableLogging = true; + //FRG::Environment::enableLogging = true; + //readSettings(); FRG::initLogger(); FRG::logger() << "[boot] File system is ready. Setting up game..." << std::endl; @@ -40,26 +73,7 @@ extern "C" void EMSCRIPTEN_KEEPALIVE onFileSystemLoaded() { // 3. Даем отмашку главному циклу g_isFsLoaded = true; } -#endif -void MainLoop() { -#if defined(EMSCRIPTEN) - // Крутим цикл вхолостую, пока JS читает IndexedDB - if (!g_isFsLoaded) return; -#endif - - g_game->update(); -} - -#ifdef EMSCRIPTEN - -EM_BOOL onWebGLContextLost(int /*eventType*/, const void* /*reserved*/, void* /*userData*/) { - return EM_TRUE; -} - -EM_BOOL onWebGLContextRestored(int /*eventType*/, const void* /*reserved*/, void* /*userData*/) { - return EM_TRUE; -} static void applyResize(int logicalW, int logicalH) { // Получаем коэффициент плотности пикселей (например, 2.625 на Pixel или 3.0 на Samsung) @@ -92,22 +106,59 @@ static void applyResize(int logicalW, int logicalH) { << " (physical: " << physicalW << "x" << physicalH << ", DPR: " << dpr << ")" << std::endl; } +#endif + +void MainLoop() { +#if defined(EMSCRIPTEN) + // Крутим цикл вхолостую, пока JS читает IndexedDB + if (!g_isFsLoaded) return; + + if (g_pendingFullscreenResize) { + g_pendingFullscreenResize = false; + + // Теперь DOM перестроен, можно забирать актуальные размеры. + // Если canvas должен занимать все окно браузера: + int canvasW = EM_ASM_INT({ return window.innerWidth; }); + int canvasH = EM_ASM_INT({ return window.innerHeight; }); + + applyResize(canvasW, canvasH); + } +#endif + + g_game->update(); +} + +#ifdef EMSCRIPTEN + + +EM_BOOL onWebGLContextLost(int /*eventType*/, const void* /*reserved*/, void* /*userData*/) { + return EM_TRUE; +} + +EM_BOOL onWebGLContextRestored(int /*eventType*/, const void* /*reserved*/, void* /*userData*/) { + return EM_TRUE; +} + 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); + double w, h; + emscripten_get_element_css_size("window", &w, &h); + + // Если "window" не поддерживается вашей версией SDK, используем данные из e: + // int logicalW = e->windowInnerWidth; + // int logicalH = e->windowInnerHeight; + + applyResize(static_cast(w), static_cast(h)); return EM_FALSE; } EM_BOOL onFullscreenChanged(int /*eventType*/, const EmscriptenFullscreenChangeEvent* e, void* /*userData*/) { FRG::Environment::isFullscreen = e->isFullscreen; + FRG::logger() << "onFullscreenChanged: isFullscreen = " << e->isFullscreen << std::endl; + + // Браузер еще не обновил DOM. Ставим флаг, чтобы применить размеры в следующем кадре. + g_pendingFullscreenResize = true; - // Вместо window.innerWidth, попробуйте запросить размер целевого элемента - // так как после перехода в FS именно он растягивается на весь экран. - double clientW, clientH; - emscripten_get_element_css_size("#canvas", &clientW, &clientH); - applyResize(clientW, clientH); return EM_FALSE; } @@ -128,7 +179,7 @@ int main(int argc, char* argv[]) { emscripten_set_webglcontextrestored_callback("#canvas", nullptr, EM_TRUE, onWebGLContextRestored); emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, nullptr, EM_FALSE, onWindowResized); - emscripten_set_fullscreenchange_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, nullptr, EM_FALSE, onFullscreenChanged); + emscripten_set_fullscreenchange_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, nullptr, EM_TRUE, onFullscreenChanged); int canvasW = EM_ASM_INT({ return window.innerWidth; }); int canvasH = EM_ASM_INT({ return window.innerHeight; }); @@ -289,36 +340,6 @@ int main(int argc, char* argv[]) { #ifndef __ANDROID__ #if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__linux__) - // 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(). -void readSettings() -{ - - const std::string settingsContent = FRG::readSavedTextFile("settings.json"); - if (!settingsContent.empty()) { - try { - const nlohmann::json settingsRoot = nlohmann::json::parse(settingsContent); - if (settingsRoot.contains("fullscreen")) { - FRG::Environment::isFullscreen = settingsRoot["fullscreen"].get(); - } - - if (settingsRoot.contains("isHighDPIEnabled")) { - FRG::Environment::isHighDPIEnabled = settingsRoot["isHighDPIEnabled"].get(); - } - - if (settingsRoot.contains("enableLogging")) { - FRG::Environment::enableLogging = settingsRoot["enableLogging"].get(); - } - - } - catch (const std::exception& e) { - std::cerr << "[settings] Failed to parse settings.json: " << e.what() << std::endl; - } - } -} - - int main(int argc, char* argv[]) { SDL_GLContext ctx = nullptr; diff --git a/src/utils/Utils.cpp b/src/utils/Utils.cpp index dadefb4..290d095 100644 --- a/src/utils/Utils.cpp +++ b/src/utils/Utils.cpp @@ -509,8 +509,13 @@ namespace FRG } #ifdef __ANDROID__ return g_androidStream; +#else +#ifdef __EMSCRIPTEN__ + return g_nullStream; //For web, remove all logging to browser console #else return std::cout; +#endif + #endif }