Compare commits

...

36 Commits

Author SHA1 Message Date
Vladislav Khorev
a11c4614ea Resize Android even more, fixing crash when changing languages on Android 2026-08-01 20:18:18 +03:00
Vladislav Khorev
5cbe7a345e Android version bug fix 2026-08-01 19:42:21 +03:00
Vladislav Khorev
2add42c1cb Minor changes for the Windows non-steam version 2026-07-25 17:27:21 +03:00
Vladislav Khorev
4039c24372 change permissions 2026-07-25 16:58:18 +03:00
Vladislav Khorev
7df68febca Fixing minor stuff, working on Web version 2026-07-25 16:56:02 +03:00
Vladislav Khorev
5ecf5fb9d7 Adapting music to web, fixing scripts, fixing bug in initial state 2026-07-24 23:58:29 +03:00
Vladislav Khorev
c3f449b999 Prepare a version for Steam and fixing bugs 2026-07-22 10:59:20 +03:00
Vladislav Khorev
8a0a8390b2 Windows, make portable version + NSIS installer 2026-07-20 21:54:43 +03:00
Vladislav Khorev
d39f5ca68b Working on adapting for Windows 2026-07-20 20:48:41 +03:00
Vladislav Khorev
1c20333f30 Fixing UI bugs, some clean up 2026-07-20 18:09:16 +03:00
Vladislav Khorev
1d8e103690 Android build is successfully uploaded to Google Play 2026-07-19 23:21:23 +03:00
Vladislav Khorev
4c9ecb31d8 Fixing shadows going apeshit in dorm on Android 2026-07-19 22:34:03 +03:00
Vladislav Khorev
2e749ed26e Fixing Android stuff 2026-07-19 21:38:22 +03:00
Vladislav Khorev
fc93eb6680 Fixing android bugs and more 2026-07-19 19:49:50 +03:00
Vladislav Khorev
5b21ba615e Added localization to Android 2026-07-19 13:58:26 +03:00
Vladislav Khorev
02ba86331b Working on shadow bug 2026-07-19 13:29:35 +03:00
Vladislav Khorev
6a79e8e41e Fixing screen resize bugs 2026-07-19 13:07:39 +03:00
Vladislav Khorev
6406c4f258 Fixing bug with save game, sounds, and some hints are out of screen on Android 2026-07-19 12:19:28 +03:00
Vladislav Khorev
9f812f0318 Android fixing logs 2026-07-18 20:44:54 +03:00
Vladislav Khorev
adf9871861 Remove accidentally added files 2026-07-18 20:22:49 +03:00
Vladislav Khorev
825a56a4a4 Added music and perfom bug fixes 2026-07-18 20:21:31 +03:00
Vladislav Khorev
e5e3d7bb28 Working on Android build 2026-07-18 17:46:05 +03:00
Vladislav Khorev
e773b51985 Working on Android version 2026-07-18 13:19:08 +03:00
Vladislav Khorev
0975f3b9cd Some clean up, web version prepare 2026-07-17 21:27:47 +03:00
Vladislav Khorev
e9c888e2bf Fixing compilation bug 2026-07-16 16:49:52 +03:00
Vladislav Khorev
a743f653dd Prepare for release002, not tested yet though 2026-07-16 10:32:27 +03:00
Vladislav Khorev
3d3522b905 Fixing last bugs, prepare for store release 2026-07-15 22:35:06 +03:00
Vladislav Khorev
b6cd17449f Fixing minor bugs, added about screen 2026-07-15 20:22:12 +03:00
Vladislav Khorev
159fac02f0 Fixing bugs in UI animations, added credits 2026-07-15 18:14:01 +03:00
Vladislav Khorev
9f17d51fc8 Fixing bugs, change dialogue translations 2026-07-15 14:28:31 +03:00
Vladislav Khorev
ca73148f4b Fixing translations 2026-07-15 12:44:29 +03:00
Vladislav Khorev
0c24d52e6d Working in dialogs and some texture fixing 2026-07-15 12:34:42 +03:00
Vladislav Khorev
beb4fee0d8 Working on Linux/Steam OS version 2026-07-14 22:47:49 +03:00
Vladislav Khorev
a7d17d9afd Fixing logger and windows version - remove terminal 2026-07-14 20:39:41 +03:00
Vladislav Khorev
76287dda46 Added Steam support for Windows 2026-07-14 19:41:36 +03:00
Vladislav Khorev
9da0ae1016 Fixing small bugs 2026-07-14 18:39:44 +03:00
427 changed files with 8569 additions and 3979 deletions

3
.gitattributes vendored
View File

@ -5,3 +5,6 @@
*.wav filter=lfs diff=lfs merge=lfs -text
*.ogg filter=lfs diff=lfs merge=lfs -text
*.mp3 filter=lfs diff=lfs merge=lfs -text
*.bin filter=lfs diff=lfs merge=lfs -text
*.dll filter=lfs diff=lfs merge=lfs -text
*.so filter=lfs diff=lfs merge=lfs -text

5
.gitignore vendored
View File

@ -409,3 +409,8 @@ web_resources/
pc_resources/
resources_hd/
web_resources_x2/
android_resources/
.artifacts/
*.zip

View File

@ -185,3 +185,44 @@ make -j$(nproc) -C build #Компилируем
Для постройки без звука
rm -rf build #Очищаем build папку
cmake -B build -DAUDIO=1 #Пересоздаём конфигурацию CMake
# Cmake Build NSIS and Portable for Windows:
```
cmake --build . --config Release
cpack -C Release
```
Если есть такая ошибка:
CPack Error: Cannot find NSIS compiler makensis: likely it is not installed, or not in your PATH
CPack Error: Could not read NSIS registry value. This is usually caused by NSIS not being installed. Please install NSIS from http://nsis.sourceforge.net
CPack Error: Cannot initialize the generator NSIS
То нужно установить nsis отсюда: https://nsis.sourceforge.io/Download
# Steam windows
```
cmake -DSTEAMSDK=ON ..
cmake --build . --config Release
```
# Steam Linux
```
docker run -it --rm -v "${PWD}:/work2" -w /work2 registry.gitlab.steamos.cloud/steamrt/sniper/sdk:latest bash
apt-get update
apt-get install libboost-dev libeigen3-dev liblua5.4-dev libzip-dev libglu1-mesa-dev
cmake -DSTEAMSDK=ON -DCMAKE_BUILD_TYPE=Release ..
cmake --build . -j 4
```

View File

