Compare commits

..
57 changed files with 2213 additions and 1576 deletions

No files matched your search

+94 -13
View File
@@ -1,10 +1,17 @@
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_REQUIRED ON)
option(STEAMSDK "Enable Steamworks SDK integration" OFF)
# ===========================================
# Поиск системных зависимостей Linux
# ===========================================
@@ -62,9 +69,9 @@ endif()
# ===========================================
# Основной проект witcher001
# Основной проект ShadowOverBishkekDemo
# ===========================================
add_executable(witcher001
add_executable(ShadowOverBishkekDemo
../src/main.cpp
../src/Game.cpp
../src/Game.h
@@ -158,7 +165,7 @@ add_executable(witcher001
)
# include-пути проекта
target_include_directories(witcher001 PRIVATE
target_include_directories(ShadowOverBishkekDemo PRIVATE
"${CMAKE_CURRENT_SOURCE_DIR}/../src"
${SDL2_INCLUDE_DIRS}
${SDL2_TTF_INCLUDE_DIRS}
@@ -166,19 +173,18 @@ target_include_directories(witcher001 PRIVATE
${LIBZIP_INCLUDE_DIRS}
${LUA_INCLUDE_DIRS}
${EIGEN3_INCLUDE_DIRS}
# sol2 - header-only, берем напрямую из thirdparty (если он там есть)
# или можно установить системный libsol-dev и убрать этот путь
# sol2 - header-only, берем напрямую из thirdparty
"${CMAKE_CURRENT_SOURCE_DIR}/../thirdparty/sol2-3.3.0/include"
)
# Убираем WIN32_LEAN_AND_MEAN
target_compile_definitions(witcher001 PRIVATE
target_compile_definitions(ShadowOverBishkekDemo PRIVATE
PNG_ENABLED
SDL_MAIN_HANDLED
)
# Линковка директорий для pkg-config (если пакеты не в /usr/lib)
target_link_directories(witcher001 PRIVATE
target_link_directories(ShadowOverBishkekDemo PRIVATE
${SDL2_LIBRARY_DIRS}
${SDL2_TTF_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::GLU
ZLIB::ZLIB
@@ -202,6 +208,83 @@ target_link_libraries(witcher001 PRIVATE
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,13 +293,11 @@ set(RUNTIME_RESOURCE_DIRS
"audio"
)
# В Linux (при использовании Make/Ninja) бинарник обычно создается в CMAKE_BINARY_DIR.
# Копируем ресурсы рядом с исполняемым файлом.
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 copy_directory
"${CMAKE_SOURCE_DIR}/../${resdir}"
"${CMAKE_BINARY_DIR}/${resdir}"
)
endforeach()
endforeach()
+149 -82
View File
@@ -1,16 +1,19 @@
cmake_minimum_required(VERSION 3.16)
project(witcher001 LANGUAGES C CXX)
project(ShadowOverBishkekDemo LANGUAGES C CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Опция для интеграции Steamworks SDK (по умолчанию выключена)
option(STEAMSDK "Enable Steamworks SDK integration" OFF)
include(${CMAKE_CURRENT_SOURCE_DIR}/../cmake/ThirdParty.cmake)
# ===========================================
# Основной проект witcher001
# Основной проект ShadowOverBishkekDemo
# ===========================================
add_executable(witcher001
add_executable(ShadowOverBishkekDemo WIN32
../src/main.cpp
../src/Game.cpp
../src/Game.h
@@ -42,34 +45,34 @@ add_executable(witcher001
../src/SparkEmitter.h
../src/TeleportZone.h
../src/TeleportZone.cpp
../src/utils/TaskManager.cpp
../src/utils/TaskManager.h
../src/render/FrameBuffer.cpp
../src/render/FrameBuffer.h
../src/render/ShadowMap.cpp
../src/render/ShadowMap.h
../src/utils/TaskManager.cpp
../src/utils/TaskManager.h
../src/render/FrameBuffer.cpp
../src/render/FrameBuffer.h
../src/render/ShadowMap.cpp
../src/render/ShadowMap.h
../src/UiManager.cpp
../src/UiManager.h
../src/render/TextRenderer.h
../src/render/TextRenderer.cpp
../src/MenuManager.h
../src/MenuManager.cpp
../src/Location.h
../src/Location.cpp
../src/LocationState.h
../src/LocationState.cpp
../src/LocationEditor.h
../src/LocationEditor.cpp
../src/NpcCar.h
../src/NpcCar.cpp
../src/render/TextRenderer.h
../src/render/TextRenderer.cpp
../src/MenuManager.h
../src/MenuManager.cpp
../src/Location.h
../src/Location.cpp
../src/LocationState.h
../src/LocationState.cpp
../src/LocationEditor.h
../src/LocationEditor.cpp
../src/NpcCar.h
../src/NpcCar.cpp
../src/GameConstants.h
../src/GameConstants.cpp
../src/GameState.h
../src/GameState.cpp
../src/ScriptEngine.h
../src/ScriptEngine.cpp
../src/navigation/PathFinder.h
../src/navigation/PathFinder.cpp
../src/navigation/PathFinder.h
../src/navigation/PathFinder.cpp
../src/items/GameObjectLoader.h
../src/items/GameObjectLoader.cpp
../src/items/Item.h
@@ -79,73 +82,121 @@ add_executable(witcher001
../src/items/InteractiveObject.h
../src/items/InteractiveObject.cpp
../src/items/InteractiveObjectState.h
../src/dialogue/DialogueTypes.h
../src/dialogue/DialogueDatabase.h
../src/dialogue/DialogueDatabase.cpp
../src/dialogue/DialogueRuntime.h
../src/dialogue/DialogueRuntime.cpp
../src/dialogue/DialogueOverlay.h
../src/dialogue/DialogueOverlay.cpp
../src/dialogue/DialogueSystem.h
../src/dialogue/DialogueSystem.cpp
../src/dialogue/TranslationDatabase.h
../src/dialogue/TranslationDatabase.cpp
../src/quest/QuestTypes.h
../src/quest/QuestJournal.h
../src/quest/QuestJournal.cpp
../src/cutscene/CutsceneTypes.h
../src/cutscene/CutsceneDatabase.h
../src/cutscene/CutsceneDatabase.cpp
../src/cutscene/CutsceneRuntime.h
../src/cutscene/CutsceneRuntime.cpp
../src/cutscene/CutsceneOverlay.h
../src/cutscene/CutsceneOverlay.cpp
../src/render/UiQuad.h
../src/dialogue/DialogueTypes.h
../src/dialogue/DialogueDatabase.h
../src/dialogue/DialogueDatabase.cpp
../src/dialogue/DialogueRuntime.h
../src/dialogue/DialogueRuntime.cpp
../src/dialogue/DialogueOverlay.h
../src/dialogue/DialogueOverlay.cpp
../src/dialogue/DialogueSystem.h
../src/dialogue/DialogueSystem.cpp
../src/dialogue/TranslationDatabase.h
../src/dialogue/TranslationDatabase.cpp
../src/quest/QuestTypes.h
../src/quest/QuestJournal.h
../src/quest/QuestJournal.cpp
../src/cutscene/CutsceneTypes.h
../src/cutscene/CutsceneDatabase.h
../src/cutscene/CutsceneDatabase.cpp
../src/cutscene/CutsceneRuntime.h
../src/cutscene/CutsceneRuntime.cpp
../src/cutscene/CutsceneOverlay.h
../src/cutscene/CutsceneOverlay.cpp
../src/render/UiQuad.h
)
# Установка проекта по умолчанию для Visual Studio
set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT witcher001)
set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT ShadowOverBishkekDemo)
# include-пути проекта
target_include_directories(witcher001 PRIVATE
target_include_directories(ShadowOverBishkekDemo PRIVATE
"${CMAKE_CURRENT_SOURCE_DIR}/../src"
)
set_target_properties(witcher001 PROPERTIES
OUTPUT_NAME "witcher001"
set_target_properties(ShadowOverBishkekDemo PROPERTIES
OUTPUT_NAME "ShadowOverBishkekDemo"
)
# Определения препроцессора:
# PNG_ENABLED – включает код PNG в TextureManager
# SDL_MAIN_HANDLED – отключает переопределение main -> SDL_main
target_compile_definitions(witcher001 PRIVATE
target_compile_definitions(ShadowOverBishkekDemo PRIVATE
WIN32_LEAN_AND_MEAN
PNG_ENABLED
SDL_MAIN_HANDLED
# DEBUG_LIGHT
# SHOW_PATH
# SDL_MAIN_HANDLED
# DEBUG_LIGHT
# SHOW_PATH
)
# Линкуем с SDL2main, если он вообще установлен
target_link_libraries(witcher001 PRIVATE SDL2main_external_lib)
target_link_libraries(ShadowOverBishkekDemo PRIVATE SDL2main_external_lib)
# Линкуем сторонние библиотеки
target_link_libraries(witcher001 PRIVATE
target_link_libraries(ShadowOverBishkekDemo PRIVATE
SDL2_external_lib
libpng_external_lib
zlib_external_lib
libzip_external_lib
freetype_external_lib
SDL2_ttf_external_lib
freetype_external_lib
SDL2_ttf_external_lib
eigen_external_lib
boost_external_lib
boost_external_lib
sol2_external_lib
SDL2_mixer_external_lib
)
# Линкуем OpenGL (Windows)
if(WIN32)
target_link_libraries(witcher001 PRIVATE opengl32)
target_link_libraries(ShadowOverBishkekDemo PRIVATE opengl32)
endif()
# ===========================================
# Интеграция Steamworks SDK (если STEAMSDK=ON)
# ===========================================
if(STEAMSDK)
message(STATUS "Steamworks SDK integration is ENABLED.")
# Макрос STEAMSDK для условной компиляции в C++ коде (#ifdef STEAMSDK)
target_compile_definitions(ShadowOverBishkekDemo PRIVATE STEAMSDK)
set(STEAM_SDK_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../thirdparty/steamworks_sdk_164/sdk")
# Подключение заголовочных файлов Steamworks
target_include_directories(ShadowOverBishkekDemo PRIVATE
"${STEAM_SDK_DIR}/public"
"${STEAM_SDK_DIR}/public/steam"
)
# Определение разрядности сборки (x86 vs x64) для выбора версий lib и dll
if(CMAKE_SIZEOF_VOID_P EQUAL 8)
set(STEAM_LIB_DIR "${STEAM_SDK_DIR}/redistributable_bin/win64")
set(STEAM_LIB_NAME "steam_api64")
set(STEAM_DLL_NAME "steam_api64.dll")
else()
set(STEAM_LIB_DIR "${STEAM_SDK_DIR}/redistributable_bin")
set(STEAM_LIB_NAME "steam_api")
set(STEAM_DLL_NAME "steam_api.dll")
endif()
# Поиск библиотеки линковки (.lib)
find_library(STEAM_LIBRARY
NAMES ${STEAM_LIB_NAME}
PATHS ${STEAM_LIB_DIR}
NO_DEFAULT_PATH
)
if(STEAM_LIBRARY)
target_link_libraries(ShadowOverBishkekDemo PRIVATE ${STEAM_LIBRARY})
message(STATUS "Found Steam API library: ${STEAM_LIBRARY}")
else()
message(FATAL_ERROR "Steam API library (${STEAM_LIB_NAME}) NOT found in ${STEAM_LIB_DIR}!")
endif()
# Генерация временного файла steam_appid.txt с ID демо-версии (4945840)
file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/steam_appid.txt" "4945840")
else()
message(STATUS "Steamworks SDK integration is DISABLED.")
endif()
# ===========================================
@@ -155,22 +206,22 @@ if (WIN32)
# 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_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(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(SDL2TTF_DLL_SRC "$<IF:$<CONFIG:Debug>,${SDL2TTF_BASE_DIR}-Debug/bin/SDL2_ttfd.dll,${SDL2TTF_BASE_DIR}-Release/bin/SDL2_ttf.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: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(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..."
# Копируем SDL2 (целевое имя всегда SDL2.dll)
# Копируем SDL2
COMMAND ${CMAKE_COMMAND} -E copy_if_different
"${SDL2_DLL_SRC}"
"${SDL2_DLL_DST}"
@@ -178,23 +229,39 @@ add_custom_command(TARGET witcher001 POST_BUILD
# Копируем LIBZIP (целевое имя всегда zip.dll)
COMMAND ${CMAKE_COMMAND} -E copy_if_different
"${LIBZIP_DLL_SRC}"
"$<TARGET_FILE_DIR:witcher001>/zip.dll"
COMMAND ${CMAKE_COMMAND} -E copy_if_different
"$<TARGET_FILE_DIR:ShadowOverBishkekDemo>/zip.dll"
COMMAND ${CMAKE_COMMAND} -E copy_if_different
"${ZLIB_DLL_SRC}"
"${ZLIB_DLL_DST}"
COMMAND ${CMAKE_COMMAND} -E copy_if_different
"${SDL2TTF_DLL_SRC}"
"$<TARGET_FILE_DIR:witcher001>"
COMMAND ${CMAKE_COMMAND} -E copy_if_different
"${SDL2TTF_DLL_SRC}"
"$<TARGET_FILE_DIR:ShadowOverBishkekDemo>"
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"
# 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"
"$<TARGET_FILE_DIR:ShadowOverBishkekDemo>/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()
# ===========================================
@@ -204,20 +271,20 @@ endif()
# Какие папки с ресурсами нужно копировать
set(RUNTIME_RESOURCE_DIRS
"resources"
"audio"
"audio"
)
# Копируем ресурсы и шейдеры в папку exe и в корень build/
foreach(resdir IN LISTS RUNTIME_RESOURCE_DIRS)
add_custom_command(TARGET witcher001 POST_BUILD
add_custom_command(TARGET ShadowOverBishkekDemo POST_BUILD
COMMAND ${CMAKE_COMMAND} -E echo "Copying ${resdir} to runtime folders..."
# 1) туда, где лежит exe (build/Debug, build/Release и т.п.)
COMMAND ${CMAKE_COMMAND} -E copy_directory
"${CMAKE_SOURCE_DIR}/../${resdir}"
"$<TARGET_FILE_DIR:witcher001>/${resdir}"
"$<TARGET_FILE_DIR:ShadowOverBishkekDemo>/${resdir}"
# 2) в корень build, если захочешь запускать из этой папки
COMMAND ${CMAKE_COMMAND} -E copy_directory
"${CMAKE_SOURCE_DIR}/../${resdir}"
"${CMAKE_BINARY_DIR}/${resdir}"
)
endforeach()
endforeach()
+1 -1
View File
@@ -26,7 +26,7 @@
"animationIdlePath": "resources/w/girlfriend/girlfriend_idle003_small.anim",
"animationWalkPath": "resources/w/girlfriend/girlfriend_walk003_small.anim",
"meshTextures": {
"Girl_Low": "resources/w/girlfriend/Girl_Base_color.png"
"Girl_Low": "resources/w/girlfriend/Girl_Base_color006.png"
},
"positionX": 0.799619,
"positionY": 0.0,
+1 -1
View File
@@ -13,7 +13,7 @@
{
"key": "Аида Дженибековна",
"ru": "Аида Дженибековна",
"en": "Aida Dzhanybekovna"
"en": "Aida Dzhenibekovna"
},
{
"key": "Алтынай",
+2 -2
View File
@@ -44,8 +44,8 @@
},
{
"id": "report_card",
"name": "Begimai's grade book",
"description": "This is Begimai's grade book. This is where a teacher should write grades for each subject or coursework.",
"name": "Begimai's record book",
"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",
"selectedIcon": "resources/w/ui/img/inv/ItemSelReportCard002.png"
},
+7 -7
View File
@@ -20,7 +20,7 @@
"objectives": [
{ "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_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.",
"objectives": [
{ "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,
"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": [
{ "id": "ghost_release_reportcard", "text": "Find Begimai's grade book", "completed": false },
{ "id": "ghost_release_mark", "text": "Put a grade for the coursework in the grade book", "completed": false },
{ "id": "ghost_release_show", "text": "Show the grade book to the ghost", "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 record book", "completed": false },
{ "id": "ghost_release_show", "text": "Show the record book to the ghost", "completed": false }
]
},
{
@@ -51,7 +51,7 @@
"title": "Manas studies",
"status": "Available",
"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": [
{ "id": "study_beginning_lecture", "text": "Attend the lecture", "completed": false },
{ "id": "study_beginning_task", "text": "Get an assignment for the module", "completed": false }
@@ -61,7 +61,7 @@
"id": "study_project",
"title": "Essay on Manaschi",
"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": [
{ "id": "study_project_book", "text": "Find the book", "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",
"speaker": "Бекзат",
"portrait": "resources/dialogue/portrait_phone.png",
"text": "Почему ты сама не можешь забрать?",
"text": "Почему ты сама не можешь забрать нож у Аиды?",
"next": "line_9",
"chatBubble": "out"
},
@@ -164,7 +164,16 @@
"type": "Line",
"speaker": "Айпери",
"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",
"chatBubble": "in"
},
@@ -182,7 +191,7 @@
"type": "Line",
"speaker": "Айпери",
"portrait": "resources/dialogue/portrait_phone.png",
"text": "Так что жду тебя в универе! Не вздумай прогулять!",
"text": "Так что жду тебя в универе! Не вздумай прогулять! Ошо!",
"next": "setflag_1",
"chatBubble": "in",
"questUnlock": "aiperi_knife"
@@ -359,7 +368,7 @@
"type": "Line",
"speaker": "Бекзат",
"portrait": "resources/dialogue/portrait_hero_neutral.png",
"text": "Я заказал такси до универа, машина уже ждет!",
"text": "Я заказал такси до универа, машина уже едет!",
"next": "end_1"
},
{
@@ -647,7 +656,7 @@
"type": "Line",
"speaker": "Алик",
"portrait": "resources/dialogue/portrait_student_boy.png",
"text": "За зданием универа есть контейнер с кучей бумажного мусора и макулатурой.",
"text": "Возле здания универа лежит куча строительного мусора и макулатуры.",
"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": "Аида Дженибековна",
"ru": "Аида Дженибековна",
"en": "Aida Dzhanybekovna"
"en": "Aida Dzhenibekovna"
},
{
"key": "Опаздывающие, заходите скорее и занимайте свои места! Лекция начинается!",
@@ -23,7 +23,7 @@
{
"key": "Этот мир описан в эпосе Манас как Кайып или Аль-Гайб, но некоторые ученые называют его миром теней.",
"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": "В этом мире обитают феи, духи и джинны. Простым смертным в этот мир дорога закрыта.",
@@ -33,17 +33,17 @@
{
"key": "Время там течет по другому - за один день в теневом мире могут пройти годы жизни обычного мира.",
"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": "Обычно, мир теней никак не пересекается с нашим миром живых людей.",
"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": "Но в критические моменты для народа, обитатели теневого мира могут приходить в наш мир.",
"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": "Совсем недавно закончилась пандемия, а сегодня мир захлестнули кровавые войны.",
+128 -78
View File
@@ -136,9 +136,9 @@
"en": "Before going outside, I have to order a taxi to the university."
},
{
"key": "Я заказал такси до универа, машина уже ждет!",
"ru": "Я заказал такси до универа, машина уже ждет!",
"en": "I ordered a taxi to the university, the car is already waiting!"
"key": "Я заказал такси до универа, машина уже едет!",
"ru": "Я заказал такси до универа, машина уже едет!",
"en": "I ordered a taxi to the university, the car is already on its way!"
},
{
"key": "Я уже заказал такси, машина уже ждет!",
@@ -261,9 +261,9 @@
"en": "And where is her coursework now?"
},
{
"key": "За зданием универа есть контейнер с кучей бумажного мусора и макулатурой.",
"ru": "За зданием универа есть контейнер с кучей бумажного мусора и макулатурой.",
"en": "Behind the university building there is a container with a pile of paper trash and waste."
"key": "Возле здания универа лежит куча строительного мусора и макулатуры.",
"ru": "Возле здания универа лежит куча строительного мусора и макулатуры.",
"en": "There is a pile of construction waste and paper near the university building."
},
{
"key": "Скорее всего, курсовая до сих пор лежит где-то там.",
@@ -303,7 +303,7 @@
{
"key": "А ведь у нас в этом году новая преподавательница, Аида Дженибековна!",
"ru": "А ведь у нас в этом году новая преподавательница, Аида Дженибековна!",
"en": "But we have a new teacher this year, Aida Dzhanybekovna!"
"en": "But we have a new teacher this year, Aida Dzhenibekovna!"
},
{
"key": "Она конечно не сахар, но если ты ходишь на пары и вовремя сдашь ей задание на модуль, то ты считай сдал экзамен.",
@@ -318,7 +318,7 @@
{
"key": "Так что Аида Дженибековна еще норм. Подойди к ней, возьми задание на модуль, и начни ходить на пары. Тогда ты сдашь экзамен успешно.",
"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": "Спасибо за информацию!",
@@ -348,7 +348,7 @@
{
"key": "Говорят, ей не очень нравится наша новая преподавательница по манасоведению, Аида Дженибековна.",
"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": "Привет Бекзат! Надеюсь ты нашел то что ищешь.",
@@ -456,9 +456,9 @@
"en": "Before I leave the gate, I have to order a taxi to the dorm."
},
{
"key": "Я заказал такси до общаги, машина уже ждет!",
"ru": "Я заказал такси до общаги, машина уже ждет!",
"en": "I ordered a taxi to the dorm, the car is already waiting!"
"key": "Я заказал такси до общаги, машина уже едет!",
"ru": "Я заказал такси до общаги, машина уже едет!",
"en": "I ordered a taxi to the dorm, the car is already on its way!"
},
{
"key": "Ого, пока я залипал в приложении, уже наступила ночь!",
@@ -478,7 +478,7 @@
{
"key": "Кажется, на этой машине в универ приезжает Аида Дженибековна.",
"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": "Это заклинание не работает днем.",
@@ -488,7 +488,7 @@
{
"key": "Чтобы перейти в теневой мир, мне нужно дождаться ночи.",
"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": "Чтобы время пролетело быстро, я могу посидеть в телефоне и позалипать в короткие видео.",
@@ -498,7 +498,7 @@
{
"key": "У меня силы закончились и мне спать хочется. Достаточно теневого мира на сегодня, поехали в общагу.",
"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": "Ты куда собрался, Бекзат?",
@@ -658,7 +658,7 @@
{
"key": "Аида Дженибековна",
"ru": "Аида Дженибековна",
"en": "Aida Dzhanybekovna"
"en": "Aida Dzhenibekovna"
},
{
"key": "Бекзат, что тебе нужно?",
@@ -688,7 +688,7 @@
{
"key": "Аида Дженибековна",
"ru": "Аида Дженибековна",
"en": "Aida Dzhanybekovna"
"en": "Aida Dzhenibekovna"
},
{
"key": "Зачем тебе в учительскую?",
@@ -828,7 +828,7 @@
{
"key": "Извините, Аида Дженибековна, я могу пойти уже?",
"ru": "Извините, Аида Дженибековна, я могу пойти уже?",
"en": "Excuse me, Aida Dzhanybekovna, can I go now?"
"en": "Excuse me, Aida Dzhenibekovna, can I go now?"
},
{
"key": "Нет, Бекзат. Ты вломился в учительскую, и теперь хочешь просто так уйти? Не получится.",
@@ -898,7 +898,7 @@
{
"key": "Аида Дженибековна, вы помните Бегимай? Она вам курсовую хотела сдать.",
"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": "Да, она говорила что приносила курсовую, но у меня ее нигде нет.",
@@ -913,7 +913,7 @@
{
"key": "Бекзат, зачетка Бегимай у тебя. Отправляйся в теневой мир и покажи зачетку призраку Бегимай, чтобы она освободилась.",
"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": "Хорошо!",
@@ -923,12 +923,12 @@
{
"key": "Аида Дженибековна, здравствуйте!",
"ru": "Аида Дженибековна, здравствуйте!",
"en": "Aida Dzhanybekovna, hello!"
"en": "Aida Dzhenibekovna, hello!"
},
{
"key": "Я принес вам зачетку Бегимай и ее курсовую работу, посмотрите пожалуйста.",
"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": "Хорошо, давай посмотрим.",
@@ -943,12 +943,12 @@
{
"key": "Вот держи зачетку с оценкой.",
"ru": "Вот держи зачетку с оценкой.",
"en": "Here, take the grade book with the grade."
"en": "Here, take the record book with the grade."
},
{
"key": "Теперь иди в теневой мир и покажи зачетку Бегимай. Расскажи ей, что она сдала курсовую работу.",
"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": "Мне стоит вернуть книгу на место, прежде чем уходить из библиотеки.",
@@ -958,7 +958,7 @@
{
"key": "Иначе Аида Дженибековна меня убъет.",
"ru": "Иначе Аида Дженибековна меня убъет.",
"en": "Otherwise Aida Dzhanybekovna will kill me."
"en": "Otherwise Aida Dzhenibekovna will kill me."
},
{
"key": "Куда я попал?",
@@ -1023,7 +1023,7 @@
{
"key": "Я уйду на покой только когда увиду оценку по курсовой в своей зачетке.",
"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": "А до тех пор, я буду появлятся здесь каждую ночь.",
@@ -1048,7 +1048,7 @@
{
"key": "Это твоя зачетка.",
"ru": "Это твоя зачетка.",
"en": "This is your grade book."
"en": "This is your record book."
},
{
"key": "Тебе поставили за курсовую максимальный балл!",
@@ -1143,17 +1143,17 @@
{
"key": "Тут лежат зачетные книжки студентов.",
"ru": "Тут лежат зачетные книжки студентов.",
"en": "Students' grade books are kept here."
"en": "Students' record books are kept here."
},
{
"key": "Здесь лежит зачетка Бегимай. Я пожалуй, возьму ее.",
"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": "Мне еще рано возвращать зачетку Бегимай обратно в шкаф.",
"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": "Чтобы заказать такси, я сначала должен выйти на улицу.",
@@ -1358,12 +1358,12 @@
{
"key": "Ко мне попала странная записка, где было написано заклинание. Я прочитал его, и попал в потусторонний мир.",
"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": "Ты что опять в свои игры переиграл? Какой потусторонний мир?",
"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": "Но это правда! Там все было по-другому, и я видел призраков...",
@@ -1378,7 +1378,7 @@
{
"key": "Аида Дженибековна увидела меня в учительской, и заставила писать эссе по этой тупой книге.",
"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": "Я писал, писал и уснул.",
@@ -1448,7 +1448,7 @@
{
"key": "Поэтому, с этого года у нас этот предмет ведет Аида Дженибековна.",
"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": "Ты не знаешь кого-нибудь, кто был знаком с Бегимай?",
@@ -1473,7 +1473,7 @@
{
"key": "Я лучше прочитаю заклинание, перейду в теневой мир и так выберусь наружу.",
"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": "Я не буду беспокоить Айпери. Я могу выбраться из универа и без ее помощи.",
@@ -1588,12 +1588,12 @@
{
"key": "Я попал в теневой мир, чтобы выбраться из запертого кабинета.",
"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": "Но по злой иронии, когда я вернулся из теневого мира, я снова оказался за запертой дверью.",
"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": "Хорошо, что уже утро. Надо написать Айпери, может она уже скоро приедет в универ?",
@@ -1623,7 +1623,7 @@
{
"key": "Здравствуйте Аида Дженибековна!",
"ru": "Здравствуйте Аида Дженибековна!",
"en": "Hello Aida Dzhanybekovna!"
"en": "Hello Aida Dzhenibekovna!"
},
{
"key": "Ну что Бекзат, эссе про Жусупа Мамая готово к сдаче?",
@@ -1683,12 +1683,12 @@
{
"key": "Это заклинание переместило тебя в мир теней. ",
"ru": "Это заклинание переместило тебя в мир теней.",
"en": "This spell transported you to the dark lands."
"en": "This spell transported you to the Darkland."
},
{
"key": "Этот мир предназначен для фей и джиннов. Обычные люди туда попасть не могут. Но у тебя это как-то получилось.",
"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": "А вы откуда все это знаете?",
@@ -1723,12 +1723,12 @@
{
"key": "А на утро бывает так, что все зачетки в шкафу раскиданы как попало.",
"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": "Призрак обитает где-то в теневом мире. Попади туда и наладь с ним диалог. Узнай, кто он и что ему нужно.",
"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": "Если ты успешно избавишься от призрака, я поставлю тебе максимальный балл за модуль.",
@@ -1753,7 +1753,7 @@
{
"key": "Обсудить теневой мир",
"ru": "Обсудить теневой мир",
"en": "Discuss the dark lands"
"en": "Discuss the Darkland"
},
{
"key": "Обсудить записку",
@@ -1763,12 +1763,12 @@
{
"key": "Что это вообще за теневой мир? Откуда он взялся?",
"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": "Теневой мир это мир, в котором обитают феи, духи и джинны. Он существовал всегда.",
"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": "Обычные люди, не воители, туда не попадают обычно. Кроме тебя, тебе это как-то удалось.",
@@ -1783,22 +1783,22 @@
{
"key": "Время в теневом мире",
"ru": "Время в теневом мире",
"en": "Time in the dark lands"
"en": "Time in the Darkland"
},
{
"key": "Призраки в теневом мире",
"ru": "Призраки в теневом мире",
"en": "Ghosts in the dark lands"
"en": "Ghosts in the Darkland"
},
{
"key": "Двери в теневом мире",
"ru": "Двери в теневом мире",
"en": "Doors in the dark lands"
"en": "Doors in the Darkland"
},
{
"key": "Выход из теневого мира",
"ru": "Выход из теневого мира",
"en": "Exit from the dark lands"
"en": "Exit from the Darkland"
},
{
"key": "Достаточно",
@@ -1808,37 +1808,37 @@
{
"key": "Почему в теневом мире нет дверей?",
"ru": "Почему в теневом мире нет дверей?",
"en": "Why are there no doors in the dark lands?"
"en": "Why are there no doors in the Darkland?"
},
{
"key": "Я думаю, в мир теней проецируются только фундаментальные объекты, типа зданий, деревьев, рельефа.",
"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": "Это происходит потому, что в теневом мире время течет сильно медленнее, чем в нашем мире.",
"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": "Двери у нас слишком часто открываются и закрываются, поэтому в теневом мире они \"размываются\" или попадают в суперпозицию.",
"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": "Поэтому в мире теней ты можешь спокойно пройти через дверной проем. Но сквозь стены ты пройти не сможешь, они более плотные.",
"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": "Откуда в теневом мире призраки?",
"ru": "Откуда в теневом мире призраки?",
"en": "Where do ghosts come from in the dark lands?"
"en": "Where do ghosts come from in the Darkland?"
},
{
"key": "Это их мир, и они там живут. Я думаю что увидев тебя, обитатели теневого мира тоже были шокированы.",
"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": "Но кое в чем ты прав. Здесь в универе, призраков раньше не было. Всякая чертовщина начала происходить совсем недавно.",
@@ -1863,12 +1863,12 @@
{
"key": "Каждый раз, когда я выпадаю из теневого мира, наступает утро. Хотя я там был всего несколько минут.",
"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": "Время в мире теней течет значительно медленнее, чем в нашем.",
"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": "И похоже, что течение времени там нелинейное.",
@@ -1878,22 +1878,22 @@
{
"key": "Это объясняет, почему в теневом мире есть деревья и строения, но отсутствуют двери, автомобили, и прочее.",
"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": "Объекты в нашем мире должны долго находится на одном месте, прежде чем их проекция появится в мире теней.",
"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": "Меня постоянно выкидывает из теневого мира. Как я могу там остаться подольше?",
"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": "Если ты потеряешь сознание в теневом мире, то ты принудительно вернешься в наш мир.",
"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": "Также избегай света луны, луна тоже может вернуть тебя в мир людей. Лучше вообще не выходи из помещения. ",
@@ -1903,17 +1903,17 @@
{
"key": "Если ты задержишься, и в человеческом мире настанет утро, тебя тоже выбросит из теневого мира.",
"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": "Но ты не беспокойся. Даже если тебя выбросит из теневого мира, ты всегда сможешь повторить свою попытку на следующую ночь.",
"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": "У тебя есть еще вопросы про теневой мир?",
"ru": "У тебя есть еще вопросы про теневой мир?",
"en": "Do you have any more questions about the dark lands?"
"en": "Do you have any more questions about the Darkland?"
},
{
"key": "Ладно, я узнал достаточно.",
@@ -1938,7 +1938,7 @@
{
"key": "Когда я узнала, что в университете начались паранормальные явления, я сразу поняла что это призрак в теневом мире.",
"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": "Я знаю заклинание, которое тебе дали. У этого заклинания есть еще одно условие - оно сработает только на людей, которые чисты сердцем.",
@@ -1953,12 +1953,12 @@
{
"key": "Тогда я предположила, что Айпери уж точно сможет прочитать заклинание и попасть в теневой мир.",
"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": "Как раз поэтому я хотела вернуть нож ей лично. И заодно отправить ее в теневой мир.",
"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": "Но ты смешал все мои планы. Сначала ты забрал нож, а потом кто-то подсунул тебе записку.",
@@ -2068,7 +2068,7 @@
{
"key": "Переходи в теневой мир, а затем иди в лекционный зал.",
"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": "Постарайся поговорить с призраком, и узнать, что ему нужно.",
@@ -2093,7 +2093,7 @@
{
"key": "Иди к шкафу в учительской. Среди зачетных книжек найти зачетку Бегимай и принеси мне. Я поставлю ей оценку за курсовую работу.",
"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": "Ты уже нашел курсовую работу Бегимай?",
@@ -2153,7 +2153,7 @@
{
"key": "Теперь поищи в шкафу зачетку Бегимай, и принеси мне, я выставлю ей оценку.",
"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": "Еще нет.",
@@ -2173,7 +2173,7 @@
{
"key": "Но если я возьму кусочек, Аида Дженибековна меня убьет.",
"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": "Здесь лежат книги по юриспруденции и праву.",
@@ -2183,12 +2183,12 @@
{
"key": "Бекзат, помнишь мы скидывались на торт для Аиды Дженибековной? Я тогда еще приносила скатерть, тарелки и серебряный нож для торта. И я до сих пор не получила назад ничего.",
"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": "Помнишь мы скидывались на торт для Аиды Дженибековной? Я тогда еще приносила скатерть, тарелки и серебряный нож для торта. И я до сих пор не получила назад ничего.",
"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": "А во вторых, мне как-то не хочется попадаться на глаза Аиде.",
@@ -2198,7 +2198,7 @@
{
"key": "Да... Только не говори Аиде Дженибековной что я здесь.",
"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": "Постарайся только Аиде Дженибековной особо на глаза не попадаться. ",
@@ -2233,7 +2233,7 @@
{
"key": "Я уйду на покой только когда увижу оценку по курсовой в своей зачетке.",
"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": "А до тех пор, я буду появляться здесь каждую ночь.",
@@ -2248,17 +2248,67 @@
{
"key": "Аида Дженибековна увидела меня в учительской, и заставила писать эссе по этой тупой книге.",
"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": "Прошу прощения Аида Дженибековна, я правда старался, но у меня возникли обстоятельства непреодолимой силы.",
"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": "Бекзат, я сейчас занята, не мешай мне!",
"ru": "Бекзат, я сейчас занята, не мешай мне!",
"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",
"speaker": "Бекзат",
"portrait": "resources/dialogue/portrait_hero_neutral.png",
"text": "Я заказал такси до общаги, машина уже ждет!",
"text": "Я заказал такси до общаги, машина уже едет!",
"next": "end_1"
},
{
@@ -334,7 +334,7 @@
"type": "Line",
"speaker": "Бекзат",
"portrait": "resources/dialogue/portrait_phone.png",
"text": "Почему ты сама не можешь забрать?",
"text": "Почему ты сама не можешь забрать нож у Аиды?",
"next": "line_9",
"chatBubble": "out"
},
@@ -343,7 +343,16 @@
"type": "Line",
"speaker": "Айпери",
"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",
"chatBubble": "in"
},
@@ -361,7 +370,7 @@
"type": "Line",
"speaker": "Айпери",
"portrait": "resources/dialogue/portrait_phone.png",
"text": "Так что жду тебя в универе! Не вздумай прогулять!",
"text": "Так что жду тебя в универе! Не вздумай прогулять! Ошо!",
"next": "setflag_1",
"chatBubble": "in",
"questUnlock": "aiperi_knife"
@@ -220,7 +220,15 @@
"type": "Line",
"speaker": "Айпери",
"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"
},
{
@@ -285,6 +293,14 @@
"speaker": "Бекзат",
"portrait": "resources/dialogue/portrait_hero_neutral.png",
"text": "Почему?",
"next": "line_30"
},
{
"id": "line_30",
"type": "Line",
"speaker": "Айпери",
"portrait": "resources/dialogue/portrait_aiperi.png",
"text": "Она опять будет скафнить.",
"next": "line_25"
},
{
@@ -292,7 +308,7 @@
"type": "Line",
"speaker": "Айпери",
"portrait": "resources/dialogue/portrait_aiperi.png",
"text": "Она меня ищет, я уверена, она хочет мне какое-то задание дать.",
"text": "Я даже уверена, она меня ищет. Она хочет мне какое-то задание дать.",
"next": "line_26"
},
{
@@ -338,7 +354,7 @@
"type": "Line",
"speaker": "Айпери",
"portrait": "resources/dialogue/portrait_aiperi.png",
"text": "Пока ты не принесешь мне нож из учительской, никуда я тебя не выпущу.",
"text": "Пока ты не принесешь мне нож из учительской, никуда я тебя не выпущу. Ошо!",
"next": "end_1"
},
{
@@ -2385,7 +2401,15 @@
"type": "Line",
"speaker": "Айпери",
"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"
},
{
@@ -2571,7 +2595,7 @@
"type": "Line",
"speaker": "Бекзат",
"portrait": "resources/dialogue/portrait_phone.png",
"text": "Почему ты сама не можешь забрать?",
"text": "Почему ты сама не можешь забрать нож у Аиды?",
"next": "line_9",
"chatBubble": "out"
},
@@ -2580,7 +2604,16 @@
"type": "Line",
"speaker": "Айпери",
"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",
"chatBubble": "in"
},
@@ -2598,7 +2631,7 @@
"type": "Line",
"speaker": "Айпери",
"portrait": "resources/dialogue/portrait_phone.png",
"text": "Так что жду тебя в универе! Не вздумай прогулять!",
"text": "Так что жду тебя в универе! Не вздумай прогулять! Ошо!",
"next": "setflag_1",
"chatBubble": "in",
"questUnlock": "aiperi_knife"
-1
View File
@@ -45,7 +45,6 @@ end
function lection_hall_zone001_enter_callback()
local lection_is_over = game_api.getIntValue("lection_is_over")
if (lection_is_over == 0) then
game_api.player_stop()
game_api.start_cutscene("lection_cutscene001")
Binary file not shown.
Binary file not shown.
+3 -3
View File
@@ -8,9 +8,9 @@
{
"type": "StaticImage",
"name": "hint2",
"width": 72,
"height": 354,
"y": -100,
"width": 36,
"height": 177,
"y": -30,
"x": 0,
"horizontal_gravity": "center",
"vertical_gravity": "center",
Binary file not shown.
Binary file not shown.
Binary file not shown.
+51 -8
View File
@@ -7,18 +7,61 @@
{
"type": "StaticImage",
"name": "aboutBkg",
"width": "match_parent",
"height": "match_parent",
"texture": "resources/w/ui/img/main/bkg.png"
"width": 2992,
"height": 1632,
"horizontal_gravity": "center",
"vertical_gravity": "center",
"texture": "resources/w/ui/img/main/MenuBkg001_v001.png"
},
{
{
"type": "StaticImage",
"name": "aboutImage",
"name": "aboutPicture",
"width": 720,
"height": 720,
"horizontal_gravity": "center",
"vertical_gravity": "center",
"texture": "resources/w/ui/img/main/aboutPage002.png"
},
{
"type": "TextButton",
"name": "steamButton",
"horizontal_gravity": "center",
"vertical_gravity": "center",
"width": 800,
"height": 500,
"texture": "resources/w/ui/img/main/about.png"
"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",
+342 -25
View File
@@ -7,34 +7,351 @@
{
"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": "Тень над Бишкеком Демо",
"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",
"name": "creditsBackButton",
"width": "match_parent",
"height": "match_parent",
"texture": "resources/w/ui/img/main/bkg.png"
},
{
"type": "StaticImage",
"name": "creditsImage",
"horizontal_gravity": "center",
"vertical_gravity": "center",
"width": 800,
"height": 500,
"texture": "resources/w/ui/img/main/about.png"
},
{
"type": "Button",
"name": "skipButton",
"horizontal_gravity": "right",
"vertical_gravity": "top",
"x": -23,
"y": -35,
"width": 200,
"height": 200,
"clickZoneWidth": 176,
"clickZoneHeight": 176,
"x": 0,
"y": 0,
"textures": {
"normal": "resources/w/ui/img/RedClose001_State=Default001.png",
"hover": "resources/w/ui/img/RedClose001_State=Selected001.png",
"pressed": "resources/w/ui/img/RedClose001_State=Tap001.png"
"normal": "resources/transparent.png",
"hover": "resources/transparent.png",
"pressed": "resources/transparent.png"
}
}
]
+18 -9
View File
@@ -47,7 +47,7 @@
"horizontal_align": "center",
"vertical_align": "center",
"spacing": 24,
"y": 150,
"y": 140,
"width": "match_parent",
"height": "match_parent",
"children": [
@@ -55,7 +55,7 @@
"type": "TextButton",
"name": "menuStartButton",
"width": 300,
"height": 50,
"height": 44,
"text": "Начать игру",
"fontSize": 48,
"textCentered": true
@@ -64,7 +64,7 @@
"type": "TextButton",
"name": "menuLoadButton",
"width": 300,
"height": 50,
"height": 44,
"text": "Загрузить игру",
"fontSize": 48,
"textCentered": true
@@ -73,7 +73,7 @@
"type": "TextButton",
"name": "menuSettingsButton",
"width": 300,
"height": 50,
"height": 44,
"text": "Настройки",
"fontSize": 48,
"textCentered": true
@@ -82,7 +82,7 @@
"type": "TextButton",
"name": "menuLanguageButton",
"width": 300,
"height": 50,
"height": 44,
"text": "Язык / Language",
"fontSize": 48,
"textCentered": true
@@ -91,8 +91,17 @@
"type": "TextButton",
"name": "menuAboutButton",
"width": 300,
"height": 50,
"text": "О нас",
"height": 44,
"text": "О игре",
"fontSize": 48,
"textCentered": true
},
{
"type": "TextButton",
"name": "menuCreditsButton",
"width": 300,
"height": 44,
"text": "Авторы",
"fontSize": 48,
"textCentered": true
},
@@ -100,7 +109,7 @@
"type": "TextButton",
"name": "menuExitButton",
"width": 300,
"height": 50,
"height": 44,
"text": "Выход",
"fontSize": 48,
"textCentered": true
@@ -116,7 +125,7 @@
"y": 10,
"width": 160,
"height": 36,
"text": "v. 0.7",
"text": "v. 1.0.1",
"fontSize": 24,
"textCentered": false,
"topAligned": true,
+3 -3
View File
@@ -8,9 +8,9 @@
{
"type": "StaticImage",
"name": "hint2",
"width": 72,
"height": 354,
"y": -100,
"width": 36,
"height": 177,
"y": -30,
"x": 0,
"horizontal_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",
"vertical_align": "center",
"spacing": 24,
"y": 150,
"y": 140,
"width": "match_parent",
"height": "match_parent",
"children": [
@@ -55,7 +55,7 @@
"type": "TextButton",
"name": "menuStartButton",
"width": 300,
"height": 50,
"height": 44,
"text": "Start Game",
"fontSize": 48,
"textCentered": true
@@ -64,7 +64,7 @@
"type": "TextButton",
"name": "menuLoadButton",
"width": 300,
"height": 50,
"height": 44,
"text": "Load Game",
"fontSize": 48,
"textCentered": true
@@ -73,7 +73,7 @@
"type": "TextButton",
"name": "menuSettingsButton",
"width": 300,
"height": 50,
"height": 44,
"text": "Settings",
"fontSize": 48,
"textCentered": true
@@ -82,7 +82,7 @@
"type": "TextButton",
"name": "menuLanguageButton",
"width": 300,
"height": 50,
"height": 44,
"text": "Язык / Language",
"fontSize": 48,
"textCentered": true
@@ -91,8 +91,17 @@
"type": "TextButton",
"name": "menuAboutButton",
"width": 300,
"height": 50,
"text": "About Us",
"height": 44,
"text": "About Game",
"fontSize": 48,
"textCentered": true
},
{
"type": "TextButton",
"name": "menuCreditsButton",
"width": 300,
"height": 44,
"text": "Credits",
"fontSize": 48,
"textCentered": true
},
@@ -100,7 +109,7 @@
"type": "TextButton",
"name": "menuExitButton",
"width": 300,
"height": 50,
"height": 44,
"text": "Exit",
"fontSize": 48,
"textCentered": true
@@ -116,7 +125,7 @@
"y": 10,
"width": 160,
"height": 36,
"text": "v. 0.7",
"text": "v. 1.0.1",
"fontSize": 24,
"textCentered": false,
"topAligned": true,
+3 -2
View File
@@ -1,5 +1,6 @@
#include "AudioPlayerAsync.h"
#include <iostream>
#include "utils/Utils.h"
#ifdef __EMSCRIPTEN__
AudioPlayerAsync::AudioPlayerAsync() {}
@@ -36,7 +37,7 @@ bool AudioPlayerAsync::init() {
Mix_AllocateChannels(16);
initialized = true;
std::cout << "AudioPlayerAsync initialized with SDL2_mixer" << std::endl;
ZL::logger() << "AudioPlayerAsync initialized with SDL2_mixer" << std::endl;
return true;
}
@@ -60,7 +61,7 @@ void AudioPlayerAsync::shutdown() {
Mix_CloseAudio();
SDL_QuitSubSystem(SDL_INIT_AUDIO);
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) {
+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);
return -1;
@@ -435,7 +435,7 @@ namespace ZL
meshNamesOrdered.push_back(meshName);
}
std::cout << "Loaded " << numberMeshes << " meshes from " << fileName << std::endl;
logger() << "Loaded " << numberMeshes << " meshes from " << fileName << std::endl;
// ---- Animation Keyframes ----
std::getline(f, tempLine); // === Animation Keyframes ===
@@ -509,7 +509,7 @@ namespace ZL
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;
}
}
@@ -706,7 +706,7 @@ namespace ZL
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;
}
}
@@ -736,7 +736,7 @@ namespace ZL
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");
}
@@ -794,7 +794,7 @@ namespace ZL
{
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");
}
vMoved = true;
+3 -3
View File
@@ -613,7 +613,7 @@ bool Character::prepareGpuSkinning() {
void Character::tryAttackCombo()
{
std::cout << "Try Attack Combo" << std::endl;
logger() << "Try Attack Combo" << std::endl;
auto it = animations.find(state.currentState);
if (it == animations.end()) return;
@@ -622,7 +622,7 @@ void Character::tryAttackCombo()
if (static_cast<int>(anim.currentFrame) >= 20 &&
(state.currentState == AnimationState::ACTION_ATTACK ||
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;
}
}
@@ -1234,7 +1234,7 @@ void Character::setupHitSparks(std::shared_ptr<Texture> sparkTexture) {
}
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;
if (state.hp < 0) state.hp = 0;
+1 -1
View File
@@ -116,7 +116,7 @@ public:
std::string weaponAttachBoneName = "RightHand";
SparkEmitter hitSparkEmitter;
AudioPlayerAsync* audioPlayer;
AudioPlayerAsync* audioPlayer = nullptr;
static Dialogue::TranslationDatabase nameTranslations;
+3 -1
View File
@@ -23,12 +23,14 @@ int Environment::height = CONST_DEFAULT_HEIGHT;
float Environment::zoom = DEFAULT_ZOOM;
bool Environment::enableLogging = false;
SDL_Window* Environment::window = nullptr;
#ifdef EMSCRIPTEN
bool Environment::isFullscreen = false;
#else
bool Environment::isFullscreen = false;
bool Environment::isFullscreen = true;
#endif
bool Environment::isHighDPIEnabled = false;
+2
View File
@@ -31,6 +31,8 @@ public:
static int height;
static float zoom;
static bool enableLogging;
static Eigen::Matrix3f inverseShipMatrix;
static SDL_Window* window;
+39 -61
View File
@@ -43,7 +43,7 @@ namespace ZL
Game* Game::s_instance = nullptr;
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) {
s_instance->resourcesDownloadProgress = 1.0f;
// 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*/) {
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) {
@@ -78,11 +78,6 @@ namespace ZL
}
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() {
@@ -134,7 +129,7 @@ namespace ZL
// Asynchronously download resources.zip; setupPart2() is called on completion.
// The loading screen stays visible until the download finishes.
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,
onResourcesZipLoaded, onResourcesZipError, onResourcesZipProgress);
#else
@@ -316,7 +311,7 @@ namespace ZL
loadSteps.push([this]() {
try {
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) {
std::cerr << "Failed to load UI: " << e.what() << std::endl;
@@ -366,10 +361,10 @@ namespace ZL
if (audioPlayer->init()) {
audioPlayer->setMusicVolume(100);
audioPlayer->setSoundVolume(80);
std::cout << "Audio initialized successfully" << std::endl;
logger() << "Audio initialized successfully" << std::endl;
}
else {
std::cout << "Audio initialization failed" << std::endl;
logger() << "Audio initialization failed" << std::endl;
}
});
@@ -480,12 +475,12 @@ namespace ZL
nlohmann::json root;
gameState.save(root);
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);
if (file.is_open()) {
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 {
std::cerr << "[reset] Could not open " << path << " for writing" << std::endl;
}
@@ -506,7 +501,7 @@ namespace ZL
LocationSetup uniInteriorParams;
uniInteriorParams.gameObjectsJsonPath = "resources/config/gameobjects_uni_interior_x.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 = {
"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"]->requestReturnToMainMenu = [this]() {
gameState.currentLocationName.clear();
menuManager.showMainMenu();
menuManager.showMenuAfterGameCompleted();
};
gameState.locations["uni_interior"]->requestSetChatUnread = [this](int idx, bool unread, const std::string& msg) {
menuManager.setChatUnread(idx, unread, msg);
@@ -643,7 +638,7 @@ namespace ZL
uniExteriorParams.scriptPath = "resources/start_uni_exterior.lua";
uniExteriorParams.playerPosition = Eigen::Vector3f(5, 0, -18.4);
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);
@@ -656,7 +651,7 @@ namespace ZL
};
gameState.locations["uni_exterior"]->requestDarklandsTransition = [this]() { return startDarklandsTransition(); };
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) {
menuManager.setChatUnread(idx, unread, msg);
};
@@ -685,7 +680,6 @@ namespace ZL
//Exterior set up taxi end
}
@@ -697,7 +691,7 @@ namespace ZL
params_dorm.gameObjectsJsonPath = "resources/config/gameobjects_dorm_new_x.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 = {
@@ -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"]->requestDarklandsTransition = [this]() { return startDarklandsTransition(); };
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) {
menuManager.setChatUnread(idx, unread, msg);
};
@@ -777,8 +771,6 @@ namespace ZL
void Game::createLocationsStep5()
{
rearmDormOneShotCallbacks();
menuManager.callTaxiFunc = [this]()
@@ -796,7 +788,7 @@ namespace ZL
// 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) {
std::cout << "[TELEPORT] " << " -> " << destName << std::endl;
logger() << "[TELEPORT] " << " -> " << destName << std::endl;
auto it = gameState.locations.find(destName);
if (it == gameState.locations.end()) {
std::cerr << "[TELEPORT] Unknown destination location: " << destName << std::endl;
@@ -903,12 +895,12 @@ namespace ZL
void Game::createShadowMapAndProparateToLocations()
{
if (shadowMap) {
std::cout << "Shadow map already exists, skipping creation" << std::endl;
logger() << "Shadow map already exists, skipping creation" << std::endl;
return;
}
shadowMap = std::make_shared<ShadowMap>(2048, 40.0f, 0.1f, 100.0f);
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) {
loc->shadowMap = shadowMap;
@@ -1186,7 +1178,7 @@ namespace ZL
if (event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_RESIZED) {
const bool wasPortrait = Environment::width < Environment::height;
//ZL::logger() << "Window resized" << std::endl;
if (Environment::isHighDPIEnabled) {
// Если High DPI включен, запрашиваем реальное физическое разрешение буфера
int drawW, drawH;
@@ -1210,10 +1202,13 @@ namespace ZL
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();
}
if (event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_RESIZED) {
}
// Suppress all input while portrait overlay is shown
//if (mobileIsPortrait) continue;
@@ -1300,7 +1295,7 @@ namespace ZL
if (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".
if (gameState.tutorialStep == TutorialStep::Step3) {
menuManager.advanceTutorialStep();
@@ -1309,7 +1304,7 @@ namespace ZL
if (event.type == SDL_KEYDOWN && event.key.repeat == 0) {
switch (event.key.keysym.sym) {
case SDLK_8:
/*case SDLK_8:
if (editorMode == EditorMode::InteractiveObjects && currentLocation()) {
currentLocation()->editor.selectInteractiveObject(8);
} else {
@@ -1345,7 +1340,7 @@ namespace ZL
currentLocation()->editor.selectInteractiveObject(event.key.keysym.sym - SDLK_0);
} else {
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;
case SDLK_f:
@@ -1376,50 +1371,50 @@ namespace ZL
const char* modeName = (editorMode == EditorMode::Navigation) ? "Navigation"
: (editorMode == EditorMode::InteractiveObjects) ? "InteractiveObjects"
: "None";
std::cout << "[EDITOR] Mode: " << modeName << std::endl;
logger() << "[EDITOR] Mode: " << modeName << std::endl;
}
break;
case SDLK_o:
x = x + 0.05;
std::cout << "current x: " << x << std::endl;
logger() << "current x: " << x << std::endl;
//y = y + 0.002;
//currentLocation->player->hp = 200;
//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;
case SDLK_k:
x = x - 0.05;
std::cout << "current x: " << x << std::endl;
std::cout << "Player pos: " << currentLocation()->player->state.position.transpose() << std::endl;
std::cout << "Player rotation: " << currentLocation()->player->state.facingAngle*180.0/M_PI << std::endl;
logger() << "current x: " << x << std::endl;
logger() << "Player pos: " << currentLocation()->player->state.position.transpose() << std::endl;
logger() << "Player rotation: " << currentLocation()->player->state.facingAngle*180.0/M_PI << std::endl;
//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;
case SDLK_p:
gameState.currentLocationName = "uni_interior";
currentLocation()->player->state.position = Eigen::Vector3f(-0.0189243, 0, -13.4314);
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;
case SDLK_l:
//x = x - 1;
//std::cout << "current x: " << x << std::endl;
std::cout << "Azimuth: " << currentLocation()->state.cameraAzimuth << std::endl;
std::cout << "Inclination: " << currentLocation()->state.cameraInclination << std::endl;
//logger() << "current x: " << x << std::endl;
logger() << "Azimuth: " << currentLocation()->state.cameraAzimuth << std::endl;
logger() << "Inclination: " << currentLocation()->state.cameraInclination << std::endl;
break;
case SDLK_c:
std::cout << "SLOW-MO activated!" << std::endl;
logger() << "SLOW-MO activated!" << std::endl;
activateSlowMoEffect();
break;
case SDLK_i:
y = y - 1;
std::cout << "current y: " << y << std::endl;
logger() << "current y: " << y << std::endl;
break;
case SDLK_b:
@@ -1429,12 +1424,6 @@ namespace ZL
break;
case SDLK_j:
/*
if (editorMode != EditorMode::None && currentLocation()) {
currentLocation()->editor.placeTree();
} else {
menuManager.toggleQuestJournal();
}*/
currentLocation()->editor.placeTree();
break;
@@ -1444,8 +1433,7 @@ namespace ZL
}
break;
case SDLK_RETURN:
case SDLK_RETURN:*/
default:
break;
}
@@ -1464,16 +1452,6 @@ namespace ZL
//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
// 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);
// --- Script globals ---
globalInts.clear();
if (in.contains("globalInts") && in["globalInts"].is_object()) {
for (const auto& [k, v] : in["globalInts"].items())
globalInts[k] = v.get<int>();
}
globalFloats.clear();
if (in.contains("globalFloats") && in["globalFloats"].is_object()) {
for (const auto& [k, v] : in["globalFloats"].items())
globalFloats[k] = v.get<float>();
+1 -1
View File
@@ -67,7 +67,7 @@ void loadLanguageSetting() {
// Если файла настроек нет (content пустой), определяем язык системы и выходим
if (content.empty()) {
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;
return;
}
+60 -51
View File
@@ -148,7 +148,7 @@ namespace ZL
player->setTarget(params.playerPosition);
player->setupHitSparks(sparkTexture);
player->audioPlayer = audioPlayer;
std::cout << "Load resurces step 9" << std::endl;
logger() << "Load resurces step 9" << std::endl;
SDL_PumpEvents();
// Load NPCs from JSON. Aggressive (canAttack) NPCs need a player target
@@ -287,7 +287,7 @@ namespace ZL
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)
@@ -400,7 +400,7 @@ namespace ZL
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)
@@ -500,28 +500,28 @@ namespace ZL
if (editorMode == EditorMode::Navigation) {
editor.buildNavMeshes();
}
std::cout << "[NAV] Switched to navigation map " << index << "\n";
logger() << "[NAV] Switched to navigation map " << index << "\n";
return true;
}
InteractiveObject* Location::raycastInteractiveObjects(const Eigen::Vector3f& rayOrigin, const Eigen::Vector3f& rayDir) {
if (interactiveObjects.empty()) {
//std::cout << "[RAYCAST] No interactive objects to check" << std::endl;
//logger() << "[RAYCAST] No interactive objects to check" << std::endl;
return nullptr;
}
//std::cout << "[RAYCAST] Starting raycast with " << interactiveObjects.size() << " objects" << std::endl;
//std::cout << "[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] Starting raycast with " << interactiveObjects.size() << " objects" << std::endl;
//logger() << "[RAYCAST] Ray origin: (" << rayOrigin.x() << ", " << rayOrigin.y() << ", " << rayOrigin.z() << ")" << std::endl;
//logger() << "[RAYCAST] Ray dir: (" << rayDir.x() << ", " << rayDir.y() << ", " << rayDir.z() << ")" << std::endl;
float closestDistance = FLT_MAX;
InteractiveObject* closestObject = nullptr;
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) {
//std::cout << "[RAYCAST] -> Object inactive or animating, skipping" << std::endl;
//logger() << "[RAYCAST] -> Object inactive or animating, skipping" << std::endl;
continue;
}
@@ -531,7 +531,7 @@ namespace ZL
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;
const bool hasBox = !(intObj.state.boundsMin.isZero() && intObj.state.boundsMax.isZero());
@@ -581,10 +581,10 @@ namespace ZL
}
/*
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 {
std::cout << "[RAYCAST] No objects hit" << std::endl;
logger() << "[RAYCAST] No objects hit" << std::endl;
}
*/
return closestObject;
@@ -600,11 +600,11 @@ namespace ZL
Character* closestNpc = nullptr;
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) {
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;
}
if (!npc->state.enabled) continue;
@@ -640,7 +640,7 @@ namespace ZL
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) {
closestDist = entryT;
@@ -649,10 +649,10 @@ namespace ZL
}
if (closestNpc) {
//std::cout << "[RAYCAST_NPC] HIT: " << closestNpc->npcId << std::endl;
//logger() << "[RAYCAST_NPC] HIT: " << closestNpc->npcId << std::endl;
}
else {
//std::cout << "[RAYCAST_NPC] No NPC hit" << std::endl;
//logger() << "[RAYCAST_NPC] No NPC hit" << std::endl;
}
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)
{
//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;
}
}
@@ -1640,17 +1640,17 @@ namespace ZL
// If player is close enough to pick up the item
if (distToObject <= obj->state.approachRadius) {
std::cout << "[PICKUP] Player reached object! Distance: " << distToObject << std::endl;
std::cout << "[PICKUP] Calling Lua callback for: " << obj->loadedObject.name << std::endl;
logger() << "[PICKUP] Player reached object! Distance: " << distToObject << std::endl;
logger() << "[PICKUP] Calling Lua callback for: " << obj->loadedObject.name << std::endl;
// Call custom activate function if specified, otherwise use fallback
try {
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);
}
else {
std::cout << "[PICKUP] Using fallback callback" << std::endl;
logger() << "[PICKUP] Using fallback callback" << std::endl;
scriptEngine.callItemPickupCallback(obj->loadedObject.name);
}
}
@@ -1666,7 +1666,7 @@ namespace ZL
if (state.targetInteractNpcIndex >= 0 && player) {
float distToNpc = (player->state.position - npcs[state.targetInteractNpcIndex]->state.position).norm();
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;
// Stop the player at the talk distance and have the NPC turn to face them.
player->setTarget(player->state.position);
@@ -1686,7 +1686,7 @@ namespace ZL
if (auto* tz = getTargetTeleportZone(); tz && player) {
float dist = (player->state.position - tz->position).norm();
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);
state.targetTeleportZoneIndex = -1;
return;
@@ -1710,7 +1710,7 @@ namespace ZL
const bool isInside = (dist <= autoR);
if (isInside && !tz.automaticPlayerInside) {
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);
return;
} else if (!isInside) {
@@ -1743,10 +1743,10 @@ namespace ZL
toTarget.y() = 0.f;
if (toTarget.norm() < car.waypointReachRadius) {
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())
{
std::cout << "waypointsOver" << std::endl;
logger() << "waypointsOver" << std::endl;
waypointsOver = true;
}
}
@@ -1895,17 +1895,17 @@ namespace ZL
return;
}
std::cout << "[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] Camera position: (" << camPos.x() << ", " << camPos.y() << ", " << camPos.z() << ")" << std::endl;
//logger() << "[CLICK] Ray direction: (" << rayDir.x() << ", " << rayDir.y() << ", " << rayDir.z() << ")" << std::endl;
// First check if we clicked on interactive object
InteractiveObject* clickedObject = raycastInteractiveObjects(camPos, rayDir);
if (clickedObject && player && clickedObject->state.isActive) {
std::cout << "[CLICK] *** SUCCESS: Clicked on interactive object: " << clickedObject->loadedObject.name << " ***" << std::endl;
std::cout << "[CLICK] Object position: (" << clickedObject->state.position.x() << ", "
<< clickedObject->state.position.y() << ", " << clickedObject->state.position.z() << ")" << std::endl;
std::cout << "[CLICK] Player position: (" << player->state.position.x() << ", "
<< player->state.position.y() << ", " << player->state.position.z() << ")" << std::endl;
//logger() << "[CLICK] *** SUCCESS: Clicked on interactive object: " << clickedObject->loadedObject.name << " ***" << std::endl;
//logger() << "[CLICK] Object position: (" << clickedObject->state.position.x() << ", "
// << clickedObject->state.position.y() << ", " << clickedObject->state.position.z() << ")" << std::endl;
//logger() << "[CLICK] Player position: (" << player->state.position.x() << ", "
// << player->state.position.y() << ", " << player->state.position.z() << ")" << std::endl;
state.targetInteractiveObjectIndex = findInteractiveObjectIndex(clickedObject);
state.targetInteractNpcIndex = -1;
@@ -1914,11 +1914,11 @@ namespace ZL
? clickedObject->state.interactionPosition
: clickedObject->state.position);
player->state.attackTargetIndex = CharacterState::kNoTarget;
std::cout << "[CLICK] Player moving to object..." << std::endl;
//logger() << "[CLICK] Player moving to object..." << std::endl;
}
else {
//std::cout << "Raycast npc step 1" << std::endl;
//logger() << "Raycast npc step 1" << std::endl;
// Check if we clicked on an NPC
Character* clickedNpc = raycastNpcs(camPos, rayDir);
if (clickedNpc && player) {
@@ -1931,25 +1931,25 @@ namespace ZL
}
}
if (npcIndex != -1) {
//std::cout << "Raycast npc step 2" << std::endl;
//logger() << "Raycast npc step 2" << std::endl;
state.targetInteractiveObjectIndex = -1;
state.targetTeleportZoneIndex = -1;
if (clickedNpc->state.canAttack) {
//std::cout << "Raycast npc step 3" << std::endl;
//std::cout << "player->state.attackTargetIndex=" << player->state.attackTargetIndex << std::endl;
//std::cout << "npcIndex=" << npcIndex << std::endl;
//logger() << "Raycast npc step 3" << std::endl;
//logger() << "player->state.attackTargetIndex=" << player->state.attackTargetIndex << std::endl;
//logger() << "npcIndex=" << npcIndex << std::endl;
// Hostile NPC: combat logic walks the player in via attackTargetIndex.
if (lastPlayerAttackTarget == npcIndex)
{
//std::cout << "Raycast npc step 4" << std::endl;
//logger() << "Raycast npc step 4" << std::endl;
player->tryAttackCombo();
}
player->state.attackTargetIndex = npcIndex;
state.targetInteractNpcIndex = -1;
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);
}
}
@@ -1959,15 +1959,15 @@ namespace ZL
if (distance <= NPC_TALK_DISTANCE) {
// 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);
npcs[npcIndex]->state.faceTargetIndex = CharacterState::kPlayerIndex;
scriptEngine.callNpcInteractCallback(npcIndex);
state.targetInteractNpcIndex = -1;
}
else {
std::cout << "[CLICK] NPC " << npcIndex << " out of talk range (distance " << distance
<< " > " << NPC_TALK_DISTANCE << "); walking to NPC..." << std::endl;
//logger() << "[CLICK] NPC " << npcIndex << " out of talk range (distance " << distance
// << " > " << NPC_TALK_DISTANCE << "); walking to NPC..." << std::endl;
player->setTarget(clickedNpc->state.position);
state.targetInteractNpcIndex = npcIndex;
}
@@ -1979,7 +1979,7 @@ namespace ZL
// Unproject click to ground plane for walk target / teleport detection
float t = -camPos.y() / rayDir.y();
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;
for (auto& tz : teleportZones) {
@@ -1989,7 +1989,7 @@ namespace ZL
}
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.targetInteractiveObjectIndex = -1;
state.targetInteractNpcIndex = -1;
@@ -2004,10 +2004,10 @@ namespace ZL
}
}
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)
{
@@ -2071,6 +2071,15 @@ namespace ZL
{
state.taxiCar.waypoints = state.taxiCarWaypoints;
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 ----
+18 -18
View File
@@ -91,15 +91,15 @@ namespace ZL
if (dx * dx + dz * dz <= removeRadius * removeRadius) {
navigationEditorPoints.erase(it);
rebuildPointsMesh();
std::cout << "[NAV_EDITOR] Removed point, " << navigationEditorPoints.size() << " remaining\n";
logger() << "[NAV_EDITOR] Removed point, " << navigationEditorPoints.size() << " remaining\n";
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 {
navigationEditorPoints.push_back(hit);
rebuildPointsMesh();
std::cout << "[NAV_EDITOR] Added point (" << hit.x() << ", " << hit.z()
logger() << "[NAV_EDITOR] Added point (" << hit.x() << ", " << hit.z()
<< "), total: " << navigationEditorPoints.size() << "\n";
}
}
@@ -107,7 +107,7 @@ namespace ZL
void LocationEditor::handleRightClick()
{
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.clear();
rebuildPointsMesh();
@@ -121,7 +121,7 @@ namespace ZL
}
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";
{
@@ -150,12 +150,12 @@ namespace ZL
if (!loc.navigation) return;
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
std::cerr << "[NAV_EDITOR] Failed to save config to: " << baseName << ".json\n";
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
std::cerr << "[NAV_EDITOR] Failed to save grid to: " << baseName << ".txt\n";
}
@@ -163,7 +163,7 @@ namespace ZL
void LocationEditor::reload()
{
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)
@@ -290,14 +290,14 @@ namespace ZL
void LocationEditor::selectInteractiveObject(int index)
{
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";
return;
}
selectedInteractiveObjectIndex = index;
boundsClickCount = 0;
buildInteractiveObjectBoundsMeshes();
std::cout << "[IO_EDITOR] Selected object " << index
logger() << "[IO_EDITOR] Selected object " << index
<< " (" << loc.interactiveObjects[index].loadedObject.name << ")\n";
}
@@ -305,7 +305,7 @@ namespace ZL
{
if (selectedInteractiveObjectIndex < 0 ||
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;
}
@@ -316,7 +316,7 @@ namespace ZL
target.state.hasInteractionPosition = true;
boundsClickCount = 0; // cancel any in-progress bounds placement
buildInteractiveObjectBoundsMeshes();
std::cout << "[IO_EDITOR] Interaction position set on object " << selectedInteractiveObjectIndex
logger() << "[IO_EDITOR] Interaction position set on object " << selectedInteractiveObjectIndex
<< " (" << target.loadedObject.name << ")"
<< " at (" << worldHit.x() << ", " << worldHit.z() << ")\n";
return;
@@ -343,7 +343,7 @@ namespace ZL
if (boundsClickCount == 0) {
boundsClickA = worldHit;
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";
} else {
const Eigen::Vector3f localA = worldToLocal(boundsClickA);
@@ -358,7 +358,7 @@ namespace ZL
boundsClickCount = 0;
buildInteractiveObjectBoundsMeshes();
std::cout << "[IO_EDITOR] Bounds set on object " << selectedInteractiveObjectIndex
logger() << "[IO_EDITOR] Bounds set on object " << selectedInteractiveObjectIndex
<< " (" << target.loadedObject.name << ")"
<< " min=(" << target.state.boundsMin.x() << ", " << target.state.boundsMin.z() << ")"
<< " max=(" << target.state.boundsMax.x() << ", " << target.state.boundsMax.z() << ")\n";
@@ -419,7 +419,7 @@ namespace ZL
std::ofstream out(filename);
if (out.is_open()) {
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";
} else {
std::cerr << "[IO_EDITOR] Failed to open " << filename << " for writing\n";
@@ -485,7 +485,7 @@ namespace ZL
loc.gameObjects[data2.name] = std::move(obj2);
editorPlacedObjects.push_back(data2);
std::cout << "[GAME_EDITOR] Placed '" << data.name << "' at ("
logger() << "[GAME_EDITOR] Placed '" << data.name << "' at ("
<< data.positionX << ", " << data.positionZ
<< ") scale=" << data.scale << " rotY=" << data.rotationY << "\n";
}
@@ -493,7 +493,7 @@ namespace ZL
void LocationEditor::saveObjects()
{
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;
}
@@ -527,7 +527,7 @@ namespace ZL
/*std::ofstream out(filename);
if (out.is_open()) {
out << j.dump(4);
std::cout << "[GAME_EDITOR] Saved " << editorPlacedObjects.size()
logger() << "[GAME_EDITOR] Saved " << editorPlacedObjects.size()
<< " object(s) to " << filename << "\n";
} else {
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_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) {
switch (status) {
@@ -215,6 +218,7 @@ namespace ZL {
portraitQuestJournalItemRoot = loadLocalizedUi("resources/w/ui/portrait_screen_journal_item.json", zipFile);
mainMenuRoot = loadLocalizedUi("resources/w/ui/screen_main_menu.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);
settingsGraphicsScreenRoot = loadLocalizedUi("resources/w/ui/screen_settings_graphics.json", zipFile);
languageScreenRoot = loadLocalizedUi("resources/w/ui/screen_language.json", zipFile);
@@ -344,6 +348,8 @@ namespace ZL {
topUiManager.clearMenuStack();
uiManager.replaceRoot(mainMenuRoot);
uiManager.setTextButtonCallback("menuStartButton", [this](const std::string&) {
audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg");
enterGameplay();
@@ -353,6 +359,10 @@ namespace ZL {
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");
@@ -378,6 +388,81 @@ namespace ZL {
#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() {
uiManager.pushMenuFromSavedRoot(aboutScreenRoot);
uiManager.setTextButtonCallback("aboutBackButton", [this](const std::string&) {
@@ -385,6 +470,35 @@ namespace ZL {
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::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() {
@@ -490,9 +604,11 @@ namespace ZL {
void MenuManager::showSettingsGraphicsScreen() {
uiManager.pushMenuFromSavedRoot(settingsGraphicsScreenRoot);
uiManager.setTextButtonCallback("settingsBackButton", [this](const std::string&) {
//saveSettings();
saveSettings();
uiManager.popMenu();
audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg");
uiManager.updateAllLayouts();
topUiManager.updateAllLayouts();
});
@@ -530,6 +646,8 @@ namespace ZL {
Environment::setFullscreen(!Environment::isFullscreen);
refreshFullscreenToggle();
audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg");
uiManager.updateAllLayouts();
topUiManager.updateAllLayouts();
});
uiManager.setTextButtonCallback("shadowToggleButton", [this, refreshShadowToggle](const std::string&) {
@@ -756,7 +874,7 @@ namespace ZL {
}
void MenuManager::selectInventoryItem(int index) {
std::cout << "MenuManager::selectInventoryItem: " << index << std::endl;
logger() << "MenuManager::selectInventoryItem: " << index << std::endl;
const auto& items = inventory->getItems();
if (index < 0 || index >= static_cast<int>(items.size())) return;
@@ -862,7 +980,7 @@ namespace ZL {
}
void MenuManager::toggleQuestJournal() {
std::cout << "[quest] toggleQuestJournal: " << (isQuestJournalOpen() ? "closing" : "opening") << std::endl;
logger() << "[quest] toggleQuestJournal: " << (isQuestJournalOpen() ? "closing" : "opening") << std::endl;
if (uiState_ == GameUiState::QuestJournal) {
closeQuestJournal();
}
@@ -1274,7 +1392,7 @@ namespace ZL {
void MenuManager::tutorialShowTaxiHint()
{
std::cout << "tutorialShowTaxiHint" << std::endl;
logger() << "tutorialShowTaxiHint" << std::endl;
gameState_.tutorialNeedOpenTaxiScreen = true;
if (uiState_ == GameUiState::Gameplay)
{
@@ -1889,7 +2007,7 @@ namespace ZL {
void MenuManager::setDarklandsMode(bool enabled)
{
std::cout << "MenuManager::setDarklandsMode called" << std::endl;
logger() << "MenuManager::setDarklandsMode called" << std::endl;
if (gameState_.currentLocationName == "uni_interior") {
if (enabled && gameState_.uniIntTutorialState == UniIntTutorialState::Step11) {
@@ -1979,11 +2097,11 @@ namespace ZL {
}
void MenuManager::applyCurrentHealthBar() {
std::cout << "MenuManager::applyCurrentHealthBar called step 1" << std::endl;
std::cout << "currentPlayerMaxHp_ is " << gameState_.playerMaxHp << std::endl;
logger() << "MenuManager::applyCurrentHealthBar called step 1" << std::endl;
logger() << "currentPlayerMaxHp_ is " << gameState_.playerMaxHp << std::endl;
if (gameState_.playerMaxHp <= 0.f) return;
std::cout << "MenuManager::applyCurrentHealthBar called step 2" << std::endl;
std::cout << "currentPlayerHp_ is " << gameState_.playerHp << std::endl;
logger() << "MenuManager::applyCurrentHealthBar called step 2" << std::endl;
logger() << "currentPlayerHp_ is " << gameState_.playerHp << std::endl;
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; }
void showMainMenu();
void showMenuAfterGameCompleted();
bool isMainMenuOpen() const { return uiState_ == GameUiState::MainMenu; }
void openPhoneScreen();
@@ -172,6 +173,7 @@ namespace ZL {
//void closePortraitJournalItemCompletely();
void showAboutScreen();
void showCreditsScreen();
void showSettingsScreen();
void showSettingsGraphicsScreen();
void showLanguageScreen();
@@ -262,6 +264,7 @@ namespace ZL {
std::shared_ptr<UiNode> mainMenuRoot;
std::shared_ptr<UiNode> aboutScreenRoot;
std::shared_ptr<UiNode> creditsScreenRoot;
std::shared_ptr<UiNode> settingsScreenRoot;
std::shared_ptr<UiNode> settingsGraphicsScreenRoot;
std::shared_ptr<UiNode> languageScreenRoot;
+40 -19
View File
@@ -5,6 +5,7 @@
#include <unordered_map>
#include "Location.h"
#include "items/ItemRegistry.h"
#include "utils/Utils.h"
#define SOL_ALL_SAFETIES_ON 1
#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);
// --- Переопределение функции 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");
// 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;
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;
});
api.set_function("player_rotate_to",
[loc](float angle) {
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;
});
@@ -172,12 +193,12 @@ namespace ZL {
const Item* item = ItemRegistry::instance().findById(itemId);
if (item) {
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;
}
else
{
std::cout << "[script] has_item: " << item->name << " returns false" << std::endl;
logger() << "[script] has_item: " << item->name << " returns false" << std::endl;
return false;
}
}
@@ -192,7 +213,7 @@ namespace ZL {
const Item* item = ItemRegistry::instance().findById(itemId);
if (item) {
inventory->addItem(item->id);
std::cout << "[script] pickup_item: " << item->name << std::endl;
logger() << "[script] pickup_item: " << item->name << std::endl;
} else {
std::cerr << "[script] pickup_item: item '" << itemId << "' not found in ItemRegistry\n";
}
@@ -200,7 +221,7 @@ namespace ZL {
// remove_item(item_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);
});
@@ -209,7 +230,7 @@ namespace ZL {
for (auto& intObj : loc->interactiveObjects) {
if (intObj.loadedObject.name == objectName) {
intObj.state.isActive = false;
std::cout << "[script] deactivate_interactive_object: " << objectName << std::endl;
logger() << "[script] deactivate_interactive_object: " << objectName << std::endl;
return;
}
}
@@ -221,7 +242,7 @@ namespace ZL {
for (auto& intObj : loc->interactiveObjects) {
if (intObj.loadedObject.name == objectName) {
intObj.state.isActive = true;
std::cout << "[script] activate_interactive_object: " << objectName << std::endl;
logger() << "[script] activate_interactive_object: " << objectName << std::endl;
return;
}
}
@@ -232,7 +253,7 @@ namespace ZL {
for (auto& intObj : loc->interactiveObjects) {
if (intObj.loadedObject.name == objectName) {
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;
}
}
@@ -243,7 +264,7 @@ namespace ZL {
for (auto& intObj : loc->interactiveObjects) {
if (intObj.loadedObject.name == objectName) {
intObj.state.alpha = value;
std::cout << "[script] set_object_alpha: " <<objectName << " " << value << std::endl;
logger() << "[script] set_object_alpha: " <<objectName << " " << value << std::endl;
return;
}
}
@@ -336,13 +357,13 @@ namespace ZL {
api.set_function("set_night",
[loc]() {
loc->requestNightDayTransition(true, false);
std::cout << "Set night called" << std::endl;
logger() << "Set night called" << std::endl;
});
api.set_function("set_dawn",
[loc]() {
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";
}
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 {
@@ -883,7 +904,7 @@ namespace ZL {
}
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];
@@ -891,7 +912,7 @@ namespace ZL {
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();
if (!result.valid()) {
@@ -899,7 +920,7 @@ namespace ZL {
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) {
@@ -914,18 +935,18 @@ namespace ZL {
sol::function activateFunc = lua["on_item_pickup"];
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);
if (!result.valid()) {
sol::error err = result;
std::cerr << "[SCRIPT] on_item_pickup callback error: " << err.what() << "\n";
}
else {
std::cout << "[SCRIPT] Callback executed successfully!" << std::endl;
logger() << "[SCRIPT] Callback executed successfully!" << std::endl;
}
}
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) {
std::cout << "Loading spark config from: " << path << std::endl;
logger() << "Loading spark config from: " << path << std::endl;
std::string content;
try {
@@ -498,13 +498,13 @@ namespace ZL {
json j;
try {
j = json::parse(content);
std::cout << "JSON parsed successfully" << std::endl;
logger() << "JSON parsed successfully" << std::endl;
}
catch (const std::exception& e) {
std::cerr << "JSON parse error: " << e.what() << std::endl;
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) {
if (!j.contains(key)) {
@@ -531,13 +531,13 @@ namespace ZL {
particles.resize(maxParticles);
drawPositions.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>();
std::cout << "Particle size: " << particleSize << std::endl;
logger() << "Particle size: " << particleSize << std::endl;
biasX = j["biasX"].get<float>();
std::cout << "Bias X: " << biasX << std::endl;
logger() << "Bias X: " << biasX << std::endl;
// emissionPoints
std::vector<Vector3f> points;
@@ -546,7 +546,7 @@ namespace ZL {
if (el.contains("position") && el["position"].is_array()) {
auto arr = el["position"];
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()) {
auto pr = el["positionRange"];
@@ -564,18 +564,18 @@ namespace ZL {
for (int k = 0; k < count; ++k) {
Vector3f randomPoint{ dx(gen), dy(gen), dz(gen) };
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;
}
}
}
if (!points.empty()) {
setEmissionPoints(points);
std::cout << "Total emission points: " << emissionPoints.size() << std::endl;
logger() << "Total emission points: " << emissionPoints.size() << std::endl;
}
else {
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!");
}
}
@@ -589,7 +589,7 @@ namespace ZL {
auto a = j["speedRange"];
speedRange.min = a[0].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 {
std::cerr << "speedRange missing or invalid" << std::endl;
@@ -600,7 +600,7 @@ namespace ZL {
auto a = j["zSpeedRange"];
zSpeedRange.min = a[0].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 {
std::cerr << "zSpeedRange missing or invalid" << std::endl;
@@ -611,7 +611,7 @@ namespace ZL {
auto a = j["scaleRange"];
scaleRange.min = a[0].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 {
std::cerr << "scaleRange missing or invalid" << std::endl;
@@ -622,7 +622,7 @@ namespace ZL {
auto a = j["lifeTimeRange"];
lifeTimeRange.min = a[0].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 {
std::cerr << "lifeTimeRange missing or invalid" << std::endl;
@@ -632,17 +632,17 @@ namespace ZL {
// texture
if (j.contains("texture") && j["texture"].is_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 {
std::cout << "Loading texture step 1" << std::endl;
logger() << "Loading texture step 1" << std::endl;
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);
std::cout << "Texture loaded successfully, ID: " << texture->getTexID() << std::endl;
logger() << "Texture loaded successfully, ID: " << texture->getTexID() << std::endl;
}
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;
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!");
}
std::cout << "Working with shaders 1" << std::endl;
logger() << "Working with shaders 1" << std::endl;
// shaders
if (j.contains("shaderProgramName") && j["shaderProgramName"].is_string()) {
shaderProgramName = j["shaderProgramName"].get<std::string>();
std::cout << "Shader program name: " << shaderProgramName << std::endl;
logger() << "Shader program name: " << shaderProgramName << std::endl;
}
else {
std::cerr << "shaderProgramName missing or invalid" << std::endl;
@@ -665,7 +665,7 @@ namespace ZL {
drawDataDirty = true;
configured = true;
std::cout << "SparkEmitter configuration loaded successfully!" << std::endl;
logger() << "SparkEmitter configuration loaded successfully!" << std::endl;
return true;
}
+6 -6
View File
@@ -81,7 +81,7 @@ namespace ZL
numberVertices = std::stoi(match.str());
}
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.");
}
@@ -116,7 +116,7 @@ namespace ZL
// UV - [7], [8]
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));
}
@@ -137,7 +137,7 @@ namespace ZL
numberTriangles = std::stoi(match.str());
}
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.");
}
@@ -162,7 +162,7 @@ namespace ZL
}
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));
}
@@ -193,7 +193,7 @@ namespace ZL
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;
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;
return result;
+101 -42
View File
@@ -182,7 +182,7 @@ namespace ZL {
mesh.RefreshVBO();
}
void UiTextButton::draw(Renderer& renderer) const {
void UiTextButton::draw(Renderer& renderer, float accumX, float accumY) const {
// Draw background texture (optional)
const std::shared_ptr<Texture>* tex = nullptr;
@@ -221,16 +221,18 @@ namespace ZL {
? wrapTextByPixels(text, *textRenderer, rect.w - textPaddingX * 2.0f, scale)
: text;
float tx = rect.x + rect.w / 2.0f + animOffsetX;
// Прибавляем accumX и accumY
float tx = rect.x + rect.w / 2.0f + animOffsetX + accumX;
if (!textCentered) {
tx = rect.x + textPaddingX + animOffsetX;
tx = rect.x + textPaddingX + animOffsetX + accumX;
}
float ty;
if (topAligned) {
ty = rect.y + rect.h - textPaddingY - static_cast<float>(fontSize) + animOffsetY;
} else {
ty = rect.y + rect.h * 0.5f + textPaddingY + animOffsetY;
ty = rect.y + rect.h - textPaddingY - static_cast<float>(fontSize) + animOffsetY + accumY;
}
else {
ty = rect.y + rect.h * 0.5f + textPaddingY + animOffsetY + accumY;
}
textRenderer->drawText(displayText, tx, ty, scale, textCentered, color);
@@ -238,24 +240,19 @@ namespace ZL {
glEnable(GL_DEPTH_TEST);
}
void UiTextView::draw(Renderer& renderer) const {
void UiTextView::draw(Renderer& renderer, float accumX, float accumY) const {
(void)renderer;
if (!textRenderer || text.empty()) {
return;
}
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;
if (!usesModernRectText) {
textRenderer->drawText(
text,
rect.x + rect.w * 0.5f,
rect.y + rect.h * 0.5f,
rect.x + rect.w * 0.5f + accumX,
rect.y + rect.h * 0.5f + accumY,
scale,
textCentered,
color
@@ -272,12 +269,10 @@ namespace ZL {
float ty = rect.y + rect.h * 0.5f;
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);
}
textRenderer->drawText(finalText, tx, ty, scale, textCentered, color);
textRenderer->drawText(finalText, tx + accumX, ty + accumY, scale, textCentered, color);
}
void UiSlider::buildTrackMesh() {
@@ -537,7 +532,7 @@ namespace ZL {
if (!t.contains(key) || !t[key].is_string()) return nullptr;
std::string path = t[key].get<std::string>();
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);
}
catch (const std::exception& e) {
@@ -571,7 +566,7 @@ namespace ZL {
if (!t.contains(key) || !t[key].is_string()) return nullptr;
std::string path = t[key].get<std::string>();
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);
}
catch (const std::exception& e) {
@@ -1480,60 +1475,61 @@ namespace ZL {
renderer.LoadIdentity();
renderer.RenderUniform1f("uAlpha", 1.0f); // ensure alpha is fully opaque unless a widget overrides it
std::function<void(const std::shared_ptr<UiNode>&)> drawNode =
[&](const std::shared_ptr<UiNode>& node) {
std::function<void(const std::shared_ptr<UiNode>&, float, float)> drawNode =
[&](const std::shared_ptr<UiNode>& node, float accumX, float accumY) {
if (!node || !node->visible) return;
// Считаем полное смещение для текущего узла и его потомков
float curAccumX = accumX + node->animOffsetX;
float curAccumY = accumY + node->animOffsetY;
renderer.PushMatrix();
renderer.TranslateMatrix({ node->screenRect.x + node->screenRect.w * 0.5f,
renderer.TranslateMatrix({ node->animOffsetX, node->animOffsetY, 0.0f });
renderer.TranslateMatrix({ node->screenRect.x + node->screenRect.w * 0.5f,
node->screenRect.y + node->screenRect.h * 0.5f, 0.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 });
// 1. Сначала изображения
if (node->staticImage) {
node->staticImage->draw(renderer);
}
// 1b. Chat bubble (composite widget)
if (node->chatBubble) {
node->chatBubble->draw(renderer, node->scaleX, node->scaleY, projW, projH);
}
// 2. Потом кнопки
if (node->button) {
node->button->draw(renderer);
}
if (node->textButton) {
node->textButton->draw(renderer);
// Передаем накопленные смещения
node->textButton->draw(renderer, curAccumX, curAccumY);
}
// 3. Потом слайдеры
if (node->slider) {
node->slider->draw(renderer);
}
// 4. Потом текстовые поля
if (node->textField) {
node->textField->draw(renderer);
}
// 5. ПОСЛЕДНИЙ - ТЕКСТ (поверх всего!)
if (node->textView) {
node->textView->draw(renderer);
// Передаем накопленные смещения
node->textView->draw(renderer, curAccumX, curAccumY);
}
// Рисуем детей
for (auto& child : node->children) {
drawNode(child);
// Прокидываем сумму дальше
drawNode(child, curAccumX, curAccumY);
}
renderer.PopMatrix();
};
drawNode(root);
drawNode(root, 0.0f, 0.0f);
renderer.PopMatrix();
renderer.PopProjectionMatrix();
@@ -1625,6 +1621,12 @@ namespace ZL {
node->textButton->animScaleX = act.origScaleX;
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.elapsedMs = 0.0f;
act.stepStarted = false;
@@ -1650,6 +1652,12 @@ namespace ZL {
node->textButton->animOffsetX = step.toX;
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") {
if (node->button) {
@@ -1681,8 +1689,9 @@ namespace ZL {
act.origScaleY = node->textButton->animScaleY;
}
else {
act.origOffsetX = act.origOffsetY = 0.0f;
act.origScaleX = act.origScaleY = 1.0f;
//logger() << "[animation] !act.stepStarted && act.stepIndex == 0 && act.elapsedMs == 0.0f: " << node->name << " y=" << node->animOffsetY << std::endl;
act.origOffsetX = node->animOffsetX;
act.origOffsetY = node->animOffsetY;
}
}
@@ -1703,8 +1712,11 @@ namespace ZL {
act.startScaleY = node->textButton->animScaleY;
}
else {
act.startOffsetX = act.startOffsetY = 0.0f;
act.startScaleX = act.startScaleY = 1.0f;
//logger() << "[animation] !act.stepStarted && prevElapsed == 0.0f: " << node->name << " y=" << node->animOffsetY << std::endl;
act.startOffsetX = node->animOffsetX;
act.startOffsetY = node->animOffsetY;
act.startScaleX = node->scaleX;
act.startScaleY = node->scaleY;
}
if (step.type == "move") {
act.endOffsetX = step.toX;
@@ -1731,6 +1743,12 @@ namespace ZL {
node->textButton->animOffsetX = nx;
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") {
float sx = act.startScaleX + (act.endScaleX - act.startScaleX) * te;
@@ -1986,12 +2004,46 @@ namespace ZL {
aa.origScaleX = node->textButton->animScaleX;
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 });
if (cbIt != animCallbacks.end()) aa.onComplete = cbIt->second;
nodeActiveAnims[node].push_back(std::move(aa));
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) {
if (!root) return false;
auto node = findNodeByName(root, nodeName);
@@ -2042,6 +2094,13 @@ namespace ZL {
aa.origScaleX = n->textButton->animScaleX;
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 });
if (cbIt != animCallbacks.end()) aa.onComplete = cbIt->second;
nodeActiveAnims[n].push_back(std::move(aa));
+5 -2
View File
@@ -187,7 +187,7 @@ namespace ZL {
float animScaleY = 1.0f;
void buildMesh();
void draw(Renderer& renderer) const;
void draw(Renderer& renderer, float accumX = 0.0f, float accumY = 0.0f) const;
};
struct UiTextView {
@@ -206,7 +206,7 @@ namespace ZL {
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 {
@@ -313,6 +313,8 @@ namespace ZL {
float scaleX = 1.0f;
float scaleY = 1.0f;
float animOffsetX = 0.0f;
float animOffsetY = 0.0f;
bool visible = true;
@@ -475,6 +477,7 @@ namespace ZL {
void update(float deltaMs);
void startAnimation(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 setAnimationCallback(const std::string& nodeName, const std::string& animName, std::function<void()> cb);
void updateAllLayouts();
+3 -2
View File
@@ -3,6 +3,7 @@
#include <algorithm>
#include <cmath>
#include <iostream>
#include "utils/Utils.h"
namespace ZL::Cutscene {
@@ -68,7 +69,7 @@ bool CutsceneRuntime::start(const std::string& cutsceneId) {
onLineStarted(firstLine.luaCallback);
}
std::cout << "[CUTSCENE] start id=" << cutsceneId
logger() << "[CUTSCENE] start id=" << cutsceneId
<< " lines=" << def->lines.size()
<< " totalDuration=" << cutsceneTotalDurationMs
<< std::endl;
@@ -176,7 +177,7 @@ void CutsceneRuntime::skip() {
}
void CutsceneRuntime::finish() {
std::cout << "[CUTSCENE] finish id=" << activeCutsceneId << std::endl;
logger() << "[CUTSCENE] finish id=" << activeCutsceneId << std::endl;
const std::string finishedId = activeCutsceneId;
stop();
if (onFinished && !finishedId.empty()) {
+14 -13
View File
@@ -9,6 +9,7 @@
#include "../Character.h"
#include <SDL.h>
namespace ZL {
using json = nlohmann::json;
@@ -73,7 +74,7 @@ namespace ZL {
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;
}
@@ -115,7 +116,7 @@ namespace ZL {
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;
}
@@ -199,7 +200,7 @@ namespace ZL {
// Service the OS message queue between loads so the window doesn't
// get flagged as "not responding" during long location loads.
SDL_PumpEvents();
std::cout << "Loading game object: " << data.name << std::endl;
logger() << "Loading game object: " << data.name << std::endl;
try {
LoadedGameObject obj = buildLoadedObject(data, renderer, zipPath);
@@ -208,13 +209,13 @@ namespace ZL {
obj.mesh.RefreshVBO();
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) {
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;
}
@@ -230,17 +231,17 @@ namespace ZL {
for (const auto& data : loadInteractiveFromJson(jsonPath, zipPath)) {
SDL_PumpEvents();
std::cout << "Loading interactive object: " << data.base.name << std::endl;
logger() << "Loading interactive object: " << data.base.name << std::endl;
try {
InteractiveObject intObj = InteractiveObject::createFromState(
data.toInteractiveObjectState(), renderer, zipPath);
intObj.loadedObject.name = data.base.name;
if (!data.activateFunctionName.empty())
std::cout << "Successfully loaded interactive: " << data.base.name
logger() << "Successfully loaded interactive: " << data.base.name
<< " (function: " << data.activateFunctionName << ")" << std::endl;
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));
} 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;
}
@@ -301,7 +302,7 @@ namespace ZL {
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) {
std::cerr << "Error loading NPCs from JSON: " << e.what() << std::endl;
}
@@ -369,11 +370,11 @@ namespace ZL {
for (const auto& npcData : loadNpcsFromJson(jsonPath, zipPath)) {
SDL_PumpEvents();
std::cout << "Loading NPC: " << npcData.name << std::endl;
logger() << "Loading NPC: " << npcData.name << std::endl;
try {
auto npc = Character::createFromState(npcData.toCharacterState(), renderer, zipPath.c_str());
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;
npc->index = index;
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;
}
+3 -2
View File
@@ -3,19 +3,20 @@
#include "external/nlohmann/json.hpp"
#include <algorithm>
#include <iostream>
#include "../utils/Utils.h"
namespace ZL {
void Inventory::addItem(const std::string& 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);
}
void Inventory::removeItem(const std::string& itemId) {
auto it = std::find(itemIds.begin(), itemIds.end(), itemId);
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);
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);
}
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 {
+293 -227
View File
@@ -11,133 +11,127 @@
#include <emscripten/html5.h>
#endif
// For Emscripten the game is heap-allocated so it can be destroyed and
// re-created when the WebGL context is lost and restored (e.g. fullscreen).
// For Android and Desktop a plain global value is used (no context loss).
#ifdef EMSCRIPTEN
ZL::Game* g_game = nullptr;
//static SDL_Window* win_x = nullptr;
static SDL_GLContext glContext_x; // Не указатель, а сам объект (который суть void*)
#else
ZL::Game game;
#ifdef STEAMSDK
#include "steam_api.h"
#endif
#ifdef EMSCRIPTEN
#if defined(EMSCRIPTEN)
static SDL_GLContext glContext_x;
#endif
// Единый указатель на объект игры для всех платформ
ZL::Game* g_game = nullptr;
void MainLoop() {
g_game->update();
}
#else
void MainLoop() {
game.update();
}
#endif
#ifdef EMSCRIPTEN
EM_BOOL onWebGLContextLost(int /*eventType*/, const void* /*reserved*/, void* /*userData*/) {
return EM_TRUE;
return EM_TRUE;
}
EM_BOOL onWebGLContextRestored(int /*eventType*/, const void* /*reserved*/, void* /*userData*/) {
return EM_TRUE;
return EM_TRUE;
}
static void applyResize(int logicalW, int logicalH) {
// Получаем коэффициент плотности пикселей (например, 2.625 на Pixel или 3.0 на Samsung)
double dpr = emscripten_get_device_pixel_ratio();
//double dpr = 1; // low quality
// Получаем коэффициент плотности пикселей (например, 2.625 на Pixel или 3.0 на Samsung)
double dpr = emscripten_get_device_pixel_ratio();
//double dpr = 1; // low quality
// Вычисляем реальные физические пиксели
int physicalW = static_cast<int>(logicalW * dpr);
int physicalH = static_cast<int>(logicalH * dpr);
// Вычисляем реальные физические пиксели
int physicalW = static_cast<int>(logicalW * dpr);
int physicalH = static_cast<int>(logicalH * dpr);
// Устанавливаем размер внутреннего буфера канваса
emscripten_set_canvas_element_size("#canvas", physicalW, physicalH);
// Устанавливаем размер внутреннего буфера канваса
emscripten_set_canvas_element_size("#canvas", physicalW, physicalH);
// Сообщаем SDL о новом размере.
// ВАЖНО: SDL2 в Emscripten ожидает здесь именно физические пиксели
// для корректной работы последующих вызовов glViewport.
if (ZL::Environment::window) {
SDL_SetWindowSize(ZL::Environment::window, physicalW, physicalH);
}
// Сообщаем SDL о новом размере.
// ВАЖНО: SDL2 в Emscripten ожидает здесь именно физические пиксели
// для корректной работы последующих вызовов glViewport.
if (ZL::Environment::window) {
SDL_SetWindowSize(ZL::Environment::window, physicalW, physicalH);
}
// Пушим событие, чтобы движок пересчитал матрицы проекции
SDL_Event e = {};
e.type = SDL_WINDOWEVENT;
e.window.event = SDL_WINDOWEVENT_RESIZED;
e.window.data1 = physicalW;
e.window.data2 = physicalH;
SDL_PushEvent(&e);
// Пушим событие, чтобы движок пересчитал матрицы проекции
SDL_Event e = {};
e.type = SDL_WINDOWEVENT;
e.window.event = SDL_WINDOWEVENT_RESIZED;
e.window.data1 = physicalW;
e.window.data2 = physicalH;
SDL_PushEvent(&e);
std::cout << "Resized, new size: " << logicalW << "x" << logicalH
<< " (physical: " << physicalW << "x" << physicalH
logger() << "Resized, new size: " << logicalW << "x" << logicalH
<< " (physical: " << physicalW << "x" << physicalH
<< ", DPR: " << dpr << ")" << std::endl;
}
EM_BOOL onWindowResized(int /*eventType*/, const EmscriptenUiEvent* e, void* /*userData*/) {
// Use the event's window dimensions — querying the canvas element would
// return its old fixed size (e.g. 1280x720) before it has been resized.
applyResize(e->windowInnerWidth, e->windowInnerHeight);
return EM_FALSE;
// Use the event's window dimensions — querying the canvas element would
// return its old fixed size (e.g. 1280x720) before it has been resized.
applyResize(e->windowInnerWidth, e->windowInnerHeight);
return EM_FALSE;
}
EM_BOOL onFullscreenChanged(int /*eventType*/, const EmscriptenFullscreenChangeEvent* e, void* /*userData*/) {
ZL::Environment::isFullscreen = e->isFullscreen;
ZL::Environment::isFullscreen = e->isFullscreen;
// Вместо window.innerWidth, попробуйте запросить размер целевого элемента
// так как после перехода в FS именно он растягивается на весь экран.
double clientW, clientH;
emscripten_get_element_css_size("#canvas", &clientW, &clientH);
applyResize(clientW, clientH);
return EM_FALSE;
// Вместо window.innerWidth, попробуйте запросить размер целевого элемента
// так как после перехода в FS именно он растягивается на весь экран.
double clientW, clientH;
emscripten_get_element_css_size("#canvas", &clientW, &clientH);
applyResize(clientW, clientH);
return EM_FALSE;
}
int main(int argc, char* argv[]) {
SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS);
SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); // Для WebGL 2.0
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); // Для WebGL 2.0
ZL::Environment::window = SDL_CreateWindow("Game", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 1280, 720, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE);
glContext_x = SDL_GL_CreateContext(ZL::Environment::window);
ZL::Environment::window = SDL_CreateWindow("Game", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 1280, 720, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE);
glContext_x = SDL_GL_CreateContext(ZL::Environment::window);
SDL_GL_MakeCurrent(ZL::Environment::window, glContext_x);
SDL_GL_MakeCurrent(ZL::Environment::window, glContext_x);
g_game = new ZL::Game();
g_game->setup();
emscripten_set_webglcontextlost_callback("#canvas", nullptr, EM_TRUE, onWebGLContextLost);
emscripten_set_webglcontextrestored_callback("#canvas", nullptr, EM_TRUE, onWebGLContextRestored);
emscripten_set_webglcontextrestored_callback("#canvas", nullptr, EM_TRUE, onWebGLContextRestored);
// Keep Environment::width/height in sync when the canvas is resized.
emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, nullptr, EM_FALSE, onWindowResized);
emscripten_set_fullscreenchange_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, nullptr, EM_FALSE, onFullscreenChanged);
// 2. ИНИЦИАЛИЗАЦИЯ РАЗМЕРОВ:
// Получаем реальные размеры окна браузера на момент запуска
int canvasW = EM_ASM_INT({ return window.innerWidth; });
int canvasH = EM_ASM_INT({ return window.innerHeight; });
// Keep Environment::width/height in sync when the canvas is resized.
emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, nullptr, EM_FALSE, onWindowResized);
emscripten_set_fullscreenchange_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, nullptr, EM_FALSE, onFullscreenChanged);
// 2. ИНИЦИАЛИЗАЦИЯ РАЗМЕРОВ:
// Получаем реальные размеры окна браузера на момент запуска
int canvasW = EM_ASM_INT({ return window.innerWidth; });
int canvasH = EM_ASM_INT({ return window.innerHeight; });
// Вызываем вашу функцию — она сама применит DPR, выставит физический размер
// канваса и отправит SDL_WINDOWEVENT_RESIZED для настройки проекции.
applyResize(canvasW, canvasH);
// Вызываем вашу функцию — она сама применит DPR, выставит физический размер
// канваса и отправит SDL_WINDOWEVENT_RESIZED для настройки проекции.
applyResize(canvasW, canvasH);
// Prevent mouse clicks from generating fake SDL_FINGERDOWN events (desktop browser)
SDL_SetHint(SDL_HINT_MOUSE_TOUCH_EVENTS, "0");
// Prevent touch events from generating fake SDL_MOUSEBUTTONDOWN events (mobile browser),
// since we now handle SDL_FINGERDOWN directly for multi-touch support.
SDL_SetHint(SDL_HINT_TOUCH_MOUSE_EVENTS, "0");
ZL::emscriptenInitFileSystem();
ZL::emscriptenInitFileSystem();
emscripten_set_main_loop(MainLoop, 0, 1);
return 0;
emscripten_set_main_loop(MainLoop, 0, 1);
return 0;
}
#endif
@@ -147,82 +141,83 @@ int main(int argc, char* argv[]) {
extern "C" int SDL_main(int argc, char* argv[]) {
// Инициализация SDL перед получением информации о дисплее
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS) != 0) {
__android_log_print(ANDROID_LOG_ERROR, "Game", "SDL init failed: %s", SDL_GetError());
return 1;
}
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS) != 0) {
__android_log_print(ANDROID_LOG_ERROR, "Game", "SDL init failed: %s", SDL_GetError());
return 1;
}
SDL_DisplayMode displayMode;
if (SDL_GetCurrentDisplayMode(0, &displayMode) != 0) {
__android_log_print(ANDROID_LOG_ERROR, "Game", "SDL_GetCurrentDisplayMode failed: %s", SDL_GetError());
SDL_Quit();
return 1;
}
ZL::Environment::width = displayMode.w;
ZL::Environment::height = displayMode.h;
SDL_DisplayMode displayMode;
if (SDL_GetCurrentDisplayMode(0, &displayMode) != 0) {
__android_log_print(ANDROID_LOG_ERROR, "Game", "SDL_GetCurrentDisplayMode failed: %s", SDL_GetError());
SDL_Quit();
return 1;
}
ZL::Environment::width = displayMode.w;
ZL::Environment::height = displayMode.h;
__android_log_print(ANDROID_LOG_INFO, "Game", "Display resolution: %dx%d",
ZL::Environment::width, ZL::Environment::height);
__android_log_print(ANDROID_LOG_INFO, "Game", "Display resolution: %dx%d",
ZL::Environment::width, ZL::Environment::height);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 5);
SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 6);
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 5);
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 16);
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 5);
SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 6);
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 5);
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 16);
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
ZL::Environment::window = SDL_CreateWindow(
"Shadow Over Bishkek",
SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
ZL::Environment::width, ZL::Environment::height,
SDL_WINDOW_FULLSCREEN | SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN
);
ZL::Environment::window = SDL_CreateWindow(
"Shadow Over Bishkek",
SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
ZL::Environment::width, ZL::Environment::height,
SDL_WINDOW_FULLSCREEN | SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN
);
if (!ZL::Environment::window) {
__android_log_print(ANDROID_LOG_ERROR, "Game", "Failed to create window: %s", SDL_GetError());
SDL_Quit();
return 1;
}
__android_log_print(ANDROID_LOG_ERROR, "Game", "Failed to create window: %s", SDL_GetError());
SDL_Quit();
return 1;
}
SDL_GLContext ctx = SDL_GL_CreateContext(ZL::Environment::window);
if (!ctx) {
__android_log_print(ANDROID_LOG_ERROR, "Game", "SDL_GL_CreateContext failed: %s", SDL_GetError());
SDL_DestroyWindow(ZL::Environment::window);
SDL_Quit();
return 1;
}
if (!ctx) {
__android_log_print(ANDROID_LOG_ERROR, "Game", "SDL_GL_CreateContext failed: %s", SDL_GetError());
SDL_DestroyWindow(ZL::Environment::window);
SDL_Quit();
return 1;
}
if (SDL_GL_MakeCurrent(ZL::Environment::window, ctx) != 0) {
__android_log_print(ANDROID_LOG_ERROR, "Game", "SDL_GL_MakeCurrent failed: %s", SDL_GetError());
SDL_GL_DeleteContext(ctx);
SDL_DestroyWindow(ZL::Environment::window);
SDL_Quit();
return 1;
}
if (SDL_GL_MakeCurrent(ZL::Environment::window, ctx) != 0) {
__android_log_print(ANDROID_LOG_ERROR, "Game", "SDL_GL_MakeCurrent failed: %s", SDL_GetError());
SDL_GL_DeleteContext(ctx);
SDL_DestroyWindow(ZL::Environment::window);
SDL_Quit();
return 1;
}
// Настройка VSync
if (SDL_GL_SetSwapInterval(1) < 0) {
__android_log_print(ANDROID_LOG_WARN, "Game", "Unable to set VSync: %s", SDL_GetError());
}
// Настройка VSync
if (SDL_GL_SetSwapInterval(1) < 0) {
__android_log_print(ANDROID_LOG_WARN, "Game", "Unable to set VSync: %s", SDL_GetError());
}
// Настройка игры
try {
game.setup();
} catch (const std::exception &e) {
__android_log_print(ANDROID_LOG_ERROR, "Game", "Game setup failed: %s", e.what());
return 1;
}
// Настройка игры
try {
game.setup();
}
catch (const std::exception& e) {
__android_log_print(ANDROID_LOG_ERROR, "Game", "Game setup failed: %s", e.what());
return 1;
}
while (!game.shouldExit()) {
game.update();
SDL_Delay(16);
}
while (!game.shouldExit()) {
game.update();
SDL_Delay(16);
}
return 0;
return 0;
}
@@ -231,113 +226,184 @@ extern "C" int SDL_main(int argc, char* argv[]) {
#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__linux__)
int main(int argc, char *argv[]) {
try
{
// 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()
{
SDL_SetHint(SDL_HINT_WINDOWS_DPI_AWARENESS, "permonitorv2");
//SDL_SetHint(SDL_HINT_WINDOWS_DPI_SCALING, "1");
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>();
}
constexpr int CONST_WIDTH = ZL::Environment::CONST_DEFAULT_WIDTH;
constexpr int CONST_HEIGHT = ZL::Environment::CONST_DEFAULT_HEIGHT;
if (settingsRoot.contains("isHighDPIEnabled")) {
ZL::Environment::isHighDPIEnabled = settingsRoot["isHighDPIEnabled"].get<bool>();
}
ZL::Environment::width = CONST_WIDTH;
ZL::Environment::height = CONST_HEIGHT;
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS) != 0) {
SDL_Log("SDL init failed: %s", SDL_GetError());
return 1;
}
if (settingsRoot.contains("enableLogging")) {
ZL::Environment::enableLogging = settingsRoot["enableLogging"].get<bool>();
}
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
}
catch (const std::exception& e) {
std::cerr << "[settings] Failed to parse settings.json: " << e.what() << std::endl;
}
}
}
// 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;
if (ZL::Environment::isFullscreen) {
// windowFlags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
}
ZL::Environment::isFullscreen = false;
int main(int argc, char* argv[]) {
SDL_GLContext ctx = nullptr;
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");
constexpr int CONST_WIDTH = ZL::Environment::CONST_DEFAULT_WIDTH;
constexpr int CONST_HEIGHT = ZL::Environment::CONST_DEFAULT_HEIGHT;
ZL::Environment::width = CONST_WIDTH;
ZL::Environment::height = CONST_HEIGHT;
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS) != 0) {
SDL_Log("SDL init failed: %s", SDL_GetError());
return 1;
}
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
Uint32 windowFlags = SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN | SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_RESIZABLE;
if (ZL::Environment::isFullscreen) {
windowFlags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
}
SDL_DisplayMode dm;
if (SDL_GetCurrentDisplayMode(0, &dm) == 0) {
ZL::logger() << "Desktop resolution: " << dm.w << "x" << dm.h << " @ " << dm.refresh_rate << "Hz\n";
}
else {
SDL_Log("SDL_GetCurrentDisplayMode failed: %s", SDL_GetError());
}
SDL_DisplayMode dm;
if (SDL_GetCurrentDisplayMode(0, &dm) == 0) {
std::cout << "Desktop resolution: " << dm.w << "x" << dm.h << " @ " << dm.refresh_rate << "Hz\n";
}
else {
SDL_Log("SDL_GetCurrentDisplayMode failed: %s", SDL_GetError());
}
int windowWidth = ZL::Environment::width;
int windowHeight = ZL::Environment::height;
//In case if the game is launched on a small screen
if (dm.w <= 1280 && dm.h <= 720) {
windowWidth = round(dm.w / 1.5);
windowWidth = round(dm.w / 1.5);
windowHeight = round(dm.h / 1.5);
}
ZL::Environment::window = SDL_CreateWindow(
"Shadow Over Bishkek",
SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
windowWidth, windowHeight,
windowFlags
);
ZL::Environment::window = SDL_CreateWindow(
"Shadow Over Bishkek Demo",
SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
windowWidth, windowHeight,
windowFlags
);
SDL_GLContext ctx = SDL_GL_CreateContext(ZL::Environment::window);
SDL_GL_MakeCurrent(ZL::Environment::window, ctx);
ctx = SDL_GL_CreateContext(ZL::Environment::window);
SDL_GL_MakeCurrent(ZL::Environment::window, ctx);
// On HiDPI displays the drawable (physical) size is larger than the
// logical window size. Use the drawable size so the GL framebuffer
// covers every physical pixel and the image stays sharp.
if (ZL::Environment::isHighDPIEnabled)
{
int drawW, drawH;
SDL_GL_GetDrawableSize(ZL::Environment::window, &drawW, &drawH);
ZL::Environment::width = drawW;
ZL::Environment::height = drawH;
std::cout << "HiDPI enabled, drawable size: " << drawW << "x" << drawH << std::endl;
}
else
{
if (ZL::Environment::isHighDPIEnabled) {
int drawW, drawH;
SDL_GL_GetDrawableSize(ZL::Environment::window, &drawW, &drawH);
ZL::Environment::width = drawW;
ZL::Environment::height = drawH;
ZL::logger() << "HiDPI enabled, drawable size: " << drawW << "x" << drawH << std::endl;
}
else {
int winW, winH;
SDL_GetWindowSize(ZL::Environment::window, &winW, &winH);
ZL::Environment::width = winW;
ZL::Environment::height = winH;
std::cout << "HiDPI disabled, window size: " << winW << "x" << winH << std::endl;
}
SDL_GetWindowSize(ZL::Environment::window, &winW, &winH);
ZL::Environment::width = winW;
ZL::Environment::height = winH;
ZL::logger() << "HiDPI disabled, window size: " << winW << "x" << winH << std::endl;
}
game.setup();
// Динамическое создание объекта игры
g_game = new ZL::Game();
g_game->setup();
while (!game.shouldExit()) {
game.update();
SDL_Delay(2);
}
}
catch (const std::exception& e)
{
std::cout << e.what() << std::endl;
}
while (!g_game->shouldExit()) {
g_game->update();
SDL_Delay(2);
return 0;
#ifdef STEAMSDK
SteamAPI_RunCallbacks();
#endif
}
//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)
{
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;
}
#endif
+2 -2
View File
@@ -100,7 +100,7 @@ void PathFinder::build(const std::string& configPath,
if (isTxt) {
if (loadGrid(configPath, zipPath)) {
ready = true;
std::cout << "[nav] Loaded precomputed grid: " << gridWidth << "x" << gridDepth
logger() << "[nav] Loaded precomputed grid: " << gridWidth << "x" << gridDepth
<< ", cell=" << cellSize << "\n";
} else {
std::cerr << "[nav] Failed to load precomputed grid: " << configPath << "\n";
@@ -120,7 +120,7 @@ void PathFinder::build(const std::string& configPath,
rebuildWalkableGrid();
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";
}
+2 -2
View File
@@ -329,7 +329,7 @@ namespace ZL {
size_t error = glGetError();
if (error != GL_NO_ERROR)
{
std::cout << "OpenGL error: " << error << std::endl;
logger() << "OpenGL error: " << error << std::endl;
throw std::runtime_error("Gl error");
}
}
@@ -338,7 +338,7 @@ namespace ZL {
size_t error = glGetError();
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");
}
}
+13 -13
View File
@@ -10,7 +10,7 @@ namespace ZL {
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;
@@ -45,7 +45,7 @@ namespace ZL {
glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &fragmentShaderCompiled);
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) {
@@ -53,11 +53,11 @@ namespace ZL {
__android_log_print(ANDROID_LOG_ERROR, "ShaderManager",
"Failed to compile vertex shader: %s", infoLog);
#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!");
}
std::cout << "Creating shader resource step 2" << std::endl;
//logger() << "Creating shader resource step 2" << std::endl;
if (!fragmentShaderCompiled) {
@@ -65,12 +65,12 @@ namespace ZL {
__android_log_print(ANDROID_LOG_ERROR, "ShaderManager",
"Failed to compile fragment shader: %s", infoLog);
#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!");
}
std::cout << "Creating shader resource step 4" << std::endl;
//logger() << "Creating shader resource step 4" << std::endl;
shaderProgram = glCreateProgram();
@@ -86,7 +86,7 @@ namespace ZL {
glGetProgramiv(shaderProgram, GL_LINK_STATUS, &programLinked);
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) {
shaderProgram = 0;
@@ -94,11 +94,11 @@ namespace ZL {
__android_log_print(ANDROID_LOG_ERROR, "ShaderManager",
"Failed to link shader program: %s", infoLog);
#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!");
}
std::cout << "Creating shader resource step 5" << std::endl;
//logger() << "Creating shader resource step 5" << std::endl;
//================= Parsing all uniforms ================
@@ -121,7 +121,7 @@ namespace ZL {
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 ================
@@ -138,7 +138,7 @@ namespace ZL {
attribList[attribName] = glGetAttribLocation(shaderProgram, attribName);
}
std::cout << "Creating shader resource step 7" << std::endl;
//logger() << "Creating shader resource step 7" << std::endl;
#ifdef __ANDROID__
@@ -173,7 +173,7 @@ namespace ZL {
"Fragment shader: %s", fragmentShaderFileName.c_str());
#endif
std::cout <<"Loading shader: " << shaderName << " " << vertexShaderFileName << " " << fragmentShaderFileName <<std::endl;
logger() <<"Loading shader: " << shaderName << " " << vertexShaderFileName << " " << fragmentShaderFileName <<std::endl;
std::string vertexShader;
std::string fragmentShader;
@@ -204,7 +204,7 @@ namespace ZL {
}
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!");
}
+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
<< " to " << newW << "x" << newH << " (POT)" << std::endl;
return dst;
@@ -353,13 +353,13 @@ namespace ZL
}
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) {
std::cout << "PNG Error: " << error_msg << std::endl;
logger() << "PNG Error: " << error_msg << std::endl;
longjmp(png_jmpbuf(png_ptr), 1);
}
@@ -474,7 +474,7 @@ namespace ZL
fileArr = !ZIPFileName.empty() ? readFileFromZIP(fullFileName, ZIPFileName) : readFile(fullFileName);
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");
}
@@ -539,7 +539,7 @@ namespace ZL
{
for (auto it = textureMap.begin(); it != textureMap.end(); ) {
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);
}
else {
+91 -4
View File
@@ -11,6 +11,7 @@
#include <string>
#include <cctype>
#include <SDL.h>
#include "../Environment.h"
#ifdef __ANDROID__
#include <android/log.h>
@@ -108,7 +109,7 @@ namespace ZL
int zipErr = 0;
zip_t* archive = zip_open(zipfilename.c_str(), ZIP_RDONLY, &zipErr);
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);
}
@@ -119,14 +120,14 @@ namespace ZL
zip_stat_init(&fileStat);
if (zip_stat(archive, cleanFilename.c_str(), 0, &fileStat) != 0 || !(fileStat.valid & ZIP_STAT_SIZE)) {
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);
}
zip_file_t* zipFile = zip_fopen(archive, cleanFilename.c_str(), 0);
if (!zipFile) {
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);
}
@@ -138,7 +139,7 @@ namespace ZL
zip_close(archive);
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);
}
@@ -348,4 +349,90 @@ namespace ZL
#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);
void initLogger();
std::ostream& logger();
}