Compare commits

...
57 changed files with 1979 additions and 1342 deletions

No files matched your search

+93 -12
View File
@@ -1,10 +1,17 @@
cmake_minimum_required(VERSION 3.16) cmake_minimum_required(VERSION 3.16)
project(witcher001 LANGUAGES C CXX) project(ShadowOverBishkekDemo LANGUAGES C CXX)
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
message(STATUS "Setting build type to 'Release' as none was specified.")
set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose the type of build." FORCE)
endif()
set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD_REQUIRED ON)
option(STEAMSDK "Enable Steamworks SDK integration" OFF)
# =========================================== # ===========================================
# Поиск системных зависимостей Linux # Поиск системных зависимостей Linux
# =========================================== # ===========================================
@@ -62,9 +69,9 @@ endif()
# =========================================== # ===========================================
# Основной проект witcher001 # Основной проект ShadowOverBishkekDemo
# =========================================== # ===========================================
add_executable(witcher001 add_executable(ShadowOverBishkekDemo
../src/main.cpp ../src/main.cpp
../src/Game.cpp ../src/Game.cpp
../src/Game.h ../src/Game.h
@@ -158,7 +165,7 @@ add_executable(witcher001
) )
# include-пути проекта # include-пути проекта
target_include_directories(witcher001 PRIVATE target_include_directories(ShadowOverBishkekDemo PRIVATE
"${CMAKE_CURRENT_SOURCE_DIR}/../src" "${CMAKE_CURRENT_SOURCE_DIR}/../src"
${SDL2_INCLUDE_DIRS} ${SDL2_INCLUDE_DIRS}
${SDL2_TTF_INCLUDE_DIRS} ${SDL2_TTF_INCLUDE_DIRS}
@@ -166,19 +173,18 @@ target_include_directories(witcher001 PRIVATE
${LIBZIP_INCLUDE_DIRS} ${LIBZIP_INCLUDE_DIRS}
${LUA_INCLUDE_DIRS} ${LUA_INCLUDE_DIRS}
${EIGEN3_INCLUDE_DIRS} ${EIGEN3_INCLUDE_DIRS}
# sol2 - header-only, берем напрямую из thirdparty (если он там есть) # sol2 - header-only, берем напрямую из thirdparty
# или можно установить системный libsol-dev и убрать этот путь
"${CMAKE_CURRENT_SOURCE_DIR}/../thirdparty/sol2-3.3.0/include" "${CMAKE_CURRENT_SOURCE_DIR}/../thirdparty/sol2-3.3.0/include"
) )
# Убираем WIN32_LEAN_AND_MEAN # Убираем WIN32_LEAN_AND_MEAN
target_compile_definitions(witcher001 PRIVATE target_compile_definitions(ShadowOverBishkekDemo PRIVATE
PNG_ENABLED PNG_ENABLED
SDL_MAIN_HANDLED SDL_MAIN_HANDLED
) )
# Линковка директорий для pkg-config (если пакеты не в /usr/lib) # Линковка директорий для pkg-config (если пакеты не в /usr/lib)
target_link_directories(witcher001 PRIVATE target_link_directories(ShadowOverBishkekDemo PRIVATE
${SDL2_LIBRARY_DIRS} ${SDL2_LIBRARY_DIRS}
${SDL2_TTF_LIBRARY_DIRS} ${SDL2_TTF_LIBRARY_DIRS}
${SDL2_MIXER_LIBRARY_DIRS} ${SDL2_MIXER_LIBRARY_DIRS}
@@ -187,7 +193,7 @@ target_link_directories(witcher001 PRIVATE
) )
# Линковка библиотек # Линковка библиотек
target_link_libraries(witcher001 PRIVATE target_link_libraries(ShadowOverBishkekDemo PRIVATE
OpenGL::GL OpenGL::GL
OpenGL::GLU OpenGL::GLU
ZLIB::ZLIB ZLIB::ZLIB
@@ -202,6 +208,83 @@ target_link_libraries(witcher001 PRIVATE
pthread pthread
) )
# ===========================================
# Интеграция Steamworks SDK (если STEAMSDK=ON)
# ===========================================
if(STEAMSDK)
message(STATUS "Steamworks SDK integration is ENABLED.")
target_compile_definitions(ShadowOverBishkekDemo PRIVATE STEAMSDK)
set(STEAM_SDK_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../thirdparty/steamworks_sdk_164/sdk")
target_include_directories(ShadowOverBishkekDemo PRIVATE
"${STEAM_SDK_DIR}/public"
"${STEAM_SDK_DIR}/public/steam"
)
set(STEAM_LIB_DIR "${STEAM_SDK_DIR}/redistributable_bin/linux64")
set(STEAM_LIB_NAME "steam_api")
set(STEAM_SO_NAME "libsteam_api.so")
find_library(STEAM_LIBRARY
NAMES ${STEAM_LIB_NAME}
PATHS ${STEAM_LIB_DIR}
NO_DEFAULT_PATH
)
if(STEAM_LIBRARY)
target_link_libraries(ShadowOverBishkekDemo PRIVATE ${STEAM_LIBRARY})
message(STATUS "Found Steam API library: ${STEAM_LIBRARY}")
else()
message(FATAL_ERROR "Steam API library (${STEAM_LIB_NAME}) NOT found in ${STEAM_LIB_DIR}!")
endif()
file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/steam_appid.txt" "4945840")
else()
message(STATUS "Steamworks SDK integration is DISABLED.")
endif()
# ===========================================
# Настройка RPATH и копирование динамических библиотек
# ===========================================
# Указываем линкеру искать .so файлы в той же папке, где лежит бинарник ($ORIGIN)
set_target_properties(ShadowOverBishkekDemo PROPERTIES
INSTALL_RPATH "$ORIGIN"
BUILD_WITH_INSTALL_RPATH TRUE
)
#FIXING BUILD BUG
set(LUA_SO_DIR "/usr/lib/x86_64-linux-gnu/")
set (LIBZIP_SO_DIR "/usr/lib/x86_64-linux-gnu/")
# Поиск директорий, где лежат библиотеки
#get_filename_component(LUA_SO_DIR ${LUA_SO_PATH} DIRECTORY)
#get_filename_component(LIBZIP_SO_DIR ${LIBZIP_SO_PATH} DIRECTORY)
add_custom_command(TARGET ShadowOverBishkekDemo POST_BUILD
COMMAND ${CMAKE_COMMAND} -E echo "Copying shared libraries with sonames..."
# Копируем библиотеки и все их симлинки (cp -P сохраняет структуру симлинков)
COMMAND sh -c "cp -P ${LUA_SO_DIR}/liblua5.4.so* '$<TARGET_FILE_DIR:ShadowOverBishkekDemo>/'"
COMMAND sh -c "cp -P ${LIBZIP_SO_DIR}/libzip.so* '$<TARGET_FILE_DIR:ShadowOverBishkekDemo>/'"
)
# Если включен Steam, копируем libsteam_api.so и steam_appid.txt
if(STEAMSDK)
add_custom_command(TARGET ShadowOverBishkekDemo POST_BUILD
COMMAND ${CMAKE_COMMAND} -E echo "Copying Steam SDK components..."
COMMAND ${CMAKE_COMMAND} -E copy_if_different
"${STEAM_LIB_DIR}/${STEAM_SO_NAME}"
"$<TARGET_FILE_DIR:ShadowOverBishkekDemo>/${STEAM_SO_NAME}"
COMMAND ${CMAKE_COMMAND} -E copy_if_different
"${CMAKE_CURRENT_BINARY_DIR}/steam_appid.txt"
"$<TARGET_FILE_DIR:ShadowOverBishkekDemo>/steam_appid.txt"
COMMAND ${CMAKE_COMMAND} -E copy_if_different
"${CMAKE_CURRENT_BINARY_DIR}/steam_appid.txt"
"${CMAKE_BINARY_DIR}/steam_appid.txt"
)
endif()
# =========================================== # ===========================================
# Копирование ресурсов после сборки # Копирование ресурсов после сборки
# =========================================== # ===========================================
@@ -210,10 +293,8 @@ set(RUNTIME_RESOURCE_DIRS
"audio" "audio"
) )
# В Linux (при использовании Make/Ninja) бинарник обычно создается в CMAKE_BINARY_DIR.
# Копируем ресурсы рядом с исполняемым файлом.
foreach(resdir IN LISTS RUNTIME_RESOURCE_DIRS) foreach(resdir IN LISTS RUNTIME_RESOURCE_DIRS)
add_custom_command(TARGET witcher001 POST_BUILD add_custom_command(TARGET ShadowOverBishkekDemo POST_BUILD
COMMAND ${CMAKE_COMMAND} -E echo "Copying ${resdir} to binary dir..." COMMAND ${CMAKE_COMMAND} -E echo "Copying ${resdir} to binary dir..."
COMMAND ${CMAKE_COMMAND} -E copy_directory COMMAND ${CMAKE_COMMAND} -E copy_directory
"${CMAKE_SOURCE_DIR}/../${resdir}" "${CMAKE_SOURCE_DIR}/../${resdir}"
+91 -24
View File
@@ -1,16 +1,19 @@
cmake_minimum_required(VERSION 3.16) cmake_minimum_required(VERSION 3.16)
project(witcher001 LANGUAGES C CXX) project(ShadowOverBishkekDemo LANGUAGES C CXX)
set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Опция для интеграции Steamworks SDK (по умолчанию выключена)
option(STEAMSDK "Enable Steamworks SDK integration" OFF)
include(${CMAKE_CURRENT_SOURCE_DIR}/../cmake/ThirdParty.cmake) include(${CMAKE_CURRENT_SOURCE_DIR}/../cmake/ThirdParty.cmake)
# =========================================== # ===========================================
# Основной проект witcher001 # Основной проект ShadowOverBishkekDemo
# =========================================== # ===========================================
add_executable(witcher001 add_executable(ShadowOverBishkekDemo WIN32
../src/main.cpp ../src/main.cpp
../src/Game.cpp ../src/Game.cpp
../src/Game.h ../src/Game.h
@@ -104,33 +107,33 @@ add_executable(witcher001
) )
# Установка проекта по умолчанию для Visual Studio # Установка проекта по умолчанию для Visual Studio
set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT witcher001) set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT ShadowOverBishkekDemo)
# include-пути проекта # include-пути проекта
target_include_directories(witcher001 PRIVATE target_include_directories(ShadowOverBishkekDemo PRIVATE
"${CMAKE_CURRENT_SOURCE_DIR}/../src" "${CMAKE_CURRENT_SOURCE_DIR}/../src"
) )
set_target_properties(witcher001 PROPERTIES set_target_properties(ShadowOverBishkekDemo PROPERTIES
OUTPUT_NAME "witcher001" OUTPUT_NAME "ShadowOverBishkekDemo"
) )
# Определения препроцессора: # Определения препроцессора:
# PNG_ENABLED – включает код PNG в TextureManager # PNG_ENABLED – включает код PNG в TextureManager
# SDL_MAIN_HANDLED – отключает переопределение main -> SDL_main # SDL_MAIN_HANDLED – отключает переопределение main -> SDL_main
target_compile_definitions(witcher001 PRIVATE target_compile_definitions(ShadowOverBishkekDemo PRIVATE
WIN32_LEAN_AND_MEAN WIN32_LEAN_AND_MEAN
PNG_ENABLED PNG_ENABLED
SDL_MAIN_HANDLED # SDL_MAIN_HANDLED
# DEBUG_LIGHT # DEBUG_LIGHT
# SHOW_PATH # SHOW_PATH
) )
# Линкуем с SDL2main, если он вообще установлен # Линкуем с SDL2main, если он вообще установлен
target_link_libraries(witcher001 PRIVATE SDL2main_external_lib) target_link_libraries(ShadowOverBishkekDemo PRIVATE SDL2main_external_lib)
# Линкуем сторонние библиотеки # Линкуем сторонние библиотеки
target_link_libraries(witcher001 PRIVATE target_link_libraries(ShadowOverBishkekDemo PRIVATE
SDL2_external_lib SDL2_external_lib
libpng_external_lib libpng_external_lib
zlib_external_lib zlib_external_lib
@@ -145,7 +148,55 @@ target_link_libraries(witcher001 PRIVATE
# Линкуем OpenGL (Windows) # Линкуем OpenGL (Windows)
if(WIN32) if(WIN32)
target_link_libraries(witcher001 PRIVATE opengl32) target_link_libraries(ShadowOverBishkekDemo PRIVATE opengl32)
endif()
# ===========================================
# Интеграция Steamworks SDK (если STEAMSDK=ON)
# ===========================================
if(STEAMSDK)
message(STATUS "Steamworks SDK integration is ENABLED.")
# Макрос STEAMSDK для условной компиляции в C++ коде (#ifdef STEAMSDK)
target_compile_definitions(ShadowOverBishkekDemo PRIVATE STEAMSDK)
set(STEAM_SDK_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../thirdparty/steamworks_sdk_164/sdk")
# Подключение заголовочных файлов Steamworks
target_include_directories(ShadowOverBishkekDemo PRIVATE
"${STEAM_SDK_DIR}/public"
"${STEAM_SDK_DIR}/public/steam"
)
# Определение разрядности сборки (x86 vs x64) для выбора версий lib и dll
if(CMAKE_SIZEOF_VOID_P EQUAL 8)
set(STEAM_LIB_DIR "${STEAM_SDK_DIR}/redistributable_bin/win64")
set(STEAM_LIB_NAME "steam_api64")
set(STEAM_DLL_NAME "steam_api64.dll")
else()
set(STEAM_LIB_DIR "${STEAM_SDK_DIR}/redistributable_bin")
set(STEAM_LIB_NAME "steam_api")
set(STEAM_DLL_NAME "steam_api.dll")
endif()
# Поиск библиотеки линковки (.lib)
find_library(STEAM_LIBRARY
NAMES ${STEAM_LIB_NAME}
PATHS ${STEAM_LIB_DIR}
NO_DEFAULT_PATH
)
if(STEAM_LIBRARY)
target_link_libraries(ShadowOverBishkekDemo PRIVATE ${STEAM_LIBRARY})
message(STATUS "Found Steam API library: ${STEAM_LIBRARY}")
else()
message(FATAL_ERROR "Steam API library (${STEAM_LIB_NAME}) NOT found in ${STEAM_LIB_DIR}!")
endif()
# Генерация временного файла steam_appid.txt с ID демо-версии (4945840)
file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/steam_appid.txt" "4945840")
else()
message(STATUS "Steamworks SDK integration is DISABLED.")
endif() endif()
# =========================================== # ===========================================
@@ -155,22 +206,22 @@ if (WIN32)
# SDL2: в Debug - SDL2d.dll, в Release - SDL2.dll # SDL2: в Debug - SDL2d.dll, в Release - SDL2.dll
set(SDL2_DLL_SRC "$<IF:$<CONFIG:Debug>,${SDL2_INSTALL_DIR}/bin/SDL2d.dll,${SDL2_INSTALL_DIR}/bin/SDL2.dll>") set(SDL2_DLL_SRC "$<IF:$<CONFIG:Debug>,${SDL2_INSTALL_DIR}/bin/SDL2d.dll,${SDL2_INSTALL_DIR}/bin/SDL2.dll>")
set(SDL2_DLL_DST "$<IF:$<CONFIG:Debug>,$<TARGET_FILE_DIR:witcher001>/SDL2d.dll,$<TARGET_FILE_DIR:witcher001>/SDL2.dll>") set(SDL2_DLL_DST "$<IF:$<CONFIG:Debug>,$<TARGET_FILE_DIR:ShadowOverBishkekDemo>/SDL2d.dll,$<TARGET_FILE_DIR:ShadowOverBishkekDemo>/SDL2.dll>")
set(LIBZIP_DLL_SRC "$<IF:$<CONFIG:Debug>,${LIBZIP_BASE_DIR}-Debug/bin/zip.dll,${LIBZIP_BASE_DIR}-Release/bin/zip.dll>") set(LIBZIP_DLL_SRC "$<IF:$<CONFIG:Debug>,${LIBZIP_BASE_DIR}-Debug/bin/zip.dll,${LIBZIP_BASE_DIR}-Release/bin/zip.dll>")
set(ZLIB_DLL_SRC "$<IF:$<CONFIG:Debug>,${ZLIB_INSTALL_DIR}/bin/zd.dll,${ZLIB_INSTALL_DIR}/bin/z.dll>") set(ZLIB_DLL_SRC "$<IF:$<CONFIG:Debug>,${ZLIB_INSTALL_DIR}/bin/zd.dll,${ZLIB_INSTALL_DIR}/bin/z.dll>")
set(ZLIB_DLL_DST "$<IF:$<CONFIG:Debug>,$<TARGET_FILE_DIR:witcher001>/zd.dll,$<TARGET_FILE_DIR:witcher001>/z.dll>") set(ZLIB_DLL_DST "$<IF:$<CONFIG:Debug>,$<TARGET_FILE_DIR:ShadowOverBishkekDemo>/zd.dll,$<TARGET_FILE_DIR:ShadowOverBishkekDemo>/z.dll>")
set(SDL2TTF_DLL_SRC "$<IF:$<CONFIG:Debug>,${SDL2TTF_BASE_DIR}-Debug/bin/SDL2_ttfd.dll,${SDL2TTF_BASE_DIR}-Release/bin/SDL2_ttf.dll>") set(SDL2TTF_DLL_SRC "$<IF:$<CONFIG:Debug>,${SDL2TTF_BASE_DIR}-Debug/bin/SDL2_ttfd.dll,${SDL2TTF_BASE_DIR}-Release/bin/SDL2_ttf.dll>")
set(SDL2MIXER_DLL_SRC "$<IF:$<CONFIG:Debug>,${SDL2MIXER_BASE_DIR}-Debug/bin/SDL2_mixerd.dll,${SDL2MIXER_BASE_DIR}-Release/bin/SDL2_mixer.dll>") set(SDL2MIXER_DLL_SRC "$<IF:$<CONFIG:Debug>,${SDL2MIXER_BASE_DIR}-Debug/bin/SDL2_mixerd.dll,${SDL2MIXER_BASE_DIR}-Release/bin/SDL2_mixer.dll>")
add_custom_command(TARGET witcher001 POST_BUILD add_custom_command(TARGET ShadowOverBishkekDemo POST_BUILD
COMMAND ${CMAKE_COMMAND} -E echo "Copying DLLs to output folder..." COMMAND ${CMAKE_COMMAND} -E echo "Copying DLLs to output folder..."
# Копируем SDL2 (целевое имя всегда SDL2.dll) # Копируем SDL2
COMMAND ${CMAKE_COMMAND} -E copy_if_different COMMAND ${CMAKE_COMMAND} -E copy_if_different
"${SDL2_DLL_SRC}" "${SDL2_DLL_SRC}"
"${SDL2_DLL_DST}" "${SDL2_DLL_DST}"
@@ -178,7 +229,7 @@ add_custom_command(TARGET witcher001 POST_BUILD
# Копируем LIBZIP (целевое имя всегда zip.dll) # Копируем LIBZIP (целевое имя всегда zip.dll)
COMMAND ${CMAKE_COMMAND} -E copy_if_different COMMAND ${CMAKE_COMMAND} -E copy_if_different
"${LIBZIP_DLL_SRC}" "${LIBZIP_DLL_SRC}"
"$<TARGET_FILE_DIR:witcher001>/zip.dll" "$<TARGET_FILE_DIR:ShadowOverBishkekDemo>/zip.dll"
COMMAND ${CMAKE_COMMAND} -E copy_if_different COMMAND ${CMAKE_COMMAND} -E copy_if_different
"${ZLIB_DLL_SRC}" "${ZLIB_DLL_SRC}"
@@ -186,15 +237,31 @@ add_custom_command(TARGET witcher001 POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different COMMAND ${CMAKE_COMMAND} -E copy_if_different
"${SDL2TTF_DLL_SRC}" "${SDL2TTF_DLL_SRC}"
"$<TARGET_FILE_DIR:witcher001>" "$<TARGET_FILE_DIR:ShadowOverBishkekDemo>"
COMMAND ${CMAKE_COMMAND} -E copy_if_different COMMAND ${CMAKE_COMMAND} -E copy_if_different
"${CMAKE_CURRENT_SOURCE_DIR}/../thirdparty/SDL_mixer-release-2.8.0/install-$<CONFIG>/bin/SDL2_mixer$<$<CONFIG:Debug>:d>.dll" "${CMAKE_CURRENT_SOURCE_DIR}/../thirdparty/SDL_mixer-release-2.8.0/install-$<CONFIG>/bin/SDL2_mixer$<$<CONFIG:Debug>:d>.dll"
"$<TARGET_FILE_DIR:ShadowOverBishkekDemo>/SDL2_mixer$<$<CONFIG:Debug>:d>.dll"
# This is only for profiling:
#"${CMAKE_CURRENT_SOURCE_DIR}/../thirdparty/SDL_mixer-release-2.8.0/install-Release/bin/SDL2_mixer$<$<CONFIG:Debug>:d>.dll"
"$<TARGET_FILE_DIR:witcher001>/SDL2_mixer$<$<CONFIG:Debug>:d>.dll"
) )
# Если включен Steam, добавляем шаги копирования DLL и appid в этот же POST_BUILD
if(STEAMSDK)
add_custom_command(TARGET ShadowOverBishkekDemo POST_BUILD
COMMAND ${CMAKE_COMMAND} -E echo "Copying Steam SDK components..."
# Копирование dll к исполняемому файлу
COMMAND ${CMAKE_COMMAND} -E copy_if_different
"${STEAM_LIB_DIR}/${STEAM_DLL_NAME}"
"$<TARGET_FILE_DIR:ShadowOverBishkekDemo>/${STEAM_DLL_NAME}"
# Копирование steam_appid.txt к исполняемому файлу
COMMAND ${CMAKE_COMMAND} -E copy_if_different
"${CMAKE_CURRENT_BINARY_DIR}/steam_appid.txt"
"$<TARGET_FILE_DIR:ShadowOverBishkekDemo>/steam_appid.txt"
# Дублируем steam_appid.txt в корень сборки (полезно при отладке прямо из MSVC)
COMMAND ${CMAKE_COMMAND} -E copy_if_different
"${CMAKE_CURRENT_BINARY_DIR}/steam_appid.txt"
"${CMAKE_BINARY_DIR}/steam_appid.txt"
)
endif()
endif() endif()
# =========================================== # ===========================================
@@ -209,12 +276,12 @@ set(RUNTIME_RESOURCE_DIRS
# Копируем ресурсы и шейдеры в папку exe и в корень build/ # Копируем ресурсы и шейдеры в папку exe и в корень build/
foreach(resdir IN LISTS RUNTIME_RESOURCE_DIRS) foreach(resdir IN LISTS RUNTIME_RESOURCE_DIRS)
add_custom_command(TARGET witcher001 POST_BUILD add_custom_command(TARGET ShadowOverBishkekDemo POST_BUILD
COMMAND ${CMAKE_COMMAND} -E echo "Copying ${resdir} to runtime folders..." COMMAND ${CMAKE_COMMAND} -E echo "Copying ${resdir} to runtime folders..."
# 1) туда, где лежит exe (build/Debug, build/Release и т.п.) # 1) туда, где лежит exe (build/Debug, build/Release и т.п.)
COMMAND ${CMAKE_COMMAND} -E copy_directory COMMAND ${CMAKE_COMMAND} -E copy_directory
"${CMAKE_SOURCE_DIR}/../${resdir}" "${CMAKE_SOURCE_DIR}/../${resdir}"
"$<TARGET_FILE_DIR:witcher001>/${resdir}" "$<TARGET_FILE_DIR:ShadowOverBishkekDemo>/${resdir}"
# 2) в корень build, если захочешь запускать из этой папки # 2) в корень build, если захочешь запускать из этой папки
COMMAND ${CMAKE_COMMAND} -E copy_directory COMMAND ${CMAKE_COMMAND} -E copy_directory
"${CMAKE_SOURCE_DIR}/../${resdir}" "${CMAKE_SOURCE_DIR}/../${resdir}"
+1 -1
View File
@@ -26,7 +26,7 @@
"animationIdlePath": "resources/w/girlfriend/girlfriend_idle003_small.anim", "animationIdlePath": "resources/w/girlfriend/girlfriend_idle003_small.anim",
"animationWalkPath": "resources/w/girlfriend/girlfriend_walk003_small.anim", "animationWalkPath": "resources/w/girlfriend/girlfriend_walk003_small.anim",
"meshTextures": { "meshTextures": {
"Girl_Low": "resources/w/girlfriend/Girl_Base_color.png" "Girl_Low": "resources/w/girlfriend/Girl_Base_color006.png"
}, },
"positionX": 0.799619, "positionX": 0.799619,
"positionY": 0.0, "positionY": 0.0,
+1 -1
View File
@@ -13,7 +13,7 @@
{ {
"key": "Аида Дженибековна", "key": "Аида Дженибековна",
"ru": "Аида Дженибековна", "ru": "Аида Дженибековна",
"en": "Aida Dzhanybekovna" "en": "Aida Dzhenibekovna"
}, },
{ {
"key": "Алтынай", "key": "Алтынай",
+2 -2
View File
@@ -44,8 +44,8 @@
}, },
{ {
"id": "report_card", "id": "report_card",
"name": "Begimai's grade book", "name": "Begimai's record book",
"description": "This is Begimai's grade book. This is where a teacher should write grades for each subject or coursework.", "description": "This is Begimai's record book. This is where a teacher should write grades for each subject or coursework.",
"icon": "resources/w/ui/img/inv/ItemReportCard002.png", "icon": "resources/w/ui/img/inv/ItemReportCard002.png",
"selectedIcon": "resources/w/ui/img/inv/ItemSelReportCard002.png" "selectedIcon": "resources/w/ui/img/inv/ItemSelReportCard002.png"
}, },
+7 -7
View File
@@ -20,7 +20,7 @@
"objectives": [ "objectives": [
{ "id": "ghost_lore_aiperi", "text": "Talk to Aiperi", "completed": false }, { "id": "ghost_lore_aiperi", "text": "Talk to Aiperi", "completed": false },
{ "id": "ghost_lore_alik", "text": "Talk to Alik", "completed": false, "visible": false }, { "id": "ghost_lore_alik", "text": "Talk to Alik", "completed": false, "visible": false },
{ "id": "ghost_lore_teacher", "text": "Talk to Aida Dzhanybekovna", "completed": false } { "id": "ghost_lore_teacher", "text": "Talk to Aida Dzhenibekovna", "completed": false }
] ]
}, },
{ {
@@ -31,7 +31,7 @@
"description": "Begimai's story is even more mysterious. While her coursework was due, the university was undergoing a general cleaning. By chance, her coursework ended up in the trash bin. As a result, her coursework was thrown away, the professor was not able to find it, and gave her a zero grade. According to Alik, the coursework still lies in a pile of trash in the university courtyard.", "description": "Begimai's story is even more mysterious. While her coursework was due, the university was undergoing a general cleaning. By chance, her coursework ended up in the trash bin. As a result, her coursework was thrown away, the professor was not able to find it, and gave her a zero grade. According to Alik, the coursework still lies in a pile of trash in the university courtyard.",
"objectives": [ "objectives": [
{ "id": "ghost_coursework_find", "text": "Find Begimai's coursework", "completed": false }, { "id": "ghost_coursework_find", "text": "Find Begimai's coursework", "completed": false },
{ "id": "ghost_coursework_mark", "text": "Show coursework to Aida Dzhanybekovna", "completed": false } { "id": "ghost_coursework_mark", "text": "Show coursework to Aida Dzhenibekovna", "completed": false }
] ]
}, },
{ {
@@ -41,9 +41,9 @@
"autoComplete": true, "autoComplete": true,
"description": "A student named Begimai actually attended university last year. She tried to submit a coursework, but due to a series of circumstances, she failed and received a zero grade. This coursework shattered all her plans and hopes, and Begimai jumped out the window. Now she's returned as a ghost, and to be liberated, she must ensure she receives a grade for the coursework.", "description": "A student named Begimai actually attended university last year. She tried to submit a coursework, but due to a series of circumstances, she failed and received a zero grade. This coursework shattered all her plans and hopes, and Begimai jumped out the window. Now she's returned as a ghost, and to be liberated, she must ensure she receives a grade for the coursework.",
"objectives": [ "objectives": [
{ "id": "ghost_release_reportcard", "text": "Find Begimai's grade book", "completed": false }, { "id": "ghost_release_reportcard", "text": "Find Begimai's record book", "completed": false },
{ "id": "ghost_release_mark", "text": "Put a grade for the coursework in the grade book", "completed": false }, { "id": "ghost_release_mark", "text": "Put a grade for the coursework in the record book", "completed": false },
{ "id": "ghost_release_show", "text": "Show the grade book to the ghost", "completed": false } { "id": "ghost_release_show", "text": "Show the record book to the ghost", "completed": false }
] ]
}, },
{ {
@@ -51,7 +51,7 @@
"title": "Manas studies", "title": "Manas studies",
"status": "Available", "status": "Available",
"autoComplete": true, "autoComplete": true,
"description": "There's a Manas Studies module coming up soon, and I've never attended a lecture before. We have a Manas Studies lecture today, so I need to attend it and get the module assignment from my teacher, Aida Dzhanybekovna.", "description": "There's a Manas Studies module coming up soon, and I've never attended a lecture before. We have a Manas Studies lecture today, so I need to attend it and get the module assignment from my teacher, Aida Dzhenibekovna.",
"objectives": [ "objectives": [
{ "id": "study_beginning_lecture", "text": "Attend the lecture", "completed": false }, { "id": "study_beginning_lecture", "text": "Attend the lecture", "completed": false },
{ "id": "study_beginning_task", "text": "Get an assignment for the module", "completed": false } { "id": "study_beginning_task", "text": "Get an assignment for the module", "completed": false }
@@ -61,7 +61,7 @@
"id": "study_project", "id": "study_project",
"title": "Essay on Manaschi", "title": "Essay on Manaschi",
"status": "Hidden", "status": "Hidden",
"description": "My Manas studies teacher, Aida Dzhanybekovna, gave me an assignment: find a book about Zhusup Mamai's Manaschi in the library and write an essay about it. The book isn't allowed out of the library, but I can use the library's computer to write the essay. The essay must be completed before tomorrow.", "description": "My Manas studies teacher, Aida Dzhenibekovna, gave me an assignment: find a book about Zhusup Mamai's Manaschi in the library and write an essay about it. The book isn't allowed out of the library, but I can use the library's computer to write the essay. The essay must be completed before tomorrow.",
"objectives": [ "objectives": [
{ "id": "study_project_book", "text": "Find the book", "completed": false }, { "id": "study_project_book", "text": "Find the book", "completed": false },
{ "id": "study_project_write", "text": "Write the essay", "completed": false }, { "id": "study_project_write", "text": "Write the essay", "completed": false },
@@ -1,290 +0,0 @@
{
"dialogues": [
{
"id": "test_cutscene_skip_hold_dialogue",
"start": "cutscene_start",
"nodes": [
{
"id": "cutscene_start",
"type": "CutsceneStart",
"cutsceneId": "test_cutscene_skip_hold_01",
"next": "end_1"
},
{
"id": "end_1",
"type": "End"
}
]
},
{
"id": "test_cutscene_images_hardcut_dialogue",
"start": "cutscene_start",
"nodes": [
{
"id": "cutscene_start",
"type": "CutsceneStart",
"cutsceneId": "test_cutscene_images_hardcut_01",
"next": "end_1"
},
{
"id": "end_1",
"type": "End"
}
]
},
{
"id": "test_cutscene_images_crossfade_dialogue",
"start": "cutscene_start",
"nodes": [
{
"id": "cutscene_start",
"type": "CutsceneStart",
"cutsceneId": "test_cutscene_images_crossfade_01",
"next": "end_1"
},
{
"id": "end_1",
"type": "End"
}
]
},
{
"id": "test_cutscene_images_silent_dialogue",
"start": "cutscene_start",
"nodes": [
{
"id": "cutscene_start",
"type": "CutsceneStart",
"cutsceneId": "test_cutscene_images_silent_01",
"next": "end_1"
},
{
"id": "end_1",
"type": "End"
}
]
}
],
"cutscenes": [
{
"id": "test_cutscene_skip_hold_01",
"background": "resources/first_cutscene.png",
"skippable": true,
"durationMs": 12000,
"cameraTrack": [
{
"durationMs": 3000,
"from": { "anchor": "Center", "zoom": 1.0, "rotationDeg": 0.0 },
"to": { "anchor": "TopLeft", "zoom": 1.45, "rotationDeg": 0.0 },
"easing": "EaseInOutSine"
},
{
"durationMs": 3000,
"from": { "anchor": "TopLeft", "zoom": 1.45, "rotationDeg": 0.0 },
"to": { "anchor": "TopRight", "zoom": 1.45, "rotationDeg": 0.0 },
"easing": "EaseInOutSine"
},
{
"durationMs": 3000,
"from": { "anchor": "TopRight", "zoom": 1.45, "rotationDeg": 0.0 },
"to": { "anchor": "BottomRight", "zoom": 1.65, "rotationDeg": 0.0 },
"easing": "EaseInCubic"
},
{
"durationMs": 3000,
"from": { "anchor": "BottomRight", "zoom": 1.65, "rotationDeg": 0.0 },
"to": { "anchor": "BottomLeft", "zoom": 1.45, "rotationDeg": 0.0 },
"easing": "EaseInOutSine"
}
],
"lines": [
{
"speaker": "Narrator",
"portrait": "",
"text": "This cutscene is long enough to test hold-to-skip.",
"durationMs": 2600
},
{
"speaker": "Narrator",
"portrait": "",
"text": "A normal click must not skip it.",
"durationMs": 2600
},
{
"speaker": "Ghost",
"portrait": "resources/ghost_avatar.png",
"text": "Only the skip button with hold should work.",
"durationMs": 2600
}
]
},
{
"id": "test_cutscene_images_hardcut_01",
"background": "resources/first_cutscene.png",
"skippable": true,
"durationMs": 9000,
"cameraTrack": [
{
"durationMs": 4500,
"from": { "anchor": "Center", "zoom": 1.0, "rotationDeg": 0.0 },
"to": { "anchor": "TopLeft", "zoom": 1.35, "rotationDeg": 0.0 },
"easing": "EaseInOutSine"
},
{
"durationMs": 4500,
"from": { "anchor": "TopLeft", "zoom": 1.35, "rotationDeg": 0.0 },
"to": { "anchor": "BottomRight", "zoom": 1.55, "rotationDeg": 0.0 },
"easing": "EaseInOutSine"
}
],
"images": [
{
"path": "resources/first_cutscene.png",
"startMs": 0,
"endMs": 4500,
"fadeInMs": 0,
"fadeOutMs": 0
},
{
"path": "resources/second_cutscene.png",
"startMs": 4500,
"endMs": 9000,
"fadeInMs": 0,
"fadeOutMs": 0
}
],
"lines": [
{
"speaker": "Narrator",
"portrait": "",
"text": "First image should switch sharply to the second one.",
"durationMs": 2800
},
{
"speaker": "Narrator",
"portrait": "",
"text": "No fade should be visible here.",
"durationMs": 2800
}
]
},
{
"id": "test_cutscene_images_crossfade_01",
"background": "resources/first_cutscene.png",
"skippable": true,
"durationMs": 10000,
"cameraTrack": [
{
"durationMs": 2500,
"from": { "anchor": "Center", "zoom": 1.0, "rotationDeg": 0.0 },
"to": { "anchor": "Custom", "centerX": 0.35, "centerY": 0.30, "zoom": 1.45, "rotationDeg": 0.0 },
"easing": "EaseInOutQuad"
},
{
"durationMs": 2500,
"from": { "anchor": "Custom", "centerX": 0.35, "centerY": 0.30, "zoom": 1.45, "rotationDeg": 0.0 },
"to": { "anchor": "Custom", "centerX": 0.70, "centerY": 0.32, "zoom": 1.45, "rotationDeg": 0.0 },
"easing": "EaseOutCubic"
},
{
"durationMs": 2500,
"from": { "anchor": "Custom", "centerX": 0.70, "centerY": 0.32, "zoom": 1.45, "rotationDeg": 0.0 },
"to": { "anchor": "BottomRight", "zoom": 1.70, "rotationDeg": 0.0 },
"easing": "EaseInCubic"
},
{
"durationMs": 2500,
"from": { "anchor": "BottomRight", "zoom": 1.70, "rotationDeg": 0.0 },
"to": { "anchor": "BottomLeft", "zoom": 1.55, "rotationDeg": 0.0 },
"easing": "EaseInOutSine"
}
],
"images": [
{
"path": "resources/first_cutscene.png",
"startMs": 0,
"endMs": 6000,
"fadeInMs": 0,
"fadeOutMs": 0
},
{
"path": "resources/second_cutscene.png",
"startMs": 4500,
"endMs": 10000,
"fadeInMs": 1500,
"fadeOutMs": 0
}
],
"lines": [
{
"speaker": "Narrator",
"portrait": "",
"text": "The second image should fade over the first one.",
"durationMs": 2600
},
{
"speaker": "Ghost",
"portrait": "resources/ghost_avatar.png",
"text": "This test checks overlap and alpha blending.",
"durationMs": 2600
}
]
},
{
"id": "test_cutscene_images_silent_01",
"background": "resources/first_cutscene.png",
"skippable": true,
"durationMs": 11000,
"cameraTrack": [
{
"durationMs": 2500,
"from": { "anchor": "Center", "zoom": 1.0, "rotationDeg": 0.0 },
"to": { "anchor": "TopLeft", "zoom": 1.35, "rotationDeg": 0.0 },
"easing": "EaseInOutSine"
},
{
"durationMs": 3000,
"from": { "anchor": "TopLeft", "zoom": 1.35, "rotationDeg": 0.0 },
"to": { "anchor": "TopRight", "zoom": 1.35, "rotationDeg": 0.0 },
"easing": "EaseInOutSine"
},
{
"durationMs": 3000,
"from": { "anchor": "TopRight", "zoom": 1.35, "rotationDeg": 0.0 },
"to": { "anchor": "BottomRight", "zoom": 1.55, "rotationDeg": 0.0 },
"easing": "EaseOutCubic"
},
{
"durationMs": 2500,
"from": { "anchor": "BottomRight", "zoom": 1.55, "rotationDeg": 0.0 },
"to": { "anchor": "BottomLeft", "zoom": 1.45, "rotationDeg": 0.0 },
"easing": "EaseInOutQuad"
}
],
"images": [
{
"path": "resources/first_cutscene.png",
"startMs": 0,
"endMs": 3500,
"fadeInMs": 0,
"fadeOutMs": 800
},
{
"path": "resources/second_cutscene.png",
"startMs": 3000,
"endMs": 7500,
"fadeInMs": 800,
"fadeOutMs": 1000
},
{
"path": "resources/loading.png",
"startMs": 7000,
"endMs": 11000,
"fadeInMs": 1000,
"fadeOutMs": 0
}
],
"lines": []
}
]
}
@@ -155,7 +155,7 @@
"type": "Line", "type": "Line",
"speaker": "Бекзат", "speaker": "Бекзат",
"portrait": "resources/dialogue/portrait_phone.png", "portrait": "resources/dialogue/portrait_phone.png",
"text": "Почему ты сама не можешь забрать?", "text": "Почему ты сама не можешь забрать нож у Аиды?",
"next": "line_9", "next": "line_9",
"chatBubble": "out" "chatBubble": "out"
}, },
@@ -164,7 +164,16 @@
"type": "Line", "type": "Line",
"speaker": "Айпери", "speaker": "Айпери",
"portrait": "resources/dialogue/portrait_phone.png", "portrait": "resources/dialogue/portrait_phone.png",
"text": "Ты же знаешь, если я встречу Аиду, она 100% даст мне какое-нибудь сложное задание.", "text": "Она опять будет скафнить.",
"next": "line_12",
"chatBubble": "in"
},
{
"id": "line_12",
"type": "Line",
"speaker": "Айпери",
"portrait": "resources/dialogue/portrait_phone.png",
"text": "А может быть даже даст мне какое-нибудь сложное задание.",
"next": "line_10", "next": "line_10",
"chatBubble": "in" "chatBubble": "in"
}, },
@@ -182,7 +191,7 @@
"type": "Line", "type": "Line",
"speaker": "Айпери", "speaker": "Айпери",
"portrait": "resources/dialogue/portrait_phone.png", "portrait": "resources/dialogue/portrait_phone.png",
"text": "Так что жду тебя в универе! Не вздумай прогулять!", "text": "Так что жду тебя в универе! Не вздумай прогулять! Ошо!",
"next": "setflag_1", "next": "setflag_1",
"chatBubble": "in", "chatBubble": "in",
"questUnlock": "aiperi_knife" "questUnlock": "aiperi_knife"
@@ -359,7 +368,7 @@
"type": "Line", "type": "Line",
"speaker": "Бекзат", "speaker": "Бекзат",
"portrait": "resources/dialogue/portrait_hero_neutral.png", "portrait": "resources/dialogue/portrait_hero_neutral.png",
"text": "Я заказал такси до универа, машина уже ждет!", "text": "Я заказал такси до универа, машина уже едет!",
"next": "end_1" "next": "end_1"
}, },
{ {
@@ -647,7 +656,7 @@
"type": "Line", "type": "Line",
"speaker": "Алик", "speaker": "Алик",
"portrait": "resources/dialogue/portrait_student_boy.png", "portrait": "resources/dialogue/portrait_student_boy.png",
"text": "За зданием универа есть контейнер с кучей бумажного мусора и макулатурой.", "text": "Возле здания универа лежит куча строительного мусора и макулатуры.",
"next": "line_14" "next": "line_14"
}, },
{ {
-501
View File
@@ -1,501 +0,0 @@
{
"dialogues": [
{
"id": "dialog_student",
"start": "line_1",
"nodes": [
{
"id": "line_1",
"type": "Line",
"speaker": "Студент",
"portrait": "resources/w/avatar_student.png",
"text": "В университете завелись призраки, мне страшно ходить на занятия.",
"next": "line_2"
},
{
"id": "line_2",
"type": "Line",
"speaker": "Hero",
"portrait": "resources/w/gg/gg2_s_podsvetkoy5.png",
"text": "Можешь рассказать подробнее?",
"next": "line_3"
},
{
"id": "line_3",
"type": "Line",
"speaker": "Студент",
"portrait": "resources/w/avatar_student.png",
"text": "Спроси у Мухтара байке, он все знает.",
"next": "line_4"
},
{
"id": "line_4",
"type": "Line",
"speaker": "Hero",
"portrait": "resources/w/gg/gg2_s_podsvetkoy5.png",
"text": "Хорошо.",
"next": "end_1"
},
{
"id": "end_1",
"type": "End"
}
]
},
{
"id": "dialog_mukhtar",
"start": "line_1",
"nodes": [
{
"id": "line_1",
"type": "Line",
"speaker": "Мухтар байке",
"portrait": "resources/w/avatar_unknown.png",
"text": "Здравствуй, мы давно тебя ждем! Ты поможешь нам избавиться от призраков?",
"next": "line_2"
},
{
"id": "line_2",
"type": "Line",
"speaker": "Hero",
"portrait": "resources/w/gg/gg2_s_podsvetkoy5.png",
"text": "Где их найти?",
"next": "line_3"
},
{
"id": "line_3",
"type": "Line",
"speaker": "Мухтар байке",
"portrait": "resources/w/avatar_unknown.png",
"text": "Заходи в здание универа и поднимайся на второй этаж.",
"next": "line_4"
},
{
"id": "line_4",
"type": "Line",
"speaker": "Мухтар байке",
"portrait": "resources/w/avatar_unknown.png",
"text": "Ты их встретишь прямо там.",
"next": "line_5"
},
{
"id": "line_4",
"type": "Line",
"speaker": "Hero",
"portrait": "resources/w/gg/gg2_s_podsvetkoy5.png",
"text": "Хорошо, я скоро вернусь!",
"next": "end_1"
},
{
"id": "end_1",
"type": "End"
}
]
},
{
"id": "dialog_female_student",
"start": "line_1",
"nodes": [
{
"id": "line_1",
"type": "Line",
"speaker": "Студентка",
"portrait": "resources/w/avatar_girl.png",
"text": "С этими призраками совсем невозможно ходить на лекции!",
"next": "end_1"
},
{
"id": "end_1",
"type": "End"
}
]
},
{
"id": "test_line_dialogue",
"start": "line_1",
"nodes": [
{
"id": "line_1",
"type": "Line",
"speaker": "Ghost",
"portrait": "resources/ghost_avatar.png",
"text": "Наконец-то ты пришел.",
"next": "line_2"
},
{
"id": "line_2",
"type": "Line",
"speaker": "Hero",
"portrait": "resources/w/gg/gg2_s_podsvetkoy5.png",
"text": "Ты сделан из дыма?",
"next": "line_3"
},
{
"id": "line_3",
"type": "Line",
"speaker": "Ghost",
"portrait": "resources/ghost_avatar.png",
"text": "Ты думаешь, это смешно?",
"next": "line_4"
},
{
"id": "line_4",
"type": "Line",
"speaker": "Hero",
"portrait": "resources/w/gg/gg2_s_podsvetkoy5.png",
"text": "Я думаю что ты пахнешь как выхлоп от Камаза.",
"next": "end_1"
},
{
"id": "end_1",
"type": "End"
}
]
},
{
"id": "ghost_choice_dialogue",
"start": "line_1",
"nodes": [
{
"id": "line_1",
"type": "Line",
"speaker": "Беспокойный Призрак",
"portrait": "resources/w/avatar_ghost.png",
"text": "Нечасто я вижу смертных, готовых разговаривать со мной.",
"next": "choice_1"
},
{
"id": "choice_1",
"type": "Choice",
"speaker": "Hero",
"portrait": "resources/w/gg/gg2_s_podsvetkoy5.png",
"text": "",
"choices": [
{
"id": "main_1",
"kind": "Main",
"text": "Не мешай студентам учиться!",
"next": "line_goods"
},
{
"id": "optional_1",
"kind": "Optional",
"text": "Почему ты появился здесь?",
"next": "line_who"
}
]
},
{
"id": "line_goods",
"type": "Line",
"speaker": "Беспокойный Призрак",
"portrait": "resources/w/avatar_ghost.png",
"text": "Это моя месть студентам за то что они призвали меня.",
"next": "end_1"
},
{
"id": "line_who",
"type": "Line",
"speaker": "Беспокойный Призрак",
"portrait": "resources/w/avatar_ghost.png",
"text": "Группа студентов совершила ритуал и призвала меня сюда. Пока проклятие не спадет, я всегда буду здесь обитать.",
"next": "choice_1"
},
{
"id": "end_1",
"type": "End"
}
]
},
{
"id": "test_condition_dialogue",
"start": "set_flag_1",
"nodes": [
{
"id": "set_flag_1",
"type": "SetFlag",
"effects": [
{ "flag": "met_ghost", "value": 1 }
],
"next": "condition_1"
},
{
"id": "condition_1",
"type": "Condition",
"conditions": [
{ "flag": "met_ghost", "op": "Equals", "value": 1 }
],
"trueNext": "line_true",
"falseNext": "line_false"
},
{
"id": "line_true",
"type": "Line",
"speaker": "Ghost",
"portrait": "resources/ghost_avatar.png",
"text": "Now you know who I am.",
"next": "end_1"
},
{
"id": "line_false",
"type": "Line",
"speaker": "Ghost",
"portrait": "resources/ghost_avatar.png",
"text": "You should not hear this line.",
"next": "end_1"
},
{
"id": "end_1",
"type": "End"
}
]
},
{
"id": "test_cutscene_dialogue",
"start": "cutscene_start",
"nodes": [
{
"id": "cutscene_start",
"type": "CutsceneStart",
"cutsceneId": "test_cutscene_01",
"next": "end_1"
},
{
"id": "end_1",
"type": "End"
}
]
},
{
"id": "test_silent_cutscene_dialogue",
"start": "cutscene_start",
"nodes": [
{
"id": "cutscene_start",
"type": "CutsceneStart",
"cutsceneId": "test_cutscene_silent_01",
"next": "end_1"
},
{
"id": "end_1",
"type": "End"
}
]
},
{
"id": "test_cutscene_pan_dialogue",
"start": "cutscene_start",
"nodes": [
{
"id": "cutscene_start",
"type": "CutsceneStart",
"cutsceneId": "test_cutscene_pan_01",
"next": "end_1"
},
{
"id": "end_1",
"type": "End"
}
]
},
{
"id": "test_cutscene_pan_dialogue_silent",
"start": "cutscene_start",
"nodes": [
{
"id": "cutscene_start",
"type": "CutsceneStart",
"cutsceneId": "test_cutscene_pan_02",
"next": "end_1"
},
{
"id": "end_1",
"type": "End"
}
]
},
{
"id": "dialog_aida",
"start": "line_1",
"nodes": [
{
"id": "line_1",
"type": "Line",
"speaker": "Асель Дженибековна",
"portrait": "resources/w/avatar_teacher.png",
"text": "Молодой человек, у меня обед! Я принимаю лабораторные работы только после двух!",
"next": "line_2"
},
{
"id": "line_2",
"type": "Line",
"speaker": "Hero",
"portrait": "resources/w/gg/gg2_s_podsvetkoy5.png",
"text": "Хорошо, Асель Дженибековна.",
"next": "end_1"
},
{
"id": "end_1",
"type": "End"
}
]
}
],
"cutscenes": [
{
"id": "test_cutscene_01",
"background": "resources/first_cutscene.png",
"durationMs": 6800,
"cameraTrack": [
{
"durationMs": 2400,
"from": { "focusX": 0.50, "focusY": 0.55, "zoom": 1.00, "rotationDeg": 0.0 },
"to": { "focusX": 0.63, "focusY": 0.58, "zoom": 1.16, "rotationDeg": -1.0 },
"easing": "EaseInOutSine"
},
{
"durationMs": 2200,
"from": { "focusX": 0.63, "focusY": 0.58, "zoom": 1.16, "rotationDeg": -1.0 },
"to": { "focusX": 0.74, "focusY": 0.52, "zoom": 1.30, "rotationDeg": -2.4 },
"easing": "EaseInOutCubic"
},
{
"durationMs": 2200,
"from": { "focusX": 0.74, "focusY": 0.52, "zoom": 1.30, "rotationDeg": -2.4 },
"to": { "focusX": 0.58, "focusY": 0.46, "zoom": 1.10, "rotationDeg": -0.6 },
"easing": "EaseOutSine"
}
],
"lines": [
{
"speaker": "Narrator",
"portrait": "resources/hero.png",
"text": "The air in the room turned cold.",
"durationMs": 2200
},
{
"speaker": "Ghost",
"portrait": "resources/w/avatar_ghost.png",
"text": "Some memories never fade.",
"durationMs": 2600,
"background": "resources/loading.png"
}
]
},
{
"id": "test_cutscene_silent_01",
"background": "resources/first_cutscene.png",
"durationMs": 5200,
"cameraTrack": [
{
"durationMs": 2600,
"from": { "focusX": 0.40, "focusY": 0.54, "zoom": 1.00, "rotationDeg": 0.0 },
"to": { "focusX": 0.58, "focusY": 0.54, "zoom": 1.22, "rotationDeg": 0.8 },
"easing": "EaseInOutSine"
},
{
"durationMs": 2600,
"from": { "focusX": 0.58, "focusY": 0.54, "zoom": 1.22, "rotationDeg": 0.8 },
"to": { "focusX": 0.72, "focusY": 0.48, "zoom": 1.34, "rotationDeg": -0.5 },
"easing": "EaseOutCubic"
}
],
"lines": []
},
{
"id": "test_cutscene_pan_01",
"background": "resources/first_cutscene.png",
"durationMs": 12000,
"cameraTrack": [
{
"durationMs": 1200,
"from": { "anchor": "Center", "zoom": 1.00, "rotationDeg": 0.0 },
"to": { "anchor": "Center", "zoom": 1.00, "rotationDeg": 0.0 },
"easing": "Linear"
},
{
"durationMs": 2500,
"from": { "anchor": "Center", "zoom": 1.00, "rotationDeg": 0.0 },
"to": { "anchor": "TopLeft", "zoom": 1.55, "rotationDeg": 0.0 },
"easing": "EaseInOutSine"
},
{
"durationMs": 2600,
"from": { "anchor": "TopLeft", "zoom": 1.55, "rotationDeg": 0.0 },
"to": { "anchor": "TopRight", "zoom": 1.55, "rotationDeg": 0.0 },
"easing": "EaseInOutSine"
},
{
"durationMs": 1800,
"from": { "anchor": "TopRight", "zoom": 1.55, "rotationDeg": 0.0 },
"to": { "anchor": "BottomRight", "zoom": 1.72, "rotationDeg": 0.0 },
"easing": "EaseInCubic"
},
{
"durationMs": 3900,
"from": { "anchor": "BottomRight", "zoom": 1.72, "rotationDeg": 0.0 },
"to": { "anchor": "BottomLeft", "zoom": 1.55, "rotationDeg": 0.0 },
"easing": "EaseInOutSine"
}
],
"lines": [
{
"speaker": "Narrator",
"portrait": "resources/hero.png",
"text": "The memory begins in silence.",
"durationMs": 2200
},
{
"speaker": "Narrator",
"portrait": "resources/hero.png",
"text": "Something is drawing your eyes across the whole scene.",
"durationMs": 2800
},
{
"speaker": "Ghost",
"portrait": "resources/ghost_avatar.png",
"text": "Do not look away.",
"durationMs": 2400
}
]
},
{
"id": "test_cutscene_pan_02",
"background": "resources/first_cutscene.png",
"durationMs": 12000,
"cameraTrack": [
{
"durationMs": 1200,
"from": { "anchor": "Center", "zoom": 1.00, "rotationDeg": 0.0 },
"to": { "anchor": "Center", "zoom": 1.00, "rotationDeg": 0.0 },
"easing": "Linear"
},
{
"durationMs": 2500,
"from": { "anchor": "Center", "zoom": 1.00, "rotationDeg": 0.0 },
"to": { "anchor": "TopLeft", "zoom": 1.55, "rotationDeg": 0.0 },
"easing": "EaseInOutSine"
},
{
"durationMs": 2600,
"from": { "anchor": "TopLeft", "zoom": 1.55, "rotationDeg": 0.0 },
"to": { "anchor": "TopRight", "zoom": 1.55, "rotationDeg": 0.0 },
"easing": "EaseInOutSine"
},
{
"durationMs": 1800,
"from": { "anchor": "TopRight", "zoom": 1.55, "rotationDeg": 0.0 },
"to": { "anchor": "BottomRight", "zoom": 1.72, "rotationDeg": 0.0 },
"easing": "EaseInCubic"
},
{
"durationMs": 3900,
"from": { "anchor": "BottomRight", "zoom": 1.72, "rotationDeg": 0.0 },
"to": { "anchor": "BottomLeft", "zoom": 1.55, "rotationDeg": 0.0 },
"easing": "EaseInOutSine"
}
],
"lines": []
}
]
}
+5 -5
View File
@@ -3,7 +3,7 @@
{ {
"key": "Аида Дженибековна", "key": "Аида Дженибековна",
"ru": "Аида Дженибековна", "ru": "Аида Дженибековна",
"en": "Aida Dzhanybekovna" "en": "Aida Dzhenibekovna"
}, },
{ {
"key": "Опаздывающие, заходите скорее и занимайте свои места! Лекция начинается!", "key": "Опаздывающие, заходите скорее и занимайте свои места! Лекция начинается!",
@@ -23,7 +23,7 @@
{ {
"key": "Этот мир описан в эпосе Манас как Кайып или Аль-Гайб, но некоторые ученые называют его миром теней.", "key": "Этот мир описан в эпосе Манас как Кайып или Аль-Гайб, но некоторые ученые называют его миром теней.",
"ru": "Этот мир описан в эпосе Манас как Кайып или Аль-Гайб, но некоторые ученые называют его миром теней.", "ru": "Этот мир описан в эпосе Манас как Кайып или Аль-Гайб, но некоторые ученые называют его миром теней.",
"en": "This world is described in the Manas epic as Kayip or Al-Ghayb, but some scholars call it the dark lands." "en": "This world is described in the Manas epic as Kayip or Al-Ghayb, but some scholars call it the Darkland."
}, },
{ {
"key": "В этом мире обитают феи, духи и джинны. Простым смертным в этот мир дорога закрыта.", "key": "В этом мире обитают феи, духи и джинны. Простым смертным в этот мир дорога закрыта.",
@@ -33,17 +33,17 @@
{ {
"key": "Время там течет по другому - за один день в теневом мире могут пройти годы жизни обычного мира.", "key": "Время там течет по другому - за один день в теневом мире могут пройти годы жизни обычного мира.",
"ru": "Время там течет по другому - за один день в теневом мире могут пройти годы жизни обычного мира.", "ru": "Время там течет по другому - за один день в теневом мире могут пройти годы жизни обычного мира.",
"en": "Time flows differently there - in one day in the dark lands, years of life in the ordinary world can pass." "en": "Time flows differently there - in one day in the Darkland, years of life in the ordinary world can pass."
}, },
{ {
"key": "Обычно, мир теней никак не пересекается с нашим миром живых людей.", "key": "Обычно, мир теней никак не пересекается с нашим миром живых людей.",
"ru": "Обычно, мир теней никак не пересекается с нашим миром живых людей.", "ru": "Обычно, мир теней никак не пересекается с нашим миром живых людей.",
"en": "Usually, the world of dark lands does not intersect with our world of living people." "en": "Usually, the world of the Darkland does not intersect with our world of living people."
}, },
{ {
"key": "Но в критические моменты для народа, обитатели теневого мира могут приходить в наш мир.", "key": "Но в критические моменты для народа, обитатели теневого мира могут приходить в наш мир.",
"ru": "Но в критические моменты для народа, обитатели теневого мира могут приходить в наш мир.", "ru": "Но в критические моменты для народа, обитатели теневого мира могут приходить в наш мир.",
"en": "But in critical moments for the people, the inhabitants of the dark lands can come to our world." "en": "But in critical moments for the people, the inhabitants of the Darkland can come to our world."
}, },
{ {
"key": "Совсем недавно закончилась пандемия, а сегодня мир захлестнули кровавые войны.", "key": "Совсем недавно закончилась пандемия, а сегодня мир захлестнули кровавые войны.",
+128 -78
View File
@@ -136,9 +136,9 @@
"en": "Before going outside, I have to order a taxi to the university." "en": "Before going outside, I have to order a taxi to the university."
}, },
{ {
"key": "Я заказал такси до универа, машина уже ждет!", "key": "Я заказал такси до универа, машина уже едет!",
"ru": "Я заказал такси до универа, машина уже ждет!", "ru": "Я заказал такси до универа, машина уже едет!",
"en": "I ordered a taxi to the university, the car is already waiting!" "en": "I ordered a taxi to the university, the car is already on its way!"
}, },
{ {
"key": "Я уже заказал такси, машина уже ждет!", "key": "Я уже заказал такси, машина уже ждет!",
@@ -261,9 +261,9 @@
"en": "And where is her coursework now?" "en": "And where is her coursework now?"
}, },
{ {
"key": "За зданием универа есть контейнер с кучей бумажного мусора и макулатурой.", "key": "Возле здания универа лежит куча строительного мусора и макулатуры.",
"ru": "За зданием универа есть контейнер с кучей бумажного мусора и макулатурой.", "ru": "Возле здания универа лежит куча строительного мусора и макулатуры.",
"en": "Behind the university building there is a container with a pile of paper trash and waste." "en": "There is a pile of construction waste and paper near the university building."
}, },
{ {
"key": "Скорее всего, курсовая до сих пор лежит где-то там.", "key": "Скорее всего, курсовая до сих пор лежит где-то там.",
@@ -303,7 +303,7 @@
{ {
"key": "А ведь у нас в этом году новая преподавательница, Аида Дженибековна!", "key": "А ведь у нас в этом году новая преподавательница, Аида Дженибековна!",
"ru": "А ведь у нас в этом году новая преподавательница, Аида Дженибековна!", "ru": "А ведь у нас в этом году новая преподавательница, Аида Дженибековна!",
"en": "But we have a new teacher this year, Aida Dzhanybekovna!" "en": "But we have a new teacher this year, Aida Dzhenibekovna!"
}, },
{ {
"key": "Она конечно не сахар, но если ты ходишь на пары и вовремя сдашь ей задание на модуль, то ты считай сдал экзамен.", "key": "Она конечно не сахар, но если ты ходишь на пары и вовремя сдашь ей задание на модуль, то ты считай сдал экзамен.",
@@ -318,7 +318,7 @@
{ {
"key": "Так что Аида Дженибековна еще норм. Подойди к ней, возьми задание на модуль, и начни ходить на пары. Тогда ты сдашь экзамен успешно.", "key": "Так что Аида Дженибековна еще норм. Подойди к ней, возьми задание на модуль, и начни ходить на пары. Тогда ты сдашь экзамен успешно.",
"ru": "Так что Аида Дженибековна еще норм. Подойди к ней, возьми задание на модуль, и начни ходить на пары. Тогда ты сдашь экзамен успешно.", "ru": "Так что Аида Дженибековна еще норм. Подойди к ней, возьми задание на модуль, и начни ходить на пары. Тогда ты сдашь экзамен успешно.",
"en": "So Aida Dzhanybekovna is still okay. Go to her, take the assignment for the module, and start going to class. Then you'll pass the exam successfully." "en": "So Aida Dzhenibekovna is still okay. Go to her, take the assignment for the module, and start going to class. Then you'll pass the exam successfully."
}, },
{ {
"key": "Спасибо за информацию!", "key": "Спасибо за информацию!",
@@ -348,7 +348,7 @@
{ {
"key": "Говорят, ей не очень нравится наша новая преподавательница по манасоведению, Аида Дженибековна.", "key": "Говорят, ей не очень нравится наша новая преподавательница по манасоведению, Аида Дженибековна.",
"ru": "Говорят, ей не очень нравится наша новая преподавательница по манасоведению, Аида Дженибековна.", "ru": "Говорят, ей не очень нравится наша новая преподавательница по манасоведению, Аида Дженибековна.",
"en": "They say she doesn't really like our new Manas studies teacher, Aida Dzhanybekovna." "en": "They say she doesn't really like our new Manas studies teacher, Aida Dzhenibekovna."
}, },
{ {
"key": "Привет Бекзат! Надеюсь ты нашел то что ищешь.", "key": "Привет Бекзат! Надеюсь ты нашел то что ищешь.",
@@ -456,9 +456,9 @@
"en": "Before I leave the gate, I have to order a taxi to the dorm." "en": "Before I leave the gate, I have to order a taxi to the dorm."
}, },
{ {
"key": "Я заказал такси до общаги, машина уже ждет!", "key": "Я заказал такси до общаги, машина уже едет!",
"ru": "Я заказал такси до общаги, машина уже ждет!", "ru": "Я заказал такси до общаги, машина уже едет!",
"en": "I ordered a taxi to the dorm, the car is already waiting!" "en": "I ordered a taxi to the dorm, the car is already on its way!"
}, },
{ {
"key": "Ого, пока я залипал в приложении, уже наступила ночь!", "key": "Ого, пока я залипал в приложении, уже наступила ночь!",
@@ -478,7 +478,7 @@
{ {
"key": "Кажется, на этой машине в универ приезжает Аида Дженибековна.", "key": "Кажется, на этой машине в универ приезжает Аида Дженибековна.",
"ru": "Кажется, на этой машине в универ приезжает Аида Дженибековна.", "ru": "Кажется, на этой машине в универ приезжает Аида Дженибековна.",
"en": "It seems that Aida Dzhanybekovna arrives at the university in this car." "en": "It seems that Aida Dzhenibekovna arrives at the university in this car."
}, },
{ {
"key": "Это заклинание не работает днем.", "key": "Это заклинание не работает днем.",
@@ -488,7 +488,7 @@
{ {
"key": "Чтобы перейти в теневой мир, мне нужно дождаться ночи.", "key": "Чтобы перейти в теневой мир, мне нужно дождаться ночи.",
"ru": "Чтобы перейти в теневой мир, мне нужно дождаться ночи.", "ru": "Чтобы перейти в теневой мир, мне нужно дождаться ночи.",
"en": "To go into the dark lands, I need to wait until night." "en": "To go into the Darkland, I need to wait until night."
}, },
{ {
"key": "Чтобы время пролетело быстро, я могу посидеть в телефоне и позалипать в короткие видео.", "key": "Чтобы время пролетело быстро, я могу посидеть в телефоне и позалипать в короткие видео.",
@@ -498,7 +498,7 @@
{ {
"key": "У меня силы закончились и мне спать хочется. Достаточно теневого мира на сегодня, поехали в общагу.", "key": "У меня силы закончились и мне спать хочется. Достаточно теневого мира на сегодня, поехали в общагу.",
"ru": "У меня силы закончились и мне спать хочется. Достаточно теневого мира на сегодня, поехали в общагу.", "ru": "У меня силы закончились и мне спать хочется. Достаточно теневого мира на сегодня, поехали в общагу.",
"en": "I'm exhausted and sleepy. Enough of the dark lands for today, let's go to the dorm." "en": "I'm exhausted and sleepy. Enough of the Darkland for today, let's go to the dorm."
}, },
{ {
"key": "Ты куда собрался, Бекзат?", "key": "Ты куда собрался, Бекзат?",
@@ -658,7 +658,7 @@
{ {
"key": "Аида Дженибековна", "key": "Аида Дженибековна",
"ru": "Аида Дженибековна", "ru": "Аида Дженибековна",
"en": "Aida Dzhanybekovna" "en": "Aida Dzhenibekovna"
}, },
{ {
"key": "Бекзат, что тебе нужно?", "key": "Бекзат, что тебе нужно?",
@@ -688,7 +688,7 @@
{ {
"key": "Аида Дженибековна", "key": "Аида Дженибековна",
"ru": "Аида Дженибековна", "ru": "Аида Дженибековна",
"en": "Aida Dzhanybekovna" "en": "Aida Dzhenibekovna"
}, },
{ {
"key": "Зачем тебе в учительскую?", "key": "Зачем тебе в учительскую?",
@@ -828,7 +828,7 @@
{ {
"key": "Извините, Аида Дженибековна, я могу пойти уже?", "key": "Извините, Аида Дженибековна, я могу пойти уже?",
"ru": "Извините, Аида Дженибековна, я могу пойти уже?", "ru": "Извините, Аида Дженибековна, я могу пойти уже?",
"en": "Excuse me, Aida Dzhanybekovna, can I go now?" "en": "Excuse me, Aida Dzhenibekovna, can I go now?"
}, },
{ {
"key": "Нет, Бекзат. Ты вломился в учительскую, и теперь хочешь просто так уйти? Не получится.", "key": "Нет, Бекзат. Ты вломился в учительскую, и теперь хочешь просто так уйти? Не получится.",
@@ -898,7 +898,7 @@
{ {
"key": "Аида Дженибековна, вы помните Бегимай? Она вам курсовую хотела сдать.", "key": "Аида Дженибековна, вы помните Бегимай? Она вам курсовую хотела сдать.",
"ru": "Аида Дженибековна, вы помните Бегимай? Она вам курсовую хотела сдать.", "ru": "Аида Дженибековна, вы помните Бегимай? Она вам курсовую хотела сдать.",
"en": "Aida Dzhanybekovna, do you remember Begimai? She wanted to submit her term paper to you." "en": "Aida Dzhenibekovna, do you remember Begimai? She wanted to submit her term paper to you."
}, },
{ {
"key": "Да, она говорила что приносила курсовую, но у меня ее нигде нет.", "key": "Да, она говорила что приносила курсовую, но у меня ее нигде нет.",
@@ -913,7 +913,7 @@
{ {
"key": "Бекзат, зачетка Бегимай у тебя. Отправляйся в теневой мир и покажи зачетку призраку Бегимай, чтобы она освободилась.", "key": "Бекзат, зачетка Бегимай у тебя. Отправляйся в теневой мир и покажи зачетку призраку Бегимай, чтобы она освободилась.",
"ru": "Бекзат, зачетка Бегимай у тебя. Отправляйся в теневой мир и покажи зачетку призраку Бегимай, чтобы она освободилась.", "ru": "Бекзат, зачетка Бегимай у тебя. Отправляйся в теневой мир и покажи зачетку призраку Бегимай, чтобы она освободилась.",
"en": "Bekzat, you have Begimai's grade book. Go to the dark lands and show the grade book to Begimai's ghost so she can be released." "en": "Bekzat, you have Begimai's record book. Go to the Darkland and show the record book to Begimai's ghost so she can be released."
}, },
{ {
"key": "Хорошо!", "key": "Хорошо!",
@@ -923,12 +923,12 @@
{ {
"key": "Аида Дженибековна, здравствуйте!", "key": "Аида Дженибековна, здравствуйте!",
"ru": "Аида Дженибековна, здравствуйте!", "ru": "Аида Дженибековна, здравствуйте!",
"en": "Aida Dzhanybekovna, hello!" "en": "Aida Dzhenibekovna, hello!"
}, },
{ {
"key": "Я принес вам зачетку Бегимай и ее курсовую работу, посмотрите пожалуйста.", "key": "Я принес вам зачетку Бегимай и ее курсовую работу, посмотрите пожалуйста.",
"ru": "Я принес вам зачетку Бегимай и ее курсовую работу, посмотрите пожалуйста.", "ru": "Я принес вам зачетку Бегимай и ее курсовую работу, посмотрите пожалуйста.",
"en": "I brought you Begimai's grade book and her coursework, please take a look." "en": "I brought you Begimai's record book and her coursework, please take a look."
}, },
{ {
"key": "Хорошо, давай посмотрим.", "key": "Хорошо, давай посмотрим.",
@@ -943,12 +943,12 @@
{ {
"key": "Вот держи зачетку с оценкой.", "key": "Вот держи зачетку с оценкой.",
"ru": "Вот держи зачетку с оценкой.", "ru": "Вот держи зачетку с оценкой.",
"en": "Here, take the grade book with the grade." "en": "Here, take the record book with the grade."
}, },
{ {
"key": "Теперь иди в теневой мир и покажи зачетку Бегимай. Расскажи ей, что она сдала курсовую работу.", "key": "Теперь иди в теневой мир и покажи зачетку Бегимай. Расскажи ей, что она сдала курсовую работу.",
"ru": "Теперь иди в теневой мир и покажи зачетку Бегимай. Расскажи ей, что она сдала курсовую работу.", "ru": "Теперь иди в теневой мир и покажи зачетку Бегимай. Расскажи ей, что она сдала курсовую работу.",
"en": "Now go to the dark lands and show Begimai her grade book. Tell her she passed her coursework." "en": "Now go to the Darkland and show Begimai her record book. Tell her she passed her coursework."
}, },
{ {
"key": "Мне стоит вернуть книгу на место, прежде чем уходить из библиотеки.", "key": "Мне стоит вернуть книгу на место, прежде чем уходить из библиотеки.",
@@ -958,7 +958,7 @@
{ {
"key": "Иначе Аида Дженибековна меня убъет.", "key": "Иначе Аида Дженибековна меня убъет.",
"ru": "Иначе Аида Дженибековна меня убъет.", "ru": "Иначе Аида Дженибековна меня убъет.",
"en": "Otherwise Aida Dzhanybekovna will kill me." "en": "Otherwise Aida Dzhenibekovna will kill me."
}, },
{ {
"key": "Куда я попал?", "key": "Куда я попал?",
@@ -1023,7 +1023,7 @@
{ {
"key": "Я уйду на покой только когда увиду оценку по курсовой в своей зачетке.", "key": "Я уйду на покой только когда увиду оценку по курсовой в своей зачетке.",
"ru": "Я уйду на покой только когда увиду оценку по курсовой в своей зачетке.", "ru": "Я уйду на покой только когда увиду оценку по курсовой в своей зачетке.",
"en": "I will retire only when I see the grade for my coursework in my grade book." "en": "I will retire only when I see the grade for my coursework in my record book."
}, },
{ {
"key": "А до тех пор, я буду появлятся здесь каждую ночь.", "key": "А до тех пор, я буду появлятся здесь каждую ночь.",
@@ -1048,7 +1048,7 @@
{ {
"key": "Это твоя зачетка.", "key": "Это твоя зачетка.",
"ru": "Это твоя зачетка.", "ru": "Это твоя зачетка.",
"en": "This is your grade book." "en": "This is your record book."
}, },
{ {
"key": "Тебе поставили за курсовую максимальный балл!", "key": "Тебе поставили за курсовую максимальный балл!",
@@ -1143,17 +1143,17 @@
{ {
"key": "Тут лежат зачетные книжки студентов.", "key": "Тут лежат зачетные книжки студентов.",
"ru": "Тут лежат зачетные книжки студентов.", "ru": "Тут лежат зачетные книжки студентов.",
"en": "Students' grade books are kept here." "en": "Students' record books are kept here."
}, },
{ {
"key": "Здесь лежит зачетка Бегимай. Я пожалуй, возьму ее.", "key": "Здесь лежит зачетка Бегимай. Я пожалуй, возьму ее.",
"ru": "Здесь лежит зачетка Бегимай. Я пожалуй, возьму ее.", "ru": "Здесь лежит зачетка Бегимай. Я пожалуй, возьму ее.",
"en": "Begimai's grade book is here. I think I'll take it." "en": "Begimai's record book is here. I think I'll take it."
}, },
{ {
"key": "Мне еще рано возвращать зачетку Бегимай обратно в шкаф.", "key": "Мне еще рано возвращать зачетку Бегимай обратно в шкаф.",
"ru": "Мне еще рано возвращать зачетку Бегимай обратно в шкаф.", "ru": "Мне еще рано возвращать зачетку Бегимай обратно в шкаф.",
"en": "It's too early for me to return Begimai's grade book back to the shelf." "en": "It's too early for me to return Begimai's record book back to the shelf."
}, },
{ {
"key": "Чтобы заказать такси, я сначала должен выйти на улицу.", "key": "Чтобы заказать такси, я сначала должен выйти на улицу.",
@@ -1358,12 +1358,12 @@
{ {
"key": "Ко мне попала странная записка, где было написано заклинание. Я прочитал его, и попал в потусторонний мир.", "key": "Ко мне попала странная записка, где было написано заклинание. Я прочитал его, и попал в потусторонний мир.",
"ru": "Ко мне попала странная записка, где было написано заклинание. Я прочитал его, и попал в потусторонний мир.", "ru": "Ко мне попала странная записка, где было написано заклинание. Я прочитал его, и попал в потусторонний мир.",
"en": "I received a strange note containing a spell. I read it and found myself in the dark lands." "en": "I received a strange note containing a spell. I read it and found myself in the Darkland."
}, },
{ {
"key": "Ты что опять в свои игры переиграл? Какой потусторонний мир?", "key": "Ты что опять в свои игры переиграл? Какой потусторонний мир?",
"ru": "Ты что опять в свои игры переиграл? Какой потусторонний мир?", "ru": "Ты что опять в свои игры переиграл? Какой потусторонний мир?",
"en": "Are you playing your video games too much? What dark lands?" "en": "Are you playing your video games too much? What Darkland?"
}, },
{ {
"key": "Но это правда! Там все было по-другому, и я видел призраков...", "key": "Но это правда! Там все было по-другому, и я видел призраков...",
@@ -1378,7 +1378,7 @@
{ {
"key": "Аида Дженибековна увидела меня в учительской, и заставила писать эссе по этой тупой книге.", "key": "Аида Дженибековна увидела меня в учительской, и заставила писать эссе по этой тупой книге.",
"ru": "Аида Дженибековна увидела меня в учительской, и заставила писать эссе по этой тупой книге.", "ru": "Аида Дженибековна увидела меня в учительской, и заставила писать эссе по этой тупой книге.",
"en": "Aida Dzhanybekovna saw me in the teachers' room and made me write an essay on this stupid book." "en": "Aida Dzhenibekovna saw me in the teachers' room and made me write an essay on this stupid book."
}, },
{ {
"key": "Я писал, писал и уснул.", "key": "Я писал, писал и уснул.",
@@ -1448,7 +1448,7 @@
{ {
"key": "Поэтому, с этого года у нас этот предмет ведет Аида Дженибековна.", "key": "Поэтому, с этого года у нас этот предмет ведет Аида Дженибековна.",
"ru": "Поэтому, с этого года у нас этот предмет ведет Аида Дженибековна.", "ru": "Поэтому, с этого года у нас этот предмет ведет Аида Дженибековна.",
"en": "Therefore, starting this year, Aida Dzhanybekovna has been teaching this subject." "en": "Therefore, starting this year, Aida Dzhenibekovna has been teaching this subject."
}, },
{ {
"key": "Ты не знаешь кого-нибудь, кто был знаком с Бегимай?", "key": "Ты не знаешь кого-нибудь, кто был знаком с Бегимай?",
@@ -1473,7 +1473,7 @@
{ {
"key": "Я лучше прочитаю заклинание, перейду в теневой мир и так выберусь наружу.", "key": "Я лучше прочитаю заклинание, перейду в теневой мир и так выберусь наружу.",
"ru": "Я лучше прочитаю заклинание, перейду в теневой мир и так выберусь наружу.", "ru": "Я лучше прочитаю заклинание, перейду в теневой мир и так выберусь наружу.",
"en": "I'd rather cast a spell, go into the dark lands, and get out that way." "en": "I'd rather cast a spell, go into the Darkland, and get out that way."
}, },
{ {
"key": "Я не буду беспокоить Айпери. Я могу выбраться из универа и без ее помощи.", "key": "Я не буду беспокоить Айпери. Я могу выбраться из универа и без ее помощи.",
@@ -1588,12 +1588,12 @@
{ {
"key": "Я попал в теневой мир, чтобы выбраться из запертого кабинета.", "key": "Я попал в теневой мир, чтобы выбраться из запертого кабинета.",
"ru": "Я попал в теневой мир, чтобы выбраться из запертого кабинета.", "ru": "Я попал в теневой мир, чтобы выбраться из запертого кабинета.",
"en": "I entered the dark lands to escape from the locked room." "en": "I entered the Darkland to escape from the locked room."
}, },
{ {
"key": "Но по злой иронии, когда я вернулся из теневого мира, я снова оказался за запертой дверью.", "key": "Но по злой иронии, когда я вернулся из теневого мира, я снова оказался за запертой дверью.",
"ru": "Но по злой иронии, когда я вернулся из теневого мира, я снова оказался за запертой дверью.", "ru": "Но по злой иронии, когда я вернулся из теневого мира, я снова оказался за запертой дверью.",
"en": "But by a cruel irony, when I returned from the dark lands, I found myself behind a locked door again." "en": "But by a cruel irony, when I returned from the Darkland, I found myself behind a locked door again."
}, },
{ {
"key": "Хорошо, что уже утро. Надо написать Айпери, может она уже скоро приедет в универ?", "key": "Хорошо, что уже утро. Надо написать Айпери, может она уже скоро приедет в универ?",
@@ -1623,7 +1623,7 @@
{ {
"key": "Здравствуйте Аида Дженибековна!", "key": "Здравствуйте Аида Дженибековна!",
"ru": "Здравствуйте Аида Дженибековна!", "ru": "Здравствуйте Аида Дженибековна!",
"en": "Hello Aida Dzhanybekovna!" "en": "Hello Aida Dzhenibekovna!"
}, },
{ {
"key": "Ну что Бекзат, эссе про Жусупа Мамая готово к сдаче?", "key": "Ну что Бекзат, эссе про Жусупа Мамая готово к сдаче?",
@@ -1683,12 +1683,12 @@
{ {
"key": "Это заклинание переместило тебя в мир теней. ", "key": "Это заклинание переместило тебя в мир теней. ",
"ru": "Это заклинание переместило тебя в мир теней.", "ru": "Это заклинание переместило тебя в мир теней.",
"en": "This spell transported you to the dark lands." "en": "This spell transported you to the Darkland."
}, },
{ {
"key": "Этот мир предназначен для фей и джиннов. Обычные люди туда попасть не могут. Но у тебя это как-то получилось.", "key": "Этот мир предназначен для фей и джиннов. Обычные люди туда попасть не могут. Но у тебя это как-то получилось.",
"ru": "Этот мир предназначен для фей и джиннов. Обычные люди туда попасть не могут. Но у тебя это как-то получилось.", "ru": "Этот мир предназначен для фей и джиннов. Обычные люди туда попасть не могут. Но у тебя это как-то получилось.",
"en": "These lands are meant for fairies and genies. Ordinary people can't get there. But somehow you made it." "en": "This land is meant for fairies and genies. Ordinary people can't get there. But somehow you made it."
}, },
{ {
"key": "А вы откуда все это знаете?", "key": "А вы откуда все это знаете?",
@@ -1723,12 +1723,12 @@
{ {
"key": "А на утро бывает так, что все зачетки в шкафу раскиданы как попало.", "key": "А на утро бывает так, что все зачетки в шкафу раскиданы как попало.",
"ru": "А на утро бывает так, что все зачетки в шкафу раскиданы как попало.", "ru": "А на утро бывает так, что все зачетки в шкафу раскиданы как попало.",
"en": "And in the morning it happens that all the grade books in the closet are scattered all over the place." "en": "And in the morning it happens that all the record books in the closet are scattered all over the place."
}, },
{ {
"key": "Призрак обитает где-то в теневом мире. Попади туда и наладь с ним диалог. Узнай, кто он и что ему нужно.", "key": "Призрак обитает где-то в теневом мире. Попади туда и наладь с ним диалог. Узнай, кто он и что ему нужно.",
"ru": "Призрак обитает где-то в теневом мире. Попади туда и наладь с ним диалог. Узнай, кто он и что ему нужно.", "ru": "Призрак обитает где-то в теневом мире. Попади туда и наладь с ним диалог. Узнай, кто он и что ему нужно.",
"en": "The ghost dwells somewhere in the dark lands. Get there and establish a dialogue with him. Find out who he is and what he wants." "en": "The ghost dwells somewhere in the Darkland. Get there and establish a dialogue with him. Find out who he is and what he wants."
}, },
{ {
"key": "Если ты успешно избавишься от призрака, я поставлю тебе максимальный балл за модуль.", "key": "Если ты успешно избавишься от призрака, я поставлю тебе максимальный балл за модуль.",
@@ -1753,7 +1753,7 @@
{ {
"key": "Обсудить теневой мир", "key": "Обсудить теневой мир",
"ru": "Обсудить теневой мир", "ru": "Обсудить теневой мир",
"en": "Discuss the dark lands" "en": "Discuss the Darkland"
}, },
{ {
"key": "Обсудить записку", "key": "Обсудить записку",
@@ -1763,12 +1763,12 @@
{ {
"key": "Что это вообще за теневой мир? Откуда он взялся?", "key": "Что это вообще за теневой мир? Откуда он взялся?",
"ru": "Что это вообще за теневой мир? Откуда он взялся?", "ru": "Что это вообще за теневой мир? Откуда он взялся?",
"en": "What is this dark lands anyway? Where did it come from?" "en": "What is this Darkland anyway? Where did it come from?"
}, },
{ {
"key": "Теневой мир это мир, в котором обитают феи, духи и джинны. Он существовал всегда.", "key": "Теневой мир это мир, в котором обитают феи, духи и джинны. Он существовал всегда.",
"ru": "Теневой мир это мир, в котором обитают феи, духи и джинны. Он существовал всегда.", "ru": "Теневой мир это мир, в котором обитают феи, духи и джинны. Он существовал всегда.",
"en": "The Dark Lands is a world inhabited by fairies, spirits, and genies. It has always existed." "en": "The Darkland is a world inhabited by fairies, spirits, and genies. It has always existed."
}, },
{ {
"key": "Обычные люди, не воители, туда не попадают обычно. Кроме тебя, тебе это как-то удалось.", "key": "Обычные люди, не воители, туда не попадают обычно. Кроме тебя, тебе это как-то удалось.",
@@ -1783,22 +1783,22 @@
{ {
"key": "Время в теневом мире", "key": "Время в теневом мире",
"ru": "Время в теневом мире", "ru": "Время в теневом мире",
"en": "Time in the dark lands" "en": "Time in the Darkland"
}, },
{ {
"key": "Призраки в теневом мире", "key": "Призраки в теневом мире",
"ru": "Призраки в теневом мире", "ru": "Призраки в теневом мире",
"en": "Ghosts in the dark lands" "en": "Ghosts in the Darkland"
}, },
{ {
"key": "Двери в теневом мире", "key": "Двери в теневом мире",
"ru": "Двери в теневом мире", "ru": "Двери в теневом мире",
"en": "Doors in the dark lands" "en": "Doors in the Darkland"
}, },
{ {
"key": "Выход из теневого мира", "key": "Выход из теневого мира",
"ru": "Выход из теневого мира", "ru": "Выход из теневого мира",
"en": "Exit from the dark lands" "en": "Exit from the Darkland"
}, },
{ {
"key": "Достаточно", "key": "Достаточно",
@@ -1808,37 +1808,37 @@
{ {
"key": "Почему в теневом мире нет дверей?", "key": "Почему в теневом мире нет дверей?",
"ru": "Почему в теневом мире нет дверей?", "ru": "Почему в теневом мире нет дверей?",
"en": "Why are there no doors in the dark lands?" "en": "Why are there no doors in the Darkland?"
}, },
{ {
"key": "Я думаю, в мир теней проецируются только фундаментальные объекты, типа зданий, деревьев, рельефа.", "key": "Я думаю, в мир теней проецируются только фундаментальные объекты, типа зданий, деревьев, рельефа.",
"ru": "Я думаю, в мир теней проецируются только фундаментальные объекты, типа зданий, деревьев, рельефа.", "ru": "Я думаю, в мир теней проецируются только фундаментальные объекты, типа зданий, деревьев, рельефа.",
"en": "I think that only fundamental objects, such as buildings, trees, and terrain, are projected into the dark lands." "en": "I think that only fundamental objects, such as buildings, trees, and terrain, are projected into the Darkland."
}, },
{ {
"key": "Это происходит потому, что в теневом мире время течет сильно медленнее, чем в нашем мире.", "key": "Это происходит потому, что в теневом мире время течет сильно медленнее, чем в нашем мире.",
"ru": "Это происходит потому, что в теневом мире время течет сильно медленнее, чем в нашем мире.", "ru": "Это происходит потому, что в теневом мире время течет сильно медленнее, чем в нашем мире.",
"en": "This happens because in the dark lands time flows much slower than in our world." "en": "This happens because in the Darkland time flows much slower than in our world."
}, },
{ {
"key": "Двери у нас слишком часто открываются и закрываются, поэтому в теневом мире они \"размываются\" или попадают в суперпозицию.", "key": "Двери у нас слишком часто открываются и закрываются, поэтому в теневом мире они \"размываются\" или попадают в суперпозицию.",
"ru": "Двери у нас слишком часто открываются и закрываются, поэтому в теневом мире они \"размываются\" или попадают в суперпозицию.", "ru": "Двери у нас слишком часто открываются и закрываются, поэтому в теневом мире они \"размываются\" или попадают в суперпозицию.",
"en": "Our doors open and close too often, so in the dark lands they become \"blurred\" or fall into some sort of superposition." "en": "Our doors open and close too often, so in the Darkland they become \"blurred\" or fall into some sort of superposition."
}, },
{ {
"key": "Поэтому в мире теней ты можешь спокойно пройти через дверной проем. Но сквозь стены ты пройти не сможешь, они более плотные.", "key": "Поэтому в мире теней ты можешь спокойно пройти через дверной проем. Но сквозь стены ты пройти не сможешь, они более плотные.",
"ru": "Поэтому в мире теней ты можешь спокойно пройти через дверной проем. Но сквозь стены ты пройти не сможешь, они более плотные.", "ru": "Поэтому в мире теней ты можешь спокойно пройти через дверной проем. Но сквозь стены ты пройти не сможешь, они более плотные.",
"en": "So, in the dark lands, you can easily pass through a doorway. But you won't be able to pass through walls; they're denser." "en": "So, in the Darkland, you can easily pass through a doorway. But you won't be able to pass through walls; they're denser."
}, },
{ {
"key": "Откуда в теневом мире призраки?", "key": "Откуда в теневом мире призраки?",
"ru": "Откуда в теневом мире призраки?", "ru": "Откуда в теневом мире призраки?",
"en": "Where do ghosts come from in the dark lands?" "en": "Where do ghosts come from in the Darkland?"
}, },
{ {
"key": "Это их мир, и они там живут. Я думаю что увидев тебя, обитатели теневого мира тоже были шокированы.", "key": "Это их мир, и они там живут. Я думаю что увидев тебя, обитатели теневого мира тоже были шокированы.",
"ru": "Это их мир, и они там живут. Я думаю что увидев тебя, обитатели теневого мира тоже были шокированы.", "ru": "Это их мир, и они там живут. Я думаю что увидев тебя, обитатели теневого мира тоже были шокированы.",
"en": "This is their world, and they live there. I think the inhabitants of the dark lands were also shocked when they saw you." "en": "This is their world, and they live there. I think the inhabitants of the Darkland were also shocked when they saw you."
}, },
{ {
"key": "Но кое в чем ты прав. Здесь в универе, призраков раньше не было. Всякая чертовщина начала происходить совсем недавно.", "key": "Но кое в чем ты прав. Здесь в универе, призраков раньше не было. Всякая чертовщина начала происходить совсем недавно.",
@@ -1863,12 +1863,12 @@
{ {
"key": "Каждый раз, когда я выпадаю из теневого мира, наступает утро. Хотя я там был всего несколько минут.", "key": "Каждый раз, когда я выпадаю из теневого мира, наступает утро. Хотя я там был всего несколько минут.",
"ru": "Каждый раз, когда я выпадаю из теневого мира, наступает утро. Хотя я там был всего несколько минут.", "ru": "Каждый раз, когда я выпадаю из теневого мира, наступает утро. Хотя я там был всего несколько минут.",
"en": "Every time I emerge from the dark lands, morning comes, even though I was only there for a few minutes." "en": "Every time I emerge from the Darkland, morning comes, even though I was only there for a few minutes."
}, },
{ {
"key": "Время в мире теней течет значительно медленнее, чем в нашем.", "key": "Время в мире теней течет значительно медленнее, чем в нашем.",
"ru": "Время в мире теней течет значительно медленнее, чем в нашем.", "ru": "Время в мире теней течет значительно медленнее, чем в нашем.",
"en": "Time in the dark lands flows much slower than in ours." "en": "Time in the Darkland flows much slower than in ours."
}, },
{ {
"key": "И похоже, что течение времени там нелинейное.", "key": "И похоже, что течение времени там нелинейное.",
@@ -1878,22 +1878,22 @@
{ {
"key": "Это объясняет, почему в теневом мире есть деревья и строения, но отсутствуют двери, автомобили, и прочее.", "key": "Это объясняет, почему в теневом мире есть деревья и строения, но отсутствуют двери, автомобили, и прочее.",
"ru": "Это объясняет, почему в теневом мире есть деревья и строения, но отсутствуют двери, автомобили, и прочее.", "ru": "Это объясняет, почему в теневом мире есть деревья и строения, но отсутствуют двери, автомобили, и прочее.",
"en": "This explains why the dark lands has trees and buildings, but no doors, cars, etc." "en": "This explains why the Darkland has trees and buildings, but no doors, cars, etc."
}, },
{ {
"key": "Объекты в нашем мире должны долго находится на одном месте, прежде чем их проекция появится в мире теней.", "key": "Объекты в нашем мире должны долго находится на одном месте, прежде чем их проекция появится в мире теней.",
"ru": "Объекты в нашем мире должны долго находится на одном месте, прежде чем их проекция появится в мире теней.", "ru": "Объекты в нашем мире должны долго находится на одном месте, прежде чем их проекция появится в мире теней.",
"en": "Objects in our world must remain in one place for a long time before their projection appears in the dark lands." "en": "Objects in our world must remain in one place for a long time before their projection appears in the Darkland."
}, },
{ {
"key": "Меня постоянно выкидывает из теневого мира. Как я могу там остаться подольше?", "key": "Меня постоянно выкидывает из теневого мира. Как я могу там остаться подольше?",
"ru": "Меня постоянно выкидывает из теневого мира. Как я могу там остаться подольше?", "ru": "Меня постоянно выкидывает из теневого мира. Как я могу там остаться подольше?",
"en": "I'm constantly being thrown out of the dark lands. How can I stay there any longer?" "en": "I'm constantly being thrown out of the Darkland. How can I stay there any longer?"
}, },
{ {
"key": "Если ты потеряешь сознание в теневом мире, то ты принудительно вернешься в наш мир.", "key": "Если ты потеряешь сознание в теневом мире, то ты принудительно вернешься в наш мир.",
"ru": "Если ты потеряешь сознание в теневом мире, то ты принудительно вернешься в наш мир.", "ru": "Если ты потеряешь сознание в теневом мире, то ты принудительно вернешься в наш мир.",
"en": "If you lose consciousness in the dark lands, you will be forcibly returned to our world." "en": "If you lose consciousness in the Darkland, you will be forcibly returned to our world."
}, },
{ {
"key": "Также избегай света луны, луна тоже может вернуть тебя в мир людей. Лучше вообще не выходи из помещения. ", "key": "Также избегай света луны, луна тоже может вернуть тебя в мир людей. Лучше вообще не выходи из помещения. ",
@@ -1903,17 +1903,17 @@
{ {
"key": "Если ты задержишься, и в человеческом мире настанет утро, тебя тоже выбросит из теневого мира.", "key": "Если ты задержишься, и в человеческом мире настанет утро, тебя тоже выбросит из теневого мира.",
"ru": "Если ты задержишься, и в человеческом мире настанет утро, тебя тоже выбросит из теневого мира.", "ru": "Если ты задержишься, и в человеческом мире настанет утро, тебя тоже выбросит из теневого мира.",
"en": "If you stay too long and morning comes in the human world, you will be thrown out of the dark lands too." "en": "If you stay too long and morning comes in the human world, you will be thrown out of the Darkland too."
}, },
{ {
"key": "Но ты не беспокойся. Даже если тебя выбросит из теневого мира, ты всегда сможешь повторить свою попытку на следующую ночь.", "key": "Но ты не беспокойся. Даже если тебя выбросит из теневого мира, ты всегда сможешь повторить свою попытку на следующую ночь.",
"ru": "Но ты не беспокойся. Даже если тебя выбросит из теневого мира, ты всегда сможешь повторить свою попытку на следующую ночь.", "ru": "Но ты не беспокойся. Даже если тебя выбросит из теневого мира, ты всегда сможешь повторить свою попытку на следующую ночь.",
"en": "But don't worry. Even if you're thrown out of the dark lands, you can always try again the next night." "en": "But don't worry. Even if you're thrown out of the Darkland, you can always try again the next night."
}, },
{ {
"key": "У тебя есть еще вопросы про теневой мир?", "key": "У тебя есть еще вопросы про теневой мир?",
"ru": "У тебя есть еще вопросы про теневой мир?", "ru": "У тебя есть еще вопросы про теневой мир?",
"en": "Do you have any more questions about the dark lands?" "en": "Do you have any more questions about the Darkland?"
}, },
{ {
"key": "Ладно, я узнал достаточно.", "key": "Ладно, я узнал достаточно.",
@@ -1938,7 +1938,7 @@
{ {
"key": "Когда я узнала, что в университете начались паранормальные явления, я сразу поняла что это призрак в теневом мире.", "key": "Когда я узнала, что в университете начались паранормальные явления, я сразу поняла что это призрак в теневом мире.",
"ru": "Когда я узнала, что в университете начались паранормальные явления, я сразу поняла что это призрак в теневом мире.", "ru": "Когда я узнала, что в университете начались паранормальные явления, я сразу поняла что это призрак в теневом мире.",
"en": "When I learned that paranormal phenomena had begun at the university, I immediately realized that it was a ghost in the dark lands." "en": "When I learned that paranormal phenomena had begun at the university, I immediately realized that it was a ghost in the Darkland."
}, },
{ {
"key": "Я знаю заклинание, которое тебе дали. У этого заклинания есть еще одно условие - оно сработает только на людей, которые чисты сердцем.", "key": "Я знаю заклинание, которое тебе дали. У этого заклинания есть еще одно условие - оно сработает только на людей, которые чисты сердцем.",
@@ -1953,12 +1953,12 @@
{ {
"key": "Тогда я предположила, что Айпери уж точно сможет прочитать заклинание и попасть в теневой мир.", "key": "Тогда я предположила, что Айпери уж точно сможет прочитать заклинание и попасть в теневой мир.",
"ru": "Тогда я предположила, что Айпери уж точно сможет прочитать заклинание и попасть в теневой мир.", "ru": "Тогда я предположила, что Айпери уж точно сможет прочитать заклинание и попасть в теневой мир.",
"en": "Then I assumed that Aiperi would definitely be able to cast the spell and enter the dark lands." "en": "Then I assumed that Aiperi would definitely be able to cast the spell and enter the Darkland."
}, },
{ {
"key": "Как раз поэтому я хотела вернуть нож ей лично. И заодно отправить ее в теневой мир.", "key": "Как раз поэтому я хотела вернуть нож ей лично. И заодно отправить ее в теневой мир.",
"ru": "Как раз поэтому я хотела вернуть нож ей лично. И заодно отправить ее в теневой мир.", "ru": "Как раз поэтому я хотела вернуть нож ей лично. И заодно отправить ее в теневой мир.",
"en": "That's exactly why I wanted to return the knife to her personally. And at the same time, ask her to go to the dark lands." "en": "That's exactly why I wanted to return the knife to her personally. And at the same time, ask her to go to the Darkland."
}, },
{ {
"key": "Но ты смешал все мои планы. Сначала ты забрал нож, а потом кто-то подсунул тебе записку.", "key": "Но ты смешал все мои планы. Сначала ты забрал нож, а потом кто-то подсунул тебе записку.",
@@ -2068,7 +2068,7 @@
{ {
"key": "Переходи в теневой мир, а затем иди в лекционный зал.", "key": "Переходи в теневой мир, а затем иди в лекционный зал.",
"ru": "Переходи в теневой мир, а затем иди в лекционный зал.", "ru": "Переходи в теневой мир, а затем иди в лекционный зал.",
"en": "Go to the dark lands and then go to the lecture hall." "en": "Go to the Darkland and then go to the lecture hall."
}, },
{ {
"key": "Постарайся поговорить с призраком, и узнать, что ему нужно.", "key": "Постарайся поговорить с призраком, и узнать, что ему нужно.",
@@ -2093,7 +2093,7 @@
{ {
"key": "Иди к шкафу в учительской. Среди зачетных книжек найти зачетку Бегимай и принеси мне. Я поставлю ей оценку за курсовую работу.", "key": "Иди к шкафу в учительской. Среди зачетных книжек найти зачетку Бегимай и принеси мне. Я поставлю ей оценку за курсовую работу.",
"ru": "Иди к шкафу в учительской. Среди зачетных книжек найти зачетку Бегимай и принеси мне. Я поставлю ей оценку за курсовую работу.", "ru": "Иди к шкафу в учительской. Среди зачетных книжек найти зачетку Бегимай и принеси мне. Я поставлю ей оценку за курсовую работу.",
"en": "Go to the cabinet in the teachers' room. Find Begimai's grade book among the grade books and bring it to me. I'll give her a grade for her coursework." "en": "Go to the cabinet in the teachers' room. Find Begimai's record book among the record books and bring it to me. I'll give her a grade for her coursework."
}, },
{ {
"key": "Ты уже нашел курсовую работу Бегимай?", "key": "Ты уже нашел курсовую работу Бегимай?",
@@ -2153,7 +2153,7 @@
{ {
"key": "Теперь поищи в шкафу зачетку Бегимай, и принеси мне, я выставлю ей оценку.", "key": "Теперь поищи в шкафу зачетку Бегимай, и принеси мне, я выставлю ей оценку.",
"ru": "Теперь поищи в шкафу зачетку Бегимай, и принеси мне, я выставлю ей оценку.", "ru": "Теперь поищи в шкафу зачетку Бегимай, и принеси мне, я выставлю ей оценку.",
"en": "Now look in the cabinet for Begimai's grade book and bring it to me, I'll give her a grade." "en": "Now look in the cabinet for Begimai's record book and bring it to me, I'll give her a grade."
}, },
{ {
"key": "Еще нет.", "key": "Еще нет.",
@@ -2173,7 +2173,7 @@
{ {
"key": "Но если я возьму кусочек, Аида Дженибековна меня убьет.", "key": "Но если я возьму кусочек, Аида Дженибековна меня убьет.",
"ru": "Но если я возьму кусочек, Аида Дженибековна меня убьет.", "ru": "Но если я возьму кусочек, Аида Дженибековна меня убьет.",
"en": "But if I take a piece, Aida Dzhanybekovna will kill me." "en": "But if I take a piece, Aida Dzhenibekovna will kill me."
}, },
{ {
"key": "Здесь лежат книги по юриспруденции и праву.", "key": "Здесь лежат книги по юриспруденции и праву.",
@@ -2183,12 +2183,12 @@
{ {
"key": "Бекзат, помнишь мы скидывались на торт для Аиды Дженибековной? Я тогда еще приносила скатерть, тарелки и серебряный нож для торта. И я до сих пор не получила назад ничего.", "key": "Бекзат, помнишь мы скидывались на торт для Аиды Дженибековной? Я тогда еще приносила скатерть, тарелки и серебряный нож для торта. И я до сих пор не получила назад ничего.",
"ru": "Бекзат, помнишь мы скидывались на торт для Аиды Дженибековной? Я тогда еще приносила скатерть, тарелки и серебряный нож для торта. И я до сих пор не получила назад ничего.", "ru": "Бекзат, помнишь мы скидывались на торт для Аиды Дженибековной? Я тогда еще приносила скатерть, тарелки и серебряный нож для торта. И я до сих пор не получила назад ничего.",
"en": "Bekzat, remember how we chipped in for a cake for Aida Dzhanybekovna? ​​I even brought a tablecloth, plates, and a silver cake knife. And I still haven't gotten anything back." "en": "Bekzat, remember how we chipped in for a cake for Aida Dzhenibekovna? ​​I even brought a tablecloth, plates, and a silver cake knife. And I still haven't gotten anything back."
}, },
{ {
"key": "Помнишь мы скидывались на торт для Аиды Дженибековной? Я тогда еще приносила скатерть, тарелки и серебряный нож для торта. И я до сих пор не получила назад ничего.", "key": "Помнишь мы скидывались на торт для Аиды Дженибековной? Я тогда еще приносила скатерть, тарелки и серебряный нож для торта. И я до сих пор не получила назад ничего.",
"ru": "Помнишь мы скидывались на торт для Аиды Дженибековной? Я тогда еще приносила скатерть, тарелки и серебряный нож для торта. И я до сих пор не получила назад ничего.", "ru": "Помнишь мы скидывались на торт для Аиды Дженибековной? Я тогда еще приносила скатерть, тарелки и серебряный нож для торта. И я до сих пор не получила назад ничего.",
"en": "Remember when we chipped in for a cake for Aida Dzhanybekovna? ​​I even brought a tablecloth, plates, and a silver cake knife. And I still haven't gotten anything back." "en": "Remember when we chipped in for a cake for Aida Dzhenibekovna? ​​I even brought a tablecloth, plates, and a silver cake knife. And I still haven't gotten anything back."
}, },
{ {
"key": "А во вторых, мне как-то не хочется попадаться на глаза Аиде.", "key": "А во вторых, мне как-то не хочется попадаться на глаза Аиде.",
@@ -2198,7 +2198,7 @@
{ {
"key": "Да... Только не говори Аиде Дженибековной что я здесь.", "key": "Да... Только не говори Аиде Дженибековной что я здесь.",
"ru": "Да... Только не говори Аиде Дженибековной что я здесь.", "ru": "Да... Только не говори Аиде Дженибековной что я здесь.",
"en": "Yes... Just don't tell Aida Dzhanybekovna that I'm here." "en": "Yes... Just don't tell Aida Dzhenibekovna that I'm here."
}, },
{ {
"key": "Постарайся только Аиде Дженибековной особо на глаза не попадаться. ", "key": "Постарайся только Аиде Дженибековной особо на глаза не попадаться. ",
@@ -2233,7 +2233,7 @@
{ {
"key": "Я уйду на покой только когда увижу оценку по курсовой в своей зачетке.", "key": "Я уйду на покой только когда увижу оценку по курсовой в своей зачетке.",
"ru": "Я уйду на покой только когда увижу оценку по курсовой в своей зачетке.", "ru": "Я уйду на покой только когда увижу оценку по курсовой в своей зачетке.",
"en": "I will rest in peace only when I see the grade for my coursework in my grade book." "en": "I will rest in peace only when I see the grade for my coursework in my record book."
}, },
{ {
"key": "А до тех пор, я буду появляться здесь каждую ночь.", "key": "А до тех пор, я буду появляться здесь каждую ночь.",
@@ -2248,17 +2248,67 @@
{ {
"key": "Аида Дженибековна увидела меня в учительской, и заставила писать эссе по этой тупой книге.", "key": "Аида Дженибековна увидела меня в учительской, и заставила писать эссе по этой тупой книге.",
"ru": "Аида Дженибековна увидела меня в учительской, и заставила писать эссе по этой тупой книге.", "ru": "Аида Дженибековна увидела меня в учительской, и заставила писать эссе по этой тупой книге.",
"en": "Aida Dzhanybekovna saw me in the teachers' room and made me write an essay on this stupid book." "en": "Aida Dzhenibekovna saw me in the teachers' room and made me write an essay on this stupid book."
}, },
{ {
"key": "Прошу прощения Аида Дженибековна, я правда старался, но у меня возникли обстоятельства непреодолимой силы.", "key": "Прошу прощения Аида Дженибековна, я правда старался, но у меня возникли обстоятельства непреодолимой силы.",
"ru": "Прошу прощения Аида Дженибековна, я правда старался, но у меня возникли обстоятельства непреодолимой силы.", "ru": "Прошу прощения Аида Дженибековна, я правда старался, но у меня возникли обстоятельства непреодолимой силы.",
"en": "I apologize, Aida Dzhanybekovna, I really tried, but I encountered force majeure circumstances." "en": "I apologize, Aida Dzhenibekovna, I really tried, but I encountered force majeure circumstances."
}, },
{ {
"key": "Бекзат, я сейчас занята, не мешай мне!", "key": "Бекзат, я сейчас занята, не мешай мне!",
"ru": "Бекзат, я сейчас занята, не мешай мне!", "ru": "Бекзат, я сейчас занята, не мешай мне!",
"en": "Bekzat, I'm busy now, don't disturb me!" "en": "Bekzat, I'm busy now, don't disturb me!"
},
{
"key": "Почему ты сама не можешь забрать нож у Аиды?",
"ru": "Почему ты сама не можешь забрать нож у Аиды?",
"en": "Why can't you take the knife from Aida yourself?"
},
{
"key": "Она опять будет скафнить.",
"ru": "Она опять будет скафнить.",
"en": "She will annoy me again."
},
{
"key": "А может быть даже даст мне какое-нибудь сложное задание.",
"ru": "А может быть даже даст мне какое-нибудь сложное задание.",
"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": "Until you bring me a knife from the teachers' room, I won't let you go anywhere."
},
{
"key": "Я слышала она не смогла сдать курсовую по манасоведению.",
"ru": "Я слышала она не смогла сдать курсовую по манасоведению.",
"en": "I heard she couldn't pass her coursework on Manas studies."
},
{
"key": "Она прыгнула с окна и разбилась насмерть деп.",
"ru": "Она прыгнула с окна и разбилась насмерть деп.",
"en": "She jumped from the window and fell to her death, they said."
},
{
"key": "Да она надоела, все время скафнит.",
"ru": "Да она надоела, все время скафнит.",
"en": "Yes, she's annoying, she's always nagging."
},
{
"key": "Если я ее встречу, она на 100% даст мне какое-нибудь сложное задание.",
"ru": "Если я ее встречу, она на 100% даст мне какое-нибудь сложное задание.",
"en": "If I meet her, she will 100% give me some difficult task."
},
{
"key": "Так что жду тебя в универе! Не вздумай прогулять! Ошо!",
"ru": "Так что жду тебя в универе! Не вздумай прогулять! Ошо!",
"en": "So I'm waiting for you at the university! Don't even think about skipping!"
} }
] ]
} }
@@ -134,7 +134,7 @@
"type": "Line", "type": "Line",
"speaker": "Бекзат", "speaker": "Бекзат",
"portrait": "resources/dialogue/portrait_hero_neutral.png", "portrait": "resources/dialogue/portrait_hero_neutral.png",
"text": "Я заказал такси до общаги, машина уже ждет!", "text": "Я заказал такси до общаги, машина уже едет!",
"next": "end_1" "next": "end_1"
}, },
{ {
@@ -334,7 +334,7 @@
"type": "Line", "type": "Line",
"speaker": "Бекзат", "speaker": "Бекзат",
"portrait": "resources/dialogue/portrait_phone.png", "portrait": "resources/dialogue/portrait_phone.png",
"text": "Почему ты сама не можешь забрать?", "text": "Почему ты сама не можешь забрать нож у Аиды?",
"next": "line_9", "next": "line_9",
"chatBubble": "out" "chatBubble": "out"
}, },
@@ -343,7 +343,16 @@
"type": "Line", "type": "Line",
"speaker": "Айпери", "speaker": "Айпери",
"portrait": "resources/dialogue/portrait_phone.png", "portrait": "resources/dialogue/portrait_phone.png",
"text": "Ты же знаешь, если я встречу Аиду, она 100% даст мне какое-нибудь сложное задание.", "text": "Она опять будет скафнить.",
"next": "line_12",
"chatBubble": "in"
},
{
"id": "line_12",
"type": "Line",
"speaker": "Айпери",
"portrait": "resources/dialogue/portrait_phone.png",
"text": "А может быть даже даст мне какое-нибудь сложное задание.",
"next": "line_10", "next": "line_10",
"chatBubble": "in" "chatBubble": "in"
}, },
@@ -361,7 +370,7 @@
"type": "Line", "type": "Line",
"speaker": "Айпери", "speaker": "Айпери",
"portrait": "resources/dialogue/portrait_phone.png", "portrait": "resources/dialogue/portrait_phone.png",
"text": "Так что жду тебя в универе! Не вздумай прогулять!", "text": "Так что жду тебя в универе! Не вздумай прогулять! Ошо!",
"next": "setflag_1", "next": "setflag_1",
"chatBubble": "in", "chatBubble": "in",
"questUnlock": "aiperi_knife" "questUnlock": "aiperi_knife"
@@ -220,7 +220,15 @@
"type": "Line", "type": "Line",
"speaker": "Айпери", "speaker": "Айпери",
"portrait": "resources/dialogue/portrait_aiperi.png", "portrait": "resources/dialogue/portrait_aiperi.png",
"text": "У меня есть ощущение что если я ее встречу, она даст мне какое-то очень сложное задание.", "text": "Она опять будет скафнить.",
"next": "line_29"
},
{
"id": "line_29",
"type": "Line",
"speaker": "Айпери",
"portrait": "resources/dialogue/portrait_aiperi.png",
"text": "А может быть даже даст мне какое-нибудь сложное задание.",
"next": "line_16" "next": "line_16"
}, },
{ {
@@ -285,6 +293,14 @@
"speaker": "Бекзат", "speaker": "Бекзат",
"portrait": "resources/dialogue/portrait_hero_neutral.png", "portrait": "resources/dialogue/portrait_hero_neutral.png",
"text": "Почему?", "text": "Почему?",
"next": "line_30"
},
{
"id": "line_30",
"type": "Line",
"speaker": "Айпери",
"portrait": "resources/dialogue/portrait_aiperi.png",
"text": "Она опять будет скафнить.",
"next": "line_25" "next": "line_25"
}, },
{ {
@@ -292,7 +308,7 @@
"type": "Line", "type": "Line",
"speaker": "Айпери", "speaker": "Айпери",
"portrait": "resources/dialogue/portrait_aiperi.png", "portrait": "resources/dialogue/portrait_aiperi.png",
"text": "Она меня ищет, я уверена, она хочет мне какое-то задание дать.", "text": "Я даже уверена, она меня ищет. Она хочет мне какое-то задание дать.",
"next": "line_26" "next": "line_26"
}, },
{ {
@@ -338,7 +354,7 @@
"type": "Line", "type": "Line",
"speaker": "Айпери", "speaker": "Айпери",
"portrait": "resources/dialogue/portrait_aiperi.png", "portrait": "resources/dialogue/portrait_aiperi.png",
"text": "Пока ты не принесешь мне нож из учительской, никуда я тебя не выпущу.", "text": "Пока ты не принесешь мне нож из учительской, никуда я тебя не выпущу. Ошо!",
"next": "end_1" "next": "end_1"
}, },
{ {
@@ -2385,7 +2401,15 @@
"type": "Line", "type": "Line",
"speaker": "Айпери", "speaker": "Айпери",
"portrait": "resources/dialogue/portrait_aiperi.png", "portrait": "resources/dialogue/portrait_aiperi.png",
"text": "Я слышала она не смогла сдать курсовую по манасоведению, прыгнула с окна и разбилась насмерть.", "text": "Я слышала она не смогла сдать курсовую по манасоведению.",
"next": "line_34"
},
{
"id": "line_34",
"type": "Line",
"speaker": "Айпери",
"portrait": "resources/dialogue/portrait_aiperi.png",
"text": "Она прыгнула с окна и разбилась насмерть деп.",
"next": "line_21" "next": "line_21"
}, },
{ {
@@ -2571,7 +2595,7 @@
"type": "Line", "type": "Line",
"speaker": "Бекзат", "speaker": "Бекзат",
"portrait": "resources/dialogue/portrait_phone.png", "portrait": "resources/dialogue/portrait_phone.png",
"text": "Почему ты сама не можешь забрать?", "text": "Почему ты сама не можешь забрать нож у Аиды?",
"next": "line_9", "next": "line_9",
"chatBubble": "out" "chatBubble": "out"
}, },
@@ -2580,7 +2604,16 @@
"type": "Line", "type": "Line",
"speaker": "Айпери", "speaker": "Айпери",
"portrait": "resources/dialogue/portrait_phone.png", "portrait": "resources/dialogue/portrait_phone.png",
"text": "Ты же знаешь, если я встречу Аиду, она 100% даст мне какое-нибудь сложное задание.", "text": "Да она надоела, все время скафнит.",
"next": "line_12",
"chatBubble": "in"
},
{
"id": "line_12",
"type": "Line",
"speaker": "Айпери",
"portrait": "resources/dialogue/portrait_phone.png",
"text": "Если я ее встречу, она на 100% даст мне какое-нибудь сложное задание.",
"next": "line_10", "next": "line_10",
"chatBubble": "in" "chatBubble": "in"
}, },
@@ -2598,7 +2631,7 @@
"type": "Line", "type": "Line",
"speaker": "Айпери", "speaker": "Айпери",
"portrait": "resources/dialogue/portrait_phone.png", "portrait": "resources/dialogue/portrait_phone.png",
"text": "Так что жду тебя в универе! Не вздумай прогулять!", "text": "Так что жду тебя в универе! Не вздумай прогулять! Ошо!",
"next": "setflag_1", "next": "setflag_1",
"chatBubble": "in", "chatBubble": "in",
"questUnlock": "aiperi_knife" "questUnlock": "aiperi_knife"
-1
View File
@@ -45,7 +45,6 @@ end
function lection_hall_zone001_enter_callback() function lection_hall_zone001_enter_callback()
local lection_is_over = game_api.getIntValue("lection_is_over") local lection_is_over = game_api.getIntValue("lection_is_over")
if (lection_is_over == 0) then if (lection_is_over == 0) then
game_api.player_stop() game_api.player_stop()
game_api.start_cutscene("lection_cutscene001") game_api.start_cutscene("lection_cutscene001")
Binary file not shown.
Binary file not shown.
+3 -3
View File
@@ -8,9 +8,9 @@
{ {
"type": "StaticImage", "type": "StaticImage",
"name": "hint2", "name": "hint2",
"width": 72, "width": 36,
"height": 354, "height": 177,
"y": -100, "y": -30,
"x": 0, "x": 0,
"horizontal_gravity": "center", "horizontal_gravity": "center",
"vertical_gravity": "center", "vertical_gravity": "center",
Binary file not shown.
Binary file not shown.
Binary file not shown.
+50 -7
View File
@@ -7,18 +7,61 @@
{ {
"type": "StaticImage", "type": "StaticImage",
"name": "aboutBkg", "name": "aboutBkg",
"width": "match_parent", "width": 2992,
"height": "match_parent", "height": 1632,
"texture": "resources/w/ui/img/main/bkg.png" "horizontal_gravity": "center",
"vertical_gravity": "center",
"texture": "resources/w/ui/img/main/MenuBkg001_v001.png"
}, },
{ {
"type": "StaticImage", "type": "StaticImage",
"name": "aboutImage", "name": "aboutPicture",
"width": 720,
"height": 720,
"horizontal_gravity": "center", "horizontal_gravity": "center",
"vertical_gravity": "center", "vertical_gravity": "center",
"width": 800, "texture": "resources/w/ui/img/main/aboutPage002.png"
"height": 500, },
"texture": "resources/w/ui/img/main/about.png" {
"type": "TextButton",
"name": "steamButton",
"horizontal_gravity": "center",
"vertical_gravity": "center",
"y": 200,
"width": 200,
"height": 60,
"text": "Добавить игру в Wishlist!",
"fontSize": 40,
"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", "type": "TextButton",
+339 -22
View File
@@ -7,34 +7,351 @@
{ {
"type": "StaticImage", "type": "StaticImage",
"name": "creditsBkg", "name": "creditsBkg",
"width": "match_parent", "width": 2992,
"height": "match_parent", "height": 1632,
"texture": "resources/w/ui/img/main/bkg.png"
},
{
"type": "StaticImage",
"name": "creditsImage",
"horizontal_gravity": "center", "horizontal_gravity": "center",
"vertical_gravity": "center", "vertical_gravity": "center",
"width": 800, "texture": "resources/w/ui/img/main/MenuBkg001_v001.png"
"height": 500, },
"texture": "resources/w/ui/img/main/about.png" {
"type": "LinearLayout",
"name": "creditsLinearLayout",
"orientation": "vertical",
"horizontal_gravity": "center",
"vertical_gravity": "top",
"horizontal_align": "left",
"animations": {
"slowScroll": {
"repeat": true,
"steps": [
{
"type": "move",
"to": [
0,
2600
],
"duration": 30.0
}
]
}
},
"width": 300,
"height": 3000,
"spacing": 20,
"children": [
{
"type": "TextView",
"name": "smallPause",
"width": 300,
"height": 700,
"text": "",
"fontSize": 40,
"textCentered": true,
"color": [1.0, 1.0, 1.0, 1.0]
},
{
"type": "TextView",
"name": "creditsTitleText",
"width": 300,
"height": 60,
"text": "Тень над Бишкеком Демо",
"fontSize": 40,
"textCentered": true,
"color": [1.0, 1.0, 1.0, 1.0]
},
{
"type": "TextView",
"name": "creditsText1",
"width": 300,
"height": 20,
"text": "Продюсер:",
"fontSize": 32,
"textCentered": false,
"color": [1.0, 0.85, 0.0, 1.0]
},
{
"type": "TextView",
"name": "creditsText2",
"width": 300,
"height": 40,
"text": "Владислав Хорев",
"fontSize": 32,
"textCentered": false,
"color": [1.0, 1.0, 1.0, 1.0]
},
{
"type": "TextView",
"name": "creditsText3",
"width": 300,
"height": 20,
"text": "Гейм-дизайн:",
"fontSize": 32,
"textCentered": false,
"color": [1.0, 0.85, 0.0, 1.0]
},
{
"type": "TextView",
"name": "creditsText4",
"width": 300,
"height": 20,
"text": "Владислав Хорев",
"fontSize": 32,
"textCentered": false,
"color": [1.0, 1.0, 1.0, 1.0]
},
{
"type": "TextView",
"name": "creditsText5",
"width": 300,
"height": 40,
"text": "Лейла Боброва",
"fontSize": 32,
"textCentered": false,
"color": [1.0, 1.0, 1.0, 1.0]
},
{
"type": "TextView",
"name": "creditsText6",
"width": 300,
"height": 20,
"text": "Сценарий:",
"fontSize": 32,
"textCentered": false,
"color": [1.0, 0.85, 0.0, 1.0]
},
{
"type": "TextView",
"name": "creditsText7",
"width": 300,
"height": 20,
"text": "Владислав Хорев",
"fontSize": 32,
"textCentered": false,
"color": [1.0, 1.0, 1.0, 1.0]
},
{
"type": "TextView",
"name": "creditsText8",
"width": 300,
"height": 40,
"text": "Лейла Боброва",
"fontSize": 32,
"textCentered": false,
"color": [1.0, 1.0, 1.0, 1.0]
},
{
"type": "TextView",
"name": "creditsText9",
"width": 300,
"height": 20,
"text": "Разработка:",
"fontSize": 32,
"textCentered": false,
"color": [1.0, 0.85, 0.0, 1.0]
},
{
"type": "TextView",
"name": "creditsText10",
"width": 300,
"height": 20,
"text": "Владислав Хорев",
"fontSize": 32,
"textCentered": false,
"color": [1.0, 1.0, 1.0, 1.0]
},
{
"type": "TextView",
"name": "creditsText11",
"width": 300,
"height": 20,
"text": "Владислав Кан",
"fontSize": 32,
"textCentered": false,
"color": [1.0, 1.0, 1.0, 1.0]
},
{
"type": "TextView",
"name": "creditsText12",
"width": 300,
"height": 20,
"text": "Бексултан Алмазбеков",
"fontSize": 32,
"textCentered": false,
"color": [1.0, 1.0, 1.0, 1.0]
},
{
"type": "TextView",
"name": "creditsText13",
"width": 300,
"height": 40,
"text": "Сергей Зотов",
"fontSize": 32,
"textCentered": false,
"color": [1.0, 1.0, 1.0, 1.0]
},
{
"type": "TextView",
"name": "creditsText14",
"width": 300,
"height": 20,
"text": "Персонажи и анимации:",
"fontSize": 32,
"textCentered": false,
"color": [1.0, 0.85, 0.0, 1.0]
},
{
"type": "TextView",
"name": "creditsText15",
"width": 300,
"height": 20,
"text": "Дмитрий Прохорчик",
"fontSize": 32,
"textCentered": false,
"color": [1.0, 1.0, 1.0, 1.0]
},
{
"type": "TextView",
"name": "creditsText16",
"width": 300,
"height": 40,
"text": "Роман Тресцов",
"fontSize": 32,
"textCentered": false,
"color": [1.0, 1.0, 1.0, 1.0]
},
{
"type": "TextView",
"name": "creditsText17",
"width": 300,
"height": 20,
"text": "3D ассеты:",
"fontSize": 32,
"textCentered": false,
"color": [1.0, 0.85, 0.0, 1.0]
},
{
"type": "TextView",
"name": "creditsText18",
"width": 300,
"height": 20,
"text": "Дмитрий Прохорчик",
"fontSize": 32,
"textCentered": false,
"color": [1.0, 1.0, 1.0, 1.0]
},
{
"type": "TextView",
"name": "creditsText19",
"width": 300,
"height": 40,
"text": "Ishmaladi (turbosquid.com)",
"fontSize": 32,
"textCentered": false,
"color": [1.0, 1.0, 1.0, 1.0]
},
{
"type": "TextView",
"name": "creditsText20",
"width": 300,
"height": 20,
"text": "Арты:",
"fontSize": 32,
"textCentered": false,
"color": [1.0, 0.85, 0.0, 1.0]
},
{
"type": "TextView",
"name": "creditsText21",
"width": 300,
"height": 40,
"text": "Maggie Tem",
"fontSize": 32,
"textCentered": false,
"color": [1.0, 1.0, 1.0, 1.0]
},
{
"type": "TextView",
"name": "creditsText22",
"width": 300,
"height": 20,
"text": "UI/UX:",
"fontSize": 32,
"textCentered": false,
"color": [1.0, 0.85, 0.0, 1.0]
},
{
"type": "TextView",
"name": "creditsText23",
"width": 300,
"height": 40,
"text": "Кенже Казматова",
"fontSize": 32,
"textCentered": false,
"color": [1.0, 1.0, 1.0, 1.0]
},
{
"type": "TextView",
"name": "creditsText24",
"width": 300,
"height": 20,
"text": "Музыка:",
"fontSize": 32,
"textCentered": false,
"color": [1.0, 0.85, 0.0, 1.0]
},
{
"type": "TextView",
"name": "creditsText25",
"width": 300,
"height": 40,
"text": "Pazetic_Ocean",
"fontSize": 32,
"textCentered": false,
"color": [1.0, 1.0, 1.0, 1.0]
},
{
"type": "TextView",
"name": "creditsText26",
"width": 300,
"height": 20,
"text": "Звуки:",
"fontSize": 32,
"textCentered": false,
"color": [1.0, 0.85, 0.0, 1.0]
},
{
"type": "TextView",
"name": "creditsText27",
"width": 300,
"height": 300,
"text": "IENBA, ImAFoley, stradie,\nItsTheGoodstuff, uwesoundboiz,\nAnthousai, Foxfire-,\nn-lerche, CogFireStudios,\nadcbicycle, AntoineRomo,\nSmallConfusion, joedeshon,\nefectirijillo (FreeSound.org)",
"fontSize": 32,
"textCentered": false,
"color": [1.0, 1.0, 1.0, 1.0]
},
{
"type": "TextView",
"name": "creditsText28",
"width": 300,
"height": 40,
"text": "Некоторые арты, 3D модели\nи текстуры были созданы\nс помощью нейросетей.",
"fontSize": 32,
"textCentered": false,
"color": [1.0, 1.0, 1.0, 1.0]
}
]
}, },
{ {
"type": "Button", "type": "Button",
"name": "skipButton", "name": "creditsBackButton",
"horizontal_gravity": "right", "width": "match_parent",
"vertical_gravity": "top", "height": "match_parent",
"x": -23, "x": 0,
"y": -35, "y": 0,
"width": 200,
"height": 200,
"clickZoneWidth": 176,
"clickZoneHeight": 176,
"textures": { "textures": {
"normal": "resources/w/ui/img/RedClose001_State=Default001.png", "normal": "resources/transparent.png",
"hover": "resources/w/ui/img/RedClose001_State=Selected001.png", "hover": "resources/transparent.png",
"pressed": "resources/w/ui/img/RedClose001_State=Tap001.png" "pressed": "resources/transparent.png"
} }
} }
] ]
+18 -9
View File
@@ -47,7 +47,7 @@
"horizontal_align": "center", "horizontal_align": "center",
"vertical_align": "center", "vertical_align": "center",
"spacing": 24, "spacing": 24,
"y": 150, "y": 140,
"width": "match_parent", "width": "match_parent",
"height": "match_parent", "height": "match_parent",
"children": [ "children": [
@@ -55,7 +55,7 @@
"type": "TextButton", "type": "TextButton",
"name": "menuStartButton", "name": "menuStartButton",
"width": 300, "width": 300,
"height": 50, "height": 44,
"text": "Начать игру", "text": "Начать игру",
"fontSize": 48, "fontSize": 48,
"textCentered": true "textCentered": true
@@ -64,7 +64,7 @@
"type": "TextButton", "type": "TextButton",
"name": "menuLoadButton", "name": "menuLoadButton",
"width": 300, "width": 300,
"height": 50, "height": 44,
"text": "Загрузить игру", "text": "Загрузить игру",
"fontSize": 48, "fontSize": 48,
"textCentered": true "textCentered": true
@@ -73,7 +73,7 @@
"type": "TextButton", "type": "TextButton",
"name": "menuSettingsButton", "name": "menuSettingsButton",
"width": 300, "width": 300,
"height": 50, "height": 44,
"text": "Настройки", "text": "Настройки",
"fontSize": 48, "fontSize": 48,
"textCentered": true "textCentered": true
@@ -82,7 +82,7 @@
"type": "TextButton", "type": "TextButton",
"name": "menuLanguageButton", "name": "menuLanguageButton",
"width": 300, "width": 300,
"height": 50, "height": 44,
"text": "Язык / Language", "text": "Язык / Language",
"fontSize": 48, "fontSize": 48,
"textCentered": true "textCentered": true
@@ -91,8 +91,17 @@
"type": "TextButton", "type": "TextButton",
"name": "menuAboutButton", "name": "menuAboutButton",
"width": 300, "width": 300,
"height": 50, "height": 44,
"text": "О нас", "text": "О игре",
"fontSize": 48,
"textCentered": true
},
{
"type": "TextButton",
"name": "menuCreditsButton",
"width": 300,
"height": 44,
"text": "Авторы",
"fontSize": 48, "fontSize": 48,
"textCentered": true "textCentered": true
}, },
@@ -100,7 +109,7 @@
"type": "TextButton", "type": "TextButton",
"name": "menuExitButton", "name": "menuExitButton",
"width": 300, "width": 300,
"height": 50, "height": 44,
"text": "Выход", "text": "Выход",
"fontSize": 48, "fontSize": 48,
"textCentered": true "textCentered": true
@@ -116,7 +125,7 @@
"y": 10, "y": 10,
"width": 160, "width": 160,
"height": 36, "height": 36,
"text": "v. 0.7", "text": "v. 1.0.1",
"fontSize": 24, "fontSize": 24,
"textCentered": false, "textCentered": false,
"topAligned": true, "topAligned": true,
+3 -3
View File
@@ -8,9 +8,9 @@
{ {
"type": "StaticImage", "type": "StaticImage",
"name": "hint2", "name": "hint2",
"width": 72, "width": 36,
"height": 354, "height": 177,
"y": -100, "y": -30,
"x": 0, "x": 0,
"horizontal_gravity": "center", "horizontal_gravity": "center",
"vertical_gravity": "center", "vertical_gravity": "center",
Binary file not shown.
+80
View File
@@ -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/aboutPage002.png"
},
{
"type": "TextButton",
"name": "steamButton",
"horizontal_gravity": "center",
"vertical_gravity": "center",
"y": 200,
"width": 200,
"height": 60,
"text": "Add to your Steam wishlist!",
"fontSize": 40,
"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
}
]
}
}
+359
View File
@@ -0,0 +1,359 @@
{
"root": {
"type": "FrameLayout",
"width": "match_parent",
"height": "match_parent",
"children": [
{
"type": "StaticImage",
"name": "creditsBkg",
"width": 2992,
"height": 1632,
"horizontal_gravity": "center",
"vertical_gravity": "center",
"texture": "resources/w/ui/img/main/MenuBkg001_v001.png"
},
{
"type": "LinearLayout",
"name": "creditsLinearLayout",
"orientation": "vertical",
"horizontal_gravity": "center",
"vertical_gravity": "top",
"horizontal_align": "left",
"animations": {
"slowScroll": {
"repeat": true,
"steps": [
{
"type": "move",
"to": [
0,
2600
],
"duration": 30.0
}
]
}
},
"width": 300,
"height": 3000,
"spacing": 20,
"children": [
{
"type": "TextView",
"name": "smallPause",
"width": 300,
"height": 700,
"text": "",
"fontSize": 40,
"textCentered": true,
"color": [1.0, 1.0, 1.0, 1.0]
},
{
"type": "TextView",
"name": "creditsTitleText",
"width": 300,
"height": 60,
"text": "Shadow over Bishkek Demo",
"fontSize": 40,
"textCentered": true,
"color": [1.0, 1.0, 1.0, 1.0]
},
{
"type": "TextView",
"name": "creditsText1",
"width": 300,
"height": 20,
"text": "Producer:",
"fontSize": 32,
"textCentered": false,
"color": [1.0, 0.85, 0.0, 1.0]
},
{
"type": "TextView",
"name": "creditsText2",
"width": 300,
"height": 40,
"text": "Vladislav Khorev",
"fontSize": 32,
"textCentered": false,
"color": [1.0, 1.0, 1.0, 1.0]
},
{
"type": "TextView",
"name": "creditsText3",
"width": 300,
"height": 20,
"text": "Game Design:",
"fontSize": 32,
"textCentered": false,
"color": [1.0, 0.85, 0.0, 1.0]
},
{
"type": "TextView",
"name": "creditsText4",
"width": 300,
"height": 20,
"text": "Vladislav Khorev",
"fontSize": 32,
"textCentered": false,
"color": [1.0, 1.0, 1.0, 1.0]
},
{
"type": "TextView",
"name": "creditsText5",
"width": 300,
"height": 40,
"text": "Leila Bobrova",
"fontSize": 32,
"textCentered": false,
"color": [1.0, 1.0, 1.0, 1.0]
},
{
"type": "TextView",
"name": "creditsText6",
"width": 300,
"height": 20,
"text": "Story:",
"fontSize": 32,
"textCentered": false,
"color": [1.0, 0.85, 0.0, 1.0]
},
{
"type": "TextView",
"name": "creditsText7",
"width": 300,
"height": 20,
"text": "Vladislav Khorev",
"fontSize": 32,
"textCentered": false,
"color": [1.0, 1.0, 1.0, 1.0]
},
{
"type": "TextView",
"name": "creditsText8",
"width": 300,
"height": 40,
"text": "Leila Bobrova",
"fontSize": 32,
"textCentered": false,
"color": [1.0, 1.0, 1.0, 1.0]
},
{
"type": "TextView",
"name": "creditsText9",
"width": 300,
"height": 20,
"text": "Development:",
"fontSize": 32,
"textCentered": false,
"color": [1.0, 0.85, 0.0, 1.0]
},
{
"type": "TextView",
"name": "creditsText10",
"width": 300,
"height": 20,
"text": "Vladislav Khorev",
"fontSize": 32,
"textCentered": false,
"color": [1.0, 1.0, 1.0, 1.0]
},
{
"type": "TextView",
"name": "creditsText11",
"width": 300,
"height": 20,
"text": "Vladislav Kan",
"fontSize": 32,
"textCentered": false,
"color": [1.0, 1.0, 1.0, 1.0]
},
{
"type": "TextView",
"name": "creditsText12",
"width": 300,
"height": 20,
"text": "Beksultan Almazbekov",
"fontSize": 32,
"textCentered": false,
"color": [1.0, 1.0, 1.0, 1.0]
},
{
"type": "TextView",
"name": "creditsText13",
"width": 300,
"height": 40,
"text": "Sergei Zotov",
"fontSize": 32,
"textCentered": false,
"color": [1.0, 1.0, 1.0, 1.0]
},
{
"type": "TextView",
"name": "creditsText14",
"width": 300,
"height": 20,
"text": "Characters and animations:",
"fontSize": 32,
"textCentered": false,
"color": [1.0, 0.85, 0.0, 1.0]
},
{
"type": "TextView",
"name": "creditsText15",
"width": 300,
"height": 20,
"text": "Dmitry Prohorchik",
"fontSize": 32,
"textCentered": false,
"color": [1.0, 1.0, 1.0, 1.0]
},
{
"type": "TextView",
"name": "creditsText16",
"width": 300,
"height": 40,
"text": "Roman Trestcov",
"fontSize": 32,
"textCentered": false,
"color": [1.0, 1.0, 1.0, 1.0]
},
{
"type": "TextView",
"name": "creditsText17",
"width": 300,
"height": 20,
"text": "3D Assets:",
"fontSize": 32,
"textCentered": false,
"color": [1.0, 0.85, 0.0, 1.0]
},
{
"type": "TextView",
"name": "creditsText18",
"width": 300,
"height": 20,
"text": "Dmitry Prohorchik",
"fontSize": 32,
"textCentered": false,
"color": [1.0, 1.0, 1.0, 1.0]
},
{
"type": "TextView",
"name": "creditsText19",
"width": 300,
"height": 40,
"text": "Ishmaladi (turbosquid.com)",
"fontSize": 32,
"textCentered": false,
"color": [1.0, 1.0, 1.0, 1.0]
},
{
"type": "TextView",
"name": "creditsText20",
"width": 300,
"height": 20,
"text": "Arts:",
"fontSize": 32,
"textCentered": false,
"color": [1.0, 0.85, 0.0, 1.0]
},
{
"type": "TextView",
"name": "creditsText21",
"width": 300,
"height": 40,
"text": "Maggie Tem",
"fontSize": 32,
"textCentered": false,
"color": [1.0, 1.0, 1.0, 1.0]
},
{
"type": "TextView",
"name": "creditsText22",
"width": 300,
"height": 20,
"text": "UI/UX:",
"fontSize": 32,
"textCentered": false,
"color": [1.0, 0.85, 0.0, 1.0]
},
{
"type": "TextView",
"name": "creditsText23",
"width": 300,
"height": 40,
"text": "Kenje Kazmatova",
"fontSize": 32,
"textCentered": false,
"color": [1.0, 1.0, 1.0, 1.0]
},
{
"type": "TextView",
"name": "creditsText24",
"width": 300,
"height": 20,
"text": "Music:",
"fontSize": 32,
"textCentered": false,
"color": [1.0, 0.85, 0.0, 1.0]
},
{
"type": "TextView",
"name": "creditsText25",
"width": 300,
"height": 40,
"text": "Pazetic_Ocean",
"fontSize": 32,
"textCentered": false,
"color": [1.0, 1.0, 1.0, 1.0]
},
{
"type": "TextView",
"name": "creditsText26",
"width": 300,
"height": 20,
"text": "Sounds:",
"fontSize": 32,
"textCentered": false,
"color": [1.0, 0.85, 0.0, 1.0]
},
{
"type": "TextView",
"name": "creditsText27",
"width": 300,
"height": 300,
"text": "IENBA, ImAFoley, stradie,\nItsTheGoodstuff, uwesoundboiz,\nAnthousai, Foxfire-,\nn-lerche, CogFireStudios,\nadcbicycle, AntoineRomo,\nSmallConfusion, joedeshon,\nefectirijillo (FreeSound.org)",
"fontSize": 32,
"textCentered": false,
"color": [1.0, 1.0, 1.0, 1.0]
},
{
"type": "TextView",
"name": "creditsText28",
"width": 300,
"height": 40,
"text": "Some art, 3D models\nand textures were created\nusing neural networks.",
"fontSize": 32,
"textCentered": false,
"color": [1.0, 1.0, 1.0, 1.0]
}
]
},
{
"type": "Button",
"name": "creditsBackButton",
"width": "match_parent",
"height": "match_parent",
"x": 0,
"y": 0,
"textures": {
"normal": "resources/transparent.png",
"hover": "resources/transparent.png",
"pressed": "resources/transparent.png"
}
}
]
}
}
+18 -9
View File
@@ -47,7 +47,7 @@
"horizontal_align": "center", "horizontal_align": "center",
"vertical_align": "center", "vertical_align": "center",
"spacing": 24, "spacing": 24,
"y": 150, "y": 140,
"width": "match_parent", "width": "match_parent",
"height": "match_parent", "height": "match_parent",
"children": [ "children": [
@@ -55,7 +55,7 @@
"type": "TextButton", "type": "TextButton",
"name": "menuStartButton", "name": "menuStartButton",
"width": 300, "width": 300,
"height": 50, "height": 44,
"text": "Start Game", "text": "Start Game",
"fontSize": 48, "fontSize": 48,
"textCentered": true "textCentered": true
@@ -64,7 +64,7 @@
"type": "TextButton", "type": "TextButton",
"name": "menuLoadButton", "name": "menuLoadButton",
"width": 300, "width": 300,
"height": 50, "height": 44,
"text": "Load Game", "text": "Load Game",
"fontSize": 48, "fontSize": 48,
"textCentered": true "textCentered": true
@@ -73,7 +73,7 @@
"type": "TextButton", "type": "TextButton",
"name": "menuSettingsButton", "name": "menuSettingsButton",
"width": 300, "width": 300,
"height": 50, "height": 44,
"text": "Settings", "text": "Settings",
"fontSize": 48, "fontSize": 48,
"textCentered": true "textCentered": true
@@ -82,7 +82,7 @@
"type": "TextButton", "type": "TextButton",
"name": "menuLanguageButton", "name": "menuLanguageButton",
"width": 300, "width": 300,
"height": 50, "height": 44,
"text": "Язык / Language", "text": "Язык / Language",
"fontSize": 48, "fontSize": 48,
"textCentered": true "textCentered": true
@@ -91,8 +91,17 @@
"type": "TextButton", "type": "TextButton",
"name": "menuAboutButton", "name": "menuAboutButton",
"width": 300, "width": 300,
"height": 50, "height": 44,
"text": "About Us", "text": "About Game",
"fontSize": 48,
"textCentered": true
},
{
"type": "TextButton",
"name": "menuCreditsButton",
"width": 300,
"height": 44,
"text": "Credits",
"fontSize": 48, "fontSize": 48,
"textCentered": true "textCentered": true
}, },
@@ -100,7 +109,7 @@
"type": "TextButton", "type": "TextButton",
"name": "menuExitButton", "name": "menuExitButton",
"width": 300, "width": 300,
"height": 50, "height": 44,
"text": "Exit", "text": "Exit",
"fontSize": 48, "fontSize": 48,
"textCentered": true "textCentered": true
@@ -116,7 +125,7 @@
"y": 10, "y": 10,
"width": 160, "width": 160,
"height": 36, "height": 36,
"text": "v. 0.7", "text": "v. 1.0.1",
"fontSize": 24, "fontSize": 24,
"textCentered": false, "textCentered": false,
"topAligned": true, "topAligned": true,
+3 -2
View File
@@ -1,5 +1,6 @@
#include "AudioPlayerAsync.h" #include "AudioPlayerAsync.h"
#include <iostream> #include <iostream>
#include "utils/Utils.h"
#ifdef __EMSCRIPTEN__ #ifdef __EMSCRIPTEN__
AudioPlayerAsync::AudioPlayerAsync() {} AudioPlayerAsync::AudioPlayerAsync() {}
@@ -36,7 +37,7 @@ bool AudioPlayerAsync::init() {
Mix_AllocateChannels(16); Mix_AllocateChannels(16);
initialized = true; initialized = true;
std::cout << "AudioPlayerAsync initialized with SDL2_mixer" << std::endl; ZL::logger() << "AudioPlayerAsync initialized with SDL2_mixer" << std::endl;
return true; return true;
} }
@@ -60,7 +61,7 @@ void AudioPlayerAsync::shutdown() {
Mix_CloseAudio(); Mix_CloseAudio();
SDL_QuitSubSystem(SDL_INIT_AUDIO); SDL_QuitSubSystem(SDL_INIT_AUDIO);
initialized = false; initialized = false;
std::cout << "AudioPlayerAsync shutdown" << std::endl; ZL::logger() << "AudioPlayerAsync shutdown" << std::endl;
} }
void AudioPlayerAsync::playSoundAsync(const std::string& filePath, int loops, int channel) { void AudioPlayerAsync::playSoundAsync(const std::string& filePath, int loops, int channel) {
+6 -6
View File
@@ -37,7 +37,7 @@ namespace ZL
} }
} }
std::cout << "Bone name not found: " << name << std::endl; logger() << "Bone name not found: " << name << std::endl;
throw std::runtime_error("Bone name not found: " + name); throw std::runtime_error("Bone name not found: " + name);
return -1; return -1;
@@ -435,7 +435,7 @@ namespace ZL
meshNamesOrdered.push_back(meshName); meshNamesOrdered.push_back(meshName);
} }
std::cout << "Loaded " << numberMeshes << " meshes from " << fileName << std::endl; logger() << "Loaded " << numberMeshes << " meshes from " << fileName << std::endl;
// ---- Animation Keyframes ---- // ---- Animation Keyframes ----
std::getline(f, tempLine); // === Animation Keyframes === std::getline(f, tempLine); // === Animation Keyframes ===
@@ -509,7 +509,7 @@ namespace ZL
if (startBones.size() > MAX_GPU_BONES) if (startBones.size() > MAX_GPU_BONES)
{ {
std::cout << "Warning: model has " << startBones.size() logger() << "Warning: model has " << startBones.size()
<< " bones, exceeding GPU skinning limit of " << MAX_GPU_BONES << std::endl; << " bones, exceeding GPU skinning limit of " << MAX_GPU_BONES << std::endl;
} }
} }
@@ -706,7 +706,7 @@ namespace ZL
if (startBones.size() > MAX_GPU_BONES) if (startBones.size() > MAX_GPU_BONES)
{ {
std::cout << "Warning: model has " << startBones.size() logger() << "Warning: model has " << startBones.size()
<< " bones, exceeding GPU skinning limit of " << MAX_GPU_BONES << std::endl; << " bones, exceeding GPU skinning limit of " << MAX_GPU_BONES << std::endl;
} }
} }
@@ -736,7 +736,7 @@ namespace ZL
if (startingKeyFrame == -1) if (startingKeyFrame == -1)
{ {
std::cout << "Exception here: frame number is out of range of keyframes. Frame: " << frame << std::endl; logger() << "Exception here: frame number is out of range of keyframes. Frame: " << frame << std::endl;
throw std::runtime_error("Exception here"); throw std::runtime_error("Exception here");
} }
@@ -794,7 +794,7 @@ namespace ZL
{ {
if (md.verticesBoneWeight[i][j].boneIndex == -1) if (md.verticesBoneWeight[i][j].boneIndex == -1)
{ {
std::cout << "Exception here: bone index is -1 but weight is > 0" << std::endl; logger() << "Exception here: bone index is -1 but weight is > 0" << std::endl;
throw std::runtime_error("Bones loaded incorrectly - bone index is -1 but weight is > 0"); throw std::runtime_error("Bones loaded incorrectly - bone index is -1 but weight is > 0");
} }
vMoved = true; vMoved = true;
+3 -3
View File
@@ -613,7 +613,7 @@ bool Character::prepareGpuSkinning() {
void Character::tryAttackCombo() void Character::tryAttackCombo()
{ {
std::cout << "Try Attack Combo" << std::endl; logger() << "Try Attack Combo" << std::endl;
auto it = animations.find(state.currentState); auto it = animations.find(state.currentState);
if (it == animations.end()) return; if (it == animations.end()) return;
@@ -622,7 +622,7 @@ void Character::tryAttackCombo()
if (static_cast<int>(anim.currentFrame) >= 20 && if (static_cast<int>(anim.currentFrame) >= 20 &&
(state.currentState == AnimationState::ACTION_ATTACK || (state.currentState == AnimationState::ACTION_ATTACK ||
state.currentState == AnimationState::ACTION_ATTACK_2)) { state.currentState == AnimationState::ACTION_ATTACK_2)) {
std::cout << "Try Attack Combo Success" << std::endl; logger() << "Try Attack Combo Success" << std::endl;
anim.currentFrame = anim.model.startingFrame; anim.currentFrame = anim.model.startingFrame;
} }
} }
@@ -1234,7 +1234,7 @@ void Character::setupHitSparks(std::shared_ptr<Texture> sparkTexture) {
} }
void Character::applyDamage(float damageAmount, const Eigen::Vector3f& attackDirection, int attackerIndex) { void Character::applyDamage(float damageAmount, const Eigen::Vector3f& attackDirection, int attackerIndex) {
std::cout << "Character::applyDamage" << std::endl; logger() << "Character::applyDamage" << std::endl;
state.hp -= damageAmount; state.hp -= damageAmount;
if (state.hp < 0) state.hp = 0; if (state.hp < 0) state.hp = 0;
+1 -1
View File
@@ -116,7 +116,7 @@ public:
std::string weaponAttachBoneName = "RightHand"; std::string weaponAttachBoneName = "RightHand";
SparkEmitter hitSparkEmitter; SparkEmitter hitSparkEmitter;
AudioPlayerAsync* audioPlayer; AudioPlayerAsync* audioPlayer = nullptr;
static Dialogue::TranslationDatabase nameTranslations; static Dialogue::TranslationDatabase nameTranslations;
+3 -1
View File
@@ -23,12 +23,14 @@ int Environment::height = CONST_DEFAULT_HEIGHT;
float Environment::zoom = DEFAULT_ZOOM; float Environment::zoom = DEFAULT_ZOOM;
bool Environment::enableLogging = false;
SDL_Window* Environment::window = nullptr; SDL_Window* Environment::window = nullptr;
#ifdef EMSCRIPTEN #ifdef EMSCRIPTEN
bool Environment::isFullscreen = false; bool Environment::isFullscreen = false;
#else #else
bool Environment::isFullscreen = false; bool Environment::isFullscreen = true;
#endif #endif
bool Environment::isHighDPIEnabled = false; bool Environment::isHighDPIEnabled = false;
+2
View File
@@ -31,6 +31,8 @@ public:
static int height; static int height;
static float zoom; static float zoom;
static bool enableLogging;
static Eigen::Matrix3f inverseShipMatrix; static Eigen::Matrix3f inverseShipMatrix;
static SDL_Window* window; static SDL_Window* window;
+39 -61
View File
@@ -43,7 +43,7 @@ namespace ZL
Game* Game::s_instance = nullptr; Game* Game::s_instance = nullptr;
void Game::onResourcesZipLoaded(unsigned /*handle*/, void* /*userData*/, const char* /*filename*/) { void Game::onResourcesZipLoaded(unsigned /*handle*/, void* /*userData*/, const char* /*filename*/) {
std::cout << "Resources.zip loaded successfully--0" << std::endl; logger() << "Resources.zip loaded successfully--0" << std::endl;
if (s_instance) { if (s_instance) {
s_instance->resourcesDownloadProgress = 1.0f; s_instance->resourcesDownloadProgress = 1.0f;
// Builds the loadSteps queue (cheap, no GL work); the actual loading // Builds the loadSteps queue (cheap, no GL work); the actual loading
@@ -54,7 +54,7 @@ namespace ZL
} }
void Game::onResourcesZipError(unsigned /*handle*/, void* /*userData*/, int /*httpStatus*/) { void Game::onResourcesZipError(unsigned /*handle*/, void* /*userData*/, int /*httpStatus*/) {
std::cout << "Failed to download resources.zip" << std::endl; logger() << "Failed to download resources.zip" << std::endl;
} }
void Game::onResourcesZipProgress(unsigned /*handle*/, void* /*userData*/, int percentComplete) { void Game::onResourcesZipProgress(unsigned /*handle*/, void* /*userData*/, int percentComplete) {
@@ -78,11 +78,6 @@ namespace ZL
} }
Game::~Game() { Game::~Game() {
#ifndef EMSCRIPTEN
// In Emscripten, SDL must stay alive across context loss/restore cycles
// so the window remains valid when the game object is re-created.
SDL_Quit();
#endif
} }
void Game::setup() { void Game::setup() {
@@ -134,7 +129,7 @@ namespace ZL
// Asynchronously download resources.zip; setupPart2() is called on completion. // Asynchronously download resources.zip; setupPart2() is called on completion.
// The loading screen stays visible until the download finishes. // The loading screen stays visible until the download finishes.
s_instance = this; s_instance = this;
std::cout << "Load resurces step 1-" << std::endl; logger() << "Load resurces step 1-" << std::endl;
emscripten_async_wget2("resources.zip", "resources.zip", "GET", nullptr, nullptr, emscripten_async_wget2("resources.zip", "resources.zip", "GET", nullptr, nullptr,
onResourcesZipLoaded, onResourcesZipError, onResourcesZipProgress); onResourcesZipLoaded, onResourcesZipError, onResourcesZipProgress);
#else #else
@@ -316,7 +311,7 @@ namespace ZL
loadSteps.push([this]() { loadSteps.push([this]() {
try { try {
menuManager.setup(gameState.inventory, CONST_ZIP_FILE); menuManager.setup(gameState.inventory, CONST_ZIP_FILE);
std::cout << "UI loaded successfully" << std::endl; logger() << "UI loaded successfully" << std::endl;
} }
catch (const std::exception& e) { catch (const std::exception& e) {
std::cerr << "Failed to load UI: " << e.what() << std::endl; std::cerr << "Failed to load UI: " << e.what() << std::endl;
@@ -366,10 +361,10 @@ namespace ZL
if (audioPlayer->init()) { if (audioPlayer->init()) {
audioPlayer->setMusicVolume(100); audioPlayer->setMusicVolume(100);
audioPlayer->setSoundVolume(80); audioPlayer->setSoundVolume(80);
std::cout << "Audio initialized successfully" << std::endl; logger() << "Audio initialized successfully" << std::endl;
} }
else { else {
std::cout << "Audio initialization failed" << std::endl; logger() << "Audio initialization failed" << std::endl;
} }
}); });
@@ -480,12 +475,12 @@ namespace ZL
nlohmann::json root; nlohmann::json root;
gameState.save(root); gameState.save(root);
const std::string path = "start_state.json"; const std::string path = "start_state.json";
std::cout << "[reset] saveInitialState called, to " << path << std::endl; logger() << "[reset] saveInitialState called, to " << path << std::endl;
std::ofstream file(path); std::ofstream file(path);
if (file.is_open()) { if (file.is_open()) {
file << root.dump(2); file << root.dump(2);
std::cout << "[reset] Saved initial state snapshot to " << path << std::endl; logger() << "[reset] Saved initial state snapshot to " << path << std::endl;
} else { } else {
std::cerr << "[reset] Could not open " << path << " for writing" << std::endl; std::cerr << "[reset] Could not open " << path << " for writing" << std::endl;
} }
@@ -506,7 +501,7 @@ namespace ZL
LocationSetup uniInteriorParams; LocationSetup uniInteriorParams;
uniInteriorParams.gameObjectsJsonPath = "resources/config/gameobjects_uni_interior_x.json"; uniInteriorParams.gameObjectsJsonPath = "resources/config/gameobjects_uni_interior_x.json";
uniInteriorParams.npcsJsonPath = "resources/config/npcs_uni_interior.json"; uniInteriorParams.npcsJsonPath = "resources/config/npcs_uni_interior.json";
uniInteriorParams.dialoguesJsonPath = "resources/dialogue/uni_interior_dialogues_009.json"; uniInteriorParams.dialoguesJsonPath = "resources/dialogue/uni_interior_dialogues_010.json";
uniInteriorParams.navigationJsonPaths = { uniInteriorParams.navigationJsonPaths = {
"resources/navigation/uni_interior4_unlocked_n2.txt", //0 "resources/navigation/uni_interior4_unlocked_n2.txt", //0
@@ -586,7 +581,7 @@ namespace ZL
gameState.locations["uni_interior"]->requestClosePhone = [this]() { menuManager.closePhoneEntirely(); }; gameState.locations["uni_interior"]->requestClosePhone = [this]() { menuManager.closePhoneEntirely(); };
gameState.locations["uni_interior"]->requestReturnToMainMenu = [this]() { gameState.locations["uni_interior"]->requestReturnToMainMenu = [this]() {
gameState.currentLocationName.clear(); gameState.currentLocationName.clear();
menuManager.showMainMenu(); menuManager.showMenuAfterGameCompleted();
}; };
gameState.locations["uni_interior"]->requestSetChatUnread = [this](int idx, bool unread, const std::string& msg) { gameState.locations["uni_interior"]->requestSetChatUnread = [this](int idx, bool unread, const std::string& msg) {
menuManager.setChatUnread(idx, unread, msg); menuManager.setChatUnread(idx, unread, msg);
@@ -643,7 +638,7 @@ namespace ZL
uniExteriorParams.scriptPath = "resources/start_uni_exterior.lua"; uniExteriorParams.scriptPath = "resources/start_uni_exterior.lua";
uniExteriorParams.playerPosition = Eigen::Vector3f(5, 0, -18.4); uniExteriorParams.playerPosition = Eigen::Vector3f(5, 0, -18.4);
uniExteriorParams.npcsJsonPath = "resources/config/npcs_uni_exterior.json"; uniExteriorParams.npcsJsonPath = "resources/config/npcs_uni_exterior.json";
uniExteriorParams.dialoguesJsonPath = "resources/dialogue/uni_exterior_dialogues002.json"; uniExteriorParams.dialoguesJsonPath = "resources/dialogue/uni_exterior_dialogues003.json";
gameState.locations["uni_exterior"] = std::make_shared<Location>(renderer, gameState.inventory); gameState.locations["uni_exterior"] = std::make_shared<Location>(renderer, gameState.inventory);
@@ -656,7 +651,7 @@ namespace ZL
}; };
gameState.locations["uni_exterior"]->requestDarklandsTransition = [this]() { return startDarklandsTransition(); }; gameState.locations["uni_exterior"]->requestDarklandsTransition = [this]() { return startDarklandsTransition(); };
gameState.locations["uni_exterior"]->requestClosePhone = [this]() { menuManager.closePhoneEntirely(); }; gameState.locations["uni_exterior"]->requestClosePhone = [this]() { menuManager.closePhoneEntirely(); };
gameState.locations["uni_exterior"]->requestReturnToMainMenu = [this]() { menuManager.showMainMenu(); }; //gameState.locations["uni_exterior"]->requestReturnToMainMenu = [this]() { menuManager.showMenuAfterGameCompleted(); };
gameState.locations["uni_exterior"]->requestSetChatUnread = [this](int idx, bool unread, const std::string& msg) { gameState.locations["uni_exterior"]->requestSetChatUnread = [this](int idx, bool unread, const std::string& msg) {
menuManager.setChatUnread(idx, unread, msg); menuManager.setChatUnread(idx, unread, msg);
}; };
@@ -685,7 +680,6 @@ namespace ZL
//Exterior set up taxi end //Exterior set up taxi end
} }
@@ -697,7 +691,7 @@ namespace ZL
params_dorm.gameObjectsJsonPath = "resources/config/gameobjects_dorm_new_x.json"; params_dorm.gameObjectsJsonPath = "resources/config/gameobjects_dorm_new_x.json";
params_dorm.npcsJsonPath = "resources/config/npcs_dorm.json"; params_dorm.npcsJsonPath = "resources/config/npcs_dorm.json";
params_dorm.dialoguesJsonPath = "resources/dialogue/dorm_dialogues003.json"; params_dorm.dialoguesJsonPath = "resources/dialogue/dorm_dialogues004.json";
/* /*
params_dorm.navigationJsonPaths = { params_dorm.navigationJsonPaths = {
@@ -741,7 +735,7 @@ namespace ZL
gameState.locations["location_dorm"]->requestNightDayTransition = [this](bool isNight, bool isDawn) { gameState.isNight = isNight; gameState.isDawn = isDawn; }; gameState.locations["location_dorm"]->requestNightDayTransition = [this](bool isNight, bool isDawn) { gameState.isNight = isNight; gameState.isDawn = isDawn; };
gameState.locations["location_dorm"]->requestDarklandsTransition = [this]() { return startDarklandsTransition(); }; gameState.locations["location_dorm"]->requestDarklandsTransition = [this]() { return startDarklandsTransition(); };
gameState.locations["location_dorm"]->requestClosePhone = [this]() { menuManager.closePhoneEntirely(); }; gameState.locations["location_dorm"]->requestClosePhone = [this]() { menuManager.closePhoneEntirely(); };
gameState.locations["location_dorm"]->requestReturnToMainMenu = [this]() { menuManager.showMainMenu(); }; //gameState.locations["location_dorm"]->requestReturnToMainMenu = [this]() { menuManager.showMenuAfterGameCompleted(); };
gameState.locations["location_dorm"]->requestSetChatUnread = [this](int idx, bool unread, const std::string& msg) { gameState.locations["location_dorm"]->requestSetChatUnread = [this](int idx, bool unread, const std::string& msg) {
menuManager.setChatUnread(idx, unread, msg); menuManager.setChatUnread(idx, unread, msg);
}; };
@@ -777,8 +771,6 @@ namespace ZL
void Game::createLocationsStep5() void Game::createLocationsStep5()
{ {
rearmDormOneShotCallbacks(); rearmDormOneShotCallbacks();
menuManager.callTaxiFunc = [this]() menuManager.callTaxiFunc = [this]()
@@ -796,7 +788,7 @@ namespace ZL
// Teleport callbacks: destination name and position come from the teleport zone data. // Teleport callbacks: destination name and position come from the teleport zone data.
auto teleportCallback = [this](const std::string& destName, const Eigen::Vector3f& destPos, float destRotY) { auto teleportCallback = [this](const std::string& destName, const Eigen::Vector3f& destPos, float destRotY) {
std::cout << "[TELEPORT] " << " -> " << destName << std::endl; logger() << "[TELEPORT] " << " -> " << destName << std::endl;
auto it = gameState.locations.find(destName); auto it = gameState.locations.find(destName);
if (it == gameState.locations.end()) { if (it == gameState.locations.end()) {
std::cerr << "[TELEPORT] Unknown destination location: " << destName << std::endl; std::cerr << "[TELEPORT] Unknown destination location: " << destName << std::endl;
@@ -903,12 +895,12 @@ namespace ZL
void Game::createShadowMapAndProparateToLocations() void Game::createShadowMapAndProparateToLocations()
{ {
if (shadowMap) { if (shadowMap) {
std::cout << "Shadow map already exists, skipping creation" << std::endl; logger() << "Shadow map already exists, skipping creation" << std::endl;
return; return;
} }
shadowMap = std::make_shared<ShadowMap>(2048, 40.0f, 0.1f, 100.0f); shadowMap = std::make_shared<ShadowMap>(2048, 40.0f, 0.1f, 100.0f);
shadowMap->setLightDirection(Eigen::Vector3f(-0.5f, -1.0f, -0.3f)); shadowMap->setLightDirection(Eigen::Vector3f(-0.5f, -1.0f, -0.3f));
std::cout << "Shadow map initialized" << std::endl; logger() << "Shadow map initialized" << std::endl;
for (auto& [name, loc] : gameState.locations) { for (auto& [name, loc] : gameState.locations) {
loc->shadowMap = shadowMap; loc->shadowMap = shadowMap;
@@ -1186,7 +1178,7 @@ namespace ZL
if (event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_RESIZED) { if (event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_RESIZED) {
const bool wasPortrait = Environment::width < Environment::height; const bool wasPortrait = Environment::width < Environment::height;
//ZL::logger() << "Window resized" << std::endl;
if (Environment::isHighDPIEnabled) { if (Environment::isHighDPIEnabled) {
// Если High DPI включен, запрашиваем реальное физическое разрешение буфера // Если High DPI включен, запрашиваем реальное физическое разрешение буфера
int drawW, drawH; int drawW, drawH;
@@ -1210,10 +1202,13 @@ namespace ZL
menuManager.onOrientationChanged(); menuManager.onOrientationChanged();
} }
std::cout << "Window resized (Render Size): " << Environment::width << "x" << Environment::height << std::endl; logger() << "Window resized (Render Size): " << Environment::width << "x" << Environment::height << std::endl;
//space.clearTextRendererCache(); //space.clearTextRendererCache();
} }
if (event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_RESIZED) {
}
// Suppress all input while portrait overlay is shown // Suppress all input while portrait overlay is shown
//if (mobileIsPortrait) continue; //if (mobileIsPortrait) continue;
@@ -1300,7 +1295,7 @@ namespace ZL
if (Environment::zoom > zoomMax) { if (Environment::zoom > zoomMax) {
Environment::zoom = zoomMax; Environment::zoom = zoomMax;
} }
std::cout << "Current zoom: " << Environment::zoom << std::endl; //logger() << "Current zoom: " << Environment::zoom << std::endl;
// Tutorial step3 → step4: any mouse-wheel scroll counts as "zoom gesture". // Tutorial step3 → step4: any mouse-wheel scroll counts as "zoom gesture".
if (gameState.tutorialStep == TutorialStep::Step3) { if (gameState.tutorialStep == TutorialStep::Step3) {
menuManager.advanceTutorialStep(); menuManager.advanceTutorialStep();
@@ -1309,7 +1304,7 @@ namespace ZL
if (event.type == SDL_KEYDOWN && event.key.repeat == 0) { if (event.type == SDL_KEYDOWN && event.key.repeat == 0) {
switch (event.key.keysym.sym) { switch (event.key.keysym.sym) {
case SDLK_8: /*case SDLK_8:
if (editorMode == EditorMode::InteractiveObjects && currentLocation()) { if (editorMode == EditorMode::InteractiveObjects && currentLocation()) {
currentLocation()->editor.selectInteractiveObject(8); currentLocation()->editor.selectInteractiveObject(8);
} else { } else {
@@ -1345,7 +1340,7 @@ namespace ZL
currentLocation()->editor.selectInteractiveObject(event.key.keysym.sym - SDLK_0); currentLocation()->editor.selectInteractiveObject(event.key.keysym.sym - SDLK_0);
} else { } else {
currentLocation()->switchNavigation(event.key.keysym.sym - SDLK_0); currentLocation()->switchNavigation(event.key.keysym.sym - SDLK_0);
std::cout << "Switched to nav mesh " << (event.key.keysym.sym - SDLK_0) << std::endl; logger() << "Switched to nav mesh " << (event.key.keysym.sym - SDLK_0) << std::endl;
} }
break; break;
case SDLK_f: case SDLK_f:
@@ -1376,50 +1371,50 @@ namespace ZL
const char* modeName = (editorMode == EditorMode::Navigation) ? "Navigation" const char* modeName = (editorMode == EditorMode::Navigation) ? "Navigation"
: (editorMode == EditorMode::InteractiveObjects) ? "InteractiveObjects" : (editorMode == EditorMode::InteractiveObjects) ? "InteractiveObjects"
: "None"; : "None";
std::cout << "[EDITOR] Mode: " << modeName << std::endl; logger() << "[EDITOR] Mode: " << modeName << std::endl;
} }
break; break;
case SDLK_o: case SDLK_o:
x = x + 0.05; x = x + 0.05;
std::cout << "current x: " << x << std::endl; logger() << "current x: " << x << std::endl;
//y = y + 0.002; //y = y + 0.002;
//currentLocation->player->hp = 200; //currentLocation->player->hp = 200;
//currentLocation->npcs[0]->walkSpeed += 0.01f; //currentLocation->npcs[0]->walkSpeed += 0.01f;
//std::cout << "Walk speed: " << currentLocation->npcs[0]->walkSpeed << std::endl; //logger() << "Walk speed: " << currentLocation->npcs[0]->walkSpeed << std::endl;
break; break;
case SDLK_k: case SDLK_k:
x = x - 0.05; x = x - 0.05;
std::cout << "current x: " << x << std::endl; logger() << "current x: " << x << std::endl;
std::cout << "Player pos: " << currentLocation()->player->state.position.transpose() << std::endl; logger() << "Player pos: " << currentLocation()->player->state.position.transpose() << std::endl;
std::cout << "Player rotation: " << currentLocation()->player->state.facingAngle*180.0/M_PI << std::endl; logger() << "Player rotation: " << currentLocation()->player->state.facingAngle*180.0/M_PI << std::endl;
//currentLocation->npcs[0]->walkSpeed -= 0.01f; //currentLocation->npcs[0]->walkSpeed -= 0.01f;
//std::cout << "Walk speed: " << currentLocation->npcs[0]->walkSpeed << std::endl; //logger() << "Walk speed: " << currentLocation->npcs[0]->walkSpeed << std::endl;
break; break;
case SDLK_p: case SDLK_p:
gameState.currentLocationName = "uni_interior"; gameState.currentLocationName = "uni_interior";
currentLocation()->player->state.position = Eigen::Vector3f(-0.0189243, 0, -13.4314); currentLocation()->player->state.position = Eigen::Vector3f(-0.0189243, 0, -13.4314);
currentLocation()->player->setTarget(currentLocation()->player->state.position); currentLocation()->player->setTarget(currentLocation()->player->state.position);
//std::cout << "Switched to location " << ((currentLocation == locations["location1"]) ? "1" : "2") << std::endl; //logger() << "Switched to location " << ((currentLocation == locations["location1"]) ? "1" : "2") << std::endl;
break; break;
case SDLK_l: case SDLK_l:
//x = x - 1; //x = x - 1;
//std::cout << "current x: " << x << std::endl; //logger() << "current x: " << x << std::endl;
std::cout << "Azimuth: " << currentLocation()->state.cameraAzimuth << std::endl; logger() << "Azimuth: " << currentLocation()->state.cameraAzimuth << std::endl;
std::cout << "Inclination: " << currentLocation()->state.cameraInclination << std::endl; logger() << "Inclination: " << currentLocation()->state.cameraInclination << std::endl;
break; break;
case SDLK_c: case SDLK_c:
std::cout << "SLOW-MO activated!" << std::endl; logger() << "SLOW-MO activated!" << std::endl;
activateSlowMoEffect(); activateSlowMoEffect();
break; break;
case SDLK_i: case SDLK_i:
y = y - 1; y = y - 1;
std::cout << "current y: " << y << std::endl; logger() << "current y: " << y << std::endl;
break; break;
case SDLK_b: case SDLK_b:
@@ -1429,12 +1424,6 @@ namespace ZL
break; break;
case SDLK_j: case SDLK_j:
/*
if (editorMode != EditorMode::None && currentLocation()) {
currentLocation()->editor.placeTree();
} else {
menuManager.toggleQuestJournal();
}*/
currentLocation()->editor.placeTree(); currentLocation()->editor.placeTree();
break; break;
@@ -1444,8 +1433,7 @@ namespace ZL
} }
break; break;
case SDLK_RETURN: case SDLK_RETURN:*/
default: default:
break; break;
} }
@@ -1464,16 +1452,6 @@ namespace ZL
//menuManager.uiManager.onKeyPress((unsigned char)event.text.text[0]); //menuManager.uiManager.onKeyPress((unsigned char)event.text.text[0]);
} }
} }
if (event.type == SDL_KEYUP) {
if (event.key.keysym.sym == SDLK_r) {
std::cout << "Camera position: x=" << x << " y=" << y << " z=" << z << std::endl;
}
}
if (event.type == SDL_KEYUP) {
}
} }
// Drain one loading step per frame so the window/tab keeps pumping events // Drain one loading step per frame so the window/tab keeps pumping events
// and repainting between steps instead of freezing for the whole load. // and repainting between steps instead of freezing for the whole load.
+2
View File
@@ -95,10 +95,12 @@ void GameState::load(const nlohmann::json& in)
money = in.value("money", money); money = in.value("money", money);
// --- Script globals --- // --- Script globals ---
globalInts.clear();
if (in.contains("globalInts") && in["globalInts"].is_object()) { if (in.contains("globalInts") && in["globalInts"].is_object()) {
for (const auto& [k, v] : in["globalInts"].items()) for (const auto& [k, v] : in["globalInts"].items())
globalInts[k] = v.get<int>(); globalInts[k] = v.get<int>();
} }
globalFloats.clear();
if (in.contains("globalFloats") && in["globalFloats"].is_object()) { if (in.contains("globalFloats") && in["globalFloats"].is_object()) {
for (const auto& [k, v] : in["globalFloats"].items()) for (const auto& [k, v] : in["globalFloats"].items())
globalFloats[k] = v.get<float>(); globalFloats[k] = v.get<float>();
+1 -1
View File
@@ -67,7 +67,7 @@ void loadLanguageSetting() {
// Если файла настроек нет (content пустой), определяем язык системы и выходим // Если файла настроек нет (content пустой), определяем язык системы и выходим
if (content.empty()) { if (content.empty()) {
g_currentLanguage = detectSystemLanguage(); g_currentLanguage = detectSystemLanguage();
std::cout << "[settings] No settings.json found. System language detected: " logger() << "[settings] No settings.json found. System language detected: "
<< languageToCode(g_currentLanguage) << std::endl; << languageToCode(g_currentLanguage) << std::endl;
return; return;
} }
+60 -51
View File
@@ -148,7 +148,7 @@ namespace ZL
player->setTarget(params.playerPosition); player->setTarget(params.playerPosition);
player->setupHitSparks(sparkTexture); player->setupHitSparks(sparkTexture);
player->audioPlayer = audioPlayer; player->audioPlayer = audioPlayer;
std::cout << "Load resurces step 9" << std::endl; logger() << "Load resurces step 9" << std::endl;
SDL_PumpEvents(); SDL_PumpEvents();
// Load NPCs from JSON. Aggressive (canAttack) NPCs need a player target // Load NPCs from JSON. Aggressive (canAttack) NPCs need a player target
@@ -287,7 +287,7 @@ namespace ZL
teleportZones.push_back(std::move(tz)); teleportZones.push_back(std::move(tz));
} }
std::cout << "[TELEPORT] Loaded " << teleportZones.size() << " teleport(s) from " << jsonPath << std::endl; logger() << "[TELEPORT] Loaded " << teleportZones.size() << " teleport(s) from " << jsonPath << std::endl;
} }
@@ -338,7 +338,7 @@ namespace ZL
} }
std::cout << "[TRIGGER] Loaded " << triggerZones.size() << " trigger zone(s) from " << jsonPath << std::endl; logger() << "[TRIGGER] Loaded " << triggerZones.size() << " trigger zone(s) from " << jsonPath << std::endl;
} }
void Location::loadPointLights(const std::string& jsonPath, const char* zipFile) void Location::loadPointLights(const std::string& jsonPath, const char* zipFile)
@@ -400,7 +400,7 @@ namespace ZL
pointLights.push_back(std::move(pl)); pointLights.push_back(std::move(pl));
} }
std::cout << "[LIGHTS] Loaded " << pointLights.size() << " light(s) from " << jsonPath << std::endl; logger() << "[LIGHTS] Loaded " << pointLights.size() << " light(s) from " << jsonPath << std::endl;
} }
void Location::updateTriggerZones(const Eigen::Vector3f& playerPos) void Location::updateTriggerZones(const Eigen::Vector3f& playerPos)
@@ -500,28 +500,28 @@ namespace ZL
if (editorMode == EditorMode::Navigation) { if (editorMode == EditorMode::Navigation) {
editor.buildNavMeshes(); editor.buildNavMeshes();
} }
std::cout << "[NAV] Switched to navigation map " << index << "\n"; logger() << "[NAV] Switched to navigation map " << index << "\n";
return true; return true;
} }
InteractiveObject* Location::raycastInteractiveObjects(const Eigen::Vector3f& rayOrigin, const Eigen::Vector3f& rayDir) { InteractiveObject* Location::raycastInteractiveObjects(const Eigen::Vector3f& rayOrigin, const Eigen::Vector3f& rayDir) {
if (interactiveObjects.empty()) { if (interactiveObjects.empty()) {
//std::cout << "[RAYCAST] No interactive objects to check" << std::endl; //logger() << "[RAYCAST] No interactive objects to check" << std::endl;
return nullptr; return nullptr;
} }
//std::cout << "[RAYCAST] Starting raycast with " << interactiveObjects.size() << " objects" << std::endl; //logger() << "[RAYCAST] Starting raycast with " << interactiveObjects.size() << " objects" << std::endl;
//std::cout << "[RAYCAST] Ray origin: (" << rayOrigin.x() << ", " << rayOrigin.y() << ", " << rayOrigin.z() << ")" << std::endl; //logger() << "[RAYCAST] Ray origin: (" << rayOrigin.x() << ", " << rayOrigin.y() << ", " << rayOrigin.z() << ")" << std::endl;
//std::cout << "[RAYCAST] Ray dir: (" << rayDir.x() << ", " << rayDir.y() << ", " << rayDir.z() << ")" << std::endl; //logger() << "[RAYCAST] Ray dir: (" << rayDir.x() << ", " << rayDir.y() << ", " << rayDir.z() << ")" << std::endl;
float closestDistance = FLT_MAX; float closestDistance = FLT_MAX;
InteractiveObject* closestObject = nullptr; InteractiveObject* closestObject = nullptr;
for (auto& intObj : interactiveObjects) { for (auto& intObj : interactiveObjects) {
//std::cout << "[RAYCAST] Checking object: " << intObj.loadedObject.name << " (active: " << intObj.isActive << ")" << std::endl; //logger() << "[RAYCAST] Checking object: " << intObj.loadedObject.name << " (active: " << intObj.isActive << ")" << std::endl;
if (!intObj.state.isActive || intObj.state.isAnimating) { if (!intObj.state.isActive || intObj.state.isAnimating) {
//std::cout << "[RAYCAST] -> Object inactive or animating, skipping" << std::endl; //logger() << "[RAYCAST] -> Object inactive or animating, skipping" << std::endl;
continue; continue;
} }
@@ -531,7 +531,7 @@ namespace ZL
if (!intObj.loadedObject.texture) continue; if (!intObj.loadedObject.texture) continue;
} }
//std::cout << "[RAYCAST] Position: (" << intObj.position.x() << ", " << intObj.position.y() << ", " //logger() << "[RAYCAST] Position: (" << intObj.position.x() << ", " << intObj.position.y() << ", "
// << intObj.position.z() << "), Radius: " << intObj.interactionRadius << std::endl; // << intObj.position.z() << "), Radius: " << intObj.interactionRadius << std::endl;
const bool hasBox = !(intObj.state.boundsMin.isZero() && intObj.state.boundsMax.isZero()); const bool hasBox = !(intObj.state.boundsMin.isZero() && intObj.state.boundsMax.isZero());
@@ -581,10 +581,10 @@ namespace ZL
} }
/* /*
if (closestObject) { if (closestObject) {
std::cout << "[RAYCAST] *** RAYCAST SUCCESS: Found object " << closestObject->loadedObject.name << " ***" << std::endl; logger() << "[RAYCAST] *** RAYCAST SUCCESS: Found object " << closestObject->loadedObject.name << " ***" << std::endl;
} }
else { else {
std::cout << "[RAYCAST] No objects hit" << std::endl; logger() << "[RAYCAST] No objects hit" << std::endl;
} }
*/ */
return closestObject; return closestObject;
@@ -600,11 +600,11 @@ namespace ZL
Character* closestNpc = nullptr; Character* closestNpc = nullptr;
float closestDist = maxDistance; float closestDist = maxDistance;
//std::cout << "[RAYCAST_NPC] Starting raycast with " << npcs.size() << " npcs" << std::endl; //logger() << "[RAYCAST_NPC] Starting raycast with " << npcs.size() << " npcs" << std::endl;
for (auto& npc : npcs) { for (auto& npc : npcs) {
if (npc->getHp() <= 0.f) { if (npc->getHp() <= 0.f) {
//std::cout << "[RAYCAST_NPC] " << npc->state.npcId << " is dead, skipping" << std::endl; //logger() << "[RAYCAST_NPC] " << npc->state.npcId << " is dead, skipping" << std::endl;
continue; continue;
} }
if (!npc->state.enabled) continue; if (!npc->state.enabled) continue;
@@ -640,7 +640,7 @@ namespace ZL
if (entryT > exitT) continue; if (entryT > exitT) continue;
//std::cout << "[RAYCAST_NPC] " << npc->npcId << " hit at t=" << entryT << std::endl; //logger() << "[RAYCAST_NPC] " << npc->npcId << " hit at t=" << entryT << std::endl;
if (entryT < closestDist) { if (entryT < closestDist) {
closestDist = entryT; closestDist = entryT;
@@ -649,10 +649,10 @@ namespace ZL
} }
if (closestNpc) { if (closestNpc) {
//std::cout << "[RAYCAST_NPC] HIT: " << closestNpc->npcId << std::endl; //logger() << "[RAYCAST_NPC] HIT: " << closestNpc->npcId << std::endl;
} }
else { else {
//std::cout << "[RAYCAST_NPC] No NPC hit" << std::endl; //logger() << "[RAYCAST_NPC] No NPC hit" << std::endl;
} }
return closestNpc; return closestNpc;
@@ -1603,7 +1603,7 @@ namespace ZL
if (npc->state.enabled && npc->state.canAttack && (!npc->state.pathWaypoints.empty() || npc->state.battle_state != 0) && npc->getHp() > 0.f) if (npc->state.enabled && npc->state.canAttack && (!npc->state.pathWaypoints.empty() || npc->state.battle_state != 0) && npc->getHp() > 0.f)
{ {
//std::cout << "[NPC] NPC '" << npc->state.npcId << "' is attacking target index " << npc->state.attackTargetIndex << std::endl; //logger() << "[NPC] NPC '" << npc->state.npcId << "' is attacking target index " << npc->state.attackTargetIndex << std::endl;
npcAttacking = true; npcAttacking = true;
} }
} }
@@ -1640,17 +1640,17 @@ namespace ZL
// If player is close enough to pick up the item // If player is close enough to pick up the item
if (distToObject <= obj->state.approachRadius) { if (distToObject <= obj->state.approachRadius) {
std::cout << "[PICKUP] Player reached object! Distance: " << distToObject << std::endl; logger() << "[PICKUP] Player reached object! Distance: " << distToObject << std::endl;
std::cout << "[PICKUP] Calling Lua callback for: " << obj->loadedObject.name << std::endl; logger() << "[PICKUP] Calling Lua callback for: " << obj->loadedObject.name << std::endl;
// Call custom activate function if specified, otherwise use fallback // Call custom activate function if specified, otherwise use fallback
try { try {
if (!obj->state.activateFunctionName.empty()) { if (!obj->state.activateFunctionName.empty()) {
std::cout << "[PICKUP] Using custom function: " << obj->state.activateFunctionName << std::endl; logger() << "[PICKUP] Using custom function: " << obj->state.activateFunctionName << std::endl;
scriptEngine.callActivateFunction(obj->state.activateFunctionName); scriptEngine.callActivateFunction(obj->state.activateFunctionName);
} }
else { else {
std::cout << "[PICKUP] Using fallback callback" << std::endl; logger() << "[PICKUP] Using fallback callback" << std::endl;
scriptEngine.callItemPickupCallback(obj->loadedObject.name); scriptEngine.callItemPickupCallback(obj->loadedObject.name);
} }
} }
@@ -1666,7 +1666,7 @@ namespace ZL
if (state.targetInteractNpcIndex >= 0 && player) { if (state.targetInteractNpcIndex >= 0 && player) {
float distToNpc = (player->state.position - npcs[state.targetInteractNpcIndex]->state.position).norm(); float distToNpc = (player->state.position - npcs[state.targetInteractNpcIndex]->state.position).norm();
if (distToNpc <= NPC_TALK_DISTANCE) { if (distToNpc <= NPC_TALK_DISTANCE) {
std::cout << "[NPC] Player reached NPC index " << state.targetInteractNpcIndex logger() << "[NPC] Player reached NPC index " << state.targetInteractNpcIndex
<< " (distance " << distToNpc << "); firing on_npc_interact" << std::endl; << " (distance " << distToNpc << "); firing on_npc_interact" << std::endl;
// Stop the player at the talk distance and have the NPC turn to face them. // Stop the player at the talk distance and have the NPC turn to face them.
player->setTarget(player->state.position); player->setTarget(player->state.position);
@@ -1686,7 +1686,7 @@ namespace ZL
if (auto* tz = getTargetTeleportZone(); tz && player) { if (auto* tz = getTargetTeleportZone(); tz && player) {
float dist = (player->state.position - tz->position).norm(); float dist = (player->state.position - tz->position).norm();
if (dist <= tz->radius) { if (dist <= tz->radius) {
std::cout << "[TELEPORT] Player reached teleport zone '" << tz->id << "'" << std::endl; logger() << "[TELEPORT] Player reached teleport zone '" << tz->id << "'" << std::endl;
if (onTeleport) onTeleport(tz->destinationLocation, tz->destinationPosition, tz->destinationRotationY); if (onTeleport) onTeleport(tz->destinationLocation, tz->destinationPosition, tz->destinationRotationY);
state.targetTeleportZoneIndex = -1; state.targetTeleportZoneIndex = -1;
return; return;
@@ -1710,7 +1710,7 @@ namespace ZL
const bool isInside = (dist <= autoR); const bool isInside = (dist <= autoR);
if (isInside && !tz.automaticPlayerInside) { if (isInside && !tz.automaticPlayerInside) {
tz.automaticPlayerInside = true; tz.automaticPlayerInside = true;
std::cout << "[TELEPORT] Auto-teleport triggered by zone '" << tz.id << "'" << std::endl; logger() << "[TELEPORT] Auto-teleport triggered by zone '" << tz.id << "'" << std::endl;
if (onTeleport) onTeleport(tz.destinationLocation, tz.destinationPosition, tz.destinationRotationY); if (onTeleport) onTeleport(tz.destinationLocation, tz.destinationPosition, tz.destinationRotationY);
return; return;
} else if (!isInside) { } else if (!isInside) {
@@ -1743,10 +1743,10 @@ namespace ZL
toTarget.y() = 0.f; toTarget.y() = 0.f;
if (toTarget.norm() < car.waypointReachRadius) { if (toTarget.norm() < car.waypointReachRadius) {
car.currentWaypoint = car.currentWaypoint + 1; car.currentWaypoint = car.currentWaypoint + 1;
std::cout << "Waypoint current: " << car.currentWaypoint << std::endl; logger() << "Waypoint current: " << car.currentWaypoint << std::endl;
if (car.currentWaypoint == car.waypoints.size()) if (car.currentWaypoint == car.waypoints.size())
{ {
std::cout << "waypointsOver" << std::endl; logger() << "waypointsOver" << std::endl;
waypointsOver = true; waypointsOver = true;
} }
} }
@@ -1895,17 +1895,17 @@ namespace ZL
return; return;
} }
std::cout << "[CLICK] Camera position: (" << camPos.x() << ", " << camPos.y() << ", " << camPos.z() << ")" << std::endl; //logger() << "[CLICK] Camera position: (" << camPos.x() << ", " << camPos.y() << ", " << camPos.z() << ")" << std::endl;
std::cout << "[CLICK] Ray direction: (" << rayDir.x() << ", " << rayDir.y() << ", " << rayDir.z() << ")" << std::endl; //logger() << "[CLICK] Ray direction: (" << rayDir.x() << ", " << rayDir.y() << ", " << rayDir.z() << ")" << std::endl;
// First check if we clicked on interactive object // First check if we clicked on interactive object
InteractiveObject* clickedObject = raycastInteractiveObjects(camPos, rayDir); InteractiveObject* clickedObject = raycastInteractiveObjects(camPos, rayDir);
if (clickedObject && player && clickedObject->state.isActive) { if (clickedObject && player && clickedObject->state.isActive) {
std::cout << "[CLICK] *** SUCCESS: Clicked on interactive object: " << clickedObject->loadedObject.name << " ***" << std::endl; //logger() << "[CLICK] *** SUCCESS: Clicked on interactive object: " << clickedObject->loadedObject.name << " ***" << std::endl;
std::cout << "[CLICK] Object position: (" << clickedObject->state.position.x() << ", " //logger() << "[CLICK] Object position: (" << clickedObject->state.position.x() << ", "
<< clickedObject->state.position.y() << ", " << clickedObject->state.position.z() << ")" << std::endl; // << clickedObject->state.position.y() << ", " << clickedObject->state.position.z() << ")" << std::endl;
std::cout << "[CLICK] Player position: (" << player->state.position.x() << ", " //logger() << "[CLICK] Player position: (" << player->state.position.x() << ", "
<< player->state.position.y() << ", " << player->state.position.z() << ")" << std::endl; // << player->state.position.y() << ", " << player->state.position.z() << ")" << std::endl;
state.targetInteractiveObjectIndex = findInteractiveObjectIndex(clickedObject); state.targetInteractiveObjectIndex = findInteractiveObjectIndex(clickedObject);
state.targetInteractNpcIndex = -1; state.targetInteractNpcIndex = -1;
@@ -1914,11 +1914,11 @@ namespace ZL
? clickedObject->state.interactionPosition ? clickedObject->state.interactionPosition
: clickedObject->state.position); : clickedObject->state.position);
player->state.attackTargetIndex = CharacterState::kNoTarget; player->state.attackTargetIndex = CharacterState::kNoTarget;
std::cout << "[CLICK] Player moving to object..." << std::endl; //logger() << "[CLICK] Player moving to object..." << std::endl;
} }
else { else {
//std::cout << "Raycast npc step 1" << std::endl; //logger() << "Raycast npc step 1" << std::endl;
// Check if we clicked on an NPC // Check if we clicked on an NPC
Character* clickedNpc = raycastNpcs(camPos, rayDir); Character* clickedNpc = raycastNpcs(camPos, rayDir);
if (clickedNpc && player) { if (clickedNpc && player) {
@@ -1931,25 +1931,25 @@ namespace ZL
} }
} }
if (npcIndex != -1) { if (npcIndex != -1) {
//std::cout << "Raycast npc step 2" << std::endl; //logger() << "Raycast npc step 2" << std::endl;
state.targetInteractiveObjectIndex = -1; state.targetInteractiveObjectIndex = -1;
state.targetTeleportZoneIndex = -1; state.targetTeleportZoneIndex = -1;
if (clickedNpc->state.canAttack) { if (clickedNpc->state.canAttack) {
//std::cout << "Raycast npc step 3" << std::endl; //logger() << "Raycast npc step 3" << std::endl;
//std::cout << "player->state.attackTargetIndex=" << player->state.attackTargetIndex << std::endl; //logger() << "player->state.attackTargetIndex=" << player->state.attackTargetIndex << std::endl;
//std::cout << "npcIndex=" << npcIndex << std::endl; //logger() << "npcIndex=" << npcIndex << std::endl;
// Hostile NPC: combat logic walks the player in via attackTargetIndex. // Hostile NPC: combat logic walks the player in via attackTargetIndex.
if (lastPlayerAttackTarget == npcIndex) if (lastPlayerAttackTarget == npcIndex)
{ {
//std::cout << "Raycast npc step 4" << std::endl; //logger() << "Raycast npc step 4" << std::endl;
player->tryAttackCombo(); player->tryAttackCombo();
} }
player->state.attackTargetIndex = npcIndex; player->state.attackTargetIndex = npcIndex;
state.targetInteractNpcIndex = -1; state.targetInteractNpcIndex = -1;
if (distance <= clickedNpc->state.interactionRadius) { if (distance <= clickedNpc->state.interactionRadius) {
std::cout << "[CLICK] Hostile NPC " << npcIndex << " in range; firing on_npc_interact" << std::endl; //logger() << "[CLICK] Hostile NPC " << npcIndex << " in range; firing on_npc_interact" << std::endl;
scriptEngine.callNpcInteractCallback(npcIndex); scriptEngine.callNpcInteractCallback(npcIndex);
} }
} }
@@ -1959,15 +1959,15 @@ namespace ZL
if (distance <= NPC_TALK_DISTANCE) { if (distance <= NPC_TALK_DISTANCE) {
// Already in talk range — fire immediately, stop, and face the player. // Already in talk range — fire immediately, stop, and face the player.
std::cout << "[CLICK] *** SUCCESS: Clicked on NPC index: " << npcIndex << " (in range) ***" << std::endl; //logger() << "[CLICK] *** SUCCESS: Clicked on NPC index: " << npcIndex << " (in range) ***" << std::endl;
player->setTarget(player->state.position); player->setTarget(player->state.position);
npcs[npcIndex]->state.faceTargetIndex = CharacterState::kPlayerIndex; npcs[npcIndex]->state.faceTargetIndex = CharacterState::kPlayerIndex;
scriptEngine.callNpcInteractCallback(npcIndex); scriptEngine.callNpcInteractCallback(npcIndex);
state.targetInteractNpcIndex = -1; state.targetInteractNpcIndex = -1;
} }
else { else {
std::cout << "[CLICK] NPC " << npcIndex << " out of talk range (distance " << distance //logger() << "[CLICK] NPC " << npcIndex << " out of talk range (distance " << distance
<< " > " << NPC_TALK_DISTANCE << "); walking to NPC..." << std::endl; // << " > " << NPC_TALK_DISTANCE << "); walking to NPC..." << std::endl;
player->setTarget(clickedNpc->state.position); player->setTarget(clickedNpc->state.position);
state.targetInteractNpcIndex = npcIndex; state.targetInteractNpcIndex = npcIndex;
} }
@@ -1979,7 +1979,7 @@ namespace ZL
// Unproject click to ground plane for walk target / teleport detection // Unproject click to ground plane for walk target / teleport detection
float t = -camPos.y() / rayDir.y(); float t = -camPos.y() / rayDir.y();
Eigen::Vector3f hit = camPos + rayDir * t; Eigen::Vector3f hit = camPos + rayDir * t;
std::cout << "[CLICK] Clicked on ground at: (" << hit.x() << ", " << hit.z() << ")" << std::endl; //logger() << "[CLICK] Clicked on ground at: (" << hit.x() << ", " << hit.z() << ")" << std::endl;
TeleportZone* clickedTeleport = nullptr; TeleportZone* clickedTeleport = nullptr;
for (auto& tz : teleportZones) { for (auto& tz : teleportZones) {
@@ -1989,7 +1989,7 @@ namespace ZL
} }
if (clickedTeleport) { if (clickedTeleport) {
std::cout << "[CLICK] Clicked teleport zone '" << clickedTeleport->id << "'" << std::endl; //logger() << "[CLICK] Clicked teleport zone '" << clickedTeleport->id << "'" << std::endl;
state.targetTeleportZoneIndex = findTeleportZoneIndex(clickedTeleport); state.targetTeleportZoneIndex = findTeleportZoneIndex(clickedTeleport);
state.targetInteractiveObjectIndex = -1; state.targetInteractiveObjectIndex = -1;
state.targetInteractNpcIndex = -1; state.targetInteractNpcIndex = -1;
@@ -2004,10 +2004,10 @@ namespace ZL
} }
} }
else { else {
std::cout << "[CLICK] No valid target found" << std::endl; //logger() << "[CLICK] No valid target found" << std::endl;
} }
} }
std::cout << "========================================\n" << std::endl; //logger() << "========================================\n" << std::endl;
} }
void Location::handleUp(int64_t fingerId, int mx, int my) void Location::handleUp(int64_t fingerId, int mx, int my)
{ {
@@ -2071,6 +2071,15 @@ namespace ZL
{ {
state.taxiCar.waypoints = state.taxiCarWaypoints; state.taxiCar.waypoints = state.taxiCarWaypoints;
state.taxiCar.mode = NpcCar::Mode::FOLLOW_WAYPOINTS; state.taxiCar.mode = NpcCar::Mode::FOLLOW_WAYPOINTS;
// For the dorm - disable trigger zone because the player already ordered taxi
for (auto& tz : triggerZones) {
if (tz.id == "taxi_zone001") {
tz.enabled = false;
break;
}
}
} }
// ---- Save / Load ---- // ---- Save / Load ----
+18 -18
View File
@@ -91,15 +91,15 @@ namespace ZL
if (dx * dx + dz * dz <= removeRadius * removeRadius) { if (dx * dx + dz * dz <= removeRadius * removeRadius) {
navigationEditorPoints.erase(it); navigationEditorPoints.erase(it);
rebuildPointsMesh(); rebuildPointsMesh();
std::cout << "[NAV_EDITOR] Removed point, " << navigationEditorPoints.size() << " remaining\n"; logger() << "[NAV_EDITOR] Removed point, " << navigationEditorPoints.size() << " remaining\n";
return; return;
} }
} }
std::cout << "[NAV_EDITOR] No point found within " << removeRadius << " units of click\n"; logger() << "[NAV_EDITOR] No point found within " << removeRadius << " units of click\n";
} else { } else {
navigationEditorPoints.push_back(hit); navigationEditorPoints.push_back(hit);
rebuildPointsMesh(); rebuildPointsMesh();
std::cout << "[NAV_EDITOR] Added point (" << hit.x() << ", " << hit.z() logger() << "[NAV_EDITOR] Added point (" << hit.x() << ", " << hit.z()
<< "), total: " << navigationEditorPoints.size() << "\n"; << "), total: " << navigationEditorPoints.size() << "\n";
} }
} }
@@ -107,7 +107,7 @@ namespace ZL
void LocationEditor::handleRightClick() void LocationEditor::handleRightClick()
{ {
if (navigationEditorPoints.size() < 3) { if (navigationEditorPoints.size() < 3) {
std::cout << "[NAV_EDITOR] Need at least 3 points to form a polygon (have " logger() << "[NAV_EDITOR] Need at least 3 points to form a polygon (have "
<< navigationEditorPoints.size() << "); clearing\n"; << navigationEditorPoints.size() << "); clearing\n";
navigationEditorPoints.clear(); navigationEditorPoints.clear();
rebuildPointsMesh(); rebuildPointsMesh();
@@ -121,7 +121,7 @@ namespace ZL
} }
if (loc.navigation) loc.navigation->addObstaclePolygon(poly); if (loc.navigation) loc.navigation->addObstaclePolygon(poly);
std::cout << "[NAV_EDITOR] Added obstacle '" << poly.name << "' with " logger() << "[NAV_EDITOR] Added obstacle '" << poly.name << "' with "
<< poly.polygon.size() << " vertices\n"; << poly.polygon.size() << " vertices\n";
{ {
@@ -150,12 +150,12 @@ namespace ZL
if (!loc.navigation) return; if (!loc.navigation) return;
if (loc.navigation->saveConfig(baseName + ".json")) if (loc.navigation->saveConfig(baseName + ".json"))
std::cout << "[NAV_EDITOR] Saved config to: " << baseName << ".json\n"; logger() << "[NAV_EDITOR] Saved config to: " << baseName << ".json\n";
else else
std::cerr << "[NAV_EDITOR] Failed to save config to: " << baseName << ".json\n"; std::cerr << "[NAV_EDITOR] Failed to save config to: " << baseName << ".json\n";
if (loc.navigation->saveGrid(baseName + ".txt")) if (loc.navigation->saveGrid(baseName + ".txt"))
std::cout << "[NAV_EDITOR] Saved grid to: " << baseName << ".txt\n"; logger() << "[NAV_EDITOR] Saved grid to: " << baseName << ".txt\n";
else else
std::cerr << "[NAV_EDITOR] Failed to save grid to: " << baseName << ".txt\n"; std::cerr << "[NAV_EDITOR] Failed to save grid to: " << baseName << ".txt\n";
} }
@@ -163,7 +163,7 @@ namespace ZL
void LocationEditor::reload() void LocationEditor::reload()
{ {
loc.setupNavigation(loc.navigationMapPaths); loc.setupNavigation(loc.navigationMapPaths);
std::cout << "[NAV_EDITOR] Reloaded navigation maps (" << loc.navigationMapPaths.size() << " maps)\n"; logger() << "[NAV_EDITOR] Reloaded navigation maps (" << loc.navigationMapPaths.size() << " maps)\n";
} }
static VertexDataStruct CreateBoundsBoxMesh(const Eigen::Vector3f corners[8], const Eigen::Vector3f& color) static VertexDataStruct CreateBoundsBoxMesh(const Eigen::Vector3f corners[8], const Eigen::Vector3f& color)
@@ -290,14 +290,14 @@ namespace ZL
void LocationEditor::selectInteractiveObject(int index) void LocationEditor::selectInteractiveObject(int index)
{ {
if (index < 0 || index >= static_cast<int>(loc.interactiveObjects.size())) { if (index < 0 || index >= static_cast<int>(loc.interactiveObjects.size())) {
std::cout << "[IO_EDITOR] Index " << index << " out of range (" logger() << "[IO_EDITOR] Index " << index << " out of range ("
<< loc.interactiveObjects.size() << " objects)\n"; << loc.interactiveObjects.size() << " objects)\n";
return; return;
} }
selectedInteractiveObjectIndex = index; selectedInteractiveObjectIndex = index;
boundsClickCount = 0; boundsClickCount = 0;
buildInteractiveObjectBoundsMeshes(); buildInteractiveObjectBoundsMeshes();
std::cout << "[IO_EDITOR] Selected object " << index logger() << "[IO_EDITOR] Selected object " << index
<< " (" << loc.interactiveObjects[index].loadedObject.name << ")\n"; << " (" << loc.interactiveObjects[index].loadedObject.name << ")\n";
} }
@@ -305,7 +305,7 @@ namespace ZL
{ {
if (selectedInteractiveObjectIndex < 0 || if (selectedInteractiveObjectIndex < 0 ||
selectedInteractiveObjectIndex >= static_cast<int>(loc.interactiveObjects.size())) { selectedInteractiveObjectIndex >= static_cast<int>(loc.interactiveObjects.size())) {
std::cout << "[IO_EDITOR] No valid object selected\n"; logger() << "[IO_EDITOR] No valid object selected\n";
return; return;
} }
@@ -316,7 +316,7 @@ namespace ZL
target.state.hasInteractionPosition = true; target.state.hasInteractionPosition = true;
boundsClickCount = 0; // cancel any in-progress bounds placement boundsClickCount = 0; // cancel any in-progress bounds placement
buildInteractiveObjectBoundsMeshes(); buildInteractiveObjectBoundsMeshes();
std::cout << "[IO_EDITOR] Interaction position set on object " << selectedInteractiveObjectIndex logger() << "[IO_EDITOR] Interaction position set on object " << selectedInteractiveObjectIndex
<< " (" << target.loadedObject.name << ")" << " (" << target.loadedObject.name << ")"
<< " at (" << worldHit.x() << ", " << worldHit.z() << ")\n"; << " at (" << worldHit.x() << ", " << worldHit.z() << ")\n";
return; return;
@@ -343,7 +343,7 @@ namespace ZL
if (boundsClickCount == 0) { if (boundsClickCount == 0) {
boundsClickA = worldHit; boundsClickA = worldHit;
boundsClickCount = 1; boundsClickCount = 1;
std::cout << "[IO_EDITOR] First corner placed at world (" logger() << "[IO_EDITOR] First corner placed at world ("
<< worldHit.x() << ", " << worldHit.z() << ") — click again for second corner\n"; << worldHit.x() << ", " << worldHit.z() << ") — click again for second corner\n";
} else { } else {
const Eigen::Vector3f localA = worldToLocal(boundsClickA); const Eigen::Vector3f localA = worldToLocal(boundsClickA);
@@ -358,7 +358,7 @@ namespace ZL
boundsClickCount = 0; boundsClickCount = 0;
buildInteractiveObjectBoundsMeshes(); buildInteractiveObjectBoundsMeshes();
std::cout << "[IO_EDITOR] Bounds set on object " << selectedInteractiveObjectIndex logger() << "[IO_EDITOR] Bounds set on object " << selectedInteractiveObjectIndex
<< " (" << target.loadedObject.name << ")" << " (" << target.loadedObject.name << ")"
<< " min=(" << target.state.boundsMin.x() << ", " << target.state.boundsMin.z() << ")" << " min=(" << target.state.boundsMin.x() << ", " << target.state.boundsMin.z() << ")"
<< " max=(" << target.state.boundsMax.x() << ", " << target.state.boundsMax.z() << ")\n"; << " max=(" << target.state.boundsMax.x() << ", " << target.state.boundsMax.z() << ")\n";
@@ -419,7 +419,7 @@ namespace ZL
std::ofstream out(filename); std::ofstream out(filename);
if (out.is_open()) { if (out.is_open()) {
out << j.dump(4); out << j.dump(4);
std::cout << "[IO_EDITOR] Saved " << loc.interactiveObjects.size() logger() << "[IO_EDITOR] Saved " << loc.interactiveObjects.size()
<< " interactive object(s) to " << filename << "\n"; << " interactive object(s) to " << filename << "\n";
} else { } else {
std::cerr << "[IO_EDITOR] Failed to open " << filename << " for writing\n"; std::cerr << "[IO_EDITOR] Failed to open " << filename << " for writing\n";
@@ -485,7 +485,7 @@ namespace ZL
loc.gameObjects[data2.name] = std::move(obj2); loc.gameObjects[data2.name] = std::move(obj2);
editorPlacedObjects.push_back(data2); editorPlacedObjects.push_back(data2);
std::cout << "[GAME_EDITOR] Placed '" << data.name << "' at (" logger() << "[GAME_EDITOR] Placed '" << data.name << "' at ("
<< data.positionX << ", " << data.positionZ << data.positionX << ", " << data.positionZ
<< ") scale=" << data.scale << " rotY=" << data.rotationY << "\n"; << ") scale=" << data.scale << " rotY=" << data.rotationY << "\n";
} }
@@ -493,7 +493,7 @@ namespace ZL
void LocationEditor::saveObjects() void LocationEditor::saveObjects()
{ {
if (editorPlacedObjects.empty()) { if (editorPlacedObjects.empty()) {
std::cout << "[GAME_EDITOR] No editor-placed objects to save\n"; logger() << "[GAME_EDITOR] No editor-placed objects to save\n";
return; return;
} }
@@ -527,7 +527,7 @@ namespace ZL
/*std::ofstream out(filename); /*std::ofstream out(filename);
if (out.is_open()) { if (out.is_open()) {
out << j.dump(4); out << j.dump(4);
std::cout << "[GAME_EDITOR] Saved " << editorPlacedObjects.size() logger() << "[GAME_EDITOR] Saved " << editorPlacedObjects.size()
<< " object(s) to " << filename << "\n"; << " object(s) to " << filename << "\n";
} else { } else {
std::cerr << "[GAME_EDITOR] Failed to open " << filename << " for writing\n"; std::cerr << "[GAME_EDITOR] Failed to open " << filename << " for writing\n";
+127 -9
View File
@@ -16,6 +16,9 @@ namespace ZL {
static const std::string EMPTY_LANGUAGE_RU = u8"(пусто)"; static const std::string EMPTY_LANGUAGE_RU = u8"(пусто)";
static const std::string EMPTY_LANGUAGE_EN = "(empty)"; static const std::string EMPTY_LANGUAGE_EN = "(empty)";
static const std::string BACK_TO_MAIN_MENU_RU = u8"Вернуться в меню";
static const std::string BACK_TO_MAIN_MENU_EN = "Back to Main Menu";
static int questStatusPriority(Quest::QuestStatus status) { static int questStatusPriority(Quest::QuestStatus status) {
switch (status) { switch (status) {
@@ -215,6 +218,7 @@ namespace ZL {
portraitQuestJournalItemRoot = loadLocalizedUi("resources/w/ui/portrait_screen_journal_item.json", zipFile); portraitQuestJournalItemRoot = loadLocalizedUi("resources/w/ui/portrait_screen_journal_item.json", zipFile);
mainMenuRoot = loadLocalizedUi("resources/w/ui/screen_main_menu.json", zipFile); mainMenuRoot = loadLocalizedUi("resources/w/ui/screen_main_menu.json", zipFile);
aboutScreenRoot = loadLocalizedUi("resources/w/ui/screen_about.json", zipFile); aboutScreenRoot = loadLocalizedUi("resources/w/ui/screen_about.json", zipFile);
creditsScreenRoot = loadLocalizedUi("resources/w/ui/screen_credits.json", zipFile);
settingsScreenRoot = loadLocalizedUi("resources/w/ui/screen_settings.json", zipFile); settingsScreenRoot = loadLocalizedUi("resources/w/ui/screen_settings.json", zipFile);
settingsGraphicsScreenRoot = loadLocalizedUi("resources/w/ui/screen_settings_graphics.json", zipFile); settingsGraphicsScreenRoot = loadLocalizedUi("resources/w/ui/screen_settings_graphics.json", zipFile);
languageScreenRoot = loadLocalizedUi("resources/w/ui/screen_language.json", zipFile); languageScreenRoot = loadLocalizedUi("resources/w/ui/screen_language.json", zipFile);
@@ -344,6 +348,8 @@ namespace ZL {
topUiManager.clearMenuStack(); topUiManager.clearMenuStack();
uiManager.replaceRoot(mainMenuRoot); uiManager.replaceRoot(mainMenuRoot);
uiManager.setTextButtonCallback("menuStartButton", [this](const std::string&) { uiManager.setTextButtonCallback("menuStartButton", [this](const std::string&) {
audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg");
enterGameplay(); enterGameplay();
@@ -353,6 +359,10 @@ namespace ZL {
showAboutScreen(); showAboutScreen();
audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); audioPlayer_.playSoundAsync("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");
});
uiManager.setTextButtonCallback("menuSettingsButton", [this](const std::string&) { uiManager.setTextButtonCallback("menuSettingsButton", [this](const std::string&) {
showSettingsScreen(); showSettingsScreen();
audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg");
@@ -378,6 +388,81 @@ namespace ZL {
#endif #endif
} }
void MenuManager::showMenuAfterGameCompleted() {
//gameState_.currentLocationName.clear();
audioPlayer_.crossFadeMusicAsync("audio/main menu final.ogg");
uiState_ = GameUiState::MainMenu;
uiManager.clearMenuStack();
topUiManager.replaceRoot(nullptr);
topUiManager.clearMenuStack();
uiManager.replaceRoot(mainMenuRoot);
uiManager.setTextButtonCallback("menuStartButton", [this](const std::string&) {
audioPlayer_.playSoundAsync("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");
});
uiManager.setTextButtonCallback("menuCreditsButton", [this](const std::string&) {
showCreditsScreen();
audioPlayer_.playSoundAsync("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");
});
uiManager.setTextButtonCallback("menuLanguageButton", [this](const std::string&) {
showLanguageScreen();
audioPlayer_.playSoundAsync("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");
});
#ifndef EMSCRIPTEN
uiManager.setTextButtonCallback("menuExitButton", [this](const std::string&) {
audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg");
Environment::exitGameLoop = true;
});
#else
uiManager.setNodeVisible("menuExitButton", false);
#endif
//Now open the about screen:
uiManager.pushMenuFromSavedRoot(aboutScreenRoot);
uiManager.findTextButton("aboutBackButton")->text = (g_currentLanguage == Language::Russian) ? BACK_TO_MAIN_MENU_RU : BACK_TO_MAIN_MENU_EN;
uiManager.setTextButtonCallback("aboutBackButton", [this](const std::string&) {
uiManager.popMenu();
uiManager.updateAllLayouts();
audioPlayer_.playSoundAsync("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");
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");
SDL_OpenURL("https://telegram.me/fishrungames");
});
uiManager.setTextButtonCallback("discordButton", [this](const std::string&) {
audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg");
SDL_OpenURL("https://discord.gg/S8jgGHXjw");
});
}
void MenuManager::showAboutScreen() { void MenuManager::showAboutScreen() {
uiManager.pushMenuFromSavedRoot(aboutScreenRoot); uiManager.pushMenuFromSavedRoot(aboutScreenRoot);
uiManager.setTextButtonCallback("aboutBackButton", [this](const std::string&) { uiManager.setTextButtonCallback("aboutBackButton", [this](const std::string&) {
@@ -385,6 +470,35 @@ namespace ZL {
uiManager.updateAllLayouts(); uiManager.updateAllLayouts();
audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); audioPlayer_.playSoundAsync("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");
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");
SDL_OpenURL("https://telegram.me/fishrungames");
});
uiManager.setTextButtonCallback("discordButton", [this](const std::string&) {
audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg");
SDL_OpenURL("https://discord.gg/S8jgGHXjw");
});
}
void MenuManager::showCreditsScreen() {
uiManager.pushMenuFromSavedRoot(creditsScreenRoot);
uiManager.setButtonCallback("creditsBackButton", [this](const std::string&) {
uiManager.popMenu();
uiManager.updateAllLayouts();
audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg");
});
uiManager.stopAnimationOnNode("creditsLinearLayout", "slowScroll");
uiManager.resetAnimationOnNode("creditsLinearLayout");
uiManager.startAnimationOnNode("creditsLinearLayout", "slowScroll");
} }
void MenuManager::showSettingsScreen() { void MenuManager::showSettingsScreen() {
@@ -490,9 +604,11 @@ namespace ZL {
void MenuManager::showSettingsGraphicsScreen() { void MenuManager::showSettingsGraphicsScreen() {
uiManager.pushMenuFromSavedRoot(settingsGraphicsScreenRoot); uiManager.pushMenuFromSavedRoot(settingsGraphicsScreenRoot);
uiManager.setTextButtonCallback("settingsBackButton", [this](const std::string&) { uiManager.setTextButtonCallback("settingsBackButton", [this](const std::string&) {
//saveSettings(); saveSettings();
uiManager.popMenu(); uiManager.popMenu();
audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg");
uiManager.updateAllLayouts();
topUiManager.updateAllLayouts();
}); });
@@ -530,6 +646,8 @@ namespace ZL {
Environment::setFullscreen(!Environment::isFullscreen); Environment::setFullscreen(!Environment::isFullscreen);
refreshFullscreenToggle(); refreshFullscreenToggle();
audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg");
uiManager.updateAllLayouts();
topUiManager.updateAllLayouts();
}); });
uiManager.setTextButtonCallback("shadowToggleButton", [this, refreshShadowToggle](const std::string&) { uiManager.setTextButtonCallback("shadowToggleButton", [this, refreshShadowToggle](const std::string&) {
@@ -756,7 +874,7 @@ namespace ZL {
} }
void MenuManager::selectInventoryItem(int index) { void MenuManager::selectInventoryItem(int index) {
std::cout << "MenuManager::selectInventoryItem: " << index << std::endl; logger() << "MenuManager::selectInventoryItem: " << index << std::endl;
const auto& items = inventory->getItems(); const auto& items = inventory->getItems();
if (index < 0 || index >= static_cast<int>(items.size())) return; if (index < 0 || index >= static_cast<int>(items.size())) return;
@@ -862,7 +980,7 @@ namespace ZL {
} }
void MenuManager::toggleQuestJournal() { void MenuManager::toggleQuestJournal() {
std::cout << "[quest] toggleQuestJournal: " << (isQuestJournalOpen() ? "closing" : "opening") << std::endl; logger() << "[quest] toggleQuestJournal: " << (isQuestJournalOpen() ? "closing" : "opening") << std::endl;
if (uiState_ == GameUiState::QuestJournal) { if (uiState_ == GameUiState::QuestJournal) {
closeQuestJournal(); closeQuestJournal();
} }
@@ -1274,7 +1392,7 @@ namespace ZL {
void MenuManager::tutorialShowTaxiHint() void MenuManager::tutorialShowTaxiHint()
{ {
std::cout << "tutorialShowTaxiHint" << std::endl; logger() << "tutorialShowTaxiHint" << std::endl;
gameState_.tutorialNeedOpenTaxiScreen = true; gameState_.tutorialNeedOpenTaxiScreen = true;
if (uiState_ == GameUiState::Gameplay) if (uiState_ == GameUiState::Gameplay)
{ {
@@ -1889,7 +2007,7 @@ namespace ZL {
void MenuManager::setDarklandsMode(bool enabled) void MenuManager::setDarklandsMode(bool enabled)
{ {
std::cout << "MenuManager::setDarklandsMode called" << std::endl; logger() << "MenuManager::setDarklandsMode called" << std::endl;
if (gameState_.currentLocationName == "uni_interior") { if (gameState_.currentLocationName == "uni_interior") {
if (enabled && gameState_.uniIntTutorialState == UniIntTutorialState::Step11) { if (enabled && gameState_.uniIntTutorialState == UniIntTutorialState::Step11) {
@@ -1979,11 +2097,11 @@ namespace ZL {
} }
void MenuManager::applyCurrentHealthBar() { void MenuManager::applyCurrentHealthBar() {
std::cout << "MenuManager::applyCurrentHealthBar called step 1" << std::endl; logger() << "MenuManager::applyCurrentHealthBar called step 1" << std::endl;
std::cout << "currentPlayerMaxHp_ is " << gameState_.playerMaxHp << std::endl; logger() << "currentPlayerMaxHp_ is " << gameState_.playerMaxHp << std::endl;
if (gameState_.playerMaxHp <= 0.f) return; if (gameState_.playerMaxHp <= 0.f) return;
std::cout << "MenuManager::applyCurrentHealthBar called step 2" << std::endl; logger() << "MenuManager::applyCurrentHealthBar called step 2" << std::endl;
std::cout << "currentPlayerHp_ is " << gameState_.playerHp << std::endl; logger() << "currentPlayerHp_ is " << gameState_.playerHp << std::endl;
const float fraction = std::clamp(gameState_.playerHp / gameState_.playerMaxHp, 0.f, 1.f); const float fraction = std::clamp(gameState_.playerHp / gameState_.playerMaxHp, 0.f, 1.f);
+3
View File
@@ -53,6 +53,7 @@ namespace ZL {
bool isQuestJournalOpen() const { return uiState_ == GameUiState::QuestJournal; } bool isQuestJournalOpen() const { return uiState_ == GameUiState::QuestJournal; }
void showMainMenu(); void showMainMenu();
void showMenuAfterGameCompleted();
bool isMainMenuOpen() const { return uiState_ == GameUiState::MainMenu; } bool isMainMenuOpen() const { return uiState_ == GameUiState::MainMenu; }
void openPhoneScreen(); void openPhoneScreen();
@@ -172,6 +173,7 @@ namespace ZL {
//void closePortraitJournalItemCompletely(); //void closePortraitJournalItemCompletely();
void showAboutScreen(); void showAboutScreen();
void showCreditsScreen();
void showSettingsScreen(); void showSettingsScreen();
void showSettingsGraphicsScreen(); void showSettingsGraphicsScreen();
void showLanguageScreen(); void showLanguageScreen();
@@ -262,6 +264,7 @@ namespace ZL {
std::shared_ptr<UiNode> mainMenuRoot; std::shared_ptr<UiNode> mainMenuRoot;
std::shared_ptr<UiNode> aboutScreenRoot; std::shared_ptr<UiNode> aboutScreenRoot;
std::shared_ptr<UiNode> creditsScreenRoot;
std::shared_ptr<UiNode> settingsScreenRoot; std::shared_ptr<UiNode> settingsScreenRoot;
std::shared_ptr<UiNode> settingsGraphicsScreenRoot; std::shared_ptr<UiNode> settingsGraphicsScreenRoot;
std::shared_ptr<UiNode> languageScreenRoot; std::shared_ptr<UiNode> languageScreenRoot;
+40 -19
View File
@@ -5,6 +5,7 @@
#include <unordered_map> #include <unordered_map>
#include "Location.h" #include "Location.h"
#include "items/ItemRegistry.h" #include "items/ItemRegistry.h"
#include "utils/Utils.h"
#define SOL_ALL_SAFETIES_ON 1 #define SOL_ALL_SAFETIES_ON 1
#include <sol/sol.hpp> #include <sol/sol.hpp>
@@ -87,6 +88,26 @@ namespace ZL {
lua.open_libraries(sol::lib::base, sol::lib::math, sol::lib::string, sol::lib::table); lua.open_libraries(sol::lib::base, sol::lib::math, sol::lib::string, sol::lib::table);
// --- Переопределение функции print ---
lua.set_function("print", [](sol::this_state ts, sol::variadic_args va) {
sol::state_view lua_view(ts);
sol::function tostring = lua_view["tostring"];
auto& log = logger();
log << "[script] ";
bool first = true;
for (auto arg : va) {
if (!first) log << "\t"; // Разделяем аргументы табуляцией, как в стандартном Lua print
std::string str = tostring(arg);
log << str;
first = false;
}
log << std::endl;
});
// -------------------------------------
auto api = lua.create_named_table("game_api"); auto api = lua.create_named_table("game_api");
// npc_walk_to(index, x, y, z [, on_arrived]) // npc_walk_to(index, x, y, z [, on_arrived])
@@ -124,14 +145,14 @@ namespace ZL {
} }
const float rad = angle * static_cast<float>(M_PI) / 180.f; const float rad = angle * static_cast<float>(M_PI) / 180.f;
std::cout << "[script] npc_rotate_to: index " << index << " angle " << angle << " degrees (" << rad << " radians)" << std::endl; logger() << "[script] npc_rotate_to: index " << index << " angle " << angle << " degrees (" << rad << " radians)" << std::endl;
npcs[index]->state.targetFacingAngle = rad; npcs[index]->state.targetFacingAngle = rad;
}); });
api.set_function("player_rotate_to", api.set_function("player_rotate_to",
[loc](float angle) { [loc](float angle) {
const float rad = angle * static_cast<float>(M_PI) / 180.f; const float rad = angle * static_cast<float>(M_PI) / 180.f;
std::cout << "[script] player_rotate_to: angle " << angle << " degrees (" << rad << " radians)" << std::endl; logger() << "[script] player_rotate_to: angle " << angle << " degrees (" << rad << " radians)" << std::endl;
loc->player->state.targetFacingAngle = rad; loc->player->state.targetFacingAngle = rad;
}); });
@@ -172,12 +193,12 @@ namespace ZL {
const Item* item = ItemRegistry::instance().findById(itemId); const Item* item = ItemRegistry::instance().findById(itemId);
if (item) { if (item) {
if (inventory->hasItem(itemId)) { if (inventory->hasItem(itemId)) {
std::cout << "[script] has_item: " << item->name << " returns true" << std::endl; logger() << "[script] has_item: " << item->name << " returns true" << std::endl;
return true; return true;
} }
else else
{ {
std::cout << "[script] has_item: " << item->name << " returns false" << std::endl; logger() << "[script] has_item: " << item->name << " returns false" << std::endl;
return false; return false;
} }
} }
@@ -192,7 +213,7 @@ namespace ZL {
const Item* item = ItemRegistry::instance().findById(itemId); const Item* item = ItemRegistry::instance().findById(itemId);
if (item) { if (item) {
inventory->addItem(item->id); inventory->addItem(item->id);
std::cout << "[script] pickup_item: " << item->name << std::endl; logger() << "[script] pickup_item: " << item->name << std::endl;
} else { } else {
std::cerr << "[script] pickup_item: item '" << itemId << "' not found in ItemRegistry\n"; std::cerr << "[script] pickup_item: item '" << itemId << "' not found in ItemRegistry\n";
} }
@@ -200,7 +221,7 @@ namespace ZL {
// remove_item(item_id) // remove_item(item_id)
api.set_function("remove_item", [loc, inventory](const std::string& id) { api.set_function("remove_item", [loc, inventory](const std::string& id) {
std::cout << "[script] remove_item: " << id << std::endl; logger() << "[script] remove_item: " << id << std::endl;
inventory->removeItem(id); inventory->removeItem(id);
}); });
@@ -209,7 +230,7 @@ namespace ZL {
for (auto& intObj : loc->interactiveObjects) { for (auto& intObj : loc->interactiveObjects) {
if (intObj.loadedObject.name == objectName) { if (intObj.loadedObject.name == objectName) {
intObj.state.isActive = false; intObj.state.isActive = false;
std::cout << "[script] deactivate_interactive_object: " << objectName << std::endl; logger() << "[script] deactivate_interactive_object: " << objectName << std::endl;
return; return;
} }
} }
@@ -221,7 +242,7 @@ namespace ZL {
for (auto& intObj : loc->interactiveObjects) { for (auto& intObj : loc->interactiveObjects) {
if (intObj.loadedObject.name == objectName) { if (intObj.loadedObject.name == objectName) {
intObj.state.isActive = true; intObj.state.isActive = true;
std::cout << "[script] activate_interactive_object: " << objectName << std::endl; logger() << "[script] activate_interactive_object: " << objectName << std::endl;
return; return;
} }
} }
@@ -232,7 +253,7 @@ namespace ZL {
for (auto& intObj : loc->interactiveObjects) { for (auto& intObj : loc->interactiveObjects) {
if (intObj.loadedObject.name == objectName) { if (intObj.loadedObject.name == objectName) {
intObj.state.rotationY = value * static_cast<float>(M_PI) / 180.f; intObj.state.rotationY = value * static_cast<float>(M_PI) / 180.f;
std::cout << "[script] set_object_rotation: " << objectName << " " << value << std::endl; logger() << "[script] set_object_rotation: " << objectName << " " << value << std::endl;
return; return;
} }
} }
@@ -243,7 +264,7 @@ namespace ZL {
for (auto& intObj : loc->interactiveObjects) { for (auto& intObj : loc->interactiveObjects) {
if (intObj.loadedObject.name == objectName) { if (intObj.loadedObject.name == objectName) {
intObj.state.alpha = value; intObj.state.alpha = value;
std::cout << "[script] set_object_alpha: " <<objectName << " " << value << std::endl; logger() << "[script] set_object_alpha: " <<objectName << " " << value << std::endl;
return; return;
} }
} }
@@ -336,13 +357,13 @@ namespace ZL {
api.set_function("set_night", api.set_function("set_night",
[loc]() { [loc]() {
loc->requestNightDayTransition(true, false); loc->requestNightDayTransition(true, false);
std::cout << "Set night called" << std::endl; logger() << "Set night called" << std::endl;
}); });
api.set_function("set_dawn", api.set_function("set_dawn",
[loc]() { [loc]() {
loc->requestNightDayTransition(true, true); loc->requestNightDayTransition(true, true);
std::cout << "Set dawn called" << std::endl; logger() << "Set dawn called" << std::endl;
}); });
@@ -857,7 +878,7 @@ namespace ZL {
std::cerr << "[SCRIPT] on_npc_interact error: " << err.what() << "\n"; std::cerr << "[SCRIPT] on_npc_interact error: " << err.what() << "\n";
} }
else { else {
std::cout << "[SCRIPT] on_npc_interact called with index " << npcIndex << std::endl; logger() << "[SCRIPT] on_npc_interact called with index " << npcIndex << std::endl;
} }
} }
else { else {
@@ -883,7 +904,7 @@ namespace ZL {
} }
sol::state& lua = impl->lua; sol::state& lua = impl->lua;
std::cout << "[SCRIPT] Looking for activate function: " << functionName << std::endl; logger() << "[SCRIPT] Looking for activate function: " << functionName << std::endl;
sol::function activateFunc = lua[functionName]; sol::function activateFunc = lua[functionName];
@@ -891,7 +912,7 @@ namespace ZL {
throw std::runtime_error("[SCRIPT] Lua function not found: " + functionName); throw std::runtime_error("[SCRIPT] Lua function not found: " + functionName);
} }
std::cout << "[SCRIPT] Found function! Calling: " << functionName << std::endl; logger() << "[SCRIPT] Found function! Calling: " << functionName << std::endl;
auto result = activateFunc(); auto result = activateFunc();
if (!result.valid()) { if (!result.valid()) {
@@ -899,7 +920,7 @@ namespace ZL {
throw std::runtime_error("[SCRIPT] Error executing " + functionName + ": " + std::string(err.what())); throw std::runtime_error("[SCRIPT] Error executing " + functionName + ": " + std::string(err.what()));
} }
std::cout << "[SCRIPT] Function executed successfully!" << std::endl; logger() << "[SCRIPT] Function executed successfully!" << std::endl;
} }
void ScriptEngine::callItemPickupCallback(const std::string& objectName) { void ScriptEngine::callItemPickupCallback(const std::string& objectName) {
@@ -914,18 +935,18 @@ namespace ZL {
sol::function activateFunc = lua["on_item_pickup"]; sol::function activateFunc = lua["on_item_pickup"];
if (activateFunc.valid()) { if (activateFunc.valid()) {
std::cout << "[SCRIPT] Callback found! Calling with argument: " << objectName << std::endl; logger() << "[SCRIPT] Callback found! Calling with argument: " << objectName << std::endl;
auto result = activateFunc(objectName); auto result = activateFunc(objectName);
if (!result.valid()) { if (!result.valid()) {
sol::error err = result; sol::error err = result;
std::cerr << "[SCRIPT] on_item_pickup callback error: " << err.what() << "\n"; std::cerr << "[SCRIPT] on_item_pickup callback error: " << err.what() << "\n";
} }
else { else {
std::cout << "[SCRIPT] Callback executed successfully!" << std::endl; logger() << "[SCRIPT] Callback executed successfully!" << std::endl;
} }
} }
else { else {
std::cout << "[SCRIPT] Fallback: on_item_pickup not found" << std::endl; logger() << "[SCRIPT] Fallback: on_item_pickup not found" << std::endl;
} }
} }
+22 -22
View File
@@ -474,7 +474,7 @@ namespace ZL {
} }
bool SparkEmitter::loadFromJsonFile(const std::string& path, Renderer& renderer, const std::string& zipFile) { bool SparkEmitter::loadFromJsonFile(const std::string& path, Renderer& renderer, const std::string& zipFile) {
std::cout << "Loading spark config from: " << path << std::endl; logger() << "Loading spark config from: " << path << std::endl;
std::string content; std::string content;
try { try {
@@ -498,13 +498,13 @@ namespace ZL {
json j; json j;
try { try {
j = json::parse(content); j = json::parse(content);
std::cout << "JSON parsed successfully" << std::endl; logger() << "JSON parsed successfully" << std::endl;
} }
catch (const std::exception& e) { catch (const std::exception& e) {
std::cerr << "JSON parse error: " << e.what() << std::endl; std::cerr << "JSON parse error: " << e.what() << std::endl;
throw std::runtime_error("Failed to load spark emitter config file 9!"); throw std::runtime_error("Failed to load spark emitter config file 9!");
} }
std::cout << "JSON content: " << j.dump(2) << std::endl; logger() << "JSON content: " << j.dump(2) << std::endl;
auto requireKey = [&](const std::string& key) { auto requireKey = [&](const std::string& key) {
if (!j.contains(key)) { if (!j.contains(key)) {
@@ -531,13 +531,13 @@ namespace ZL {
particles.resize(maxParticles); particles.resize(maxParticles);
drawPositions.reserve(maxParticles * 6); drawPositions.reserve(maxParticles * 6);
drawTexCoords.reserve(maxParticles * 6); drawTexCoords.reserve(maxParticles * 6);
std::cout << "Max particles: " << maxParticles << std::endl; logger() << "Max particles: " << maxParticles << std::endl;
particleSize = j["particleSize"].get<float>(); particleSize = j["particleSize"].get<float>();
std::cout << "Particle size: " << particleSize << std::endl; logger() << "Particle size: " << particleSize << std::endl;
biasX = j["biasX"].get<float>(); biasX = j["biasX"].get<float>();
std::cout << "Bias X: " << biasX << std::endl; logger() << "Bias X: " << biasX << std::endl;
// emissionPoints // emissionPoints
std::vector<Vector3f> points; std::vector<Vector3f> points;
@@ -546,7 +546,7 @@ namespace ZL {
if (el.contains("position") && el["position"].is_array()) { if (el.contains("position") && el["position"].is_array()) {
auto arr = el["position"]; auto arr = el["position"];
points.push_back(Vector3f{ arr[0].get<float>(), arr[1].get<float>(), arr[2].get<float>() }); points.push_back(Vector3f{ arr[0].get<float>(), arr[1].get<float>(), arr[2].get<float>() });
std::cout << "Fixed point: [" << arr[0] << ", " << arr[1] << ", " << arr[2] << "]" << std::endl; logger() << "Fixed point: [" << arr[0] << ", " << arr[1] << ", " << arr[2] << "]" << std::endl;
} }
else if (el.contains("positionRange") && el["positionRange"].is_object()) { else if (el.contains("positionRange") && el["positionRange"].is_object()) {
auto pr = el["positionRange"]; auto pr = el["positionRange"];
@@ -564,18 +564,18 @@ namespace ZL {
for (int k = 0; k < count; ++k) { for (int k = 0; k < count; ++k) {
Vector3f randomPoint{ dx(gen), dy(gen), dz(gen) }; Vector3f randomPoint{ dx(gen), dy(gen), dz(gen) };
points.push_back(randomPoint); points.push_back(randomPoint);
std::cout << "Random point " << k + 1 << ": [" << randomPoint(0) logger() << "Random point " << k + 1 << ": [" << randomPoint(0)
<< ", " << randomPoint(1) << ", " << randomPoint(2) << "]" << std::endl; << ", " << randomPoint(1) << ", " << randomPoint(2) << "]" << std::endl;
} }
} }
} }
if (!points.empty()) { if (!points.empty()) {
setEmissionPoints(points); setEmissionPoints(points);
std::cout << "Total emission points: " << emissionPoints.size() << std::endl; logger() << "Total emission points: " << emissionPoints.size() << std::endl;
} }
else { else {
setEmissionPoints({}); setEmissionPoints({});
std::cout << "Emission points parsed but empty" << std::endl; logger() << "Emission points parsed but empty" << std::endl;
//throw std::runtime_error("Failed to load spark emitter config file 10!"); //throw std::runtime_error("Failed to load spark emitter config file 10!");
} }
} }
@@ -589,7 +589,7 @@ namespace ZL {
auto a = j["speedRange"]; auto a = j["speedRange"];
speedRange.min = a[0].get<float>(); speedRange.min = a[0].get<float>();
speedRange.max = a[1].get<float>(); speedRange.max = a[1].get<float>();
std::cout << "Speed range: [" << speedRange.min << ", " << speedRange.max << "]" << std::endl; logger() << "Speed range: [" << speedRange.min << ", " << speedRange.max << "]" << std::endl;
} }
else { else {
std::cerr << "speedRange missing or invalid" << std::endl; std::cerr << "speedRange missing or invalid" << std::endl;
@@ -600,7 +600,7 @@ namespace ZL {
auto a = j["zSpeedRange"]; auto a = j["zSpeedRange"];
zSpeedRange.min = a[0].get<float>(); zSpeedRange.min = a[0].get<float>();
zSpeedRange.max = a[1].get<float>(); zSpeedRange.max = a[1].get<float>();
std::cout << "Z speed range: [" << zSpeedRange.min << ", " << zSpeedRange.max << "]" << std::endl; logger() << "Z speed range: [" << zSpeedRange.min << ", " << zSpeedRange.max << "]" << std::endl;
} }
else { else {
std::cerr << "zSpeedRange missing or invalid" << std::endl; std::cerr << "zSpeedRange missing or invalid" << std::endl;
@@ -611,7 +611,7 @@ namespace ZL {
auto a = j["scaleRange"]; auto a = j["scaleRange"];
scaleRange.min = a[0].get<float>(); scaleRange.min = a[0].get<float>();
scaleRange.max = a[1].get<float>(); scaleRange.max = a[1].get<float>();
std::cout << "Scale range: [" << scaleRange.min << ", " << scaleRange.max << "]" << std::endl; logger() << "Scale range: [" << scaleRange.min << ", " << scaleRange.max << "]" << std::endl;
} }
else { else {
std::cerr << "scaleRange missing or invalid" << std::endl; std::cerr << "scaleRange missing or invalid" << std::endl;
@@ -622,7 +622,7 @@ namespace ZL {
auto a = j["lifeTimeRange"]; auto a = j["lifeTimeRange"];
lifeTimeRange.min = a[0].get<float>(); lifeTimeRange.min = a[0].get<float>();
lifeTimeRange.max = a[1].get<float>(); lifeTimeRange.max = a[1].get<float>();
std::cout << "Life time range: [" << lifeTimeRange.min << ", " << lifeTimeRange.max << "]" << std::endl; logger() << "Life time range: [" << lifeTimeRange.min << ", " << lifeTimeRange.max << "]" << std::endl;
} }
else { else {
std::cerr << "lifeTimeRange missing or invalid" << std::endl; std::cerr << "lifeTimeRange missing or invalid" << std::endl;
@@ -632,17 +632,17 @@ namespace ZL {
// texture // texture
if (j.contains("texture") && j["texture"].is_string()) { if (j.contains("texture") && j["texture"].is_string()) {
std::string texPath = j["texture"].get<std::string>(); std::string texPath = j["texture"].get<std::string>();
std::cout << "Loading texture: " << texPath << " From zip file: " << zipFile << std::endl; logger() << "Loading texture: " << texPath << " From zip file: " << zipFile << std::endl;
try { try {
std::cout << "Loading texture step 1" << std::endl; logger() << "Loading texture step 1" << std::endl;
auto texData = CreateTextureDataFromPng(texPath.c_str(), zipFile.c_str()); auto texData = CreateTextureDataFromPng(texPath.c_str(), zipFile.c_str());
std::cout << "Loading texture step 2" << std::endl; logger() << "Loading texture step 2" << std::endl;
texture = std::make_shared<Texture>(texData); texture = std::make_shared<Texture>(texData);
std::cout << "Texture loaded successfully, ID: " << texture->getTexID() << std::endl; logger() << "Texture loaded successfully, ID: " << texture->getTexID() << std::endl;
} }
catch (const std::exception& e) { catch (const std::exception& e) {
std::cout << "Texture load error: " << e.what() << std::endl; logger() << "Texture load error: " << e.what() << std::endl;
std::cerr << "Texture load error: " << e.what() << std::endl; std::cerr << "Texture load error: " << e.what() << std::endl;
throw std::runtime_error("Failed to load spark emitter config file 16!"); throw std::runtime_error("Failed to load spark emitter config file 16!");
} }
@@ -652,11 +652,11 @@ namespace ZL {
throw std::runtime_error("Failed to load spark emitter config file 17!"); throw std::runtime_error("Failed to load spark emitter config file 17!");
} }
std::cout << "Working with shaders 1" << std::endl; logger() << "Working with shaders 1" << std::endl;
// shaders // shaders
if (j.contains("shaderProgramName") && j["shaderProgramName"].is_string()) { if (j.contains("shaderProgramName") && j["shaderProgramName"].is_string()) {
shaderProgramName = j["shaderProgramName"].get<std::string>(); shaderProgramName = j["shaderProgramName"].get<std::string>();
std::cout << "Shader program name: " << shaderProgramName << std::endl; logger() << "Shader program name: " << shaderProgramName << std::endl;
} }
else { else {
std::cerr << "shaderProgramName missing or invalid" << std::endl; std::cerr << "shaderProgramName missing or invalid" << std::endl;
@@ -665,7 +665,7 @@ namespace ZL {
drawDataDirty = true; drawDataDirty = true;
configured = true; configured = true;
std::cout << "SparkEmitter configuration loaded successfully!" << std::endl; logger() << "SparkEmitter configuration loaded successfully!" << std::endl;
return true; return true;
} }
+6 -6
View File
@@ -81,7 +81,7 @@ namespace ZL
numberVertices = std::stoi(match.str()); numberVertices = std::stoi(match.str());
} }
else { else {
std::cout << "Vertices header not found or invalid: " << tempLine << std::endl; logger() << "Vertices header not found or invalid: " << tempLine << std::endl;
throw std::runtime_error("Vertices header not found or invalid."); throw std::runtime_error("Vertices header not found or invalid.");
} }
@@ -116,7 +116,7 @@ namespace ZL
// UV - [7], [8] // UV - [7], [8]
if (floatValues.size() < 9) { if (floatValues.size() < 9) {
std::cout << "Malformed vertex line at index " << i << ": " << tempLine << std::endl; logger() << "Malformed vertex line at index " << i << ": " << tempLine << std::endl;
throw std::runtime_error("Malformed vertex line at index " + std::to_string(i)); throw std::runtime_error("Malformed vertex line at index " + std::to_string(i));
} }
@@ -137,7 +137,7 @@ namespace ZL
numberTriangles = std::stoi(match.str()); numberTriangles = std::stoi(match.str());
} }
else { else {
std::cout << "Triangles header not found or invalid: " << tempLine << std::endl; logger() << "Triangles header not found or invalid: " << tempLine << std::endl;
throw std::runtime_error("Triangles header not found."); throw std::runtime_error("Triangles header not found.");
} }
@@ -162,7 +162,7 @@ namespace ZL
} }
if (indices.size() != 3) { if (indices.size() != 3) {
std::cout << "Malformed triangle line at index " << i << ": " << tempLine << std::endl; logger() << "Malformed triangle line at index " << i << ": " << tempLine << std::endl;
throw std::runtime_error("Malformed triangle line at index " + std::to_string(i)); throw std::runtime_error("Malformed triangle line at index " + std::to_string(i));
} }
@@ -193,7 +193,7 @@ namespace ZL
result.NormalData[i](2) = originalNorm(0); result.NormalData[i](2) = originalNorm(0);
} }
std::cout << "Model loaded: " << numberVertices << " verts, " << numberTriangles << " tris." << std::endl; logger() << "Model loaded: " << numberVertices << " verts, " << numberTriangles << " tris." << std::endl;
s_meshCache[key] = result; s_meshCache[key] = result;
return result; return result;
@@ -266,7 +266,7 @@ namespace ZL
} }
} }
std::cout << "Binary model loaded: " << numVertices << " verts, " << numTriangles << " tris." << std::endl; logger() << "Binary model loaded: " << numVertices << " verts, " << numTriangles << " tris." << std::endl;
s_meshCache[key] = result; s_meshCache[key] = result;
return result; return result;
+96 -37
View File
@@ -182,7 +182,7 @@ namespace ZL {
mesh.RefreshVBO(); mesh.RefreshVBO();
} }
void UiTextButton::draw(Renderer& renderer) const { void UiTextButton::draw(Renderer& renderer, float accumX, float accumY) const {
// Draw background texture (optional) // Draw background texture (optional)
const std::shared_ptr<Texture>* tex = nullptr; const std::shared_ptr<Texture>* tex = nullptr;
@@ -221,16 +221,18 @@ namespace ZL {
? wrapTextByPixels(text, *textRenderer, rect.w - textPaddingX * 2.0f, scale) ? wrapTextByPixels(text, *textRenderer, rect.w - textPaddingX * 2.0f, scale)
: text; : text;
float tx = rect.x + rect.w / 2.0f + animOffsetX; // Прибавляем accumX и accumY
float tx = rect.x + rect.w / 2.0f + animOffsetX + accumX;
if (!textCentered) { if (!textCentered) {
tx = rect.x + textPaddingX + animOffsetX; tx = rect.x + textPaddingX + animOffsetX + accumX;
} }
float ty; float ty;
if (topAligned) { if (topAligned) {
ty = rect.y + rect.h - textPaddingY - static_cast<float>(fontSize) + animOffsetY; ty = rect.y + rect.h - textPaddingY - static_cast<float>(fontSize) + animOffsetY + accumY;
} else { }
ty = rect.y + rect.h * 0.5f + textPaddingY + animOffsetY; else {
ty = rect.y + rect.h * 0.5f + textPaddingY + animOffsetY + accumY;
} }
textRenderer->drawText(displayText, tx, ty, scale, textCentered, color); textRenderer->drawText(displayText, tx, ty, scale, textCentered, color);
@@ -238,24 +240,19 @@ namespace ZL {
glEnable(GL_DEPTH_TEST); glEnable(GL_DEPTH_TEST);
} }
void UiTextView::draw(Renderer& renderer) const { void UiTextView::draw(Renderer& renderer, float accumX, float accumY) const {
(void)renderer; (void)renderer;
if (!textRenderer || text.empty()) { if (!textRenderer || text.empty()) {
return; return;
} }
const float scale = 1.0f; const float scale = 1.0f;
// Backward compatibility:
// Old UI files, including the original inventory panel, positioned TextView
// around the rect center. If a TextView does not explicitly request wrapping,
// top alignment, padding or line limiting, keep that old behavior.
const bool usesModernRectText = wrap || topAligned || paddingX != 0.0f || paddingY != 0.0f || maxLines > 0; const bool usesModernRectText = wrap || topAligned || paddingX != 0.0f || paddingY != 0.0f || maxLines > 0;
if (!usesModernRectText) { if (!usesModernRectText) {
textRenderer->drawText( textRenderer->drawText(
text, text,
rect.x + rect.w * 0.5f, rect.x + rect.w * 0.5f + accumX,
rect.y + rect.h * 0.5f, rect.y + rect.h * 0.5f + accumY,
scale, scale,
textCentered, textCentered,
color color
@@ -272,12 +269,10 @@ namespace ZL {
float ty = rect.y + rect.h * 0.5f; float ty = rect.y + rect.h * 0.5f;
if (topAligned) { if (topAligned) {
// TextRenderer expects a baseline position. This offset places the first
// visible line close to the top inside the TextView rectangle.
ty = rect.y + rect.h - paddingY - static_cast<float>(fontSize); ty = rect.y + rect.h - paddingY - static_cast<float>(fontSize);
} }
textRenderer->drawText(finalText, tx, ty, scale, textCentered, color); textRenderer->drawText(finalText, tx + accumX, ty + accumY, scale, textCentered, color);
} }
void UiSlider::buildTrackMesh() { void UiSlider::buildTrackMesh() {
@@ -537,7 +532,7 @@ namespace ZL {
if (!t.contains(key) || !t[key].is_string()) return nullptr; if (!t.contains(key) || !t[key].is_string()) return nullptr;
std::string path = t[key].get<std::string>(); std::string path = t[key].get<std::string>();
try { try {
std::cout << "UiManager: loading texture for button '" << btn->name << "' : " << path << " Zip file: " << zipFile << std::endl; logger() << "UiManager: loading texture for button '" << btn->name << "' : " << path << " Zip file: " << zipFile << std::endl;
return renderer.textureManager.LoadFromPng(path, zipFile, true); return renderer.textureManager.LoadFromPng(path, zipFile, true);
} }
catch (const std::exception& e) { catch (const std::exception& e) {
@@ -571,7 +566,7 @@ namespace ZL {
if (!t.contains(key) || !t[key].is_string()) return nullptr; if (!t.contains(key) || !t[key].is_string()) return nullptr;
std::string path = t[key].get<std::string>(); std::string path = t[key].get<std::string>();
try { try {
std::cout << "UiManager: --loading texture for slider '" << s->name << "' : " << path << " Zip file: " << zipFile << std::endl; logger() << "UiManager: --loading texture for slider '" << s->name << "' : " << path << " Zip file: " << zipFile << std::endl;
return renderer.textureManager.LoadFromPng(path, zipFile, true); return renderer.textureManager.LoadFromPng(path, zipFile, true);
} }
catch (const std::exception& e) { catch (const std::exception& e) {
@@ -1480,60 +1475,61 @@ namespace ZL {
renderer.LoadIdentity(); renderer.LoadIdentity();
renderer.RenderUniform1f("uAlpha", 1.0f); // ensure alpha is fully opaque unless a widget overrides it renderer.RenderUniform1f("uAlpha", 1.0f); // ensure alpha is fully opaque unless a widget overrides it
std::function<void(const std::shared_ptr<UiNode>&)> drawNode = std::function<void(const std::shared_ptr<UiNode>&, float, float)> drawNode =
[&](const std::shared_ptr<UiNode>& node) { [&](const std::shared_ptr<UiNode>& node, float accumX, float accumY) {
if (!node || !node->visible) return; if (!node || !node->visible) return;
// Считаем полное смещение для текущего узла и его потомков
float curAccumX = accumX + node->animOffsetX;
float curAccumY = accumY + node->animOffsetY;
renderer.PushMatrix(); renderer.PushMatrix();
renderer.TranslateMatrix({ node->animOffsetX, node->animOffsetY, 0.0f });
renderer.TranslateMatrix({ node->screenRect.x + node->screenRect.w * 0.5f, renderer.TranslateMatrix({ node->screenRect.x + node->screenRect.w * 0.5f,
node->screenRect.y + node->screenRect.h * 0.5f, 0.0f }); node->screenRect.y + node->screenRect.h * 0.5f, 0.0f });
renderer.ScaleMatrix({ node->scaleX, node->scaleY, 1.0f }); renderer.ScaleMatrix({ node->scaleX, node->scaleY, 1.0f });
renderer.TranslateMatrix({ -(node->screenRect.x + node->screenRect.w * 0.5f), renderer.TranslateMatrix({ -(node->screenRect.x + node->screenRect.w * 0.5f),
-(node->screenRect.y + node->screenRect.h * 0.5f), 0.0f }); -(node->screenRect.y + node->screenRect.h * 0.5f), 0.0f });
// 1. Сначала изображения
if (node->staticImage) { if (node->staticImage) {
node->staticImage->draw(renderer); node->staticImage->draw(renderer);
} }
// 1b. Chat bubble (composite widget)
if (node->chatBubble) { if (node->chatBubble) {
node->chatBubble->draw(renderer, node->scaleX, node->scaleY, projW, projH); node->chatBubble->draw(renderer, node->scaleX, node->scaleY, projW, projH);
} }
// 2. Потом кнопки
if (node->button) { if (node->button) {
node->button->draw(renderer); node->button->draw(renderer);
} }
if (node->textButton) { if (node->textButton) {
node->textButton->draw(renderer); // Передаем накопленные смещения
node->textButton->draw(renderer, curAccumX, curAccumY);
} }
// 3. Потом слайдеры
if (node->slider) { if (node->slider) {
node->slider->draw(renderer); node->slider->draw(renderer);
} }
// 4. Потом текстовые поля
if (node->textField) { if (node->textField) {
node->textField->draw(renderer); node->textField->draw(renderer);
} }
// 5. ПОСЛЕДНИЙ - ТЕКСТ (поверх всего!)
if (node->textView) { if (node->textView) {
node->textView->draw(renderer); // Передаем накопленные смещения
node->textView->draw(renderer, curAccumX, curAccumY);
} }
// Рисуем детей
for (auto& child : node->children) { for (auto& child : node->children) {
drawNode(child); // Прокидываем сумму дальше
drawNode(child, curAccumX, curAccumY);
} }
renderer.PopMatrix(); renderer.PopMatrix();
}; };
drawNode(root); drawNode(root, 0.0f, 0.0f);
renderer.PopMatrix(); renderer.PopMatrix();
renderer.PopProjectionMatrix(); renderer.PopProjectionMatrix();
@@ -1625,6 +1621,12 @@ namespace ZL {
node->textButton->animScaleX = act.origScaleX; node->textButton->animScaleX = act.origScaleX;
node->textButton->animScaleY = act.origScaleY; node->textButton->animScaleY = act.origScaleY;
} }
else
{
//logger() << "[animation] Resetting animation for node: " << node->name << " y=" << act.origOffsetY << std::endl;
node->animOffsetX = act.origOffsetX;
node->animOffsetY = act.origOffsetY;
}
act.stepIndex = 0; act.stepIndex = 0;
act.elapsedMs = 0.0f; act.elapsedMs = 0.0f;
act.stepStarted = false; act.stepStarted = false;
@@ -1650,6 +1652,12 @@ namespace ZL {
node->textButton->animOffsetX = step.toX; node->textButton->animOffsetX = step.toX;
node->textButton->animOffsetY = step.toY; node->textButton->animOffsetY = step.toY;
} }
else
{
//logger() << "[animation] step.durationMs <= 0.0f: " << node->name << " y=" << step.toY << std::endl;
node->animOffsetX = step.toX;
node->animOffsetY = step.toY;
}
} }
else if (step.type == "scale") { else if (step.type == "scale") {
if (node->button) { if (node->button) {
@@ -1681,8 +1689,9 @@ namespace ZL {
act.origScaleY = node->textButton->animScaleY; act.origScaleY = node->textButton->animScaleY;
} }
else { else {
act.origOffsetX = act.origOffsetY = 0.0f; //logger() << "[animation] !act.stepStarted && act.stepIndex == 0 && act.elapsedMs == 0.0f: " << node->name << " y=" << node->animOffsetY << std::endl;
act.origScaleX = act.origScaleY = 1.0f; act.origOffsetX = node->animOffsetX;
act.origOffsetY = node->animOffsetY;
} }
} }
@@ -1703,8 +1712,11 @@ namespace ZL {
act.startScaleY = node->textButton->animScaleY; act.startScaleY = node->textButton->animScaleY;
} }
else { else {
act.startOffsetX = act.startOffsetY = 0.0f; //logger() << "[animation] !act.stepStarted && prevElapsed == 0.0f: " << node->name << " y=" << node->animOffsetY << std::endl;
act.startScaleX = act.startScaleY = 1.0f; act.startOffsetX = node->animOffsetX;
act.startOffsetY = node->animOffsetY;
act.startScaleX = node->scaleX;
act.startScaleY = node->scaleY;
} }
if (step.type == "move") { if (step.type == "move") {
act.endOffsetX = step.toX; act.endOffsetX = step.toX;
@@ -1731,6 +1743,12 @@ namespace ZL {
node->textButton->animOffsetX = nx; node->textButton->animOffsetX = nx;
node->textButton->animOffsetY = ny; node->textButton->animOffsetY = ny;
} }
else {
//logger() << "[animation] if (step.type ==move): " << node->name << " y=" << ny << std::endl;
node->animOffsetX = nx;
node->animOffsetY = ny;
}
} }
else if (step.type == "scale") { else if (step.type == "scale") {
float sx = act.startScaleX + (act.endScaleX - act.startScaleX) * te; float sx = act.startScaleX + (act.endScaleX - act.startScaleX) * te;
@@ -1986,12 +2004,46 @@ namespace ZL {
aa.origScaleX = node->textButton->animScaleX; aa.origScaleX = node->textButton->animScaleX;
aa.origScaleY = node->textButton->animScaleY; aa.origScaleY = node->textButton->animScaleY;
} }
else
{
//logger() << "[animation] startAnimationOnNode: " << node->name << " y=" << node->animOffsetY << std::endl;
aa.origOffsetX = node->animOffsetX;
aa.origOffsetY = node->animOffsetY;
}
auto cbIt = animCallbacks.find({ nodeName, animName }); auto cbIt = animCallbacks.find({ nodeName, animName });
if (cbIt != animCallbacks.end()) aa.onComplete = cbIt->second; if (cbIt != animCallbacks.end()) aa.onComplete = cbIt->second;
nodeActiveAnims[node].push_back(std::move(aa)); nodeActiveAnims[node].push_back(std::move(aa));
return true; return true;
} }
bool UiManager::resetAnimationOnNode(const std::string& nodeName)
{
if (!root) return false;
auto node = findNodeByName(root, nodeName);
if (!node) return false;
if (node->button) {
node->button->animOffsetX = 0;
node->button->animOffsetY = 0;
node->button->animScaleX = 1.0f;
node->button->animScaleY = 1.0f;
}
else if (node->textButton) {
node->textButton->animOffsetX = 0;
node->textButton->animOffsetY = 0;
node->textButton->animScaleX = 1.0f;
node->textButton->animScaleY = 1.0f;
}
else
{
//logger() << "[animation] startAnimationOnNode: " << node->name << " y=" << node->animOffsetY << std::endl;
node->animOffsetX = 0;
node->animOffsetY = 0;
}
return true;
}
bool UiManager::stopAnimationOnNode(const std::string& nodeName, const std::string& animName) { bool UiManager::stopAnimationOnNode(const std::string& nodeName, const std::string& animName) {
if (!root) return false; if (!root) return false;
auto node = findNodeByName(root, nodeName); auto node = findNodeByName(root, nodeName);
@@ -2042,6 +2094,13 @@ namespace ZL {
aa.origScaleX = n->textButton->animScaleX; aa.origScaleX = n->textButton->animScaleX;
aa.origScaleY = n->textButton->animScaleY; aa.origScaleY = n->textButton->animScaleY;
} }
else
{
//logger() << "[animation] startAnimation: " << n->name << " y=" << n->animOffsetY << std::endl;
aa.origOffsetX = n->animOffsetX;
aa.origOffsetY = n->animOffsetY;
}
auto cbIt = animCallbacks.find({ n->name, animName }); auto cbIt = animCallbacks.find({ n->name, animName });
if (cbIt != animCallbacks.end()) aa.onComplete = cbIt->second; if (cbIt != animCallbacks.end()) aa.onComplete = cbIt->second;
nodeActiveAnims[n].push_back(std::move(aa)); nodeActiveAnims[n].push_back(std::move(aa));
+5 -2
View File
@@ -187,7 +187,7 @@ namespace ZL {
float animScaleY = 1.0f; float animScaleY = 1.0f;
void buildMesh(); void buildMesh();
void draw(Renderer& renderer) const; void draw(Renderer& renderer, float accumX = 0.0f, float accumY = 0.0f) const;
}; };
struct UiTextView { struct UiTextView {
@@ -206,7 +206,7 @@ namespace ZL {
std::unique_ptr<TextRenderer> textRenderer; std::unique_ptr<TextRenderer> textRenderer;
void draw(Renderer& renderer) const; void draw(Renderer& renderer, float accumX = 0.0f, float accumY = 0.0f) const;
}; };
struct UiTextField { struct UiTextField {
@@ -313,6 +313,8 @@ namespace ZL {
float scaleX = 1.0f; float scaleX = 1.0f;
float scaleY = 1.0f; float scaleY = 1.0f;
float animOffsetX = 0.0f;
float animOffsetY = 0.0f;
bool visible = true; bool visible = true;
@@ -475,6 +477,7 @@ namespace ZL {
void update(float deltaMs); void update(float deltaMs);
void startAnimation(const std::string& animName); void startAnimation(const std::string& animName);
bool startAnimationOnNode(const std::string& nodeName, const std::string& animName); bool startAnimationOnNode(const std::string& nodeName, const std::string& animName);
bool resetAnimationOnNode(const std::string& nodeName);
bool stopAnimationOnNode(const std::string& nodeName, const std::string& animName); bool stopAnimationOnNode(const std::string& nodeName, const std::string& animName);
bool setAnimationCallback(const std::string& nodeName, const std::string& animName, std::function<void()> cb); bool setAnimationCallback(const std::string& nodeName, const std::string& animName, std::function<void()> cb);
void updateAllLayouts(); void updateAllLayouts();
+3 -2
View File
@@ -3,6 +3,7 @@
#include <algorithm> #include <algorithm>
#include <cmath> #include <cmath>
#include <iostream> #include <iostream>
#include "utils/Utils.h"
namespace ZL::Cutscene { namespace ZL::Cutscene {
@@ -68,7 +69,7 @@ bool CutsceneRuntime::start(const std::string& cutsceneId) {
onLineStarted(firstLine.luaCallback); onLineStarted(firstLine.luaCallback);
} }
std::cout << "[CUTSCENE] start id=" << cutsceneId logger() << "[CUTSCENE] start id=" << cutsceneId
<< " lines=" << def->lines.size() << " lines=" << def->lines.size()
<< " totalDuration=" << cutsceneTotalDurationMs << " totalDuration=" << cutsceneTotalDurationMs
<< std::endl; << std::endl;
@@ -176,7 +177,7 @@ void CutsceneRuntime::skip() {
} }
void CutsceneRuntime::finish() { void CutsceneRuntime::finish() {
std::cout << "[CUTSCENE] finish id=" << activeCutsceneId << std::endl; logger() << "[CUTSCENE] finish id=" << activeCutsceneId << std::endl;
const std::string finishedId = activeCutsceneId; const std::string finishedId = activeCutsceneId;
stop(); stop();
if (onFinished && !finishedId.empty()) { if (onFinished && !finishedId.empty()) {
+14 -13
View File
@@ -9,6 +9,7 @@
#include "../Character.h" #include "../Character.h"
#include <SDL.h> #include <SDL.h>
namespace ZL { namespace ZL {
using json = nlohmann::json; using json = nlohmann::json;
@@ -73,7 +74,7 @@ namespace ZL {
objects.push_back(std::move(data)); objects.push_back(std::move(data));
} }
std::cout << "Loaded " << objects.size() << " static objects from " << jsonPath << std::endl; logger() << "Loaded " << objects.size() << " static objects from " << jsonPath << std::endl;
return objects; return objects;
} }
@@ -115,7 +116,7 @@ namespace ZL {
objects.push_back(std::move(data)); objects.push_back(std::move(data));
} }
std::cout << "Loaded " << objects.size() << " interactive objects from " << jsonPath << std::endl; logger() << "Loaded " << objects.size() << " interactive objects from " << jsonPath << std::endl;
return objects; return objects;
} }
@@ -199,7 +200,7 @@ namespace ZL {
// Service the OS message queue between loads so the window doesn't // Service the OS message queue between loads so the window doesn't
// get flagged as "not responding" during long location loads. // get flagged as "not responding" during long location loads.
SDL_PumpEvents(); SDL_PumpEvents();
std::cout << "Loading game object: " << data.name << std::endl; logger() << "Loading game object: " << data.name << std::endl;
try { try {
LoadedGameObject obj = buildLoadedObject(data, renderer, zipPath); LoadedGameObject obj = buildLoadedObject(data, renderer, zipPath);
@@ -208,13 +209,13 @@ namespace ZL {
obj.mesh.RefreshVBO(); obj.mesh.RefreshVBO();
gameObjects[data.name] = std::move(obj); gameObjects[data.name] = std::move(obj);
std::cout << "Successfully loaded: " << data.name << std::endl; logger() << "Successfully loaded: " << data.name << std::endl;
} catch (const std::exception& e) { } catch (const std::exception& e) {
std::cerr << "GameObjectLoader: Failed to load '" << data.name << "': " << e.what() << std::endl; std::cerr << "GameObjectLoader: Failed to load '" << data.name << "': " << e.what() << std::endl;
} }
} }
std::cout << "Total game objects loaded: " << gameObjects.size() << std::endl; logger() << "Total game objects loaded: " << gameObjects.size() << std::endl;
return gameObjects; return gameObjects;
} }
@@ -230,17 +231,17 @@ namespace ZL {
for (const auto& data : loadInteractiveFromJson(jsonPath, zipPath)) { for (const auto& data : loadInteractiveFromJson(jsonPath, zipPath)) {
SDL_PumpEvents(); SDL_PumpEvents();
std::cout << "Loading interactive object: " << data.base.name << std::endl; logger() << "Loading interactive object: " << data.base.name << std::endl;
try { try {
InteractiveObject intObj = InteractiveObject::createFromState( InteractiveObject intObj = InteractiveObject::createFromState(
data.toInteractiveObjectState(), renderer, zipPath); data.toInteractiveObjectState(), renderer, zipPath);
intObj.loadedObject.name = data.base.name; intObj.loadedObject.name = data.base.name;
if (!data.activateFunctionName.empty()) if (!data.activateFunctionName.empty())
std::cout << "Successfully loaded interactive: " << data.base.name logger() << "Successfully loaded interactive: " << data.base.name
<< " (function: " << data.activateFunctionName << ")" << std::endl; << " (function: " << data.activateFunctionName << ")" << std::endl;
else else
std::cout << "Successfully loaded interactive: " << data.base.name << std::endl; logger() << "Successfully loaded interactive: " << data.base.name << std::endl;
interactiveObjects.push_back(std::move(intObj)); interactiveObjects.push_back(std::move(intObj));
} catch (const std::exception& e) { } catch (const std::exception& e) {
@@ -248,7 +249,7 @@ namespace ZL {
} }
} }
std::cout << "Total interactive objects loaded: " << interactiveObjects.size() << std::endl; logger() << "Total interactive objects loaded: " << interactiveObjects.size() << std::endl;
return interactiveObjects; return interactiveObjects;
} }
@@ -301,7 +302,7 @@ namespace ZL {
npcs.push_back(std::move(data)); npcs.push_back(std::move(data));
} }
std::cout << "Successfully loaded " << npcs.size() << " NPCs from " << jsonPath << std::endl; logger() << "Successfully loaded " << npcs.size() << " NPCs from " << jsonPath << std::endl;
} catch (const std::exception& e) { } catch (const std::exception& e) {
std::cerr << "Error loading NPCs from JSON: " << e.what() << std::endl; std::cerr << "Error loading NPCs from JSON: " << e.what() << std::endl;
} }
@@ -369,11 +370,11 @@ namespace ZL {
for (const auto& npcData : loadNpcsFromJson(jsonPath, zipPath)) { for (const auto& npcData : loadNpcsFromJson(jsonPath, zipPath)) {
SDL_PumpEvents(); SDL_PumpEvents();
std::cout << "Loading NPC: " << npcData.name << std::endl; logger() << "Loading NPC: " << npcData.name << std::endl;
try { try {
auto npc = Character::createFromState(npcData.toCharacterState(), renderer, zipPath.c_str()); auto npc = Character::createFromState(npcData.toCharacterState(), renderer, zipPath.c_str());
if (npc) { if (npc) {
std::cout << "Successfully loaded NPC: " << npcData.name << " at (" logger() << "Successfully loaded NPC: " << npcData.name << " at ("
<< npcData.positionX << ", " << npcData.positionY << ", " << npcData.positionZ << ")" << std::endl; << npcData.positionX << ", " << npcData.positionY << ", " << npcData.positionZ << ")" << std::endl;
npc->index = index; npc->index = index;
npcs.push_back(std::move(npc)); npcs.push_back(std::move(npc));
@@ -384,7 +385,7 @@ namespace ZL {
} }
} }
std::cout << "Total NPCs loaded: " << npcs.size() << std::endl; logger() << "Total NPCs loaded: " << npcs.size() << std::endl;
return npcs; return npcs;
} }
+3 -2
View File
@@ -3,19 +3,20 @@
#include "external/nlohmann/json.hpp" #include "external/nlohmann/json.hpp"
#include <algorithm> #include <algorithm>
#include <iostream> #include <iostream>
#include "../utils/Utils.h"
namespace ZL { namespace ZL {
void Inventory::addItem(const std::string& itemId) { void Inventory::addItem(const std::string& itemId) {
itemIds.push_back(itemId); itemIds.push_back(itemId);
std::cout << "Item added to inventory: " << itemId << std::endl; logger() << "Item added to inventory: " << itemId << std::endl;
if (onItemAdded) onItemAdded(itemId); if (onItemAdded) onItemAdded(itemId);
} }
void Inventory::removeItem(const std::string& itemId) { void Inventory::removeItem(const std::string& itemId) {
auto it = std::find(itemIds.begin(), itemIds.end(), itemId); auto it = std::find(itemIds.begin(), itemIds.end(), itemId);
if (it != itemIds.end()) { if (it != itemIds.end()) {
std::cout << "Item removed from inventory: " << itemId << std::endl; logger() << "Item removed from inventory: " << itemId << std::endl;
if (onItemRemoved) onItemRemoved(itemId); if (onItemRemoved) onItemRemoved(itemId);
itemIds.erase(it); itemIds.erase(it);
} }
+1 -1
View File
@@ -48,7 +48,7 @@ void ItemRegistry::loadFromJson(const std::string& jsonPath, const std::string&
items_[id] = std::move(item); items_[id] = std::move(item);
} }
std::cout << "[ItemRegistry] Loaded " << items_.size() << " items from " << jsonPath << "\n"; logger() << "[ItemRegistry] Loaded " << items_.size() << " items from " << jsonPath << "\n";
} }
const Item* ItemRegistry::findById(const std::string& id) const { const Item* ItemRegistry::findById(const std::string& id) const {
+127 -61
View File
@@ -11,29 +11,23 @@
#include <emscripten/html5.h> #include <emscripten/html5.h>
#endif #endif
#ifdef STEAMSDK
// For Emscripten the game is heap-allocated so it can be destroyed and #include "steam_api.h"
// re-created when the WebGL context is lost and restored (e.g. fullscreen).
// For Android and Desktop a plain global value is used (no context loss).
#ifdef EMSCRIPTEN
ZL::Game* g_game = nullptr;
//static SDL_Window* win_x = nullptr;
static SDL_GLContext glContext_x; // Не указатель, а сам объект (который суть void*)
#else
ZL::Game game;
#endif #endif
#ifdef EMSCRIPTEN #if defined(EMSCRIPTEN)
static SDL_GLContext glContext_x;
#endif
// Единый указатель на объект игры для всех платформ
ZL::Game* g_game = nullptr;
void MainLoop() { void MainLoop() {
g_game->update(); g_game->update();
} }
#else
void MainLoop() {
game.update();
}
#endif
#ifdef EMSCRIPTEN #ifdef EMSCRIPTEN
@@ -73,7 +67,7 @@ static void applyResize(int logicalW, int logicalH) {
e.window.data2 = physicalH; e.window.data2 = physicalH;
SDL_PushEvent(&e); SDL_PushEvent(&e);
std::cout << "Resized, new size: " << logicalW << "x" << logicalH logger() << "Resized, new size: " << logicalW << "x" << logicalH
<< " (physical: " << physicalW << "x" << physicalH << " (physical: " << physicalW << "x" << physicalH
<< ", DPR: " << dpr << ")" << std::endl; << ", DPR: " << dpr << ")" << std::endl;
} }
@@ -212,7 +206,8 @@ extern "C" int SDL_main(int argc, char* argv[]) {
// Настройка игры // Настройка игры
try { try {
game.setup(); game.setup();
} catch (const std::exception &e) { }
catch (const std::exception& e) {
__android_log_print(ANDROID_LOG_ERROR, "Game", "Game setup failed: %s", e.what()); __android_log_print(ANDROID_LOG_ERROR, "Game", "Game setup failed: %s", e.what());
return 1; return 1;
} }
@@ -231,12 +226,62 @@ extern "C" int SDL_main(int argc, char* argv[]) {
#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__linux__) #if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__linux__)
int main(int argc, char *argv[]) { // 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 = ZL::readSavedTextFile("settings.json");
if (!settingsContent.empty()) {
try {
const nlohmann::json settingsRoot = nlohmann::json::parse(settingsContent);
if (settingsRoot.contains("fullscreen")) {
ZL::Environment::isFullscreen = settingsRoot["fullscreen"].get<bool>();
}
if (settingsRoot.contains("isHighDPIEnabled")) {
ZL::Environment::isHighDPIEnabled = settingsRoot["isHighDPIEnabled"].get<bool>();
}
if (settingsRoot.contains("enableLogging")) {
ZL::Environment::enableLogging = settingsRoot["enableLogging"].get<bool>();
}
}
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;
try try
{ {
readSettings();
if (ZL::Environment::enableLogging)
{
ZL::initLogger();
}
ZL::logger() << "Log started!" << std::endl;
#ifdef STEAMSDK
// Передаем явный App ID демо-версии
if (SteamAPI_RestartAppIfNecessary(4945840)) {
return 0;
}
if (!SteamAPI_Init()) {
std::cerr << "SteamAPI_Init() failed! Steam client might not be running.\n";
return 1;
}
#endif
SDL_SetHint(SDL_HINT_WINDOWS_DPI_AWARENESS, "permonitorv2"); SDL_SetHint(SDL_HINT_WINDOWS_DPI_AWARENESS, "permonitorv2");
//SDL_SetHint(SDL_HINT_WINDOWS_DPI_SCALING, "1");
constexpr int CONST_WIDTH = ZL::Environment::CONST_DEFAULT_WIDTH; constexpr int CONST_WIDTH = ZL::Environment::CONST_DEFAULT_WIDTH;
constexpr int CONST_HEIGHT = ZL::Environment::CONST_DEFAULT_HEIGHT; constexpr int CONST_HEIGHT = ZL::Environment::CONST_DEFAULT_HEIGHT;
@@ -252,36 +297,14 @@ int main(int argc, char *argv[]) {
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
// Read the persisted fullscreen preference (if any) before creating the
// window, so it opens directly in the right mode instead of flashing
// windowed -> fullscreen once settings are loaded later during setupPart2().
{
const std::string settingsContent = ZL::readSavedTextFile("settings.json");
if (!settingsContent.empty()) {
try {
const nlohmann::json settingsRoot = nlohmann::json::parse(settingsContent);
if (settingsRoot.contains("fullscreen")) {
ZL::Environment::isFullscreen = settingsRoot["fullscreen"].get<bool>();
}
if (settingsRoot.contains("isHighDPIEnabled")) {
ZL::Environment::isHighDPIEnabled = settingsRoot["isHighDPIEnabled"].get<bool>();
}
} catch (const std::exception& e) {
std::cerr << "[settings] Failed to parse settings.json: " << e.what() << std::endl;
}
}
}
Uint32 windowFlags = SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN | SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_RESIZABLE; Uint32 windowFlags = SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN | SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_RESIZABLE;
if (ZL::Environment::isFullscreen) { if (ZL::Environment::isFullscreen) {
// windowFlags |= SDL_WINDOW_FULLSCREEN_DESKTOP; windowFlags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
} }
ZL::Environment::isFullscreen = false;
SDL_DisplayMode dm; SDL_DisplayMode dm;
if (SDL_GetCurrentDisplayMode(0, &dm) == 0) { if (SDL_GetCurrentDisplayMode(0, &dm) == 0) {
std::cout << "Desktop resolution: " << dm.w << "x" << dm.h << " @ " << dm.refresh_rate << "Hz\n"; ZL::logger() << "Desktop resolution: " << dm.w << "x" << dm.h << " @ " << dm.refresh_rate << "Hz\n";
} }
else { else {
SDL_Log("SDL_GetCurrentDisplayMode failed: %s", SDL_GetError()); SDL_Log("SDL_GetCurrentDisplayMode failed: %s", SDL_GetError());
@@ -290,54 +313,97 @@ int main(int argc, char *argv[]) {
int windowWidth = ZL::Environment::width; int windowWidth = ZL::Environment::width;
int windowHeight = ZL::Environment::height; int windowHeight = ZL::Environment::height;
//In case if the game is launched on a small screen
if (dm.w <= 1280 && dm.h <= 720) { if (dm.w <= 1280 && dm.h <= 720) {
windowWidth = round(dm.w / 1.5); windowWidth = round(dm.w / 1.5);
windowHeight = round(dm.h / 1.5); windowHeight = round(dm.h / 1.5);
} }
ZL::Environment::window = SDL_CreateWindow( ZL::Environment::window = SDL_CreateWindow(
"Shadow Over Bishkek", "Shadow Over Bishkek Demo",
SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
windowWidth, windowHeight, windowWidth, windowHeight,
windowFlags windowFlags
); );
SDL_GLContext ctx = SDL_GL_CreateContext(ZL::Environment::window); ctx = SDL_GL_CreateContext(ZL::Environment::window);
SDL_GL_MakeCurrent(ZL::Environment::window, ctx); SDL_GL_MakeCurrent(ZL::Environment::window, ctx);
// On HiDPI displays the drawable (physical) size is larger than the if (ZL::Environment::isHighDPIEnabled) {
// logical window size. Use the drawable size so the GL framebuffer
// covers every physical pixel and the image stays sharp.
if (ZL::Environment::isHighDPIEnabled)
{
int drawW, drawH; int drawW, drawH;
SDL_GL_GetDrawableSize(ZL::Environment::window, &drawW, &drawH); SDL_GL_GetDrawableSize(ZL::Environment::window, &drawW, &drawH);
ZL::Environment::width = drawW; ZL::Environment::width = drawW;
ZL::Environment::height = drawH; ZL::Environment::height = drawH;
std::cout << "HiDPI enabled, drawable size: " << drawW << "x" << drawH << std::endl; ZL::logger() << "HiDPI enabled, drawable size: " << drawW << "x" << drawH << std::endl;
} }
else else {
{
int winW, winH; int winW, winH;
SDL_GetWindowSize(ZL::Environment::window, &winW, &winH); SDL_GetWindowSize(ZL::Environment::window, &winW, &winH);
ZL::Environment::width = winW; ZL::Environment::width = winW;
ZL::Environment::height = winH; ZL::Environment::height = winH;
std::cout << "HiDPI disabled, window size: " << winW << "x" << winH << std::endl; ZL::logger() << "HiDPI disabled, window size: " << winW << "x" << winH << std::endl;
} }
game.setup(); // Динамическое создание объекта игры
g_game = new ZL::Game();
g_game->setup();
while (!game.shouldExit()) { while (!g_game->shouldExit()) {
game.update(); g_game->update();
SDL_Delay(2); SDL_Delay(2);
#ifdef STEAMSDK
SteamAPI_RunCallbacks();
#endif
} }
//ZL::logger() << "Quitting step 1: " << std::endl;
// Явное уничтожение объекта игры ДО завершения работы Steamworks
delete g_game;
//ZL::logger() << "Quitting step 2: " << std::endl;
g_game = nullptr;
} }
catch (const std::exception& e) catch (const std::exception& e)
{ {
std::cout << e.what() << std::endl; std::cerr << "Exception caught: " << e.what() << std::endl;
if (g_game) {
delete g_game;
g_game = nullptr;
}
}
catch (...)
{
std::cerr << "Unknown exception happened: " << std::endl;
if (g_game) {
delete g_game;
g_game = nullptr;
}
} }
//ZL::logger() << "Quitting step 3: " << std::endl;
#ifdef STEAMSDK
// Деинициализация Steamworks происходит только тогда, когда все объекты игры гарантированно уничтожены
SteamAPI_Shutdown();
#endif
//ZL::logger() << "Quitting step 4: " << std::endl;
// Очистка ресурсов SDL
if (ctx) {
//ZL::logger() << "Quitting step 5: " << std::endl;
SDL_GL_DeleteContext(ctx);
}
//ZL::logger() << "Quitting step 6: " << std::endl;
if (ZL::Environment::window) {
//ZL::logger() << "Quitting step 7: " << std::endl;
SDL_DestroyWindow(ZL::Environment::window);
}
//ZL::logger() << "Quitting step 8: " << std::endl;
#ifndef EMSCRIPTEN
// In Emscripten, SDL must stay alive across context loss/restore cycles
// so the window remains valid when the game object is re-created.
SDL_Quit();
#endif
//ZL::logger() << "Quitting step 9: " << std::endl;
return 0; return 0;
} }
#endif #endif
+2 -2
View File
@@ -100,7 +100,7 @@ void PathFinder::build(const std::string& configPath,
if (isTxt) { if (isTxt) {
if (loadGrid(configPath, zipPath)) { if (loadGrid(configPath, zipPath)) {
ready = true; ready = true;
std::cout << "[nav] Loaded precomputed grid: " << gridWidth << "x" << gridDepth logger() << "[nav] Loaded precomputed grid: " << gridWidth << "x" << gridDepth
<< ", cell=" << cellSize << "\n"; << ", cell=" << cellSize << "\n";
} else { } else {
std::cerr << "[nav] Failed to load precomputed grid: " << configPath << "\n"; std::cerr << "[nav] Failed to load precomputed grid: " << configPath << "\n";
@@ -120,7 +120,7 @@ void PathFinder::build(const std::string& configPath,
rebuildWalkableGrid(); rebuildWalkableGrid();
ready = true; ready = true;
std::cout << "[nav] Polygon grid built: " << gridWidth << "x" << gridDepth logger() << "[nav] Polygon grid built: " << gridWidth << "x" << gridDepth
<< ", cell=" << cellSize << ", areas=" << areas.size() << "\n"; << ", cell=" << cellSize << ", areas=" << areas.size() << "\n";
} }
+2 -2
View File
@@ -329,7 +329,7 @@ namespace ZL {
size_t error = glGetError(); size_t error = glGetError();
if (error != GL_NO_ERROR) if (error != GL_NO_ERROR)
{ {
std::cout << "OpenGL error: " << error << std::endl; logger() << "OpenGL error: " << error << std::endl;
throw std::runtime_error("Gl error"); throw std::runtime_error("Gl error");
} }
} }
@@ -338,7 +338,7 @@ namespace ZL {
size_t error = glGetError(); size_t error = glGetError();
if (error != GL_NO_ERROR) if (error != GL_NO_ERROR)
{ {
std::cout << "OpenGL error: " << error << " happened in file: " << file << " at line: " << line << std::endl; logger() << "OpenGL error: " << error << " happened in file: " << file << " at line: " << line << std::endl;
throw std::runtime_error("Gl error"); throw std::runtime_error("Gl error");
} }
} }
+13 -13
View File
@@ -10,7 +10,7 @@ namespace ZL {
ShaderResource::ShaderResource(const std::string &vertexCode, const std::string &fragmentCode) { ShaderResource::ShaderResource(const std::string &vertexCode, const std::string &fragmentCode) {
std::cout << "Started creating shader resource" << std::endl; logger() << "Started creating shader resource" << std::endl;
const int CONST_INFOLOG_LENGTH = 256; const int CONST_INFOLOG_LENGTH = 256;
@@ -45,7 +45,7 @@ namespace ZL {
glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &fragmentShaderCompiled); glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &fragmentShaderCompiled);
glGetShaderInfoLog(fragmentShader, CONST_INFOLOG_LENGTH, &infoLogLength2, infoLog2); glGetShaderInfoLog(fragmentShader, CONST_INFOLOG_LENGTH, &infoLogLength2, infoLog2);
std::cout << "Creating shader resource step 1" << std::endl; //logger() << "Creating shader resource step 1" << std::endl;
if (!vertexShaderCompiled) { if (!vertexShaderCompiled) {
@@ -53,11 +53,11 @@ namespace ZL {
__android_log_print(ANDROID_LOG_ERROR, "ShaderManager", __android_log_print(ANDROID_LOG_ERROR, "ShaderManager",
"Failed to compile vertex shader: %s", infoLog); "Failed to compile vertex shader: %s", infoLog);
#endif #endif
std::cout << "Vertex shader compilation failed: " << infoLog << std::endl; logger() << "Vertex shader compilation failed: " << infoLog << std::endl;
throw std::runtime_error("Failed to compile vertex shader code!"); throw std::runtime_error("Failed to compile vertex shader code!");
} }
std::cout << "Creating shader resource step 2" << std::endl; //logger() << "Creating shader resource step 2" << std::endl;
if (!fragmentShaderCompiled) { if (!fragmentShaderCompiled) {
@@ -65,12 +65,12 @@ namespace ZL {
__android_log_print(ANDROID_LOG_ERROR, "ShaderManager", __android_log_print(ANDROID_LOG_ERROR, "ShaderManager",
"Failed to compile fragment shader: %s", infoLog); "Failed to compile fragment shader: %s", infoLog);
#endif #endif
std::cout << "Fragment shader compilation failed: " << infoLog << std::endl; logger() << "Fragment shader compilation failed: " << infoLog << std::endl;
throw std::runtime_error("Failed to compile fragment shader code!"); throw std::runtime_error("Failed to compile fragment shader code!");
} }
std::cout << "Creating shader resource step 4" << std::endl; //logger() << "Creating shader resource step 4" << std::endl;
shaderProgram = glCreateProgram(); shaderProgram = glCreateProgram();
@@ -86,7 +86,7 @@ namespace ZL {
glGetProgramiv(shaderProgram, GL_LINK_STATUS, &programLinked); glGetProgramiv(shaderProgram, GL_LINK_STATUS, &programLinked);
glGetProgramInfoLog(shaderProgram, CONST_INFOLOG_LENGTH, &infoLogLength, infoLog); glGetProgramInfoLog(shaderProgram, CONST_INFOLOG_LENGTH, &infoLogLength, infoLog);
std::cout << "Creating shader resource step 4" << std::endl; //logger() << "Creating shader resource step 4" << std::endl;
if (!programLinked) { if (!programLinked) {
shaderProgram = 0; shaderProgram = 0;
@@ -94,11 +94,11 @@ namespace ZL {
__android_log_print(ANDROID_LOG_ERROR, "ShaderManager", __android_log_print(ANDROID_LOG_ERROR, "ShaderManager",
"Failed to link shader program: %s", infoLog); "Failed to link shader program: %s", infoLog);
#endif #endif
std::cout << "Failed to link shader program: " << infoLog << std::endl; logger() << "Failed to link shader program: " << infoLog << std::endl;
throw std::runtime_error("Failed to link shader program!"); throw std::runtime_error("Failed to link shader program!");
} }
std::cout << "Creating shader resource step 5" << std::endl; //logger() << "Creating shader resource step 5" << std::endl;
//================= Parsing all uniforms ================ //================= Parsing all uniforms ================
@@ -121,7 +121,7 @@ namespace ZL {
uniformList[uniformName] = glGetUniformLocation(shaderProgram, uniformName); uniformList[uniformName] = glGetUniformLocation(shaderProgram, uniformName);
} }
std::cout << "Creating shader resource step 6" << std::endl; //logger() << "Creating shader resource step 6" << std::endl;
//================= Parsing all attributes ================ //================= Parsing all attributes ================
@@ -138,7 +138,7 @@ namespace ZL {
attribList[attribName] = glGetAttribLocation(shaderProgram, attribName); attribList[attribName] = glGetAttribLocation(shaderProgram, attribName);
} }
std::cout << "Creating shader resource step 7" << std::endl; //logger() << "Creating shader resource step 7" << std::endl;
#ifdef __ANDROID__ #ifdef __ANDROID__
@@ -173,7 +173,7 @@ namespace ZL {
"Fragment shader: %s", fragmentShaderFileName.c_str()); "Fragment shader: %s", fragmentShaderFileName.c_str());
#endif #endif
std::cout <<"Loading shader: " << shaderName << " " << vertexShaderFileName << " " << fragmentShaderFileName <<std::endl; logger() <<"Loading shader: " << shaderName << " " << vertexShaderFileName << " " << fragmentShaderFileName <<std::endl;
std::string vertexShader; std::string vertexShader;
std::string fragmentShader; std::string fragmentShader;
@@ -204,7 +204,7 @@ namespace ZL {
} }
if (shaderResourceMap.find(shaderName) == shaderResourceMap.end()) { if (shaderResourceMap.find(shaderName) == shaderResourceMap.end()) {
std::cout << "Shader does not exist: " << shaderName << std::endl; logger() << "Shader does not exist: " << shaderName << std::endl;
throw std::runtime_error("Shader does not exist!"); throw std::runtime_error("Shader does not exist!");
} }
+5 -5
View File
@@ -62,7 +62,7 @@ namespace ZL
} }
} }
} }
std::cout << "TextureManager: resized texture from " logger() << "TextureManager: resized texture from "
<< src.width << "x" << src.height << src.width << "x" << src.height
<< " to " << newW << "x" << newH << " (POT)" << std::endl; << " to " << newW << "x" << newH << " (POT)" << std::endl;
return dst; return dst;
@@ -353,13 +353,13 @@ namespace ZL
} }
void user_warning_fn(png_structp png_ptr, png_const_charp warning_msg) { void user_warning_fn(png_structp png_ptr, png_const_charp warning_msg) {
std::cout << "PNG Warning: " << warning_msg << std::endl; logger() << "PNG Warning: " << warning_msg << std::endl;
} }
void user_error_fn(png_structp png_ptr, png_const_charp error_msg) { void user_error_fn(png_structp png_ptr, png_const_charp error_msg) {
std::cout << "PNG Error: " << error_msg << std::endl; logger() << "PNG Error: " << error_msg << std::endl;
longjmp(png_jmpbuf(png_ptr), 1); longjmp(png_jmpbuf(png_ptr), 1);
} }
@@ -474,7 +474,7 @@ namespace ZL
fileArr = !ZIPFileName.empty() ? readFileFromZIP(fullFileName, ZIPFileName) : readFile(fullFileName); fileArr = !ZIPFileName.empty() ? readFileFromZIP(fullFileName, ZIPFileName) : readFile(fullFileName);
if (fileArr.empty()) { if (fileArr.empty()) {
std::cout << "Could not read file data into memory: " << fullFileName << " zip file name: " << ZIPFileName << std::endl; logger() << "Could not read file data into memory: " << fullFileName << " zip file name: " << ZIPFileName << std::endl;
throw std::runtime_error("Could not read file data into memory"); throw std::runtime_error("Could not read file data into memory");
} }
@@ -539,7 +539,7 @@ namespace ZL
{ {
for (auto it = textureMap.begin(); it != textureMap.end(); ) { for (auto it = textureMap.begin(); it != textureMap.end(); ) {
if (it->first.substr(0, prefix.size()) == prefix) { if (it->first.substr(0, prefix.size()) == prefix) {
std::cout << "Unloading texture with prefix: " << it->first << std::endl; logger() << "Unloading texture with prefix: " << it->first << std::endl;
it = textureMap.erase(it); it = textureMap.erase(it);
} }
else { else {
+91 -4
View File
@@ -11,6 +11,7 @@
#include <string> #include <string>
#include <cctype> #include <cctype>
#include <SDL.h> #include <SDL.h>
#include "../Environment.h"
#ifdef __ANDROID__ #ifdef __ANDROID__
#include <android/log.h> #include <android/log.h>
@@ -108,7 +109,7 @@ namespace ZL
int zipErr = 0; int zipErr = 0;
zip_t* archive = zip_open(zipfilename.c_str(), ZIP_RDONLY, &zipErr); zip_t* archive = zip_open(zipfilename.c_str(), ZIP_RDONLY, &zipErr);
if (!archive) { if (!archive) {
std::cout << "Failed to open ZIP: " << zipfilename << " (error code " << zipErr << ")" << std::endl; logger() << "Failed to open ZIP: " << zipfilename << " (error code " << zipErr << ")" << std::endl;
throw std::runtime_error("Failed to open ZIP: " + zipfilename); throw std::runtime_error("Failed to open ZIP: " + zipfilename);
} }
@@ -119,14 +120,14 @@ namespace ZL
zip_stat_init(&fileStat); zip_stat_init(&fileStat);
if (zip_stat(archive, cleanFilename.c_str(), 0, &fileStat) != 0 || !(fileStat.valid & ZIP_STAT_SIZE)) { if (zip_stat(archive, cleanFilename.c_str(), 0, &fileStat) != 0 || !(fileStat.valid & ZIP_STAT_SIZE)) {
zip_close(archive); zip_close(archive);
std::cout << "Failed to stat file in ZIP: " << cleanFilename << " in " << zipfilename << std::endl; logger() << "Failed to stat file in ZIP: " << cleanFilename << " in " << zipfilename << std::endl;
throw std::runtime_error("Can't stat file in ZIP: " + cleanFilename); throw std::runtime_error("Can't stat file in ZIP: " + cleanFilename);
} }
zip_file_t* zipFile = zip_fopen(archive, cleanFilename.c_str(), 0); zip_file_t* zipFile = zip_fopen(archive, cleanFilename.c_str(), 0);
if (!zipFile) { if (!zipFile) {
zip_close(archive); zip_close(archive);
std::cout << "Failed to open file in ZIP: " << cleanFilename << std::endl; logger() << "Failed to open file in ZIP: " << cleanFilename << std::endl;
throw std::runtime_error("Can't open file in ZIP: " + cleanFilename); throw std::runtime_error("Can't open file in ZIP: " + cleanFilename);
} }
@@ -138,7 +139,7 @@ namespace ZL
zip_close(archive); zip_close(archive);
if (bytesRead < 0 || static_cast<zip_uint64_t>(bytesRead) != fileStat.size) { if (bytesRead < 0 || static_cast<zip_uint64_t>(bytesRead) != fileStat.size) {
std::cout << "Failed to read file from ZIP: " << cleanFilename << " (bytes read: " << bytesRead << ", expected: " << fileStat.size << ")" << std::endl; logger() << "Failed to read file from ZIP: " << cleanFilename << " (bytes read: " << bytesRead << ", expected: " << fileStat.size << ")" << std::endl;
throw std::runtime_error("Error reading data from ZIP: " + cleanFilename); throw std::runtime_error("Error reading data from ZIP: " + cleanFilename);
} }
@@ -348,4 +349,90 @@ namespace ZL
#endif #endif
} }
// --- Инфраструктура для подавления вывода (когда лог выключен) ---
class NullBuffer : public std::streambuf {
protected:
int overflow(int c) override { return c; }
};
class NullStream : public std::ostream {
NullBuffer buf;
public:
NullStream() : std::ostream(&buf) {}
};
// --- Инфраструктура для дублирования вывода (Tee-поток) ---
class TeeBuffer : public std::streambuf {
std::streambuf* out1; // Буфер cout
std::streambuf* out2; // Буфер файла
protected:
int overflow(int c) override {
if (c != EOF) {
out1->sputc(c); // Игнорируем потенциальный EOF от отключенной консоли
int r2 = out2->sputc(c);
if (r2 == EOF) return EOF; // Возвращаем ошибку только если сломалась запись в файл
}
return c;
}
int sync() override {
out1->pubsync(); // Игнорируем ошибку сброса консоли
int r2 = out2->pubsync();
return (r2 == -1) ? -1 : 0; // Сообщаем об ошибке потоку только если не удалось сбросить в файл
}
public:
TeeBuffer(std::streambuf* sb1, std::streambuf* sb2) : out1(sb1), out2(sb2) {}
};
// --- Глобальные переменные логгера ---
namespace {
std::ofstream g_logFile;
std::unique_ptr<TeeBuffer> g_teeBuffer;
std::unique_ptr<std::ostream> g_teeStream;
NullStream g_nullStream;
}
// --- Инициализация логгера ---
void initLogger() {
namespace fs = std::filesystem;
try {
fs::path dir = getSaveDirectory();
// Если директория не существует, ofstream не сможет создать файл,
// поэтому форсируем создание всей иерархии папок.
if (!fs::exists(dir)) {
fs::create_directories(dir);
}
fs::path logPath = dir / "log.txt";
// std::ios::trunc очищает файл при каждом новом запуске
g_logFile.open(logPath, std::ios::out | std::ios::trunc);
if (g_logFile.is_open()) {
g_teeBuffer = std::make_unique<TeeBuffer>(std::cout.rdbuf(), g_logFile.rdbuf());
g_teeStream = std::make_unique<std::ostream>(g_teeBuffer.get());
}
else {
std::cerr << "[logger] Could not open " << logPath << " for writing" << std::endl;
}
}
catch (const fs::filesystem_error& e) {
std::cerr << "[logger] Filesystem error during init: " << e.what() << std::endl;
}
}
// --- Сама функция логгера ---
std::ostream& logger() {
if (Environment::enableLogging) {
if (g_teeStream) {
return *g_teeStream;
}
// Фолбэк на стандартный вывод, если файл по какой-то причине не открылся
return std::cout;
}
// Если логирование отключено, возвращаем заглушку.
return g_nullStream;
}
}; };
+3
View File
@@ -35,4 +35,7 @@ namespace ZL
bool saveJsonToFile(const nlohmann::json& root, const std::string& filename); bool saveJsonToFile(const nlohmann::json& root, const std::string& filename);
void initLogger();
std::ostream& logger();
} }