@ -0,0 +1,128 @@
#!/usr/bin/env python3
"""
optimize_web_resources.py - Resize PNG images for the web build.
Copies <src> to <dst>/resources/, then downscales all PNG files:
- resources/w/ui/img/** -> 1/8 original size
- all other PNGs -> 1/4 original size
Files listed in EXCEPTIONS are copied unchanged.
Usage (manual):
python optimize_web_resources.py [--src resources] [--dst web_resources]
Usage (CMake):
${Python3_EXECUTABLE} optimize_web_resources.py --src <abs_src> --dst <abs_dst>
"""
import argparse
import os
import shutil
try:
from PIL import Image
except ImportError:
raise SystemExit("Pillow is required: pip install Pillow")
# Paths relative to the resources directory — these files are never resized.
EXCEPTIONS = {
"loading.png",
"loading_land003_en.png",
"loading_land003_ru.png",
"black.png",
"transparent.png",
"w/blue.png",
"w/red.png",
"w/spark.png",
"w/star.png",
"w/star_red.png",
"w/white.png",
"w/ui/img/main/aboutPage003android.png",
"w/ui_en/img/main/aboutPage003android.png",
"w/exterior/texAtlas002.png",
}
# Path prefixes (relative to resources root, forward-slash) that get 8x reduction.
HIGH_REDUCTION_PREFIXES = (
"w/ui/img",
)
LOW_REDUCTION_POSTFIXES = (
"texAtlas002.png",
"Building_work014.png",
"Building_Floors_tex004.png",
"Building_Walls_tex006.png",
"asphalt003small.png",
"Ext_Building004.png",
"Plane3_tex002.png",
"darklands_building002.png",
"Staircase001.png",
"Staircase_Obj002.png"
)
def _resize_factor(rel: str) -> int:
norm = rel.replace("\\", "/")
for prefix in HIGH_REDUCTION_PREFIXES:
if norm.startswith(prefix):
return 8
for prefix in LOW_REDUCTION_POSTFIXES:
if norm.endswith(prefix):
return 2
return 4
def _next_pot(n: int) -> int:
if n < 1:
return 1
p = 1
while p < n:
p <<= 1
return p
def optimize(src_dir: str, dst_parent: str) -> None:
src_dir = os.path.abspath(src_dir)
dst_resources = os.path.join(os.path.abspath(dst_parent), "resources")
print(f"Copying {src_dir} -> {dst_resources}")
if os.path.exists(dst_resources):
shutil.rmtree(dst_resources)
shutil.copytree(src_dir, dst_resources)
total = 0
for root, _dirs, files in os.walk(dst_resources):
for filename in files:
if not filename.lower().endswith(".png"):
continue
full_path = os.path.join(root, filename)
rel = os.path.relpath(full_path, dst_resources).replace("\\", "/")
if rel in EXCEPTIONS:
print(f" skip (exception): {rel}")
continue
factor = _resize_factor(rel)
with Image.open(full_path) as img:
orig_w, orig_h = img.size
new_w = _next_pot(max(1, orig_w // factor))
new_h = _next_pot(max(1, orig_h // factor))
resized = img.resize((new_w, new_h), Image.LANCZOS)
resized.save(full_path)
print(f" {rel}: {orig_w}x{orig_h} -> {new_w}x{new_h} (/{factor} + POT)")
total += 1
print(f"Done. Resized {total} PNG file(s).")
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Optimize PNG images for web build")
parser.add_argument("--src", default="resources",
help="Source resources directory (default: resources)")
parser.add_argument("--dst", default="android_resources",
help="Destination parent directory; will contain a 'resources' subdir "
"(default: android_resources)")
args = parser.parse_args()
optimize(args.src, args.dst)

View File

@ -27,7 +27,18 @@ except ImportError:
# Paths relative to the resources directory — these files are never resized.
EXCEPTIONS = {
"loading.png",
"loading_land003_en.png",
"loading_land003_ru.png",
"black.png",
"transparent.png",
"w/blue.png",
"w/red.png",
"w/spark.png",
"w/star.png",
"w/star_red.png",
"w/white.png",
"w/ui/img/main/aboutPage003android.png",
"w/ui_en/img/main/aboutPage003android.png"
}
# Path prefixes (relative to resources root, forward-slash) that get 8x reduction.
@ -35,13 +46,28 @@ HIGH_REDUCTION_PREFIXES = (
"w/ui/img",
)
LOW_REDUCTION_POSTFIXES = (
"texAtlas002.png",
"Building_work014.png",
"Building_Floors_tex004.png",
"Building_Walls_tex006.png",
"asphalt003small.png",
"Ext_Building004.png",
"Plane3_tex002.png",
"darklands_building002.png",
"Staircase001.png",
"Staircase_Obj002.png"
)
def _resize_factor(rel: str) -> int:
norm = rel.replace("\\", "/")
for prefix in HIGH_REDUCTION_PREFIXES:
if norm.startswith(prefix):
return 8
return 4
for prefix in LOW_REDUCTION_POSTFIXES:
if norm.endswith(prefix):
return 2
return 8
def _next_pot(n: int) -> int:

View File

@ -61,5 +61,5 @@ app/jni/libpng
app/jni/SDL
app/jni/zlib
app/src/main/assets/resources
app/src/main/assets

View File

@ -1 +1 @@
### Перед запуском в папке ```app/jni/```(рядом с src) нужно создать три папки с исходниками библиотек ```libpng```, ```SDL```, ```zlib```
### Android version

View File

@ -1,4 +1,4 @@
def buildAsLibrary = project.hasProperty('BUILD_AS_LIBRARY');
def buildAsLibrary = project.hasProperty('BUILD_AS_LIBRARY')
def buildAsApplication = !buildAsLibrary
if (buildAsApplication) {
apply plugin: 'com.android.application'
@ -9,30 +9,33 @@ else {
android {
if (buildAsApplication) {
namespace "org.libsdl.app"
namespace "fishrungames.shadowoverbishkek"
}
compileSdkVersion 34
compileSdkVersion 37
defaultConfig {
minSdkVersion 19
targetSdkVersion 34
versionCode 1
versionName "1.0"
minSdkVersion 21
targetSdkVersion 37
versionCode 4
versionName "1.0.7"
externalNativeBuild {
/*ndkBuild {
arguments "APP_PLATFORM=android-19"
abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
}*/
cmake {
arguments "-DANDROID_APP_PLATFORM=android-19", "-DANDROID_STL=c++_static"
cppFlags "-std=c++11 -frtti -fexceptions"
abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
arguments /*"-DANDROID_APP_PLATFORM=android-19",*/ "-DANDROID_STL=c++_static"
cppFlags "-std=c++17 -frtti -fexceptions"
abiFilters 'arm64-v8a', 'x86_64'
}
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
// Enables code optimizations.
minifyEnabled = true
// Enables resource shrinking.
shrinkResources = true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
applicationVariants.all { variant ->
@ -62,7 +65,7 @@ android {
libraryVariants.all { variant ->
variant.outputs.all { output ->
if (output.outputFileName != null && output.outputFileName.endsWith(".aar")) {
output.outputFileName = "org.libsdl.app.aar"
output.outputFileName = "fishrungames.shadowoverbishkek.aar"
}
}
}
@ -70,5 +73,6 @@ android {
}
dependencies {
implementation 'androidx.core:core-splashscreen:1.0.1'
implementation fileTree(include: ['*.jar'], dir: 'libs')
}

View File

@ -21,7 +21,7 @@ set(TP_ROOT "${THIRDPARTY_DIR}")
# ==============================================================================
# --- ZLIB ---
add_subdirectory("${TP_ROOT}/zlib-1.3.1" zlib-build)
#add_subdirectory("${TP_ROOT}/zlib-1.3.2" zlib-build)
# --- LIBPNG ---
set(PNG_STATIC ON CACHE BOOL "Build static library" FORCE)
@ -39,6 +39,10 @@ add_subdirectory("${TP_ROOT}/libpng-1.6.51" libpng-build)
# --- SDL2 ---
# Android-версия SDL требует специфичных настроек, но add_subdirectory обычно подхватывает их сама
add_subdirectory("${TP_ROOT}/SDL-release-2.32.10" sdl-build)
add_subdirectory("${TP_ROOT}/SDL_mixer-release-2.8.0" sdl-mixer-build)
add_subdirectory("${TP_ROOT}/SDL_ttf-release-2.24.0" sdl-ttf-build)
# --- LIBZIP ---
# Отключаем поиск системных крипто-библиотек, так как в NDK их может не быть в стандартных путях

View File

@ -33,62 +33,145 @@ endforeach()
# Создаем кастомную цель, которая будет запускать процесс копирования
add_custom_target(sync_resources ALL DEPENDS ${RES_OUTPUTS})
set(LUA_SRC_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../../../thirdparty/lua-5.4.8")
file(GLOB LUA_SOURCES "${LUA_SRC_DIR}/*.c")
list(REMOVE_ITEM LUA_SOURCES
"${LUA_SRC_DIR}/lua.c"
"${LUA_SRC_DIR}/luac.c"
"${LUA_SRC_DIR}/onelua.c"
)
add_library(lua_static STATIC ${LUA_SOURCES})
target_include_directories(lua_static PUBLIC "${LUA_SRC_DIR}")
add_library(main SHARED
SDL_android_main.c
../../../../src/BoneAnimatedModel.cpp
../../../../src/Environment.cpp
../../../../src/Game.cpp
../../../../src/main.cpp
../../../../src/Projectile.cpp
../../../../src/SparkEmitter.cpp
../../../../src/TextModel.cpp
../../../../src/UiManager.cpp
../../../../src/utils/Perlin.cpp
../../../../src/utils/TaskManager.cpp
../../../../src/utils/Utils.cpp
../../../../src/navigation/PathFinder.cpp
../../../../src/planet/PlanetData.cpp
../../../../src/planet/PlanetObject.cpp
../../../../src/planet/StoneObject.cpp
../../../../src/render/FrameBuffer.cpp
../../../../src/render/ShadowMap.cpp
../../../../src/Game.cpp
../../../../src/Game.h
../../../../src/Character.cpp
../../../../src/Character.h
../../../../src/CharacterState.cpp
../../../../src/CharacterState.h
../../../../src/Environment.cpp
../../../../src/Environment.h
../../../../src/Localization.cpp
../../../../src/Localization.h
../../../../src/render/Renderer.cpp
../../../../src/render/Renderer.h
../../../../src/render/ShaderManager.cpp
../../../../src/render/ShaderManager.h
../../../../src/render/TextureManager.cpp
../../../../src/render/TextureManager.h
../../../../src/TextModel.cpp
../../../../src/TextModel.h
../../../../src/AudioPlayerAsync.cpp
../../../../src/AudioPlayerAsync.h
../../../../src/BoneAnimatedModelNew.cpp
../../../../src/BoneAnimatedModelNew.h
../../../../src/render/OpenGlExtensions.cpp
../../../../src/render/OpenGlExtensions.h
../../../../src/utils/Utils.cpp
../../../../src/utils/Utils.h
../../../../src/SparkEmitter.cpp
../../../../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/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/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/items/GameObjectLoader.h
../../../../src/items/GameObjectLoader.cpp
../../../../src/items/Item.h
../../../../src/items/Item.cpp
../../../../src/items/ItemRegistry.h
../../../../src/items/ItemRegistry.cpp
../../../../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
)
# Подключаем заголовки
target_include_directories(main PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/../SDL/include
${CMAKE_CURRENT_SOURCE_DIR}/../zlib
${CMAKE_CURRENT_SOURCE_DIR}/../libpng
${CMAKE_CURRENT_SOURCE_DIR}/../../../../thirdparty/eigen-5.0.0
${CMAKE_CURRENT_SOURCE_DIR}/../../../../thirdparty/boost_1_90_0
${CMAKE_CURRENT_SOURCE_DIR}/../../../../src
${CMAKE_CURRENT_SOURCE_DIR}/../libzip
)
# ВАЖНО: Линкуемся с png_static (статика) или png_shared (динамика)
# Так как мы установили PNG_STATIC=ON и PNG_SHARED=OFF,
# должна создаться цель png_static
find_library(OPENGLES3_LIB GLESv3)
target_link_libraries(main
png_static # ЭТО ПРАВИЛЬНОЕ ИМЯ ЦЕЛИ!
${OPENGLES3_LIB}
png_static
z
SDL2
)
find_library(OPENGLES2_LIB GLESv2)
target_link_libraries(main
${OPENGLES2_LIB} # OpenGL ES 2.0/3.0
SDL2_ttf
SDL2_mixer
log
android
OpenSLES
dl
zip
lua_static
#freetype
)
# Подключаем заголовки
target_include_directories(main PRIVATE
#${CMAKE_CURRENT_SOURCE_DIR}/../SDL/include
${CMAKE_CURRENT_SOURCE_DIR}/../../../../thirdparty/SDL_ttf-release-2.24.0/external/freetype/include
#${CMAKE_CURRENT_SOURCE_DIR}/../zlib
#${CMAKE_CURRENT_SOURCE_DIR}/../libpng
${CMAKE_CURRENT_SOURCE_DIR}/../../../../thirdparty/eigen-5.0.0
${CMAKE_CURRENT_SOURCE_DIR}/../../../../thirdparty/boost_1_90_0
${CMAKE_CURRENT_SOURCE_DIR}/../../../../src
${CMAKE_CURRENT_SOURCE_DIR}/../../../../thirdparty/sol2-3.3.0/include
#${CMAKE_CURRENT_SOURCE_DIR}/../libzip
)
add_dependencies(main sync_resources)

View File

@ -16,6 +16,9 @@
# public *;
#}
# SDL2 specific rules
-keep class org.libsdl.app.** { *; }
-keep,includedescriptorclasses,allowoptimization class org.libsdl.app.SDLInputConnection {
void nativeCommitText(java.lang.String, int);
void nativeGenerateScancodeForUnichar(char);
@ -96,3 +99,6 @@
void hapticRun(int, float, int);
void hapticStop(int);
}
# Keep our main activity
-keep class fishrungames.shadowoverbishkek.ShadowOverBishkekActivity { *; }

View File

@ -8,7 +8,7 @@
android:installLocation="auto">
<!-- OpenGL ES 2.0 -->
<uses-feature android:glEsVersion="0x00020000" />
<uses-feature android:glEsVersion="0x00030000" />
<!-- Touchscreen support -->
<uses-feature
@ -52,7 +52,7 @@
<!-- <uses-permission android:name="android.permission.RECORD_AUDIO" /> -->
<!-- Create a Java class extending SDLActivity and place it in a
directory under app/src/main/java matching the package, e.g. app/src/main/java/com/gamemaker/game/MyGame.java
directory under shadowoverbishkek/main/java matching the package, e.g. shadowoverbishkek/main/java/com/gamemaker/game/MyGame.java
then replace "SDLActivity" with the name of your class (e.g. "MyGame")
in the XML below.
@ -62,14 +62,14 @@
<application android:label="@string/app_name"
android:icon="@mipmap/ic_launcher"
android:allowBackup="true"
android:theme="@style/AppTheme"
android:theme="@style/Theme.App.Starting"
android:hardwareAccelerated="true" >
<!-- Example of setting SDL hints from AndroidManifest.xml:
<meta-data android:name="SDL_ENV.SDL_ACCELEROMETER_AS_JOYSTICK" android:value="0"/>
-->
<activity android:name="SDLActivity"
<activity android:name=".ShadowOverBishkekActivity"
android:label="@string/app_name"
android:alwaysRetainTaskState="true"
android:launchMode="singleInstance"

View File

@ -0,0 +1,13 @@
package fishrungames.shadowoverbishkek;
import android.os.Bundle;
import androidx.core.splashscreen.SplashScreen;
import org.libsdl.app.SDLActivity;
public class ShadowOverBishkekActivity extends SDLActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
SplashScreen.installSplashScreen(this);
super.onCreate(savedInstanceState);
}
}

BIN
proj-android/app/src/main/res/drawable/splash_icon.png (Stored with Git LFS) Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,3 @@
<resources>
<string name="app_name">Тень над Бишкеком</string>
</resources>

View File

@ -1,3 +1,3 @@
<resources>
<string name="app_name">Game</string>
<string name="app_name">Shadow Over Bishkek</string>
</resources>

View File

@ -4,4 +4,13 @@
<style name="AppTheme" parent="android:Theme.NoTitleBar.Fullscreen">
<!-- Customize your theme here. -->
</style>
<!-- Тема для Splash Screen -->
<style name="Theme.App.Starting" parent="Theme.SplashScreen">
<!-- Черный фон -->
<item name="windowSplashScreenBackground">#000000</item>
<!-- Ваша картинка 512x512 -->
<item name="windowSplashScreenAnimatedIcon">@drawable/splash_icon</item>
<!-- Ссылка на основную тему, которая применится после загрузки -->
<item name="postSplashScreenTheme">@style/AppTheme</item>
</style>
</resources>

View File

@ -6,7 +6,7 @@ buildscript {
google()
}
dependencies {
classpath 'com.android.tools.build:gradle:8.1.1'
classpath 'com.android.tools.build:gradle:9.3.0'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files

View File

@ -9,7 +9,18 @@
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx1536m
android.builtInKotlin=false
android.defaults.buildfeatures.resvalues=true
android.dependency.useConstraints=true
android.enableAppCompileTimeRClass=false
android.newDsl=false
android.r8.optimizedResourceShrinking=false
android.r8.strictFullModeForKeepRules=false
android.sdk.defaultTargetSdkToCompileSdkIfUnset=false
android.uniquePackageNames=false
android.usesSdkInManifest.disallowed=false
org.gradle.jvmargs=-Xmx4096m
android.useAndroidX=true
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit

View File

@ -0,0 +1,12 @@
#This file is generated by updateDaemonJvm
toolchainUrl.FREE_BSD.AARCH64=https\://api.foojay.io/disco/v3.0/ids/ec7520a1e057cd116f9544c42142a16b/redirect
toolchainUrl.FREE_BSD.X86_64=https\://api.foojay.io/disco/v3.0/ids/4c4f879899012ff0a8b2e2117df03b0e/redirect
toolchainUrl.LINUX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/ec7520a1e057cd116f9544c42142a16b/redirect
toolchainUrl.LINUX.X86_64=https\://api.foojay.io/disco/v3.0/ids/4c4f879899012ff0a8b2e2117df03b0e/redirect
toolchainUrl.MAC_OS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/73bcfb608d1fde9fb62e462f834a3299/redirect
toolchainUrl.MAC_OS.X86_64=https\://api.foojay.io/disco/v3.0/ids/846ee0d876d26a26f37aa1ce8de73224/redirect
toolchainUrl.UNIX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/ec7520a1e057cd116f9544c42142a16b/redirect
toolchainUrl.UNIX.X86_64=https\://api.foojay.io/disco/v3.0/ids/4c4f879899012ff0a8b2e2117df03b0e/redirect
toolchainUrl.WINDOWS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/9482ddec596298c84656d31d16652665/redirect
toolchainUrl.WINDOWS.X86_64=https\://api.foojay.io/disco/v3.0/ids/39701d92e1756bb2f141eb67cd4c660e/redirect
toolchainVersion=21

View File

@ -1,6 +1,6 @@
#Sat Jan 10 10:31:05 MSK 2026
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.0-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

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,18 +208,93 @@ 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()
# ===========================================
# Копирование ресурсов после сборки
# ===========================================
set(RUNTIME_RESOURCE_DIRS
"resources"
"audio"
"music"
)
# В 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}"

View File

@ -8,13 +8,19 @@ if(NOT CMAKE_MAKE_PROGRAM AND WIN32)
endif()
endif()
project(bishkek-witcher LANGUAGES C CXX)
project(ShadowOverBishkekDemo LANGUAGES C CXX)
option(OPTIMIZE_WEB_RESOURCES "Resize PNG images for web build (requires Python3 + Pillow)" ON)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
if (CMAKE_BUILD_TYPE STREQUAL "Release")
# Максимальная оптимизация кода и LTO (Link-Time Optimization)
add_compile_options(-O3 -flto)
add_link_options(-O3 -flto)
endif()
# --- АВТО-ЗАГРУЗКА ЗАВИСИМОСТЕЙ ---
include("${CMAKE_CURRENT_SOURCE_DIR}/../cmake/FetchDependencies.cmake")
# Теперь гарантированно есть папка ../thirdparty со всеми исходниками
@ -109,6 +115,8 @@ set(SOURCES
../src/LocationState.cpp
../src/LocationEditor.h
../src/LocationEditor.cpp
../src/NpcCar.h
../src/NpcCar.cpp
../src/GameConstants.h
../src/GameConstants.cpp
../src/GameState.h
@ -150,10 +158,10 @@ set(SOURCES
../src/render/UiQuad.h
)
add_executable(bishkek-witcher ${SOURCES})
add_executable(ShadowOverBishkekDemo ${SOURCES})
# Настройка путей к инклудам (используем скачанные исходники)
target_include_directories(bishkek-witcher PRIVATE
target_include_directories(ShadowOverBishkekDemo PRIVATE
../src
../thirdparty/eigen-5.0.0
../thirdparty/boost_1_90_0
@ -168,7 +176,7 @@ set(ENABLE_COMMONCRYPTO OFF CACHE BOOL "" FORCE)
add_subdirectory("../thirdparty/libzip-1.11.4" libzip-build)
target_link_libraries(bishkek-witcher PRIVATE zip z lua_static websocket.js)
target_link_libraries(ShadowOverBishkekDemo PRIVATE zip z lua_static websocket.js)
# Эмскриптен-флаги
set(EMSCRIPTEN_FLAGS
@ -185,7 +193,7 @@ set(EMSCRIPTEN_FLAGS
"-DNETWORK"
)
target_compile_options(bishkek-witcher PRIVATE ${EMSCRIPTEN_FLAGS} "-O2")
target_compile_options(ShadowOverBishkekDemo PRIVATE ${EMSCRIPTEN_FLAGS} "-O2")
# Only loading.png and the shaders used before resources.zip is ready are preloaded.
# resources.zip is downloaded asynchronously at runtime and served as a separate file.
@ -195,6 +203,7 @@ set(EMSCRIPTEN_LINK_FLAGS
#"-sPTHREAD_POOL_SIZE=4"
"-sALLOW_MEMORY_GROWTH=1"
"-sFULL_ES3=1"
"-lidbfs.js"
"--preload-file ${CMAKE_CURRENT_SOURCE_DIR}/../resources/loading.png@resources/loading.png"
"--preload-file ${CMAKE_CURRENT_SOURCE_DIR}/../resources/loadingProgressBar.png@resources/loadingProgressBar.png"
"--preload-file ${CMAKE_CURRENT_SOURCE_DIR}/../resources/loadingProgressBarFrame.png@resources/loadingProgressBarFrame.png"
@ -202,15 +211,14 @@ set(EMSCRIPTEN_LINK_FLAGS
"--preload-file ${CMAKE_CURRENT_SOURCE_DIR}/../resources/start_uni_interior.lua@resources/start_uni_interior.lua"
"--preload-file ${CMAKE_CURRENT_SOURCE_DIR}/../resources/start_uni_exterior.lua@resources/start_uni_exterior.lua"
"--preload-file ${CMAKE_CURRENT_SOURCE_DIR}/../resources/start_dorm.lua@resources/start_dorm.lua"
"--preload-file ${CMAKE_CURRENT_SOURCE_DIR}/../audio@audio"
)
# Применяем настройки линковки
target_link_options(bishkek-witcher PRIVATE ${EMSCRIPTEN_LINK_FLAGS})
target_link_options(ShadowOverBishkekDemo PRIVATE ${EMSCRIPTEN_LINK_FLAGS})
# Для совместимости со старыми версиями CMake, если target_link_options недостаточно
string(REPLACE ";" " " EMSCRIPTEN_LINK_FLAGS_STR "${EMSCRIPTEN_LINK_FLAGS}")
set_target_properties(bishkek-witcher PROPERTIES
set_target_properties(ShadowOverBishkekDemo PROPERTIES
LINK_FLAGS "${EMSCRIPTEN_LINK_FLAGS_STR}"
SUFFIX ".html"
)
@ -254,23 +262,40 @@ else()
)
endif()
# music resources
set(MUSIC_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../music")
set(MUSIC_ZIP "${CMAKE_CURRENT_BINARY_DIR}/music.zip")
get_filename_component(MUSIC_PARENT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/.." ABSOLUTE)
add_custom_command(
OUTPUT "${MUSIC_ZIP}"
COMMAND ${CMAKE_COMMAND} -E tar "cf" "${MUSIC_ZIP}" --format=zip "music"
WORKING_DIRECTORY "${MUSIC_PARENT_DIR}"
DEPENDS "${MUSIC_PARENT_DIR}/music"
)
add_custom_target(pack_resources DEPENDS "${RESOURCES_ZIP}")
add_dependencies(bishkek-witcher pack_resources)
add_dependencies(ShadowOverBishkekDemo pack_resources)
add_custom_target(pack_music DEPENDS "${MUSIC_ZIP}")
add_dependencies(ShadowOverBishkekDemo pack_music)
# Определяем путь к директории установки (относительно папки билда)
set(CMAKE_INSTALL_PREFIX "${CMAKE_BINARY_DIR}/public")
# 1. Устанавливаем основной HTML файл
install(TARGETS bishkek-witcher
install(TARGETS ShadowOverBishkekDemo
RUNTIME DESTINATION .
)
# 2. Устанавливаем сопутствующие файлы (JS, WASM и сгенерированный архив данных)
install(FILES
"${CMAKE_CURRENT_BINARY_DIR}/bishkek-witcher.js"
"${CMAKE_CURRENT_BINARY_DIR}/bishkek-witcher.wasm"
"${CMAKE_CURRENT_BINARY_DIR}/bishkek-witcher.data"
"${CMAKE_CURRENT_BINARY_DIR}/ShadowOverBishkekDemo.js"
"${CMAKE_CURRENT_BINARY_DIR}/ShadowOverBishkekDemo.wasm"
"${CMAKE_CURRENT_BINARY_DIR}/ShadowOverBishkekDemo.data"
DESTINATION .
)
@ -278,10 +303,15 @@ install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/index.html"
DESTINATION .
)
install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/favicon.ico"
DESTINATION .
)
# resources.zip is served separately and downloaded asynchronously at runtime
install(FILES "${RESOURCES_ZIP}" DESTINATION .)
install(FILES "${MUSIC_ZIP}" DESTINATION .)
add_custom_command(TARGET bishkek-witcher POST_BUILD
add_custom_command(TARGET ShadowOverBishkekDemo POST_BUILD
COMMAND ${CMAKE_COMMAND} --install .
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}"
COMMENT "Automatically deploying to public directory..."

View File

@ -37,3 +37,14 @@ Run:
```
emrun --no_browser --port 8080 public
```
# Release build
```
mkdir build_release
cd build_release
emcmake cmake -G Ninja -DCMAKE_BUILD_TYPE=Release ..
cmake --build . --config Release
cmake --install . --prefix ../dist
```

File diff suppressed because one or more lines are too long

BIN
proj-web/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

View File

@ -39,7 +39,7 @@
function loadGameScript() {
var s = document.createElement('script');
s.src = 'bishkek-witcher.js';
s.src = 'ShadowOverBishkekDemo.js';
s.async = true;
document.body.appendChild(s);
}

View File

@ -1,2 +0,0 @@
<!doctypehtml><html lang=en-us><head><meta charset=utf-8><meta content="text/html; charset=utf-8"http-equiv=Content-Type><title>Emscripten-Generated Code</title><style>body{font-family:arial;margin:0;padding:none}.emscripten{padding-right:0;margin-left:auto;margin-right:auto;display:block}div.emscripten{text-align:center}div.emscripten_border{border:1px solid #000}canvas.emscripten{border:0 none;background-color:#000}#emscripten_logo{display:inline-block;margin:0;padding:6px;width:265px}.spinner{height:30px;width:30px;margin:0;margin-top:20px;margin-left:20px;display:inline-block;vertical-align:top;-webkit-animation:rotation .8s linear infinite;-moz-animation:rotation .8s linear infinite;-o-animation:rotation .8s linear infinite;animation:rotation .8s linear infinite;border-left:5px solid #ebebeb;border-right:5px solid #ebebeb;border-bottom:5px solid #ebebeb;border-top:5px solid #787878;border-radius:100%;background-color:#bdd72e}@-webkit-keyframes rotation{from{-webkit-transform:rotate(0)}to{-webkit-transform:rotate(360deg)}}@-moz-keyframes rotation{from{-moz-transform:rotate(0)}to{-moz-transform:rotate(360deg)}}@-o-keyframes rotation{from{-o-transform:rotate(0)}to{-o-transform:rotate(360deg)}}@keyframes rotation{from{transform:rotate(0)}to{transform:rotate(360deg)}}#status{display:inline-block;vertical-align:top;margin-top:30px;margin-left:20px;font-weight:700;color:#787878}#progress{height:20px;width:300px}#controls{display:inline-block;float:right;vertical-align:top;margin-top:30px;margin-right:20px}#output{width:100%;height:200px;margin:0 auto;margin-top:10px;border-left:0;border-right:0px;padding-left:0;padding-right:0;display:block;background-color:#000;color:#fff;font-family:'Lucida Console',Monaco,monospace;outline:0}</style></head><body><script src="https://cdn.jsdelivr.net/npm/eruda"></script>
<script>eruda.init();</script><a href=http://emscripten.org><img id=emscripten_logo src=""></a><div class=spinner id=spinner></div><div class=emscripten id=status>Downloading...</div><span id=controls><span><input type=checkbox id=resize>Resize canvas</span> <span><input type=checkbox id=pointerLock checked>Lock/hide mouse pointer    </span><span><input type=button onclick='Module.requestFullscreen(document.getElementById("pointerLock").checked,document.getElementById("resize").checked)'value=Fullscreen></span></span><div class=emscripten><progress hidden id=progress max=100 value=0></progress></div><div class=emscripten_border><canvas class=emscripten id=canvas oncontextmenu=event.preventDefault() tabindex=-1></canvas></div><textarea id=output rows=8></textarea><script>var statusElement=document.getElementById("status"),progressElement=document.getElementById("progress"),spinnerElement=document.getElementById("spinner"),canvasElement=document.getElementById("canvas"),outputElement=document.getElementById("output");outputElement&&(outputElement.value=""),canvasElement.addEventListener("webglcontextlost",(e=>{alert("WebGL context lost. You will need to reload the page."),e.preventDefault()}),!1);var Module={print(...e){if(console.log(...e),outputElement){var t=e.join(" ");outputElement.value+=t+"\n",outputElement.scrollTop=outputElement.scrollHeight}},canvas:canvasElement,setStatus(e){if(Module.setStatus.last||(Module.setStatus.last={time:Date.now(),text:""}),e!==Module.setStatus.last.text){var t=e.match(/([^(]+)\((\d+(\.\d+)?)\/(\d+)\)/),n=Date.now();t&&n-Module.setStatus.last.time<30||(Module.setStatus.last.time=n,Module.setStatus.last.text=e,t?(e=t[1],progressElement.value=100*parseInt(t[2]),progressElement.max=100*parseInt(t[4]),progressElement.hidden=!1,spinnerElement.hidden=!1):(progressElement.value=null,progressElement.max=null,progressElement.hidden=!0,e||(spinnerElement.style.display="none")),statusElement.innerHTML=e)}},totalDependencies:0,monitorRunDependencies(e){this.totalDependencies=Math.max(this.totalDependencies,e),Module.setStatus(e?"Preparing... ("+(this.totalDependencies-e)+"/"+this.totalDependencies+")":"All downloads complete.")}};Module.setStatus("Downloading..."),window.onerror=e=>{Module.setStatus("Exception thrown, see JavaScript console"),spinnerElement.style.display="none",Module.setStatus=e=>{e&&console.error("[post-exception status] "+e)}}</script><script async src="space-game001.js" crossorigin="anonymous"></script></body></html>

View File

@ -1,200 +0,0 @@
<!DOCTYPE html>
<html lang="en-us">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
<title>Sky Trek Tales</title>
<style>
body, html {
margin: 0; padding: 0; width: 100%; height: 100%;
overflow: hidden; background-color: #000;
position: fixed;
}
#canvas {
display: block;
position: absolute;
top: 0; left: 0;
width: 100vw; height: 100vh;
border: none;
}
#fs-button {
position: absolute;
top: 10px; right: 10px;
padding: 10px;
z-index: 10;
background: rgba(255,255,255,0.3);
color: white; border: 1px solid white;
cursor: pointer;
font-family: sans-serif;
border-radius: 5px;
}
#status { color: white; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); }
/* Nick modal */
#nickOverlay {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
background: rgba(0,0,0,0.85);
z-index: 9999;
}
#nickBox {
background: #111;
border: 1px solid #444;
padding: 24px;
width: 320px;
box-shadow: 0 8px 24px rgba(0,0,0,0.6);
text-align: center;
}
#nickBox h2 { margin: 0 0 12px 0; font-size: 18px; color: #eee; }
#nickBox input[type="text"] {
width: 100%;
padding: 10px;
font-size: 16px;
box-sizing: border-box;
margin-bottom: 12px;
border: 1px solid #333;
background: #000;
color: #fff;
}
#nickBox button {
padding: 10px 16px;
font-size: 16px;
background: #2a9fd6;
border: none;
color: #fff;
cursor: pointer;
}
</style>
</head>
<body>
<button id="fs-button">Fullscreen</button>
<div id="status">Downloading...</div>
<canvas id="canvas" oncontextmenu="event.preventDefault()" tabindex="-1"></canvas>
<div id="nickOverlay" style="display:none;">
<div id="nickBox">
<h2>Enter your nickname</h2>
<input id="nickInput" type="text" maxlength="32" placeholder="Player" />
<div>
<button id="nickSubmit">Start</button>
</div>
</div>
</div>
<script>
// Utility: подготовить глобальный Module до загрузки Emscripten-скрипта
function prepareModuleEnvironment() {
window.Module = window.Module || {};
var canvasEl = document.getElementById('canvas');
// Устанавливаем canvas для Emscripten, чтобы createContext не падал
window.Module.canvas = canvasEl;
// Подготовим заглушку setStatus, если ещё нет
window.Module.setStatus = window.Module.setStatus || function (text) {
var statusElement = document.getElementById("status");
statusElement.innerHTML = text;
statusElement.style.display = text ? 'block' : 'none';
};
}
// Show overlay only if no nickname saved.
function loadGameScript() {
var s = document.createElement('script');
s.src = 'space-game001.js';
s.async = true;
document.body.appendChild(s);
}
function showNickOverlay() {
var overlay = document.getElementById('nickOverlay');
overlay.style.display = 'flex';
var input = document.getElementById('nickInput');
input.focus();
}
function hideNickOverlay() {
var overlay = document.getElementById('nickOverlay');
overlay.style.display = 'none';
}
function saveNickAndStart(nick) {
try {
if (!nick || nick.trim() === '') nick = 'Player';
localStorage.setItem('spacegame_nick', nick);
} catch (e) {
console.warn('localStorage not available', e);
}
hideNickOverlay();
// перед загрузкой скрипта гарантируем, что Module.canvas задан
prepareModuleEnvironment();
loadGameScript();
}
document.getElementById('fs-button').addEventListener('click', function() {
if (!document.fullscreenElement) {
document.documentElement.requestFullscreen().catch(function(e) {
console.error('Fullscreen error: ' + e.message);
});
} else {
document.exitFullscreen();
}
});
document.addEventListener('DOMContentLoaded', function() {
// Готовим Module сразу — даже если откроется модалка, поле canvas будет доступно для скрипта (если он загружается позже)
prepareModuleEnvironment();
var stored = null;
try {
stored = localStorage.getItem('spacegame_nick');
} catch (e) {
console.warn('localStorage not available', e);
}
if (stored && stored.trim() !== '') {
// Nick is present — start immediately
loadGameScript();
} else {
// Show modal to request nickname before loading WASM
showNickOverlay();
var submit = document.getElementById('nickSubmit');
var input = document.getElementById('nickInput');
submit.addEventListener('click', function() {
saveNickAndStart(input.value);
});
input.addEventListener('input', function() {
// Strip any character that is not a-z, A-Z, 0-9 or space
var pos = this.selectionStart;
var cleaned = this.value.replace(/[^a-zA-Z0-9 ]/g, '');
if (cleaned !== this.value) {
this.value = cleaned;
this.setSelectionRange(pos - 1, pos - 1);
}
});
input.addEventListener('keydown', function(e) {
if (e.key === 'Enter') {
saveNickAndStart(input.value);
}
});
}
});
window.addEventListener("orientationchange", function() {
// Chrome на Android обновляет innerWidth/Height не мгновенно.
// Ждем завершения анимации поворота.
setTimeout(() => {
// В Emscripten это вызовет ваш onWindowResized в C++
window.dispatchEvent(new Event('resize'));
}, 200);
});
</script>
</body>
</html>

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
@ -101,36 +104,32 @@ add_executable(witcher001
../src/cutscene/CutsceneOverlay.h
../src/cutscene/CutsceneOverlay.cpp
../src/render/UiQuad.h
app.rc
)
# Установка проекта по умолчанию для 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
)
# Линкуем с 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
@ -145,79 +144,186 @@ target_link_libraries(witcher001 PRIVATE
# Линкуем OpenGL (Windows)
if(WIN32)
target_link_libraries(witcher001 PRIVATE opengl32)
target_link_libraries(ShadowOverBishkekDemo PRIVATE opengl32)
endif()
# ===========================================
# Копирование SDL2d.dll и zlibd.dll рядом с exe
# Интеграция 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"
)
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()
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()
# ===========================================
# Копирование DLL и ресурсов для локального запуска в Visual Studio
# ===========================================
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(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)
COMMAND ${CMAKE_COMMAND} -E copy_if_different
"${SDL2_DLL_SRC}"
"${SDL2_DLL_DST}"
# Копируем 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
"${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 "${SDL2_DLL_SRC}" "${SDL2_DLL_DST}"
COMMAND ${CMAKE_COMMAND} -E copy_if_different "${LIBZIP_DLL_SRC}" "$<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: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"
)
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_DLL_NAME}"
"$<TARGET_FILE_DIR:ShadowOverBishkekDemo>/${STEAM_DLL_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()
endif()
# ===========================================
# Копирование ресурсов после сборки
# ===========================================
# Какие папки с ресурсами нужно копировать
set(RUNTIME_RESOURCE_DIRS
"resources"
"audio"
)
# Копируем ресурсы и шейдеры в папку exe и в корень build/
# Копирование ресурсов локально
set(RUNTIME_RESOURCE_DIRS "resources" "music")
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}"
# 2) в корень build, если захочешь запускать из этой папки
"$<TARGET_FILE_DIR:ShadowOverBishkekDemo>/${resdir}"
COMMAND ${CMAKE_COMMAND} -E copy_directory
"${CMAKE_SOURCE_DIR}/../${resdir}"
"${CMAKE_BINARY_DIR}/${resdir}"
)
endforeach()
# ===========================================
# ПРАВИЛА ИНСТАЛЛЯЦИИ (Для CPack и создания пакетов)
# ===========================================
# 1. Основной исполняемый файл
install(TARGETS ShadowOverBishkekDemo DESTINATION .)
# 2. Необходимые DLL
if(WIN32)
install(FILES
"${SDL2_INSTALL_DIR}/bin/SDL2.dll"
"${LIBZIP_BASE_DIR}-Release/bin/zip.dll"
"${ZLIB_INSTALL_DIR}/bin/z.dll"
"${SDL2TTF_BASE_DIR}-Release/bin/SDL2_ttf.dll"
"${CMAKE_CURRENT_SOURCE_DIR}/../thirdparty/SDL_mixer-release-2.8.0/install-Release/bin/SDL2_mixer.dll"
"${CMAKE_CURRENT_SOURCE_DIR}/dist_dlls/concrt140.dll"
"${CMAKE_CURRENT_SOURCE_DIR}/dist_dlls/msvcp140.dll"
"${CMAKE_CURRENT_SOURCE_DIR}/dist_dlls/msvcp140_1.dll"
"${CMAKE_CURRENT_SOURCE_DIR}/dist_dlls/msvcp140_2.dll"
"${CMAKE_CURRENT_SOURCE_DIR}/dist_dlls/msvcp140_atomic_wait.dll"
"${CMAKE_CURRENT_SOURCE_DIR}/dist_dlls/msvcp140_clr0400.dll"
"${CMAKE_CURRENT_SOURCE_DIR}/dist_dlls/msvcp140_codecvt_ids.dll"
"${CMAKE_CURRENT_SOURCE_DIR}/dist_dlls/msvcp140d.dll"
"${CMAKE_CURRENT_SOURCE_DIR}/dist_dlls/OpenAL32.dll"
"${CMAKE_CURRENT_SOURCE_DIR}/dist_dlls/vcamp140.dll"
"${CMAKE_CURRENT_SOURCE_DIR}/dist_dlls/vccorlib140.dll"
"${CMAKE_CURRENT_SOURCE_DIR}/dist_dlls/vcomp140.dll"
"${CMAKE_CURRENT_SOURCE_DIR}/dist_dlls/vcruntime140.dll"
"${CMAKE_CURRENT_SOURCE_DIR}/dist_dlls/vcruntime140_1.dll"
"${CMAKE_CURRENT_SOURCE_DIR}/dist_dlls/vcruntime140_1_clr0400.dll"
"${CMAKE_CURRENT_SOURCE_DIR}/dist_dlls/vcruntime140_threads.dll"
"${CMAKE_CURRENT_SOURCE_DIR}/dist_dlls/wrap_oal.dll"
DESTINATION .
)
if(STEAMSDK)
install(FILES
"${STEAM_LIB_DIR}/${STEAM_DLL_NAME}"
"${CMAKE_CURRENT_BINARY_DIR}/steam_appid.txt"
DESTINATION .
)
endif()
endif()
# 3. Директории с ресурсами
foreach(resdir IN LISTS RUNTIME_RESOURCE_DIRS)
install(DIRECTORY "${CMAKE_SOURCE_DIR}/../${resdir}" DESTINATION .)
endforeach()
# ===========================================
# НАСТРОЙКА CPACK (Создание ZIP Portable и NSIS Installer)
# ===========================================
set(CPACK_PACKAGE_NAME "ShadowOverBishkekDemo")
set(CPACK_PACKAGE_VENDOR "Fish Run Games")
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Shadow Over Bishkek Demo Version")
set(CPACK_PACKAGE_VERSION_MAJOR "1")
set(CPACK_PACKAGE_VERSION_MINOR "0")
set(CPACK_PACKAGE_VERSION_PATCH "5")
set(CPACK_PACKAGE_INSTALL_DIRECTORY "Fish Run Games/Shadow Over Bishkek Demo")
set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE.txt")
# Настройки NSIS инсталлятора
set(CPACK_NSIS_DISPLAY_NAME "Shadow Over Bishkek Demo")
set(CPACK_NSIS_PACKAGE_NAME "Shadow Over Bishkek Demo")
set(CPACK_NSIS_HELP_LINK "https://fishrungames.com/contact.html")
set(CPACK_NSIS_URL_INFO_ABOUT "https://shadowoverbishkek.com/")
set(CPACK_NSIS_MODIFY_PATH OFF)
set(CPACK_NSIS_ENABLE_UNINSTALL_BEFORE_INSTALL ON)
# Иконки и ярлыки в меню Пуск и на Рабочем столе
set(CPACK_NSIS_MUI_ICON "${CMAKE_CURRENT_SOURCE_DIR}/icon.ico")
set(CPACK_NSIS_MUI_UNIICON "${CMAKE_CURRENT_SOURCE_DIR}/icon.ico")
set(CPACK_NSIS_CREATE_ICONS_EXTRA
"CreateShortCut '$DESKTOP\\\\Shadow Over Bishkek Demo.lnk' '$INSTDIR\\\\ShadowOverBishkekDemo.exe'"
)
# Форматы генераторов: ZIP (Portable) и NSIS (Установщик)
set(CPACK_GENERATOR "ZIP;NSIS")
include(CPack)

21
proj-windows/LICENSE.txt Normal file
View File

@ -0,0 +1,21 @@
END USER LICENSE AGREEMENT (EULA)
Game: Shadow Over Bishkek (Demo Version)
Developer: Fish Run Games
1. GRANT OF LICENSE
Fish Run Games grants you a non-exclusive, non-transferable, limited license to download, install, and play the Shadow Over Bishkek Demo for personal, non-commercial entertainment purposes.
2. OWNERSHIP & INTELLECTUAL PROPERTY
The game, including all code, graphics, audio, characters, storyline, and assets, is the intellectual property of Fish Run Games and is protected by copyright and other intellectual property laws. You may not reverse engineer, decompile, modify, or redistribute the software without express written permission.
3. DISCLAIMER OF WARRANTY
THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT, OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
4. USER DATA AND SAVES
Save files and local configuration options are stored locally on your device. Fish Run Games is not responsible for any loss of save data resulting from software updates, system crashes, or uninstallation.
5. TERMINATION
This license is effective until terminated. Your rights under this license will terminate automatically without notice from Fish Run Games if you fail to comply with any term(s) of this agreement.
Copyright (c) 2026 Fish Run Games. All rights reserved.

1
proj-windows/app.rc Normal file
View File

@ -0,0 +1 @@
IDI_ICON1 ICON "icon.ico"

BIN
proj-windows/dist_dlls/OpenAL32.dll (Stored with Git LFS) Normal file

Binary file not shown.

BIN
proj-windows/dist_dlls/concrt140.dll (Stored with Git LFS) Normal file

Binary file not shown.

BIN
proj-windows/dist_dlls/msvcp140.dll (Stored with Git LFS) Normal file

Binary file not shown.

BIN
proj-windows/dist_dlls/msvcp140_1.dll (Stored with Git LFS) Normal file

Binary file not shown.

BIN
proj-windows/dist_dlls/msvcp140_2.dll (Stored with Git LFS) Normal file

Binary file not shown.

BIN
proj-windows/dist_dlls/msvcp140_atomic_wait.dll (Stored with Git LFS) Normal file

Binary file not shown.

BIN
proj-windows/dist_dlls/msvcp140_clr0400.dll (Stored with Git LFS) Normal file

Binary file not shown.

BIN
proj-windows/dist_dlls/msvcp140_codecvt_ids.dll (Stored with Git LFS) Normal file

Binary file not shown.

BIN
proj-windows/dist_dlls/msvcp140d.dll (Stored with Git LFS) Normal file

Binary file not shown.

BIN
proj-windows/dist_dlls/vcamp140.dll (Stored with Git LFS) Normal file

Binary file not shown.

BIN
proj-windows/dist_dlls/vccorlib140.dll (Stored with Git LFS) Normal file

Binary file not shown.

BIN
proj-windows/dist_dlls/vcomp140.dll (Stored with Git LFS) Normal file

Binary file not shown.

BIN
proj-windows/dist_dlls/vcruntime140.dll (Stored with Git LFS) Normal file

Binary file not shown.

BIN
proj-windows/dist_dlls/vcruntime140_1.dll (Stored with Git LFS) Normal file

Binary file not shown.

BIN
proj-windows/dist_dlls/vcruntime140_1_clr0400.dll (Stored with Git LFS) Normal file

Binary file not shown.

BIN
proj-windows/dist_dlls/vcruntime140_threads.dll (Stored with Git LFS) Normal file

Binary file not shown.

BIN
proj-windows/dist_dlls/wrap_oal.dll (Stored with Git LFS) Normal file

Binary file not shown.

BIN
proj-windows/icon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 361 KiB

View File

@ -3,7 +3,7 @@
{
"id": "main_hall",
"positionX": -3.1,
"positionY": 6.0,
"positionY": 4.8,
"positionZ": 0.0,
"directionX": 0.0,
"directionY": -1.0,
@ -20,7 +20,7 @@
"limitX": 3.75,
"limitY": 2.75,
"positionX": 4.95,
"positionY": 3.0,
"positionY": 3.6,
"positionZ": -14.25,
"directionX": 0.0,
"directionY": -1.0,
@ -55,7 +55,7 @@
"limitX": 3.75,
"limitY": 2.75,
"positionX": -4.95,
"positionY": 4.0,
"positionY": 4.2,
"positionZ": -19.95,
"directionX": 0.0,
"directionY": -1.0,

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,

View File

@ -1122,9 +1122,9 @@
"waypointReachRadius": 3.0,
"waypoints": []
},
"taxiCarTriggerPositionX": -1.7014118346046923e+38,
"taxiCarTriggerPositionY": -1.7014118346046923e+38,
"taxiCarTriggerPositionZ": -1.7014118346046923e+38,
"taxiCarTriggerPositionX": 0.0,
"taxiCarTriggerPositionY": 0.0,
"taxiCarTriggerPositionZ": 0.0,
"taxiDefaultWaypoints": []
},
"tutorialInteractiveObjectsLocked": false
@ -1476,7 +1476,6 @@
"morning_can_open_door_index": 0,
"morning_did_open_door_index": 0,
"player_hold_book": false,
"player_hold_knife": false,
"teacher_arrived_to_library": false,
"teacher_door_opened": false,
"teacher_told_about_book": false,
@ -1652,7 +1651,7 @@
}
]
},
"savedAt": "2026-07-14 14:00",
"savedAt": "2026-07-24 23:40",
"taxiIsCalled": false,
"tutorialJournalPickedUp": false,
"tutorialJournalScreenOpened": false,

View File

@ -13,7 +13,7 @@
{
"key": "Аида Дженибековна",
"ru": "Аида Дженибековна",
"en": "Aida Dzhanybekovna"
"en": "Aida Dzhenibekovna"
},
{
"key": "Алтынай",

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"
},

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 },

View File

@ -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": []
}
]
}

