1241 lines
49 KiB
C++
1241 lines
49 KiB
C++
#include "Game.h"
|
|
#include "AnimatedModel.h"
|
|
#include "utils/Utils.h"
|
|
#include "items/ItemRegistry.h"
|
|
#include "render/OpenGlExtensions.h"
|
|
#include <iostream>
|
|
#include "render/TextureManager.h"
|
|
#include "TextModel.h"
|
|
#include <random>
|
|
#include <cmath>
|
|
#include <algorithm>
|
|
#include <functional>
|
|
#ifdef __ANDROID__
|
|
#include <android/log.h>
|
|
#endif
|
|
|
|
|
|
|
|
#ifdef EMSCRIPTEN
|
|
#include <emscripten.h>
|
|
#endif
|
|
|
|
#include "GameConstants.h"
|
|
|
|
namespace ZL
|
|
{
|
|
/*
|
|
void set_Texture(Character& npc, const TextureDataStruct& texture)
|
|
{
|
|
auto tt = std::make_shared<Texture>(texture);
|
|
npc.setTexture(tt);
|
|
}
|
|
void set_Texture(Character& npc, const std::string& meshName, const TextureDataStruct& texture)
|
|
{
|
|
auto tt = std::make_shared<Texture>(texture);
|
|
npc.setTexture(meshName, tt);
|
|
}*/
|
|
|
|
#ifdef EMSCRIPTEN
|
|
const char* CONST_ZIP_FILE = "resources.zip";
|
|
#else
|
|
const char* CONST_ZIP_FILE = "";
|
|
#endif
|
|
|
|
float x = 0;
|
|
float y = 0;
|
|
float z = 0;
|
|
|
|
#ifdef EMSCRIPTEN
|
|
Game* Game::s_instance = nullptr;
|
|
|
|
void Game::onResourcesZipLoaded(const char* /*filename*/) {
|
|
std::cout << "Resources.zip loaded successfully" << std::endl;
|
|
if (s_instance) {
|
|
s_instance->mainThreadHandler.EnqueueMainThreadTask([&]() {
|
|
s_instance->setupPart2();
|
|
});
|
|
}
|
|
}
|
|
|
|
void Game::onResourcesZipError(const char* /*filename*/) {
|
|
std::cout << "Failed to download resources.zip" << std::endl;
|
|
}
|
|
#endif
|
|
|
|
Game::Game()
|
|
: newTickCount(0)
|
|
, lastTickCount(0)
|
|
, menuManager(renderer)
|
|
, audioPlayer(std::make_unique<AudioPlayerAsync>())
|
|
{
|
|
}
|
|
|
|
Game::~Game() {
|
|
#ifndef EMSCRIPTEN
|
|
// In Emscripten, SDL must stay alive across context loss/restore cycles
|
|
// so the window remains valid when the game object is re-created.
|
|
SDL_Quit();
|
|
#endif
|
|
}
|
|
|
|
void Game::setup() {
|
|
Environment::width = Environment::CONST_DEFAULT_WIDTH;
|
|
Environment::height = Environment::CONST_DEFAULT_HEIGHT;
|
|
Environment::computeProjectionDimensions();
|
|
|
|
ZL::BindOpenGlFunctions();
|
|
ZL::CheckGlError(__FILE__, __LINE__);
|
|
renderer.InitOpenGL();
|
|
|
|
#ifdef EMSCRIPTEN
|
|
// These shaders and loading.png are preloaded separately (not from zip),
|
|
// so they are available immediately without waiting for resources.zip.
|
|
renderer.shaderManager.AddShaderFromFiles("defaultColor", "resources/shaders/defaultColor.vertex", "resources/shaders/defaultColor_web.fragment", "");
|
|
renderer.shaderManager.AddShaderFromFiles("default", "resources/shaders/default.vertex", "resources/shaders/default_web.fragment", "");
|
|
#else
|
|
renderer.shaderManager.AddShaderFromFiles("defaultColor", "resources/shaders/defaultColor.vertex", "resources/shaders/defaultColor_desktop.fragment", CONST_ZIP_FILE);
|
|
renderer.shaderManager.AddShaderFromFiles("default", "resources/shaders/default.vertex", "resources/shaders/default_desktop.fragment", CONST_ZIP_FILE);
|
|
#endif
|
|
loadingTexture = renderer.textureManager.LoadFromPng("resources/loading.png", "");
|
|
|
|
float minDimension;
|
|
float width = Environment::projectionWidth;
|
|
float height = Environment::projectionHeight;
|
|
|
|
if (width >= height)
|
|
{
|
|
minDimension = height;
|
|
}
|
|
else
|
|
{
|
|
minDimension = width;
|
|
}
|
|
|
|
loadingMesh.data = CreateRect2D({ 0.0f, 0.0f }, { minDimension * 0.5f, minDimension * 0.5f }, 3);
|
|
loadingMesh.RefreshVBO();
|
|
|
|
#ifdef EMSCRIPTEN
|
|
// Asynchronously download resources.zip; setupPart2() is called on completion.
|
|
// The loading screen stays visible until the download finishes.
|
|
s_instance = this;
|
|
std::cout << "Load resurces step 1" << std::endl;
|
|
emscripten_async_wget("resources.zip", "resources.zip", onResourcesZipLoaded, onResourcesZipError);
|
|
#else
|
|
mainThreadHandler.EnqueueMainThreadTask([this]() {
|
|
std::cout << "Load resurces step 2" << std::endl;
|
|
this->setupPart2();
|
|
std::cout << "Load resurces step 3" << std::endl;
|
|
});
|
|
#endif
|
|
|
|
}
|
|
|
|
void Game::setupPart2()
|
|
{
|
|
|
|
#ifdef EMSCRIPTEN
|
|
renderer.shaderManager.AddShaderFromFiles("env_sky", "resources/shaders/env_sky.vertex", "resources/shaders/env_sky_web.fragment", CONST_ZIP_FILE);
|
|
renderer.shaderManager.AddShaderFromFiles("defaultAtmosphere", "resources/shaders/defaultAtmosphere.vertex", "resources/shaders/defaultAtmosphere_web.fragment", CONST_ZIP_FILE);
|
|
renderer.shaderManager.AddShaderFromFiles("planetBake", "resources/shaders/planet_bake.vertex", "resources/shaders/planet_bake_web.fragment", CONST_ZIP_FILE);
|
|
renderer.shaderManager.AddShaderFromFiles("planetStone", "resources/shaders/planet_stone.vertex", "resources/shaders/planet_stone_web.fragment", CONST_ZIP_FILE);
|
|
renderer.shaderManager.AddShaderFromFiles("planetLand", "resources/shaders/planet_land.vertex", "resources/shaders/planet_land_web.fragment", CONST_ZIP_FILE);
|
|
renderer.shaderManager.AddShaderFromFiles("spark", "resources/shaders/spark.vertex", "resources/shaders/spark_web.fragment", CONST_ZIP_FILE);
|
|
renderer.shaderManager.AddShaderFromFiles("skinning", "resources/shaders/skinning.vertex", "resources/shaders/default_web.fragment", CONST_ZIP_FILE);
|
|
renderer.shaderManager.AddShaderFromFiles("fog", "resources/shaders/fog.vertex", "resources/shaders/fog_web.fragment", CONST_ZIP_FILE);
|
|
renderer.shaderManager.AddShaderFromFiles("fog_skinning", "resources/shaders/fog_skinning.vertex", "resources/shaders/fog_web.fragment", CONST_ZIP_FILE);
|
|
renderer.shaderManager.AddShaderFromFiles("darklands_fog", "resources/shaders/darklands_fog.vertex", "resources/shaders/darklands_fog_web.fragment", CONST_ZIP_FILE);
|
|
renderer.shaderManager.AddShaderFromFiles("darklands_fog_skinning", "resources/shaders/darklands_fog_skinning.vertex", "resources/shaders/darklands_fog_web.fragment", CONST_ZIP_FILE);
|
|
renderer.shaderManager.AddShaderFromFiles("darklands_flash", "resources/shaders/default.vertex", "resources/shaders/darklands_flash_web.fragment", CONST_ZIP_FILE);
|
|
renderer.shaderManager.AddShaderFromFiles("cutsceneFade", "resources/shaders/default.vertex", "resources/shaders/cutscene_fade_web.fragment", CONST_ZIP_FILE);
|
|
renderer.shaderManager.AddShaderFromFiles("cutsceneBlack", "resources/shaders/default.vertex", "resources/shaders/cutscene_black_web.fragment", CONST_ZIP_FILE);
|
|
renderer.shaderManager.AddShaderFromFiles("shadow_depth", "resources/shaders/shadow_depth.vertex", "resources/shaders/shadow_depth_web.fragment", CONST_ZIP_FILE);
|
|
std::cout << "Load resurces step 12x1" << std::endl;
|
|
renderer.shaderManager.AddShaderFromFiles("shadow_depth_skinning", "resources/shaders/shadow_depth_skinning.vertex", "resources/shaders/shadow_depth_web.fragment", CONST_ZIP_FILE);
|
|
renderer.shaderManager.AddShaderFromFiles("default_shadow", "resources/shaders/default_shadow.vertex", "resources/shaders/default_shadow_web.fragment", CONST_ZIP_FILE);
|
|
std::cout << "Load resurces step 12x3" << std::endl;
|
|
renderer.shaderManager.AddShaderFromFiles("skinning_shadow", "resources/shaders/skinning_shadow.vertex", "resources/shaders/default_shadow_web.fragment", CONST_ZIP_FILE);
|
|
std::cout << "Load resurces step 12x4" << std::endl;
|
|
renderer.shaderManager.AddShaderFromFiles("fog_shadow", "resources/shaders/fog_shadow.vertex", "resources/shaders/fog_shadow_web.fragment", CONST_ZIP_FILE);
|
|
std::cout << "Load resurces step 12x5" << std::endl;
|
|
renderer.shaderManager.AddShaderFromFiles("fog_skinning_shadow", "resources/shaders/fog_skinning_shadow.vertex", "resources/shaders/fog_shadow_web.fragment", CONST_ZIP_FILE);
|
|
renderer.shaderManager.AddShaderFromFiles("night_fog", "resources/shaders/night_fog.vertex", "resources/shaders/night_fog_web.fragment", CONST_ZIP_FILE);
|
|
renderer.shaderManager.AddShaderFromFiles("night_fog_skinning", "resources/shaders/night_fog_skinning.vertex", "resources/shaders/night_fog_web.fragment", CONST_ZIP_FILE);
|
|
renderer.shaderManager.AddShaderFromFiles("night_fog_shadow", "resources/shaders/night_fog_shadow.vertex", "resources/shaders/night_fog_shadow_web.fragment", CONST_ZIP_FILE);
|
|
renderer.shaderManager.AddShaderFromFiles("night_fog_skinning_shadow", "resources/shaders/night_fog_skinning_shadow.vertex", "resources/shaders/night_fog_shadow_web.fragment", CONST_ZIP_FILE);
|
|
|
|
#else
|
|
renderer.shaderManager.AddShaderFromFiles("env_sky", "resources/shaders/env_sky.vertex", "resources/shaders/env_sky_desktop.fragment", CONST_ZIP_FILE);
|
|
renderer.shaderManager.AddShaderFromFiles("defaultAtmosphere", "resources/shaders/defaultAtmosphere.vertex", "resources/shaders/defaultAtmosphere_desktop.fragment", CONST_ZIP_FILE);
|
|
renderer.shaderManager.AddShaderFromFiles("planetBake", "resources/shaders/planet_bake.vertex", "resources/shaders/planet_bake_desktop.fragment", CONST_ZIP_FILE);
|
|
renderer.shaderManager.AddShaderFromFiles("planetStone", "resources/shaders/planet_stone.vertex", "resources/shaders/planet_stone_desktop.fragment", CONST_ZIP_FILE);
|
|
renderer.shaderManager.AddShaderFromFiles("planetLand", "resources/shaders/planet_land.vertex", "resources/shaders/planet_land_desktop.fragment", CONST_ZIP_FILE);
|
|
renderer.shaderManager.AddShaderFromFiles("spark", "resources/shaders/spark.vertex", "resources/shaders/spark_desktop.fragment", CONST_ZIP_FILE);
|
|
renderer.shaderManager.AddShaderFromFiles("skinning", "resources/shaders/skinning.vertex", "resources/shaders/default_desktop.fragment", CONST_ZIP_FILE);
|
|
renderer.shaderManager.AddShaderFromFiles("fog", "resources/shaders/fog.vertex", "resources/shaders/fog_desktop.fragment", CONST_ZIP_FILE);
|
|
renderer.shaderManager.AddShaderFromFiles("fog_skinning", "resources/shaders/fog_skinning.vertex", "resources/shaders/fog_desktop.fragment", CONST_ZIP_FILE);
|
|
renderer.shaderManager.AddShaderFromFiles("darklands_fog", "resources/shaders/darklands_fog.vertex", "resources/shaders/darklands_fog_desktop.fragment", CONST_ZIP_FILE);
|
|
renderer.shaderManager.AddShaderFromFiles("darklands_fog_skinning", "resources/shaders/darklands_fog_skinning.vertex", "resources/shaders/darklands_fog_desktop.fragment", CONST_ZIP_FILE);
|
|
renderer.shaderManager.AddShaderFromFiles("darklands_flash", "resources/shaders/default.vertex", "resources/shaders/darklands_flash_desktop.fragment", CONST_ZIP_FILE);
|
|
renderer.shaderManager.AddShaderFromFiles("cutsceneFade", "resources/shaders/default.vertex", "resources/shaders/cutscene_fade_desktop.fragment", CONST_ZIP_FILE);
|
|
renderer.shaderManager.AddShaderFromFiles("cutsceneBlack", "resources/shaders/default.vertex", "resources/shaders/cutscene_black_desktop.fragment", CONST_ZIP_FILE);
|
|
renderer.shaderManager.AddShaderFromFiles("shadow_depth", "resources/shaders/shadow_depth.vertex", "resources/shaders/shadow_depth_desktop.fragment", CONST_ZIP_FILE);
|
|
renderer.shaderManager.AddShaderFromFiles("shadow_depth_skinning", "resources/shaders/shadow_depth_skinning.vertex", "resources/shaders/shadow_depth_desktop.fragment", CONST_ZIP_FILE);
|
|
renderer.shaderManager.AddShaderFromFiles("default_shadow", "resources/shaders/default_shadow.vertex", "resources/shaders/default_shadow_desktop.fragment", CONST_ZIP_FILE);
|
|
renderer.shaderManager.AddShaderFromFiles("skinning_shadow", "resources/shaders/skinning_shadow.vertex", "resources/shaders/default_shadow_desktop.fragment", CONST_ZIP_FILE);
|
|
renderer.shaderManager.AddShaderFromFiles("fog_shadow", "resources/shaders/fog_shadow.vertex", "resources/shaders/fog_shadow_desktop.fragment", CONST_ZIP_FILE);
|
|
renderer.shaderManager.AddShaderFromFiles("fog_skinning_shadow", "resources/shaders/fog_skinning_shadow.vertex", "resources/shaders/fog_shadow_desktop.fragment", CONST_ZIP_FILE);
|
|
renderer.shaderManager.AddShaderFromFiles("night_fog", "resources/shaders/night_fog.vertex", "resources/shaders/night_fog_desktop.fragment", CONST_ZIP_FILE);
|
|
renderer.shaderManager.AddShaderFromFiles("night_fog_skinning", "resources/shaders/night_fog_skinning.vertex", "resources/shaders/night_fog_desktop.fragment", CONST_ZIP_FILE);
|
|
renderer.shaderManager.AddShaderFromFiles("night_fog_shadow", "resources/shaders/night_fog_shadow.vertex", "resources/shaders/night_fog_shadow_desktop.fragment", CONST_ZIP_FILE);
|
|
renderer.shaderManager.AddShaderFromFiles("night_fog_skinning_shadow", "resources/shaders/night_fog_skinning_shadow.vertex", "resources/shaders/night_fog_shadow_desktop.fragment", CONST_ZIP_FILE);
|
|
|
|
#endif
|
|
|
|
std::cout << "Load resurces step 4" << std::endl;
|
|
|
|
ItemRegistry::instance().loadFromJson("resources/config2/items.json", CONST_ZIP_FILE);
|
|
|
|
globalFloats["player_hp"] = 200;
|
|
|
|
LocationSetup uniInteriorParams;
|
|
uniInteriorParams.gameObjectsJsonPath = "resources/config2/gameobjects_uni_interior.json";
|
|
uniInteriorParams.npcsJsonPath = "resources/config2/npcs_uni_interior.json";
|
|
uniInteriorParams.dialoguesJsonPath = "resources/dialogue/uni_interior_dialogues.json";
|
|
|
|
|
|
/*
|
|
uniInteriorParams.navigationJsonPaths = {
|
|
"resources/navigation/uni_interior0_all_locked.txt",
|
|
"resources/navigation/uni_interior0_hall.txt",
|
|
"resources/navigation/uni_interior0_lr_hall.txt",
|
|
"resources/navigation/uni_interior_basic0_objects_opendoors.txt",
|
|
"resources/navigation/uni_interior0_aiperi_lr_tr_hall.txt",
|
|
"resources/navigation/uni_interior1_night.txt",
|
|
"resources/navigation/uni_interior1_darklands_open.txt", //6
|
|
"resources/navigation/uni_interior2_locked.txt",
|
|
"resources/navigation/uni_interior2_unlocked_s1.txt",
|
|
"resources/navigation/uni_interior2_unlocked_s2.txt",
|
|
"resources/navigation/uni_interior2_unlocked_s3.txt",
|
|
"resources/navigation/uni_interior2_unlocked_n2.txt",
|
|
"resources/navigation/uni_interior2_unlocked_n3.txt",
|
|
"resources/navigation/uni_interior2_unlocked_hall.txt"
|
|
};
|
|
*/
|
|
|
|
|
|
/*
|
|
uniInteriorParams.navigationJsonPaths = {
|
|
"resources/navigation/uni_interior0_all_locked.json",
|
|
"resources/navigation/uni_interior0_hall.json",
|
|
"resources/navigation/uni_interior0_lr_hall.json",
|
|
"resources/navigation/uni_interior_basic0_objects_opendoors.json",
|
|
"resources/navigation/uni_interior0_aiperi_lr_tr_hall.json",
|
|
"resources/navigation/uni_interior1_night.json",
|
|
"resources/navigation/uni_interior1_darklands_open.json", //6
|
|
"resources/navigation/uni_interior2_locked.json",
|
|
"resources/navigation/uni_interior2_unlocked_s1.json",
|
|
"resources/navigation/uni_interior2_unlocked_s2.json",
|
|
"resources/navigation/uni_interior2_unlocked_s3.json",
|
|
"resources/navigation/uni_interior2_unlocked_n2.json",
|
|
"resources/navigation/uni_interior2_unlocked_n3.json",
|
|
"resources/navigation/uni_interior2_unlocked_hall.json"
|
|
};
|
|
*/
|
|
|
|
uniInteriorParams.navigationJsonPaths = {
|
|
"resources/navigation/uni_interior3_all_locked.json",
|
|
"resources/navigation/uni_interior3_hall.json",
|
|
"resources/navigation/uni_interior3_lr_hall.json",
|
|
"resources/navigation/uni_interior3_lr_tr_hall.json",
|
|
"resources/navigation/uni_interior3_lr_tr_hall_aiperi.json",
|
|
"resources/navigation/uni_interior3_night.json",
|
|
"resources/navigation/uni_interior3_darklands_all_open.json", //6
|
|
"resources/navigation/uni_interior3_locked.json",
|
|
"resources/navigation/uni_interior3_unlocked_s1.json",
|
|
"resources/navigation/uni_interior3_unlocked_s2.json",
|
|
"resources/navigation/uni_interior3_unlocked_s3.json",
|
|
"resources/navigation/uni_interior3_unlocked_n2.json",
|
|
"resources/navigation/uni_interior3_unlocked_n3.json",
|
|
"resources/navigation/uni_interior3_unlocked_hall.json"
|
|
};
|
|
|
|
uniInteriorParams.teleportsJsonPath = "resources/config2/teleports_uni_interior.json";
|
|
uniInteriorParams.triggerZonesJsonPath = "resources/config2/trigger_zones_uni_interior.json";
|
|
uniInteriorParams.lightsJsonPath = "resources/config2/lights_uni_interior.json";
|
|
uniInteriorParams.scriptPath = "resources/start_uni_interior.lua";
|
|
uniInteriorParams.interactiveObjectsJsonPath = "resources/config2/interactive_objects_uni_interior.json";
|
|
uniInteriorParams.playerPosition = Eigen::Vector3f(0.942694, 0, -9.63104);
|
|
|
|
locations["uni_interior"] = std::make_shared<Location>(renderer, inventory);
|
|
locations["uni_interior"]->setup(uniInteriorParams, &menuManager.questJournal);
|
|
locations["uni_interior"]->scriptEngine.setGlobalStore(&globalInts);
|
|
locations["uni_interior"]->scriptEngine.setGlobalFloatStore(&globalFloats);
|
|
locations["uni_interior"]->requestNightDayTransition = [this](bool isNight, bool isDawn) { this->menuManager.isNight = isNight; this->menuManager.isDawn = isDawn; };
|
|
locations["uni_interior"]->requestDarklandsTransition = [this]() { return startDarklandsTransition(); };
|
|
locations["uni_interior"]->requestAdvanceDarklandsHud = [this]() { menuManager.advanceUniIntDarklandsHud(); };
|
|
if (locations["uni_interior"]->player)
|
|
locations["uni_interior"]->player->onDeathAnimComplete = [this]() { startDarklandsTransition(); };
|
|
for (auto& npc : locations["uni_interior"]->npcs) {
|
|
if (npc && npc->canAttack) {
|
|
npc->onDeathAnimComplete = [this]() { menuManager.onEnemyKilledInUniInterior(); };
|
|
}
|
|
}
|
|
|
|
|
|
LocationSetup uniExteriorParams = uniInteriorParams;
|
|
uniExteriorParams.gameObjectsJsonPath = "resources/config2/gameobjects_uni_exterior.json";
|
|
uniExteriorParams.interactiveObjectsJsonPath = "resources/config2/interactive_objects_uni_exterior.json";
|
|
uniExteriorParams.navigationJsonPaths = {"resources/navigation/uni_exterior2_all.txt"};
|
|
//uniExteriorParams.navigationJsonPaths = { "resources/navigation/uni_exterior2_all.json" };
|
|
uniExteriorParams.teleportsJsonPath = "resources/config2/teleports_uni_exterior.json";
|
|
uniExteriorParams.triggerZonesJsonPath = "resources/config2/trigger_zones_uni_exterior.json";
|
|
uniExteriorParams.lightsJsonPath = "resources/config2/lights_uni_exterior.json";
|
|
uniExteriorParams.scriptPath = "resources/start_uni_exterior.lua";
|
|
uniExteriorParams.playerPosition = Eigen::Vector3f(5, 0, -18.4);
|
|
uniExteriorParams.npcsJsonPath = "resources/config2/npcs_uni_exterior.json";
|
|
uniExteriorParams.dialoguesJsonPath = "resources/dialogue/uni_exterior_dialogues.json";
|
|
|
|
locations["uni_exterior"] = std::make_shared<Location>(renderer, inventory);
|
|
locations["uni_exterior"]->setup(uniExteriorParams, &menuManager.questJournal);
|
|
locations["uni_exterior"]->scriptEngine.setGlobalStore(&globalInts);
|
|
locations["uni_exterior"]->scriptEngine.setGlobalFloatStore(&globalFloats);
|
|
locations["uni_exterior"]->requestNightDayTransition = [this](bool isNight, bool isDawn) { this->menuManager.isNight = isNight; this->menuManager.isDawn = isDawn; };
|
|
locations["uni_exterior"]->requestDarklandsTransition = [this]() { return startDarklandsTransition(); };
|
|
if (locations["uni_exterior"]->player)
|
|
locations["uni_exterior"]->player->onDeathAnimComplete = [this]() { startDarklandsTransition(); };
|
|
|
|
LocationSetup params_dorm;
|
|
//params_dorm.gameObjectsJsonPath = "resources/config2/gameobjects_dorm.json";
|
|
//params_dorm.gameObjectsJsonPath = "resources/config2/gameobjects_dorm_trees001.json";
|
|
params_dorm.gameObjectsJsonPath = "resources/config2/gameobjects_dorm_new.json";
|
|
params_dorm.npcsJsonPath = "resources/config2/npcs_dorm.json";
|
|
params_dorm.dialoguesJsonPath = "resources/dialogue/dorm_dialogues.json";
|
|
|
|
/*
|
|
params_dorm.navigationJsonPaths = {
|
|
"resources/navigation/dorm3_bca.json",
|
|
"resources/navigation/dorm3_ca.json",
|
|
"resources/navigation/dorm3_ba.json",
|
|
"resources/navigation/dorm3_a.json",
|
|
"resources/navigation/dorm3_b.json",
|
|
"resources/navigation/dorm3_all_open.json",
|
|
};*/
|
|
/*
|
|
params_dorm.navigationJsonPaths = {
|
|
"resources/navigation/dorm2_bca2.json",
|
|
"resources/navigation/dorm2_ca.json",
|
|
"resources/navigation/dorm2_ba.json",
|
|
"resources/navigation/dorm2_a.json",
|
|
"resources/navigation/dorm2_b.json",
|
|
"resources/navigation/dorm2_all_open.json",
|
|
};*/
|
|
|
|
|
|
/*
|
|
params_dorm.navigationJsonPaths = {
|
|
"resources/navigation/dorm2_bca2.txt",
|
|
"resources/navigation/dorm2_ca.txt",
|
|
"resources/navigation/dorm2_ba.txt",
|
|
"resources/navigation/dorm2_a.txt",
|
|
"resources/navigation/dorm2_b.txt",
|
|
"resources/navigation/dorm2_all_open.txt",
|
|
};*/
|
|
|
|
params_dorm.navigationJsonPaths = {
|
|
"resources/navigation/dorm0_large.json",
|
|
"resources/navigation/dorm0_large.json",
|
|
"resources/navigation/dorm0_large.json",
|
|
"resources/navigation/dorm0_large.json",
|
|
"resources/navigation/dorm0_large.json",
|
|
"resources/navigation/dorm0_large.json",
|
|
};
|
|
|
|
|
|
params_dorm.teleportsJsonPath = "resources/config2/teleports_dorm.json";
|
|
params_dorm.triggerZonesJsonPath = "resources/config2/trigger_zones_dorm.json";
|
|
params_dorm.lightsJsonPath = "resources/config2/lights_dorm.json";
|
|
params_dorm.scriptPath = "resources/start_dorm.lua";
|
|
params_dorm.interactiveObjectsJsonPath = "resources/config2/interactive_objects_dorm.json";
|
|
params_dorm.playerPosition = Eigen::Vector3f(6.76345, 0, -14.6022);
|
|
|
|
locations["location_dorm"] = std::make_shared<Location>(renderer, inventory);
|
|
locations["location_dorm"]->setup(params_dorm, &menuManager.questJournal);
|
|
locations["location_dorm"]->scriptEngine.setGlobalStore(&globalInts);
|
|
locations["location_dorm"]->scriptEngine.setGlobalFloatStore(&globalFloats);
|
|
locations["location_dorm"]->requestNightDayTransition = [this](bool isNight, bool isDawn) { this->menuManager.isNight = isNight; this->menuManager.isDawn = isDawn; };
|
|
locations["location_dorm"]->requestDarklandsTransition = [this]() { return startDarklandsTransition(); };
|
|
if (locations["location_dorm"]->player)
|
|
locations["location_dorm"]->player->onDeathAnimComplete = [this]() { startDarklandsTransition(); };
|
|
|
|
// Teleport callbacks: destination name and position come from the teleport zone data.
|
|
auto teleportCallback = [this](const std::string& destName, const Eigen::Vector3f& destPos, float destRotY) {
|
|
std::cout << "[TELEPORT] " << " -> " << destName << std::endl;
|
|
auto it = locations.find(destName);
|
|
if (it == locations.end()) {
|
|
std::cerr << "[TELEPORT] Unknown destination location: " << destName << std::endl;
|
|
return;
|
|
}
|
|
if (currentLocation)
|
|
currentLocation->scriptEngine.callLocationExitCallback();
|
|
currentLocation = it->second;
|
|
if (currentLocation->player) {
|
|
currentLocation->player->position = destPos;
|
|
currentLocation->player->setTarget(destPos);
|
|
currentLocation->player->facingAngle = destRotY;
|
|
currentLocation->player->targetFacingAngle = destRotY;
|
|
}
|
|
currentLocation->cameraAzimuth = destRotY;
|
|
currentLocation->scriptEngine.callLocationEnterCallback();
|
|
menuManager.onLocationChanged(destName);
|
|
};
|
|
|
|
locations["uni_exterior"]->onTeleport = teleportCallback;
|
|
locations["uni_interior"]->onTeleport = teleportCallback;
|
|
locations["location_dorm"]->onTeleport = teleportCallback;
|
|
|
|
// Wire tutorial advance callbacks for all locations.
|
|
// advanceTutorialStep() guards against double-advancing, so sharing is safe.
|
|
for (auto& [name, loc] : locations) {
|
|
loc->dialogueSystem.setOnDialogueAdvanced([this]() {
|
|
menuManager.advanceTutorialStep();
|
|
});
|
|
loc->onPlayerFloorWalk = [this]() {
|
|
if (menuManager.tutorialStep == TutorialStep::Step2) {
|
|
menuManager.advanceTutorialStep();
|
|
}
|
|
menuManager.onPlayerStartedWalking();
|
|
};
|
|
}
|
|
|
|
// Wire inventory item-pickup callback for tutorial step4/5 item tracking.
|
|
inventory.onItemAdded = [this](const std::string& itemId) {
|
|
menuManager.onItemPickedUp(itemId);
|
|
};
|
|
|
|
// Wire phone dialogue start function so MenuManager can trigger dialogues.
|
|
menuManager.startDialogueFunc = [this](const std::string& id) {
|
|
if (currentLocation) currentLocation->dialogueSystem.startDialogue(id);
|
|
};
|
|
|
|
menuManager.startDarklandsTransitionFunc = [this]() {
|
|
startDarklandsTransition();
|
|
};
|
|
|
|
// Wire bubble-slot callback so chat bubbles appear as dialogue lines are shown.
|
|
for (auto& [name, loc] : locations) {
|
|
loc->dialogueSystem.setOnBubbleSlotReady([this](const std::string& bubbleSlot) {
|
|
menuManager.revealPhoneChatBubble(bubbleSlot);
|
|
});
|
|
}
|
|
|
|
currentLocation = locations["location_dorm"];
|
|
currentLocation->scriptEngine.callLocationEnterCallback();
|
|
|
|
std::cout << "Load resurces step 5" << std::endl;
|
|
/*
|
|
std::cout << "Load resurces step 12" << std::endl;
|
|
|
|
// Shadow mapping shaders
|
|
#ifdef EMSCRIPTEN
|
|
renderer.shaderManager.AddShaderFromFiles("shadow_depth", "resources/shaders/shadow_depth.vertex", "resources/shaders/shadow_depth_web.fragment", CONST_ZIP_FILE);
|
|
std::cout << "Load resurces step 12x1" << std::endl;
|
|
renderer.shaderManager.AddShaderFromFiles("shadow_depth_skinning", "resources/shaders/shadow_depth_skinning.vertex", "resources/shaders/shadow_depth_web.fragment", CONST_ZIP_FILE);
|
|
std::cout << "Load resurces step 12x2" << std::endl;
|
|
renderer.shaderManager.AddShaderFromFiles("default_shadow", "resources/shaders/default_shadow.vertex", "resources/shaders/default_shadow_web.fragment", CONST_ZIP_FILE);
|
|
std::cout << "Load resurces step 12x3" << std::endl;
|
|
renderer.shaderManager.AddShaderFromFiles("skinning_shadow", "resources/shaders/skinning_shadow.vertex", "resources/shaders/default_shadow_web.fragment", CONST_ZIP_FILE);
|
|
std::cout << "Load resurces step 12x4" << std::endl;
|
|
renderer.shaderManager.AddShaderFromFiles("fog_shadow", "resources/shaders/fog_shadow.vertex", "resources/shaders/fog_shadow_web.fragment", CONST_ZIP_FILE);
|
|
std::cout << "Load resurces step 12x5" << std::endl;
|
|
renderer.shaderManager.AddShaderFromFiles("fog_skinning_shadow", "resources/shaders/fog_skinning_shadow.vertex", "resources/shaders/fog_shadow_web.fragment", CONST_ZIP_FILE);
|
|
std::cout << "Load resurces step 12x6" << std::endl;
|
|
renderer.shaderManager.AddShaderFromFiles("cutsceneFade", "resources/shaders/default.vertex", "resources/shaders/cutscene_fade_web.fragment", CONST_ZIP_FILE);
|
|
renderer.shaderManager.AddShaderFromFiles("cutsceneBlack", "resources/shaders/default.vertex", "resources/shaders/cutscene_black_web.fragment", CONST_ZIP_FILE);
|
|
|
|
|
|
#else
|
|
renderer.shaderManager.AddShaderFromFiles("shadow_depth", "resources/shaders/shadow_depth.vertex", "resources/shaders/shadow_depth_desktop.fragment", CONST_ZIP_FILE);
|
|
renderer.shaderManager.AddShaderFromFiles("shadow_depth_skinning", "resources/shaders/shadow_depth_skinning.vertex", "resources/shaders/shadow_depth_desktop.fragment", CONST_ZIP_FILE);
|
|
renderer.shaderManager.AddShaderFromFiles("default_shadow", "resources/shaders/default_shadow.vertex", "resources/shaders/default_shadow_desktop.fragment", CONST_ZIP_FILE);
|
|
renderer.shaderManager.AddShaderFromFiles("skinning_shadow", "resources/shaders/skinning_shadow.vertex", "resources/shaders/default_shadow_desktop.fragment", CONST_ZIP_FILE);
|
|
renderer.shaderManager.AddShaderFromFiles("fog_shadow", "resources/shaders/fog_shadow.vertex", "resources/shaders/fog_shadow_desktop.fragment", CONST_ZIP_FILE);
|
|
renderer.shaderManager.AddShaderFromFiles("fog_skinning_shadow", "resources/shaders/fog_skinning_shadow.vertex", "resources/shaders/fog_shadow_desktop.fragment", CONST_ZIP_FILE);
|
|
renderer.shaderManager.AddShaderFromFiles("cutsceneFade", "resources/shaders/default.vertex", "resources/shaders/cutscene_fade_desktop.fragment", CONST_ZIP_FILE);
|
|
renderer.shaderManager.AddShaderFromFiles("cutsceneBlack", "resources/shaders/default.vertex", "resources/shaders/cutscene_black_desktop.fragment", CONST_ZIP_FILE);
|
|
#endif*/
|
|
|
|
std::cout << "Load resurces step 13" << std::endl;
|
|
|
|
try {
|
|
menuManager.setup(inventory, CONST_ZIP_FILE);
|
|
std::cout << "UI loaded successfully" << std::endl;
|
|
}
|
|
catch (const std::exception& e) {
|
|
|
|
std::cerr << "Failed to load UI: " << e.what() << std::endl;
|
|
}
|
|
|
|
// Wire HP-change callbacks so all player instances update the health bar HUD.
|
|
for (auto& [name, loc] : locations) {
|
|
if (loc->player) {
|
|
loc->player->onHpChanged = [this](float hp, float maxHp) {
|
|
menuManager.updateHealthBar(hp, maxHp);
|
|
};
|
|
}
|
|
}
|
|
|
|
loadingCompleted = true;
|
|
|
|
if (audioPlayer->init()) {
|
|
audioPlayer->setMusicVolume(100);
|
|
audioPlayer->setSoundVolume(80);
|
|
std::cout << "Audio initialized successfully" << std::endl;
|
|
}
|
|
else {
|
|
std::cout << "Audio initialization failed" << std::endl;
|
|
}
|
|
|
|
}
|
|
|
|
void Game::drawUI()
|
|
{
|
|
glClear(GL_DEPTH_BUFFER_BIT);
|
|
|
|
glDisable(GL_DEPTH_TEST);
|
|
glDepthMask(GL_FALSE);
|
|
|
|
renderer.shaderManager.PushShader(defaultShaderName);
|
|
renderer.RenderUniform1i(textureUniformName, 0);
|
|
glEnable(GL_BLEND);
|
|
|
|
menuManager.uiManager.draw(renderer);
|
|
if (currentLocation)
|
|
{
|
|
currentLocation->dialogueSystem.draw(renderer);
|
|
}
|
|
|
|
glDisable(GL_BLEND);
|
|
renderer.shaderManager.PopShader();
|
|
|
|
glDepthMask(GL_TRUE);
|
|
glEnable(GL_DEPTH_TEST);
|
|
|
|
CheckGlError(__FILE__, __LINE__);
|
|
}
|
|
|
|
|
|
|
|
void Game::drawScene() {
|
|
glViewport(0, 0, Environment::width, Environment::height);
|
|
if (!loadingCompleted) {
|
|
drawLoading();
|
|
}
|
|
else
|
|
{
|
|
if (currentLocation)
|
|
{
|
|
// Sync global flags so Location's draw functions see them.
|
|
currentLocation->isDarklands = isDarklands;
|
|
currentLocation->isNight = menuManager.isNight;
|
|
currentLocation->isDawn = menuManager.isDawn;
|
|
|
|
if (isDarklands) {
|
|
currentLocation->drawGameDarklands();
|
|
CheckGlError(__FILE__, __LINE__);
|
|
}
|
|
else if (menuManager.isNight) {
|
|
currentLocation->drawGameNight();
|
|
CheckGlError(__FILE__, __LINE__);
|
|
}
|
|
else if (currentLocation->shadowMap) {
|
|
CheckGlError(__FILE__, __LINE__);
|
|
currentLocation->drawShadowDepthPass();
|
|
CheckGlError(__FILE__, __LINE__);
|
|
currentLocation->drawGameWithShadows();
|
|
CheckGlError(__FILE__, __LINE__);
|
|
}
|
|
else {
|
|
currentLocation->drawGame();
|
|
CheckGlError(__FILE__, __LINE__);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// ??? Main menu???
|
|
}
|
|
drawUI();
|
|
drawDarklandsFlash();
|
|
}
|
|
CheckGlError(__FILE__, __LINE__);
|
|
}
|
|
|
|
void Game::drawLoading()
|
|
{
|
|
glClear(GL_DEPTH_BUFFER_BIT);
|
|
|
|
renderer.shaderManager.PushShader(defaultShaderName);
|
|
renderer.RenderUniform1i(textureUniformName, 0);
|
|
renderer.RenderUniform1f("uAlpha", 1.0);
|
|
|
|
|
|
float width = Environment::projectionWidth;
|
|
float height = Environment::projectionHeight;
|
|
|
|
renderer.PushProjectionMatrix(
|
|
-width * 0.5f, width * 0.5f,
|
|
-height * 0.5f, height * 0.5f,
|
|
-10, 10);
|
|
|
|
renderer.PushMatrix();
|
|
renderer.LoadIdentity();
|
|
|
|
glBindTexture(GL_TEXTURE_2D, loadingTexture->getTexID());
|
|
renderer.DrawVertexRenderStruct(loadingMesh);
|
|
|
|
renderer.PopMatrix();
|
|
renderer.PopProjectionMatrix();
|
|
renderer.shaderManager.PopShader();
|
|
CheckGlError(__FILE__, __LINE__);
|
|
}
|
|
|
|
|
|
int64_t Game::getSyncTimeMs() {
|
|
int64_t localNow = std::chrono::duration_cast<std::chrono::milliseconds>(
|
|
std::chrono::system_clock::now().time_since_epoch()).count();
|
|
return localNow;
|
|
}
|
|
|
|
|
|
void Game::processTickCount() {
|
|
|
|
if (lastTickCount == 0) {
|
|
lastTickCount = getSyncTimeMs();
|
|
|
|
lastTickCount = (lastTickCount / 50) * 50;
|
|
|
|
return;
|
|
}
|
|
|
|
newTickCount = getSyncTimeMs();
|
|
|
|
newTickCount = (newTickCount / 50) * 50;
|
|
|
|
if (newTickCount - lastTickCount > CONST_TIMER_INTERVAL) {
|
|
|
|
int64_t delta = newTickCount - lastTickCount;
|
|
|
|
lastTickCount = newTickCount;
|
|
|
|
updateDarklandsFlash(delta);
|
|
|
|
menuManager.uiManager.update(static_cast<float>(delta));
|
|
|
|
if (currentLocation)
|
|
{
|
|
currentLocation->update(delta);
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
void Game::render() {
|
|
ZL::CheckGlError(__FILE__, __LINE__);
|
|
|
|
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
|
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
|
|
|
drawScene();
|
|
processTickCount();
|
|
|
|
SDL_GL_SwapWindow(ZL::Environment::window);
|
|
}
|
|
|
|
void Game::update() {
|
|
SDL_Event event;
|
|
while (SDL_PollEvent(&event)) {
|
|
if (event.type == SDL_QUIT) {
|
|
Environment::exitGameLoop = true;
|
|
}
|
|
|
|
|
|
if (event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_RESIZED) {
|
|
// Обновляем размеры и сбрасываем кеш текстов, т.к. меши хранятся в пикселях
|
|
Environment::width = event.window.data1;
|
|
Environment::height = event.window.data2;
|
|
Environment::computeProjectionDimensions();
|
|
//menuManager.uiManager.updateAllLayouts();
|
|
std::cout << "Window resized: " << Environment::width << "x" << Environment::height << std::endl;
|
|
|
|
//space.clearTextRendererCache();
|
|
}
|
|
|
|
#ifdef __ANDROID__
|
|
if (event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_AC_BACK) {
|
|
Environment::exitGameLoop = true;
|
|
}
|
|
#endif
|
|
|
|
// Touch events (real fingers on Android and on mobile browsers via Emscripten).
|
|
// SDL_HINT_TOUCH_MOUSE_EVENTS="0" is set in main.cpp so these do not
|
|
// also fire SDL_MOUSEBUTTONDOWN, preventing double-processing.
|
|
if (event.type == SDL_FINGERDOWN || event.type == SDL_FINGERUP || event.type == SDL_FINGERMOTION) {
|
|
int eventX = static_cast<int>(event.tfinger.x * Environment::width);
|
|
int eventY = static_cast<int>(event.tfinger.y * Environment::height);
|
|
int mx = static_cast<int>(event.tfinger.x * Environment::projectionWidth);
|
|
int my = static_cast<int>(event.tfinger.y * Environment::projectionHeight);
|
|
int64_t fingerId = static_cast<int64_t>(event.tfinger.fingerId);
|
|
|
|
if (event.type == SDL_FINGERDOWN) {
|
|
onPointerDown(fingerId, eventX, eventY, mx, my);
|
|
}
|
|
else if (event.type == SDL_FINGERUP) {
|
|
onPointerUp(fingerId, eventX, eventY, mx, my);
|
|
}
|
|
else {
|
|
onPointerMotion(fingerId, eventX, eventY, mx, my);
|
|
}
|
|
continue;
|
|
}
|
|
|
|
// Mouse-left maps to a single virtual touch with MOUSE_FINGER_ID.
|
|
// Right mouse is intentionally unused now — camera rotation is on hold-and-drag.
|
|
if (event.type == SDL_MOUSEBUTTONDOWN || event.type == SDL_MOUSEBUTTONUP) {
|
|
if (event.button.button == SDL_BUTTON_LEFT) {
|
|
int eventX = event.button.x;
|
|
int eventY = event.button.y;
|
|
int mx = static_cast<int>((float)eventX / Environment::width * Environment::projectionWidth);
|
|
int my = static_cast<int>((float)eventY / Environment::height * Environment::projectionHeight);
|
|
|
|
if (event.type == SDL_MOUSEBUTTONDOWN) {
|
|
onPointerDown(ZL::UiManager::MOUSE_FINGER_ID, eventX, eventY, mx, my);
|
|
}
|
|
else {
|
|
onPointerUp(ZL::UiManager::MOUSE_FINGER_ID, eventX, eventY, mx, my);
|
|
}
|
|
}
|
|
else if (event.button.button == SDL_BUTTON_RIGHT
|
|
&& event.type == SDL_MOUSEBUTTONUP
|
|
&& editorMode == EditorMode::Navigation
|
|
&& currentLocation) {
|
|
currentLocation->editor.handleRightClick();
|
|
}
|
|
}
|
|
else if (event.type == SDL_MOUSEMOTION) {
|
|
int eventX = event.motion.x;
|
|
int eventY = event.motion.y;
|
|
int mx = static_cast<int>((float)eventX / Environment::width * Environment::projectionWidth);
|
|
int my = static_cast<int>((float)eventY / Environment::height * Environment::projectionHeight);
|
|
onPointerMotion(ZL::UiManager::MOUSE_FINGER_ID, eventX, eventY, mx, my);
|
|
}
|
|
|
|
if (event.type == SDL_MOUSEWHEEL) {
|
|
static const float zoomstep = 2.0f;
|
|
if (event.wheel.y > 0) {
|
|
Environment::zoom -= zoomstep;
|
|
}
|
|
else if (event.wheel.y < 0) {
|
|
Environment::zoom += zoomstep;
|
|
}
|
|
if (Environment::zoom < zoomstep) {
|
|
Environment::zoom = zoomstep;
|
|
}
|
|
// Tutorial step3 → step4: any mouse-wheel scroll counts as "zoom gesture".
|
|
if (menuManager.tutorialStep == TutorialStep::Step3) {
|
|
menuManager.advanceTutorialStep();
|
|
}
|
|
}
|
|
|
|
if (event.type == SDL_KEYDOWN && event.key.repeat == 0) {
|
|
switch (event.key.keysym.sym) {
|
|
case SDLK_8:
|
|
if (editorMode == EditorMode::InteractiveObjects && currentLocation) {
|
|
currentLocation->editor.selectInteractiveObject(8);
|
|
} else {
|
|
menuManager.isDawn = !menuManager.isDawn;
|
|
if (menuManager.isDawn) menuManager.isNight = true;
|
|
}
|
|
break;
|
|
case SDLK_9:
|
|
if (editorMode == EditorMode::InteractiveObjects && currentLocation) {
|
|
currentLocation->editor.selectInteractiveObject(9);
|
|
} else {
|
|
if (menuManager.isDawn) {
|
|
menuManager.isDawn = false; // step back: dawn → plain night
|
|
} else {
|
|
menuManager.isNight = !menuManager.isNight;
|
|
}
|
|
}
|
|
break;
|
|
case SDLK_0:
|
|
case SDLK_1:
|
|
case SDLK_2:
|
|
case SDLK_3:
|
|
case SDLK_4:
|
|
case SDLK_5:
|
|
case SDLK_6:
|
|
case SDLK_7:
|
|
if (editorMode == EditorMode::InteractiveObjects && currentLocation) {
|
|
currentLocation->editor.selectInteractiveObject(event.key.keysym.sym - SDLK_0);
|
|
} else if (event.key.keysym.sym == SDLK_0) {
|
|
startDarklandsTransition();
|
|
}
|
|
break;
|
|
case SDLK_f:
|
|
currentLocation->dialogueSystem.startDialogue("dialog_start001");
|
|
|
|
break;
|
|
case SDLK_e:
|
|
currentLocation->dialogueSystem.startCutscene("test_cutscene_01"); //.startDialogue("test_cutscene_pan_dialogue");
|
|
break;
|
|
|
|
case SDLK_n:
|
|
if (editorMode == EditorMode::None)
|
|
editorMode = EditorMode::Navigation;
|
|
else if (editorMode == EditorMode::Navigation)
|
|
editorMode = EditorMode::InteractiveObjects;
|
|
else
|
|
editorMode = EditorMode::None;
|
|
if (currentLocation) {
|
|
currentLocation->editorMode = editorMode;
|
|
if (editorMode == EditorMode::Navigation)
|
|
currentLocation->editor.buildNavMeshes();
|
|
else if (editorMode == EditorMode::InteractiveObjects)
|
|
currentLocation->editor.buildInteractiveObjectBoundsMeshes();
|
|
}
|
|
{
|
|
const char* modeName = (editorMode == EditorMode::Navigation) ? "Navigation"
|
|
: (editorMode == EditorMode::InteractiveObjects) ? "InteractiveObjects"
|
|
: "None";
|
|
std::cout << "[EDITOR] Mode: " << modeName << std::endl;
|
|
}
|
|
break;
|
|
|
|
case SDLK_o:
|
|
x = x + 1;
|
|
std::cout << "current x: " << x << std::endl;
|
|
//y = y + 0.002;
|
|
//currentLocation->player->hp = 200;
|
|
//currentLocation->npcs[0]->walkSpeed += 0.01f;
|
|
//std::cout << "Walk speed: " << currentLocation->npcs[0]->walkSpeed << std::endl;
|
|
break;
|
|
|
|
case SDLK_k:
|
|
//y = y + 1;
|
|
//std::cout << "current y: " << y << std::endl;
|
|
//y = y - 0.002;
|
|
|
|
std::cout << "Player pos: " << currentLocation->player->position.transpose() << std::endl;
|
|
//currentLocation->npcs[0]->walkSpeed -= 0.01f;
|
|
//std::cout << "Walk speed: " << currentLocation->npcs[0]->walkSpeed << std::endl;
|
|
break;
|
|
|
|
case SDLK_p:
|
|
currentLocation = locations["uni_interior"];
|
|
currentLocation->player->position = Eigen::Vector3f(-0.0189243, 0, -13.4314);
|
|
currentLocation->player->setTarget(currentLocation->player->position);
|
|
//std::cout << "Switched to location " << ((currentLocation == locations["location1"]) ? "1" : "2") << std::endl;
|
|
break;
|
|
|
|
case SDLK_l:
|
|
//x = x - 1;
|
|
//std::cout << "current x: " << x << std::endl;
|
|
|
|
std::cout << "Azimuth: " << currentLocation->cameraAzimuth << std::endl;
|
|
std::cout << "Inclination: " << currentLocation->cameraInclination << std::endl;
|
|
break;
|
|
|
|
case SDLK_c:
|
|
std::cout << "SLOW-MO activated!" << std::endl;
|
|
activateSlowMoEffect();
|
|
break;
|
|
|
|
case SDLK_i:
|
|
y = y - 1;
|
|
std::cout << "current y: " << y << std::endl;
|
|
break;
|
|
|
|
case SDLK_b:
|
|
if (editorMode != EditorMode::None && currentLocation) {
|
|
currentLocation->editor.saveAll();
|
|
}
|
|
break;
|
|
|
|
case SDLK_j:
|
|
if (editorMode != EditorMode::None && currentLocation) {
|
|
currentLocation->editor.placeTree();
|
|
} else {
|
|
menuManager.toggleQuestJournal();
|
|
}
|
|
break;
|
|
|
|
case SDLK_v:
|
|
if (editorMode != EditorMode::None && currentLocation) {
|
|
currentLocation->editor.saveObjects();
|
|
}
|
|
break;
|
|
|
|
case SDLK_RETURN:
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Обработка ввода текста
|
|
if (event.type == SDL_KEYDOWN) {
|
|
if (event.key.keysym.sym == SDLK_BACKSPACE) {
|
|
//menuManager.uiManager.onKeyBackspace();
|
|
}
|
|
}
|
|
|
|
if (event.type == SDL_TEXTINPUT) {
|
|
// Пропускаем ctrl+c и другие команды
|
|
if ((event.text.text[0] & 0x80) == 0) { // ASCII символы
|
|
//menuManager.uiManager.onKeyPress((unsigned char)event.text.text[0]);
|
|
}
|
|
}
|
|
|
|
if (event.type == SDL_KEYUP) {
|
|
if (event.key.keysym.sym == SDLK_r) {
|
|
std::cout << "Camera position: x=" << x << " y=" << y << " z=" << z << std::endl;
|
|
}
|
|
}
|
|
|
|
if (event.type == SDL_KEYUP) {
|
|
|
|
}
|
|
}
|
|
render();
|
|
|
|
mainThreadHandler.processMainThreadTasks();
|
|
|
|
}
|
|
|
|
int Game::countNonUiPointers() const
|
|
{
|
|
int n = 0;
|
|
for (const auto& kv : activePointers) {
|
|
if (!kv.second.capturedByUi) ++n;
|
|
}
|
|
return n;
|
|
}
|
|
|
|
void Game::activateSlowMoEffect() {
|
|
if (!currentLocation) return;
|
|
|
|
if (currentLocation->player) {
|
|
currentLocation->player->slowMoTimeRemaining = currentLocation->player->slowMoActiveTime;
|
|
}
|
|
|
|
for (auto& npc : currentLocation->npcs) {
|
|
if (npc) {
|
|
npc->slowMoTimeRemaining = npc->slowMoActiveTime;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Game::enterCameraDragMode(int eventX, int eventY)
|
|
{
|
|
cameraDragging = true;
|
|
if (currentLocation) {
|
|
currentLocation->cameraDragging = true;
|
|
// Anchor on the *current* position, not the original press, so the
|
|
// camera doesn't snap by however far the finger drifted before the
|
|
// movement threshold was crossed.
|
|
currentLocation->lastMouseX = eventX;
|
|
currentLocation->lastMouseY = eventY;
|
|
|
|
// Snapshot current angles so we can measure how far the user rotates.
|
|
dragStartAzimuth = currentLocation->cameraAzimuth;
|
|
dragStartInclination = currentLocation->cameraInclination;
|
|
}
|
|
}
|
|
|
|
void Game::exitCameraDragMode()
|
|
{
|
|
cameraDragging = false;
|
|
if (currentLocation) {
|
|
currentLocation->cameraDragging = false;
|
|
}
|
|
}
|
|
|
|
void Game::startPinch()
|
|
{
|
|
// Pick the first two non-UI pointers as pinch anchors.
|
|
bool foundA = false, foundB = false;
|
|
for (auto& kv : activePointers) {
|
|
if (kv.second.capturedByUi) continue;
|
|
if (!foundA) {
|
|
pinchFingerA = kv.first;
|
|
foundA = true;
|
|
}
|
|
else if (!foundB) {
|
|
pinchFingerB = kv.first;
|
|
foundB = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!foundA || !foundB) return;
|
|
|
|
const PointerState& a = activePointers[pinchFingerA];
|
|
const PointerState& b = activePointers[pinchFingerB];
|
|
float dx = static_cast<float>(a.eventX - b.eventX);
|
|
float dy = static_cast<float>(a.eventY - b.eventY);
|
|
pinchStartDistance = std::sqrt(dx * dx + dy * dy);
|
|
pinchStartZoom = Environment::zoom;
|
|
pinchActive = true;
|
|
}
|
|
|
|
void Game::updatePinchZoom()
|
|
{
|
|
auto itA = activePointers.find(pinchFingerA);
|
|
auto itB = activePointers.find(pinchFingerB);
|
|
if (itA == activePointers.end() || itB == activePointers.end()) return;
|
|
if (pinchStartDistance <= 1.0f) return;
|
|
|
|
float dx = static_cast<float>(itA->second.eventX - itB->second.eventX);
|
|
float dy = static_cast<float>(itA->second.eventY - itB->second.eventY);
|
|
float dist = std::sqrt(dx * dx + dy * dy);
|
|
if (dist <= 1.0f) return;
|
|
|
|
float newZoom = pinchStartZoom * (pinchStartDistance / dist);
|
|
// Match the wheel-zoom lower bound so both gestures clamp the same way.
|
|
static const float zoomMin = 2.0f;
|
|
if (newZoom < zoomMin) newZoom = zoomMin;
|
|
Environment::zoom = newZoom;
|
|
|
|
// Tutorial step3 → step4: detect a significant pinch-zoom (≥ 2 zoom units).
|
|
if (menuManager.tutorialStep == TutorialStep::Step3) {
|
|
if (std::abs(Environment::zoom - pinchStartZoom) >= 2.0f) {
|
|
menuManager.advanceTutorialStep();
|
|
}
|
|
}
|
|
}
|
|
|
|
void Game::endPinch()
|
|
{
|
|
pinchActive = false;
|
|
pinchStartDistance = 0.0f;
|
|
pinchStartZoom = 0.0f;
|
|
}
|
|
|
|
void Game::onPointerDown(int64_t fingerId, int eventX, int eventY, int mx, int my)
|
|
{
|
|
PointerState st;
|
|
st.eventX = st.downEventX = eventX;
|
|
st.eventY = st.downEventY = eventY;
|
|
st.mx = st.downMx = mx;
|
|
st.my = st.downMy = my;
|
|
|
|
const int uiX = mx;
|
|
const int uiY = Environment::projectionHeight - my;
|
|
const bool dialogueActive = currentLocation && currentLocation->dialogueSystem.isActive();
|
|
if (!dialogueActive) {
|
|
menuManager.uiManager.onTouchDown(fingerId, uiX, uiY);
|
|
}
|
|
st.capturedByUi = !dialogueActive && menuManager.uiManager.isUiInteractionForFinger(fingerId);
|
|
|
|
activePointers[fingerId] = st;
|
|
|
|
if (st.capturedByUi) {
|
|
// UI button / slider / text-field grabbed this press; don't drive
|
|
// gameplay or pinch from it.
|
|
return;
|
|
}
|
|
|
|
if (countNonUiPointers() >= 2) {
|
|
// Second finger landed on the gameplay area: switch to pinch-zoom
|
|
// and abandon any in-flight tap/camera-drag.
|
|
if (cameraDragging) {
|
|
exitCameraDragMode();
|
|
}
|
|
hasPrimaryPointer = false;
|
|
if (!pinchActive) {
|
|
startPinch();
|
|
}
|
|
}
|
|
else {
|
|
hasPrimaryPointer = true;
|
|
primaryPointerId = fingerId;
|
|
}
|
|
}
|
|
|
|
void Game::onPointerUp(int64_t fingerId, int eventX, int eventY, int mx, int my)
|
|
{
|
|
const int uiX = mx;
|
|
const int uiY = Environment::projectionHeight - my;
|
|
const bool dialogueActive = currentLocation && currentLocation->dialogueSystem.isActive();
|
|
if (!dialogueActive) {
|
|
menuManager.uiManager.onTouchUp(fingerId, uiX, uiY);
|
|
}
|
|
|
|
auto it = activePointers.find(fingerId);
|
|
if (it == activePointers.end()) return;
|
|
PointerState st = it->second;
|
|
activePointers.erase(it);
|
|
|
|
// Pinch ends as soon as either anchor is lifted. The remaining finger,
|
|
// if any, is *not* promoted back to a primary tap — the user was mid-
|
|
// gesture, not starting a fresh press.
|
|
if (pinchActive && (fingerId == pinchFingerA || fingerId == pinchFingerB)) {
|
|
endPinch();
|
|
return;
|
|
}
|
|
|
|
if (!hasPrimaryPointer || fingerId != primaryPointerId) {
|
|
return;
|
|
}
|
|
|
|
const bool wasDragging = cameraDragging;
|
|
if (cameraDragging) {
|
|
exitCameraDragMode();
|
|
}
|
|
hasPrimaryPointer = false;
|
|
|
|
if (st.capturedByUi || wasDragging) {
|
|
// UI handled the press, or it was a camera-rotation drag — no tap action.
|
|
return;
|
|
}
|
|
|
|
// Tap → walk-to / interact, using the original press coords so the target
|
|
// isn't shifted by tiny finger drift before release.
|
|
if (currentLocation) {
|
|
currentLocation->handleDown(fingerId, st.downEventX, st.downEventY, st.downMx, st.downMy);
|
|
}
|
|
}
|
|
|
|
void Game::onPointerMotion(int64_t fingerId, int eventX, int eventY, int mx, int my)
|
|
{
|
|
const int uiX = mx;
|
|
const int uiY = Environment::projectionHeight - my;
|
|
const bool dialogueActive = currentLocation && currentLocation->dialogueSystem.isActive();
|
|
if (!dialogueActive) {
|
|
menuManager.uiManager.onTouchMove(fingerId, uiX, uiY);
|
|
}
|
|
|
|
auto it = activePointers.find(fingerId);
|
|
if (it != activePointers.end()) {
|
|
it->second.eventX = eventX;
|
|
it->second.eventY = eventY;
|
|
it->second.mx = mx;
|
|
it->second.my = my;
|
|
}
|
|
|
|
if (pinchActive) {
|
|
updatePinchZoom();
|
|
return;
|
|
}
|
|
|
|
// Only the primary, non-UI-captured pointer can promote itself into a
|
|
// camera-rotation drag once it crosses the movement threshold.
|
|
if (hasPrimaryPointer && fingerId == primaryPointerId && it != activePointers.end()
|
|
&& !it->second.capturedByUi && !cameraDragging) {
|
|
int dx = mx - it->second.downMx;
|
|
int dy = my - it->second.downMy;
|
|
if (dx * dx + dy * dy >= CAMERA_DRAG_PIXEL_THRESHOLD * CAMERA_DRAG_PIXEL_THRESHOLD) {
|
|
enterCameraDragMode(eventX, eventY);
|
|
}
|
|
}
|
|
|
|
if (currentLocation) {
|
|
// Forwarded for dialogue hover and (when cameraDragging) camera rotation.
|
|
currentLocation->handleMotion(fingerId, eventX, eventY, mx, my);
|
|
|
|
// Tutorial step1 → step2: detect a significant camera rotation on BOTH axes.
|
|
// ~0.15 rad (≈8.6°) per axis ensures the user intentionally panned in 2D,
|
|
// not just nudged a single axis by accident.
|
|
static constexpr float TUTORIAL_ROTATION_THRESHOLD = 0.15f;
|
|
if (cameraDragging
|
|
&& menuManager.tutorialStep == TutorialStep::Step1) {
|
|
float deltaAz = std::abs(currentLocation->cameraAzimuth - dragStartAzimuth);
|
|
float deltaInc = std::abs(currentLocation->cameraInclination - dragStartInclination);
|
|
if (deltaAz >= TUTORIAL_ROTATION_THRESHOLD && deltaInc >= TUTORIAL_ROTATION_THRESHOLD) {
|
|
menuManager.advanceTutorialStep();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool Game::startDarklandsTransition()
|
|
{
|
|
if (darklandsFlashActive) return false;
|
|
darklandsFlashActive = true;
|
|
darklandsFlashFadingIn = true;
|
|
darklandsFlashAlpha = 0.0f;
|
|
return true;
|
|
}
|
|
|
|
void Game::updateDarklandsFlash(int64_t deltaMs)
|
|
{
|
|
if (!darklandsFlashActive) return;
|
|
static constexpr float kFadeDurationMs = 500.0f;
|
|
const float step = static_cast<float>(deltaMs) / kFadeDurationMs;
|
|
|
|
if (darklandsFlashFadingIn) {
|
|
darklandsFlashAlpha = min(darklandsFlashAlpha + step, 1.0f);
|
|
if (darklandsFlashAlpha >= 1.0f) {
|
|
isDarklands = !isDarklands;
|
|
|
|
// Change HUD
|
|
menuManager.setDarklandsMode(isDarklands);
|
|
|
|
if (currentLocation) {
|
|
if (isDarklands)
|
|
currentLocation->scriptEngine.callDarklandsEnterCallback();
|
|
else
|
|
currentLocation->scriptEngine.callDarklandsExitCallback();
|
|
}
|
|
darklandsFlashFadingIn = false;
|
|
}
|
|
} else {
|
|
darklandsFlashAlpha = max(darklandsFlashAlpha - step, 0.0f);
|
|
if (darklandsFlashAlpha <= 0.0f) {
|
|
darklandsFlashAlpha = 0.0f;
|
|
darklandsFlashActive = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Game::drawDarklandsFlash()
|
|
{
|
|
if (darklandsFlashAlpha <= 0.001f) return;
|
|
|
|
const float W = static_cast<float>(Environment::projectionWidth);
|
|
const float H = static_cast<float>(Environment::projectionHeight);
|
|
|
|
if (darklandsFlashQuadW != W || darklandsFlashQuadH != H) {
|
|
darklandsFlashQuadW = W;
|
|
darklandsFlashQuadH = H;
|
|
VertexDataStruct data;
|
|
data.PositionData = {
|
|
{0.f, 0.f, 0.f}, {0.f, H, 0.f}, {W, H, 0.f},
|
|
{W, H, 0.f}, {W, 0.f, 0.f}, {0.f, 0.f, 0.f}
|
|
};
|
|
data.TexCoordData = {
|
|
{0.f, 0.f}, {0.f, 1.f}, {1.f, 1.f},
|
|
{1.f, 1.f}, {1.f, 0.f}, {0.f, 0.f}
|
|
};
|
|
darklandsFlashQuad.data = std::move(data);
|
|
darklandsFlashQuad.RefreshVBO();
|
|
}
|
|
|
|
glEnable(GL_BLEND);
|
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
|
|
|
renderer.shaderManager.PushShader("darklands_flash");
|
|
renderer.PushProjectionMatrix(0.0f, W, 0.0f, H, -10.0f, 10.0f);
|
|
renderer.PushMatrix();
|
|
renderer.LoadIdentity();
|
|
|
|
renderer.RenderUniform1f("uAlpha", darklandsFlashAlpha);
|
|
renderer.DrawVertexRenderStruct(darklandsFlashQuad);
|
|
|
|
renderer.PopMatrix();
|
|
renderer.PopProjectionMatrix();
|
|
renderer.shaderManager.PopShader();
|
|
|
|
glDisable(GL_BLEND);
|
|
}
|
|
|
|
} // namespace ZL
|