Adapting music to web, fixing scripts, fixing bug in initial state

This commit is contained in:
Vladislav Khorev committed 2026-07-24 23:58:29 +03:00
1 parent c3f449b999
commit 5ecf5fb9d7
43 files changed
+875 -388

No files matched your search

File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
+1 -1
View File
@@ -290,7 +290,7 @@ endif()
# ===========================================
set(RUNTIME_RESOURCE_DIRS
"resources"
"audio"
"music"
)
foreach(resdir IN LISTS RUNTIME_RESOURCE_DIRS)
+31 -14
View File
@@ -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..."
-5
View File
@@ -1,5 +0,0 @@
<!doctypehtml><html lang=en-us><head><meta charset=utf-8><meta content="text/html; charset=utf-8"http-equiv=Content-Type><title>Emscripten-Generated Code</title><style>body{font-family:arial;margin:0;padding:none}.emscripten{padding-right:0;margin-left:auto;margin-right:auto;display:block}div.emscripten{text-align:center}div.emscripten_border{border:1px solid #000}canvas.emscripten{border:0 none;background-color:#000}#emscripten_logo{display:inline-block;margin:0;padding:6px;width:265px}.spinner{height:30px;width:30px;margin:0;margin-top:20px;margin-left:20px;display:inline-block;vertical-align:top;-webkit-animation:rotation .8s linear infinite;-moz-animation:rotation .8s linear infinite;-o-animation:rotation .8s linear infinite;animation:rotation .8s linear infinite;border-left:5px solid #ebebeb;border-right:5px solid #ebebeb;border-bottom:5px solid #ebebeb;border-top:5px solid #787878;border-radius:100%;background-color:#bdd72e}@-webkit-keyframes rotation{from{-webkit-transform:rotate(0)}to{-webkit-transform:rotate(360deg)}}@-moz-keyframes rotation{from{-moz-transform:rotate(0)}to{-moz-transform:rotate(360deg)}}@-o-keyframes rotation{from{-o-transform:rotate(0)}to{-o-transform:rotate(360deg)}}@keyframes rotation{from{transform:rotate(0)}to{transform:rotate(360deg)}}#status{display:inline-block;vertical-align:top;margin-top:30px;margin-left:20px;font-weight:700;color:#787878}#progress{height:20px;width:300px}#controls{display:inline-block;float:right;vertical-align:top;margin-top:30px;margin-right:20px}#output{width:100%;height:200px;margin:0 auto;margin-top:10px;border-left:0;border-right:0px;padding-left:0;padding-right:0;display:block;background-color:#000;color:#fff;font-family:'Lucida Console',Monaco,monospace;outline:0}</style></head><body>
<script src="https://cdn.jsdelivr.net/npm/eruda"></script>
<script>eruda.init();</script>
<a href=http://emscripten.org><img id=emscripten_logo src=data:image/png;base64,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 truncated
+1 -1
View File
@@ -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);
}
-2
View File
@@ -1,2 +0,0 @@
<!doctypehtml><html lang=en-us><head><meta charset=utf-8><meta content="text/html; charset=utf-8"http-equiv=Content-Type><title>Emscripten-Generated Code</title><style>body{font-family:arial;margin:0;padding:none}.emscripten{padding-right:0;margin-left:auto;margin-right:auto;display:block}div.emscripten{text-align:center}div.emscripten_border{border:1px solid #000}canvas.emscripten{border:0 none;background-color:#000}#emscripten_logo{display:inline-block;margin:0;padding:6px;width:265px}.spinner{height:30px;width:30px;margin:0;margin-top:20px;margin-left:20px;display:inline-block;vertical-align:top;-webkit-animation:rotation .8s linear infinite;-moz-animation:rotation .8s linear infinite;-o-animation:rotation .8s linear infinite;animation:rotation .8s linear infinite;border-left:5px solid #ebebeb;border-right:5px solid #ebebeb;border-bottom:5px solid #ebebeb;border-top:5px solid #787878;border-radius:100%;background-color:#bdd72e}@-webkit-keyframes rotation{from{-webkit-transform:rotate(0)}to{-webkit-transform:rotate(360deg)}}@-moz-keyframes rotation{from{-moz-transform:rotate(0)}to{-moz-transform:rotate(360deg)}}@-o-keyframes rotation{from{-o-transform:rotate(0)}to{-o-transform:rotate(360deg)}}@keyframes rotation{from{transform:rotate(0)}to{transform:rotate(360deg)}}#status{display:inline-block;vertical-align:top;margin-top:30px;margin-left:20px;font-weight:700;color:#787878}#progress{height:20px;width:300px}#controls{display:inline-block;float:right;vertical-align:top;margin-top:30px;margin-right:20px}#output{width:100%;height:200px;margin:0 auto;margin-top:10px;border-left:0;border-right:0px;padding-left:0;padding-right:0;display:block;background-color:#000;color:#fff;font-family:'Lucida Console',Monaco,monospace;outline:0}</style></head><body><script src="https://cdn.jsdelivr.net/npm/eruda"></script>
<script>eruda.init();</script><a href=http://emscripten.org><img id=emscripten_logo src=""></a><div class=spinner id=spinner></div><div class=emscripten id=status>Downloading...</div><span id=controls><span><input type=checkbox id=resize>Resize canvas</span> <span><input type=checkbox id=pointerLock checked>Lock/hide mouse pointer    </span><span><input type=button onclick='Module.requestFullscreen(document.getElementById("pointerLock").checked,document.getElementById("resize").checked)'value=Fullscreen></span></span><div class=emscripten><progress hidden id=progress max=100 value=0></progress></div><div class=emscripten_border><canvas class=emscripten id=canvas oncontextmenu=event.preventDefault() tabindex=-1></canvas></div><textarea id=output rows=8></textarea><script>var statusElement=document.getElementById("status"),progressElement=document.getElementById("progress"),spinnerElement=document.getElementById("spinner"),canvasElement=document.getElementById("canvas"),outputElement=document.getElementById("output");outputElement&&(outputElement.value=""),canvasElement.addEventListener("webglcontextlost",(e=>{alert("WebGL context lost. You will need to reload the page."),e.preventDefault()}),!1);var Module={print(...e){if(console.log(...e),outputElement){var t=e.join(" ");outputElement.value+=t+"\n",outputElement.scrollTop=outputElement.scrollHeight}},canvas:canvasElement,setStatus(e){if(Module.setStatus.last||(Module.setStatus.last={time:Date.now(),text:""}),e!==Module.setStatus.last.text){var t=e.match(/([^(]+)\((\d+(\.\d+)?)\/(\d+)\)/),n=Date.now();t&&n-Module.setStatus.last.time<30||(Module.setStatus.last.time=n,Module.setStatus.last.text=e,t?(e=t[1],progressElement.value=100*parseInt(t[2]),progressElement.max=100*parseInt(t[4]),progressElement.hidden=!1,spinnerElement.hidden=!1):(progressElement.value=null,progressElement.max=null,progressElement.hidden=!0,e||(spinnerElement.style.display="none")),statusElement.innerHTML=e)}},totalDependencies:0,monitorRunDependencies(e){this.totalDependencies=Math.max(this.totalDependencies,e),Module.setStatus(e?"Preparing... ("+(this.totalDependencies-e)+"/"+this.totalDependencies+")":"All downloads complete.")}};Module.setStatus("Downloading..."),window.onerror=e=>{Module.setStatus("Exception thrown, see JavaScript console"),spinnerElement.style.display="none",Module.setStatus=e=>{e&&console.error("[post-exception status] "+e)}}</script><script async src="space-game001.js" crossorigin="anonymous"></script></body></html>
-200
View File
@@ -1,200 +0,0 @@
<!DOCTYPE html>
<html lang="en-us">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
<title>Sky Trek Tales</title>
<style>
body, html {
margin: 0; padding: 0; width: 100%; height: 100%;
overflow: hidden; background-color: #000;
position: fixed;
}
#canvas {
display: block;
position: absolute;
top: 0; left: 0;
width: 100vw; height: 100vh;
border: none;
}
#fs-button {
position: absolute;
top: 10px; right: 10px;
padding: 10px;
z-index: 10;
background: rgba(255,255,255,0.3);
color: white; border: 1px solid white;
cursor: pointer;
font-family: sans-serif;
border-radius: 5px;
}
#status { color: white; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); }
/* Nick modal */
#nickOverlay {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
background: rgba(0,0,0,0.85);
z-index: 9999;
}
#nickBox {
background: #111;
border: 1px solid #444;
padding: 24px;
width: 320px;
box-shadow: 0 8px 24px rgba(0,0,0,0.6);
text-align: center;
}
#nickBox h2 { margin: 0 0 12px 0; font-size: 18px; color: #eee; }
#nickBox input[type="text"] {
width: 100%;
padding: 10px;
font-size: 16px;
box-sizing: border-box;
margin-bottom: 12px;
border: 1px solid #333;
background: #000;
color: #fff;
}
#nickBox button {
padding: 10px 16px;
font-size: 16px;
background: #2a9fd6;
border: none;
color: #fff;
cursor: pointer;
}
</style>
</head>
<body>
<button id="fs-button">Fullscreen</button>
<div id="status">Downloading...</div>
<canvas id="canvas" oncontextmenu="event.preventDefault()" tabindex="-1"></canvas>
<div id="nickOverlay" style="display:none;">
<div id="nickBox">
<h2>Enter your nickname</h2>
<input id="nickInput" type="text" maxlength="32" placeholder="Player" />
<div>
<button id="nickSubmit">Start</button>
</div>
</div>
</div>
<script>
// Utility: подготовить глобальный Module до загрузки Emscripten-скрипта
function prepareModuleEnvironment() {
window.Module = window.Module || {};
var canvasEl = document.getElementById('canvas');
// Устанавливаем canvas для Emscripten, чтобы createContext не падал
window.Module.canvas = canvasEl;
// Подготовим заглушку setStatus, если ещё нет
window.Module.setStatus = window.Module.setStatus || function (text) {
var statusElement = document.getElementById("status");
statusElement.innerHTML = text;
statusElement.style.display = text ? 'block' : 'none';
};
}
// Show overlay only if no nickname saved.
function loadGameScript() {
var s = document.createElement('script');
s.src = 'space-game001.js';
s.async = true;
document.body.appendChild(s);
}
function showNickOverlay() {
var overlay = document.getElementById('nickOverlay');
overlay.style.display = 'flex';
var input = document.getElementById('nickInput');
input.focus();
}
function hideNickOverlay() {
var overlay = document.getElementById('nickOverlay');
overlay.style.display = 'none';
}
function saveNickAndStart(nick) {
try {
if (!nick || nick.trim() === '') nick = 'Player';
localStorage.setItem('spacegame_nick', nick);
} catch (e) {
console.warn('localStorage not available', e);
}
hideNickOverlay();
// перед загрузкой скрипта гарантируем, что Module.canvas задан
prepareModuleEnvironment();
loadGameScript();
}
document.getElementById('fs-button').addEventListener('click', function() {
if (!document.fullscreenElement) {
document.documentElement.requestFullscreen().catch(function(e) {
console.error('Fullscreen error: ' + e.message);
});
} else {
document.exitFullscreen();
}
});
document.addEventListener('DOMContentLoaded', function() {
// Готовим Module сразу — даже если откроется модалка, поле canvas будет доступно для скрипта (если он загружается позже)
prepareModuleEnvironment();
var stored = null;
try {
stored = localStorage.getItem('spacegame_nick');
} catch (e) {
console.warn('localStorage not available', e);
}
if (stored && stored.trim() !== '') {
// Nick is present — start immediately
loadGameScript();
} else {
// Show modal to request nickname before loading WASM
showNickOverlay();
var submit = document.getElementById('nickSubmit');
var input = document.getElementById('nickInput');
submit.addEventListener('click', function() {
saveNickAndStart(input.value);
});
input.addEventListener('input', function() {
// Strip any character that is not a-z, A-Z, 0-9 or space
var pos = this.selectionStart;
var cleaned = this.value.replace(/[^a-zA-Z0-9 ]/g, '');
if (cleaned !== this.value) {
this.value = cleaned;
this.setSelectionRange(pos - 1, pos - 1);
}
});
input.addEventListener('keydown', function(e) {
if (e.key === 'Enter') {
saveNickAndStart(input.value);
}
});
}
});
window.addEventListener("orientationchange", function() {
// Chrome на Android обновляет innerWidth/Height не мгновенно.
// Ждем завершения анимации поворота.
setTimeout(() => {
// В Emscripten это вызовет ваш onWindowResized в C++
window.dispatchEvent(new Event('resize'));
}, 200);
});
</script>
</body>
</html>
+1 -1
View File
@@ -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..."
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+4 -5
View File
@@ -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,
@@ -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
}
]
}
}
+46 -15
View File
@@ -6,6 +6,13 @@
#include <android/log.h>
#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<char> buffer = FRG::readFile(filePath);
//Sounds are loaded from regular resource file
std::vector<char> 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;
+2 -2
View File
@@ -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");
}
}
}
+1 -1
View File
@@ -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;
+103 -27
View File
@@ -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<char> 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<char> 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";
}
}
}
+4 -1
View File
@@ -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);
+29
View File
@@ -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<int>();
}
//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<float>();
}
//logger() << "GameState::load step 5" << std::endl;
// --- Tutorial ---
tutorialStep = static_cast<TutorialStep>(in.value("tutorialStep", static_cast<int>(tutorialStep)));
uniIntTutorialState = static_cast<UniIntTutorialState>(in.value("uniIntTutorialState", static_cast<int>(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<int>(in["chatUnread"].size()); ++i)
chatUnread[i] = in["chatUnread"][i].get<bool>();
}
//logger() << "GameState::load step 7" << std::endl;
if (in.contains("chatPreviewMsg") && in["chatPreviewMsg"].is_array()) {
for (int i = 0; i < 3 && i < static_cast<int>(in["chatPreviewMsg"].size()); ++i)
chatPreviewMsg[i] = in["chatPreviewMsg"][i].get<std::string>();
}
//logger() << "GameState::load step 8" << std::endl;
if (in.contains("chatHistory") && in["chatHistory"].is_array()) {
for (int i = 0; i < 3 && i < static_cast<int>(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
+26
View File
@@ -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
+23 -13
View File
@@ -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<NpcCar::Mode>(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<int>(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<NpcCar::Mode>(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<int>(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<int>(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
+1 -1
View File
@@ -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<Vector3f> taxiCarWaypoints;
// (trigger/teleport zone runtime flags are serialised by id directly in Location::save/load)
+324 -89
View File
@@ -10,8 +10,16 @@
#include <string>
#include <SDL.h>
#ifdef __EMSCRIPTEN__
#include <emscripten.h>
#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<float, 4>{ 0.2f, 0.9f, 0.2f, 1.0f }
: std::array<float, 4>{ 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<float, 4>{ 0.2f, 0.9f, 0.2f, 1.0f }
: std::array<float, 4>{ 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<int>(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<float, 4>{ 0.2f, 0.9f, 0.2f, 1.0f }
: std::array<float, 4>{ 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<float, 4>{ 0.2f, 0.9f, 0.2f, 1.0f }
: std::array<float, 4>{ 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<UiNode> 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)
+21
View File
@@ -130,7 +130,20 @@ namespace FRG {
std::shared_ptr<Texture> 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<UiNode> creditsScreenRoot;
std::shared_ptr<UiNode> portraitCreditsScreenRoot;
#ifdef __EMSCRIPTEN__
std::shared_ptr<UiNode> settingsScreenNoMusicWebRoot;
#endif
std::shared_ptr<UiNode> settingsScreenRoot;
std::shared_ptr<UiNode> settingsGraphicsScreenRoot;
std::shared_ptr<UiNode> languageScreenRoot;
@@ -343,6 +363,7 @@ namespace FRG {
std::string fullScreenToggleOff;
std::string shadowToggleOn;
std::string shadowToggleOff;
std::string musicDownloadProgressPrefix;
};
+73 -10
View File
@@ -105,12 +105,20 @@ namespace FRG
}
std::vector<char> 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
};
+8
View File
@@ -23,6 +23,8 @@ namespace FRG
std::vector<char> 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
}