View File

@ -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"
},
{

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": []
}
]
}

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": "Совсем недавно закончилась пандемия, а сегодня мир захлестнули кровавые войны.",

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 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. Osho!"
},
{
"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, dep."
},
{
"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! Osho!"
}
]
}

View File

@ -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"
@ -403,6 +412,70 @@
}
]
},
{
"id": "dialog_chat_aiperi003",
"start": "line_1",
"nodes": [
{
"id": "line_1",
"type": "Line",
"speaker": "Бекзат",
"portrait": "resources/dialogue/portrait_phone.png",
"text": "Айпери ты куда убежала?",
"next": "line_2",
"chatBubble": "out"
},
{
"id": "line_2",
"type": "Line",
"speaker": "Айпери",
"portrait": "resources/dialogue/portrait_phone.png",
"text": "Ты достал уже, я не могу тебя ждать вечно! Возишься как сонная муха.",
"next": "line_3",
"chatBubble": "in"
},
{
"id": "line_3",
"type": "Line",
"speaker": "Айпери",
"portrait": "resources/dialogue/portrait_phone.png",
"text": "У меня нет времени ждать тебя, мне уже пора на курсы ехать.",
"next": "line_4",
"chatBubble": "in"
},
{
"id": "line_4",
"type": "Line",
"speaker": "Бекзат",
"portrait": "resources/dialogue/portrait_phone.png",
"text": "Так когда я тебе нож верну?",
"next": "line_5",
"chatBubble": "out"
},
{
"id": "line_5",
"type": "Line",
"speaker": "Айпери",
"portrait": "resources/dialogue/portrait_phone.png",
"text": "Приходи завтра в универ! Я каждый день в унике с раннего утра.",
"next": "line_6",
"chatBubble": "in"
},
{
"id": "line_6",
"type": "Line",
"speaker": "Бекзат",
"portrait": "resources/dialogue/portrait_phone.png",
"text": "Хорошо, встретимся завтра.",
"next": "end_1",
"chatBubble": "out"
},
{
"id": "end_1",
"type": "End"
}
]
},
{
"id": "dialog_car001",
"start": "line_1",

View File

@ -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"
},
{
@ -292,7 +300,7 @@
"type": "Line",
"speaker": "Айпери",
"portrait": "resources/dialogue/portrait_aiperi.png",
"text": "Она меня ищет, я уверена, она хочет мне какое-то задание дать.",
"text": "Я уверена, она меня ищет. Она хочет мне какое-то задание дать.",
"next": "line_26"
},
{
@ -338,7 +346,7 @@
"type": "Line",
"speaker": "Айпери",
"portrait": "resources/dialogue/portrait_aiperi.png",
"text": "Пока ты не принесешь мне нож из учительской, никуда я тебя не выпущу.",
"text": "Пока ты не принесешь мне нож из учительской, никуда я тебя не выпущу. Ошо!",
"next": "end_1"
},
{
@ -2385,7 +2393,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 +2587,7 @@
"type": "Line",
"speaker": "Бекзат",
"portrait": "resources/dialogue/portrait_phone.png",
"text": "Почему ты сама не можешь забрать?",
"text": "Почему ты сама не можешь забрать нож у Аиды?",
"next": "line_9",
"chatBubble": "out"
},
@ -2580,7 +2596,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 +2623,7 @@
"type": "Line",
"speaker": "Айпери",
"portrait": "resources/dialogue/portrait_phone.png",
"text": "Так что жду тебя в универе! Не вздумай прогулять!",
"text": "Так что жду тебя в универе! Не вздумай прогулять! Ошо!",
"next": "setflag_1",
"chatBubble": "in",
"questUnlock": "aiperi_knife"

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,20 @@
precision highp float;
attribute vec3 vPosition;
attribute vec2 vTexCoord;
attribute vec3 vNormal;
varying vec2 texCoord;
varying vec4 fragPosLightSpace;
varying vec3 fragNormal;
uniform mat4 ProjectionModelViewMatrix;
uniform mat4 ModelViewMatrix;
uniform mat4 uLightFromCamera;
void main()
{
gl_Position = ProjectionModelViewMatrix * vec4(vPosition, 1.0);
texCoord = vTexCoord;
fragPosLightSpace = uLightFromCamera * ModelViewMatrix * vec4(vPosition, 1.0);
fragNormal = mat3(ModelViewMatrix) * vNormal;
}

View File

@ -0,0 +1,23 @@
attribute vec3 vPosition;
attribute vec2 vTexCoord;
attribute vec3 vNormal;
varying vec2 texCoord;
varying vec4 fragPosLightSpace;
varying vec3 fragNormal;
varying float fogDistance;
uniform mat4 ProjectionModelViewMatrix;
uniform mat4 ModelViewMatrix;
uniform mat4 uLightFromCamera;
uniform vec3 uPlayerEyePos;
void main()
{
vec4 eyePos = ModelViewMatrix * vec4(vPosition, 1.0);
fogDistance = length(eyePos.xyz - uPlayerEyePos);
gl_Position = ProjectionModelViewMatrix * vec4(vPosition, 1.0);
texCoord = vTexCoord;
fragPosLightSpace = uLightFromCamera * eyePos;
fragNormal = mat3(ModelViewMatrix) * vNormal;
}

View File

@ -0,0 +1,69 @@
attribute vec3 vPosition;
attribute vec2 vTexCoord;
attribute vec3 vNormal;
attribute vec4 aBoneIndices0;
attribute vec2 aBoneIndices1;
attribute vec4 aBoneWeights0;
attribute vec2 aBoneWeights1;
varying vec2 texCoord;
varying vec4 fragPosLightSpace;
varying vec3 fragNormal;
varying float fogDistance;
uniform mat4 ProjectionModelViewMatrix;
uniform mat4 ModelViewMatrix;
uniform mat4 uLightFromCamera;
uniform mat4 uBoneMatrices[58];
uniform vec3 uPlayerEyePos;
void main()
{
vec4 skinnedPos = vec4(0.0, 0.0, 0.0, 0.0);
vec3 skinnedNormal = vec3(0.0, 0.0, 0.0);
vec4 originalPos = vec4(vPosition, 1.0);
float totalWeight = 0.0;
if (aBoneWeights0.x > 0.0) {
skinnedPos += uBoneMatrices[int(aBoneIndices0.x)] * originalPos * aBoneWeights0.x;
skinnedNormal += mat3(uBoneMatrices[int(aBoneIndices0.x)]) * vNormal * aBoneWeights0.x;
totalWeight += aBoneWeights0.x;
}
if (aBoneWeights0.y > 0.0) {
skinnedPos += uBoneMatrices[int(aBoneIndices0.y)] * originalPos * aBoneWeights0.y;
skinnedNormal += mat3(uBoneMatrices[int(aBoneIndices0.y)]) * vNormal * aBoneWeights0.y;
totalWeight += aBoneWeights0.y;
}
if (aBoneWeights0.z > 0.0) {
skinnedPos += uBoneMatrices[int(aBoneIndices0.z)] * originalPos * aBoneWeights0.z;
skinnedNormal += mat3(uBoneMatrices[int(aBoneIndices0.z)]) * vNormal * aBoneWeights0.z;
totalWeight += aBoneWeights0.z;
}
if (aBoneWeights0.w > 0.0) {
skinnedPos += uBoneMatrices[int(aBoneIndices0.w)] * originalPos * aBoneWeights0.w;
skinnedNormal += mat3(uBoneMatrices[int(aBoneIndices0.w)]) * vNormal * aBoneWeights0.w;
totalWeight += aBoneWeights0.w;
}
if (aBoneWeights1.x > 0.0) {
skinnedPos += uBoneMatrices[int(aBoneIndices1.x)] * originalPos * aBoneWeights1.x;
skinnedNormal += mat3(uBoneMatrices[int(aBoneIndices1.x)]) * vNormal * aBoneWeights1.x;
totalWeight += aBoneWeights1.x;
}
if (aBoneWeights1.y > 0.0) {
skinnedPos += uBoneMatrices[int(aBoneIndices1.y)] * originalPos * aBoneWeights1.y;
skinnedNormal += mat3(uBoneMatrices[int(aBoneIndices1.y)]) * vNormal * aBoneWeights1.y;
totalWeight += aBoneWeights1.y;
}
if (totalWeight < 0.001) {
skinnedPos = originalPos;
skinnedNormal = vNormal;
}
vec4 eyePos = ModelViewMatrix * skinnedPos;
fogDistance = length(eyePos.xyz - uPlayerEyePos);
gl_Position = ProjectionModelViewMatrix * skinnedPos;
texCoord = vTexCoord;
fragPosLightSpace = uLightFromCamera * eyePos;
fragNormal = mat3(ModelViewMatrix) * skinnedNormal;
}

View File

@ -0,0 +1,55 @@
attribute vec3 vPosition;
attribute vec2 vTexCoord;
attribute vec4 aBoneIndices0;
attribute vec2 aBoneIndices1;
attribute vec4 aBoneWeights0;
attribute vec2 aBoneWeights1;
varying vec2 texCoord;
varying float fogDistance;
uniform mat4 ProjectionModelViewMatrix;
uniform mat4 ModelViewMatrix;
uniform mat4 uBoneMatrices[58];
uniform vec3 uPlayerEyePos;
void main()
{
vec4 skinnedPos = vec4(0.0, 0.0, 0.0, 0.0);
vec4 originalPos = vec4(vPosition, 1.0);
float totalWeight = 0.0;
if (aBoneWeights0.x > 0.0) {
skinnedPos += uBoneMatrices[int(aBoneIndices0.x)] * originalPos * aBoneWeights0.x;
totalWeight += aBoneWeights0.x;
}
if (aBoneWeights0.y > 0.0) {
skinnedPos += uBoneMatrices[int(aBoneIndices0.y)] * originalPos * aBoneWeights0.y;
totalWeight += aBoneWeights0.y;
}
if (aBoneWeights0.z > 0.0) {
skinnedPos += uBoneMatrices[int(aBoneIndices0.z)] * originalPos * aBoneWeights0.z;
totalWeight += aBoneWeights0.z;
}
if (aBoneWeights0.w > 0.0) {
skinnedPos += uBoneMatrices[int(aBoneIndices0.w)] * originalPos * aBoneWeights0.w;
totalWeight += aBoneWeights0.w;
}
if (aBoneWeights1.x > 0.0) {
skinnedPos += uBoneMatrices[int(aBoneIndices1.x)] * originalPos * aBoneWeights1.x;
totalWeight += aBoneWeights1.x;
}
if (aBoneWeights1.y > 0.0) {
skinnedPos += uBoneMatrices[int(aBoneIndices1.y)] * originalPos * aBoneWeights1.y;
totalWeight += aBoneWeights1.y;
}
if (totalWeight < 0.001) {
skinnedPos = originalPos;
}
vec4 eyePos = ModelViewMatrix * skinnedPos;
fogDistance = length(eyePos.xyz - uPlayerEyePos);
gl_Position = ProjectionModelViewMatrix * skinnedPos;
texCoord = vTexCoord;
}

View File

@ -1,4 +1,4 @@
precision mediump float;
precision highp float;
uniform sampler2D Texture;
uniform sampler2D uShadowMap;

View File

@ -0,0 +1,28 @@
precision highp float;
attribute vec3 vPosition;
attribute vec2 vTexCoord;
attribute vec3 vNormal;
varying vec2 texCoord;
varying vec4 fragPosLightSpace;
varying vec3 fragNormal;
varying float fogDistance;
varying vec3 fragViewPos;
varying vec2 fragWorldXZ;
uniform mat4 ProjectionModelViewMatrix;
uniform mat4 ModelViewMatrix;
uniform mat4 uLightFromCamera;
uniform vec3 uPlayerEyePos;
void main()
{
vec4 eyePos = ModelViewMatrix * vec4(vPosition, 1.0);
fogDistance = length(eyePos.xyz - uPlayerEyePos);
gl_Position = ProjectionModelViewMatrix * vec4(vPosition, 1.0);
texCoord = vTexCoord;
fragViewPos = eyePos.xyz;
fragNormal = mat3(ModelViewMatrix) * vNormal;
fragPosLightSpace = uLightFromCamera * eyePos;
fragWorldXZ = vPosition.xz;
}

View File

@ -0,0 +1,74 @@
attribute vec3 vPosition;
attribute vec2 vTexCoord;
attribute vec3 vNormal;
attribute vec4 aBoneIndices0;
attribute vec2 aBoneIndices1;
attribute vec4 aBoneWeights0;
attribute vec2 aBoneWeights1;
varying vec2 texCoord;
varying vec4 fragPosLightSpace;
varying vec3 fragNormal;
varying float fogDistance;
varying vec3 fragViewPos;
varying vec2 fragWorldXZ;
uniform mat4 ProjectionModelViewMatrix;
uniform mat4 ModelViewMatrix;
uniform mat4 uLightFromCamera;
uniform mat4 uViewInverse;
uniform mat4 uBoneMatrices[58];
uniform vec3 uPlayerEyePos;
void main()
{
vec4 skinnedPos = vec4(0.0, 0.0, 0.0, 0.0);
vec3 skinnedNormal = vec3(0.0, 0.0, 0.0);
vec4 originalPos = vec4(vPosition, 1.0);
float totalWeight = 0.0;
if (aBoneWeights0.x > 0.0) {
skinnedPos += uBoneMatrices[int(aBoneIndices0.x)] * originalPos * aBoneWeights0.x;
skinnedNormal += mat3(uBoneMatrices[int(aBoneIndices0.x)]) * vNormal * aBoneWeights0.x;
totalWeight += aBoneWeights0.x;
}
if (aBoneWeights0.y > 0.0) {
skinnedPos += uBoneMatrices[int(aBoneIndices0.y)] * originalPos * aBoneWeights0.y;
skinnedNormal += mat3(uBoneMatrices[int(aBoneIndices0.y)]) * vNormal * aBoneWeights0.y;
totalWeight += aBoneWeights0.y;
}
if (aBoneWeights0.z > 0.0) {
skinnedPos += uBoneMatrices[int(aBoneIndices0.z)] * originalPos * aBoneWeights0.z;
skinnedNormal += mat3(uBoneMatrices[int(aBoneIndices0.z)]) * vNormal * aBoneWeights0.z;
totalWeight += aBoneWeights0.z;
}
if (aBoneWeights0.w > 0.0) {
skinnedPos += uBoneMatrices[int(aBoneIndices0.w)] * originalPos * aBoneWeights0.w;
skinnedNormal += mat3(uBoneMatrices[int(aBoneIndices0.w)]) * vNormal * aBoneWeights0.w;
totalWeight += aBoneWeights0.w;
}
if (aBoneWeights1.x > 0.0) {
skinnedPos += uBoneMatrices[int(aBoneIndices1.x)] * originalPos * aBoneWeights1.x;
skinnedNormal += mat3(uBoneMatrices[int(aBoneIndices1.x)]) * vNormal * aBoneWeights1.x;
totalWeight += aBoneWeights1.x;
}
if (aBoneWeights1.y > 0.0) {
skinnedPos += uBoneMatrices[int(aBoneIndices1.y)] * originalPos * aBoneWeights1.y;
skinnedNormal += mat3(uBoneMatrices[int(aBoneIndices1.y)]) * vNormal * aBoneWeights1.y;
totalWeight += aBoneWeights1.y;
}
if (totalWeight < 0.001) {
skinnedPos = originalPos;
skinnedNormal = vNormal;
}
vec4 eyePos = ModelViewMatrix * skinnedPos;
fogDistance = length(eyePos.xyz - uPlayerEyePos);
gl_Position = ProjectionModelViewMatrix * skinnedPos;
texCoord = vTexCoord;
fragViewPos = eyePos.xyz;
fragNormal = mat3(ModelViewMatrix) * skinnedNormal;
fragPosLightSpace = uLightFromCamera * eyePos;
fragWorldXZ = (uViewInverse * eyePos).xz;
}

View File

@ -0,0 +1,71 @@
attribute vec3 vPosition;
attribute vec2 vTexCoord;
attribute vec3 vNormal;
attribute vec4 aBoneIndices0;
attribute vec2 aBoneIndices1;
attribute vec4 aBoneWeights0;
attribute vec2 aBoneWeights1;
varying vec2 texCoord;
varying float fogDistance;
varying vec3 fragViewPos;
varying vec3 fragNormal;
varying vec2 fragWorldXZ;
uniform mat4 ProjectionModelViewMatrix;
uniform mat4 ModelViewMatrix;
uniform mat4 uViewInverse;
uniform mat4 uBoneMatrices[58];
uniform vec3 uPlayerEyePos;
void main()
{
vec4 skinnedPos = vec4(0.0, 0.0, 0.0, 0.0);
vec3 skinnedNormal = vec3(0.0, 0.0, 0.0);
vec4 originalPos = vec4(vPosition, 1.0);
float totalWeight = 0.0;
if (aBoneWeights0.x > 0.0) {
skinnedPos += uBoneMatrices[int(aBoneIndices0.x)] * originalPos * aBoneWeights0.x;
skinnedNormal += mat3(uBoneMatrices[int(aBoneIndices0.x)]) * vNormal * aBoneWeights0.x;
totalWeight += aBoneWeights0.x;
}
if (aBoneWeights0.y > 0.0) {
skinnedPos += uBoneMatrices[int(aBoneIndices0.y)] * originalPos * aBoneWeights0.y;
skinnedNormal += mat3(uBoneMatrices[int(aBoneIndices0.y)]) * vNormal * aBoneWeights0.y;
totalWeight += aBoneWeights0.y;
}
if (aBoneWeights0.z > 0.0) {
skinnedPos += uBoneMatrices[int(aBoneIndices0.z)] * originalPos * aBoneWeights0.z;
skinnedNormal += mat3(uBoneMatrices[int(aBoneIndices0.z)]) * vNormal * aBoneWeights0.z;
totalWeight += aBoneWeights0.z;
}
if (aBoneWeights0.w > 0.0) {
skinnedPos += uBoneMatrices[int(aBoneIndices0.w)] * originalPos * aBoneWeights0.w;
skinnedNormal += mat3(uBoneMatrices[int(aBoneIndices0.w)]) * vNormal * aBoneWeights0.w;
totalWeight += aBoneWeights0.w;
}
if (aBoneWeights1.x > 0.0) {
skinnedPos += uBoneMatrices[int(aBoneIndices1.x)] * originalPos * aBoneWeights1.x;
skinnedNormal += mat3(uBoneMatrices[int(aBoneIndices1.x)]) * vNormal * aBoneWeights1.x;
totalWeight += aBoneWeights1.x;
}
if (aBoneWeights1.y > 0.0) {
skinnedPos += uBoneMatrices[int(aBoneIndices1.y)] * originalPos * aBoneWeights1.y;
skinnedNormal += mat3(uBoneMatrices[int(aBoneIndices1.y)]) * vNormal * aBoneWeights1.y;
totalWeight += aBoneWeights1.y;
}
if (totalWeight < 0.001) {
skinnedPos = originalPos;
skinnedNormal = vNormal;
}
vec4 eyePos = ModelViewMatrix * skinnedPos;
fogDistance = length(eyePos.xyz - uPlayerEyePos);
gl_Position = ProjectionModelViewMatrix * skinnedPos;
texCoord = vTexCoord;
fragViewPos = eyePos.xyz;
fragNormal = mat3(ModelViewMatrix) * skinnedNormal;
fragWorldXZ = (uViewInverse * eyePos).xz;
}

View File

@ -0,0 +1,24 @@
attribute vec3 vPosition;
attribute vec2 vTexCoord;
attribute vec3 vNormal;
varying vec2 texCoord;
varying float fogDistance;
varying vec3 fragViewPos;
varying vec3 fragNormal;
varying vec2 fragWorldXZ;
uniform mat4 ProjectionModelViewMatrix;
uniform mat4 ModelViewMatrix;
uniform vec3 uPlayerEyePos;
void main()
{
vec4 eyePos = ModelViewMatrix * vec4(vPosition.xyz, 1.0);
fogDistance = length(eyePos.xyz - uPlayerEyePos);
gl_Position = ProjectionModelViewMatrix * vec4(vPosition.xyz, 1.0);
texCoord = vTexCoord;
fragViewPos = eyePos.xyz;
fragNormal = mat3(ModelViewMatrix) * vNormal;
fragWorldXZ = vPosition.xz;
}

Some files were not shown because too many files have changed in this diff Show More